So-net無料ブログ作成

Eclipse [Programming]

Apache, MySQL, PHP で構築する AMP ソリューションに喧嘩を売っているような IIS, SQL Server, PHP の環境が組みあがった。

ただ、このままだと Hello World (に毛の生えた程度) くらいしかできそうもないので、開発環境を導入しようと思う。

よく聞く Eclipse を日本語化したパッケージを公開しているので、それを利用させていただこうと思う。よくわからないので、最新版 (Neon) のフルセット (Ultimate - x64 Full Edition) をダウンロードしてきてインストール(zipを展開するだけ)してみた。

PHP のバージョンが 5.6.21 となっているようなので、これを新しいものに差し替える。Hello World にブレークポイントを仕掛けてデバッグすると、停止できるところまでは意外にすんなり通った。

 


Security 対策 [Programming]

最近、システム受け入れの条件としてセキュリティ対策のエビデンスを求められることが多い。私が対応した案件では、セキュリティ対策ソフト(lこの場合はウィルス対策ソフト等ではなく、ソースコードの静的分析を行いセキュリティリスクを報告してくれるもの)の出力を提出することを求められるものがいくつかあった。

全ての警告を取り去ることができれば(作成する書類も少なく)楽なのだけど、なかなかそううまくはいかないことも多い。ウィザードの類が生成したファイルからソースコードを生成すような場合や、他者から供給を受けているライブラリの仕様に依存する場合は、非対応とする理由を添える必要があり、これが結構時間をとられる。

また、セキュリティソフトの常ではあるのだけれど、偽陽性(False Positive) と呼ばれる誤検知に遭遇してしまうと工数がさらに跳ね上がってしまうことがある。Web で情報を集めようとしても、1 ライセンスあたり数10万円という高額なものが多く、個人利用がほとんどないため大抵が徒労に終わってしまう。仮に見つけられたとしても、質問だけで全くレスが付かずに放置された英語の掲示板なんてことも結構ある。開発元の Web サイトにも、不具合の情報なんて殆ど公開されていないしね。

.NET Framework には、実行ファイル(.exe や .dll)を逆アセンブルして中間言語を見せてくれるツールがある。ソースコードを追ってもセキュリティ対策ソフトの警告がどうしても取れない場合、中間言語を追って警告の原因を突き止めるのが次なる手段。

今回遭遇した件については(上で誤検知とは書いたけれど)警告を発してもしょうがないのかもしれないとも思える。ただし、チョット詳しく見れば絶対通らないルートとなるため障害は発生することはないと解る。さらに言えば中間言語は JIT コンパイラにより最適化される可能性があるため、当該箇所が消えてなくなる可能性もある。

と、原因も解明し、さらに対応方法も突き止めた後、対応件数が多いと解った場合、選ぶべき道がふたつある。放置と決めて「セキュリティ対策ソフトの誤検出」としてレポートを作るか、全項目を対応するか。

 


結果 [Programming]

タイムアタックの結果は以下の通りだった。

TimeOuterMethod
00:00:00.0089023TargetAny Contains
00:00:00.0120850TargetContains
00:00:00.0155687Targetforeach
00:00:00.0161814Targetunsafe
00:00:00.0303619Targetfor
00:00:00.0311945Sourceunsafe
00:00:00.0465872Sourcefor
00:00:00.0556998Sourceforeach
00:00:00.1207731TargetAny Any
00:00:00.1708128SourceIntersect
00:00:00.1890885TargetAny
00:00:00.5188410SourceLINQ Join
00:00:00.5590802SourceJoin
00:00:00.5764444SourceAny Contains
00:00:00.7542342SourceContains
00:00:00.8405565SourceAny Any
00:00:00.8763593TargetIntersect
00:00:01.2191880SourceAny
00:00:05.1704911TargetLINQ Join
00:00:07.0252086TargetJoin

所要時間については±50%程度のバラつきが出る感じなので、そのまま鵜呑みにはしない方がいい。順位についてはほぼ予想通りといったところだろうか、スクラッチで作り上げたものや要求にぴったりくるものについては速く、オーバースペック気味のものは遅い。

LINQ の、最も LINQ らしい使い方をする Join に関しては条件が悪すぎたというところなのだろうか。さすがにこれだけ差が出てしまうと、使いどころを考えないといけないかもしれない。

Any の使い勝手がよかったのはちょっとした誤算。メソッド構文特有の使い方にはなるのだけど、最近はドット (.) でパイプラインを繋ぐような記述方法を採用されることが多いので割と馴染みやすいかもしれない。

unsafe を含めスクラッチで作ったものは確かに速い。それでも、拡張メソッドを使うことで簡潔な表記ができるメリットも捨てがたいところ。処理する件数が少なければ速度に関しては多少目を瞑ることも可能かもしれない。


準備 [Programming]

LINQ が使える用途で、それが便利に思えるところって言ったらコレクションの中から条件に合ったオブジェクトを拾い出すもの。名前の通りクエリーが必要になるところだろう。特定のキーのオブジェクトを探し出すだけなら Dictionary<K,V> のようなコレクションを使えば済んでしまいそうなので、今回は全件検索して条件に合うものがあるかを探し出すことを考える。

外部のリソースに依存しない LINQ to Object に検証を実施する。同じパラメーターを受け、同じ結果を返す(成功/不成功の判断の方法は異なる可能性もある)メソッドを作成してみて、見た目と性能を評価してみたい。今回はクエリー形式の構文はあまり使用していない。標準的に IEnumerable<T> には実装されているものの、クエリー構文から直接利用できないものが多く、選択肢が限られてしまうというのが理由。LINQ の性能というよりは拡張メソッドの使い方による性能の出し方みたいになってしまいそうだけど、、、。

  • 1000万件のレコードを持つテーブル(source)と1件のレコードを持つテーブル(target)を使う。
  • テーブルに含まれるレコードは単純な整数型。テーブルは単純な整数の配列。
  • ふたつのテーブルをマッチングさせて、同じキーを持つレコードがあるかどうかを判断する。
    処理方法によっては若干オーバースペックとなってしまうものもある。
  • ネタバレ:ふたつのテーブルは同じ値のレコードを持たないため、処理結果は常に False (あるいは 0 件)となり、処理時間は常に最悪値となるはず。
    できる訳ないと解っている作業を人にやらせたらパワハラとか言われてしまいそうだけど、コンピューターは健気に作業して結果を報告してくれる。予想以上の短時間で。

処理方法は以下のものを考える。

  1. LINQ の Join の機能を使ってテーブルを結合させてみる。Join のシグネチャを見ると解るのだけど、パラメーターの数が多いのでちょっと不利かもしれない。また、一致するレコードの有無を調べるだけ事を行うにはかなりオーバースペック。さらに、欲しい結果を得るためにさらに余計なことをしなければならない等、不利な条件のオンパレード確実に最も遅い実装になっるんじゃないかと思われる。コードは以下の通りメソッド構文で記述してみた。
    return target.Join(source, o => o, i => i, (o, i) => o).Count();
  2. Join をクエリー構文で記述してみる。クエリー構文はメソッド構文の単なるシンタックスシュガーなので生成されるコードは同じものになるはず。
    return (from t in target join s in source on t equals s select t).Count();
  3. IEnumerable<T> には、コレクションに条件に合致するレコードがあるかを確認するための Any というメソッドが定義されている。ただ、コレクション vs コレクションという使い方はできないので、外側のループに foreach を使ってみた。
    foreach (var t in target)
    if (source.Any(s => s==t))
    return true;
    return false;
  4. Any の 2 段使い。後述の処理と比較してみると解るけど、Any ひとつで foreach ひとつ分の処理が賄える場合がある。はまると異様なほどに簡潔に記述できてしまうのだけれど、知らない人に説明するのはかなり面倒かも。
    return target.Any(t => source.Any(s => s==t));
  5. コレクションから指定したレコードと同じものを探してくる Contains メソッド。LINQ の構造に疎くてもインテリセンスが教えてくれるので重宝している人も多いかもしれない。今回の計測に於いては最も過不足のない機能なので上位に食い込んでくれるとうれしいかも。外側のループには foreach を使っている。
    foreach (var t in target)
    if (source.Contains(t))
    return true;
    return false;
  6. Any と Contains の合体技。個人的にはこの記述方法が一番すっきりする。
    return target.Any(t => source.Contains(t));
  7. ふたつのコレクションの積集合を作る Intersect メソッド。構文的には最も簡単に見えるし短い。ただし、若干オーバースペック。今回は積集合が空になる前提なのであまり関係ないかもしれないけれど、一致するもの全てを収集してくるので条件によっては使いづらいかもしれない。
    return target.Any(t => source.Contains(t));
  8. 自力でふたつのコレクションをスキャンする。foreach の二重ループ。
    foreach (var t in target)
    foreach (var s in source)
    if (s==t)
    return true;
    return false;
  9. こちらは for の二重ループ。なんとなく、VC++ で Windows API と戦っていたころを思い出すようなコード。
    int tn=target.Count();
    int sn=source.Count();
    for (int i=0; i<tn; i++)
    for (int j=0; j<sn; j++)
    if (source[j]==target[i])
    return true;
    return false;
  10. unsafe を指定してポインターで処理してみる。Visual Basic は unsafe に相当する機能がないので C# 特有のコードになる。これで多少速くなったとしても、常にこんなコードを書くのは遠慮したい。
    int tn=target.Count();
    int sn=source.Count();
    unsafe {
    fixed (int* t=target, s=source) {
    int* tp=t;
    for (int i=0; i<tn; tp++, i++) {
    int* sp=s;
    for (int j=0; j<sn; sp++, j++)
    if (*sp==*tp)
    return true;
    }
    return false;
    }
    }

これらの機能を呼び出して、処理にかかった時間を計測すればとりあえずは性能の確認はできるだろう。また、ちょっと古典的な話になるけど、二重ループを構成する場合にどちらのループに何を割り当てるかで所要時間が変わることがある。ついでなので、与えるパラメーターの順番を入れ替えて、どの程度変わるものか計測してみることにする。


疑問 [Programming]

.NET Framework には LINQ (Language Integrated Query) という機能をサポートする言語 (C#, Visual Basic 等)がある。LINQ の適用は必須というわけではないけれど、適用する事によって見渡しやすいコードが記述できることができるので「好きだ」という人も少なくない。また、LINQ を適用したコードは必ずしも速くはないという事も言われている。

この点について web 上で公開されている文書もかなり多いことから、少なくとも嘘ではないだろうとは思うのだけど、未だ明確な基準は示されていないと思う。

LINQ は使うべきなのか、使わざるべきなのか、条件があるとしたらどこで線引きするべきなのか。ちょっと考察してみたいと思う。

まあ、私も LINQ は気に入っているので、いきなり「もう、使わない」という結論にはならないとは思うのだけどネ。

 


配列とコレクション [Programming]

.NET Framework には、複数のオブジェクトを管理するための機能として、配列とコレクションというものを提供している。前者は以前のプログラミング言語が備えていた配列をそのまま実装したようなもの、後者は用途に合わせてより高機能なオブジェクトとして提供されていて、その種類も結構多い。また、.NET Framework 2.0 以降ではジェネリックという機構が取り入れられていて型に対する安全性が高まったコレクションも提供されている。

 配列にもコレクションのような機能やインターフェースが実装されていたりして結構高機能になっているようではあるけれど、多少なりとも実装が異なっているようなので、その棲み分けについて考えてみたいと思う。

.NET Framework がジェネリックをサポートする前から、配列は型に対する安全性を確保していた。同時期に公開されたコレクション ArrayList が拡張性のある配列の機能を提供していたにも関わらず Object 型を保持することしかできなかったのとは対照的ともいえる。そして、ジェネリックの登場と共に利用可能になったコレクション List<T> は型に対して安全であり拡張が可能な配列といういいとこどりのような実装になっている。

因みに、.NET Framework の ArrayList や List<T> というのは動的配列を指す。MFC (Microsoft Foundation Classes) に含まれていた CList のような連結リストは、.NET Framework では LinkedList<T> によって提供されている。非ジェネリックの連結リストは提供されていない。

配列、ArrayList、List<T>ともに、要素を取得するためのオーダーは O(1) であり、検索のためのオーダーは O(n) となります。

 ArrayList はコンパイル時に型が決められないため、常にキャストとの抱き合わせになってしまう。なので、実行速度の点でも残念なところがある。それでも、ちょっとしたツールを作るくらいだったら十分に適用可能ではある。

List<T> のネックは、書式が難しいこと?慣れてしまえばそんなこともないのだけど、ジェネリックの書式(特に where 句)は初心者には辛いところがあるみたい。ただ、可変長の配列というのは非常に便利な機構なので、覚えておいて損のないものだとは思う。

配列は言語機能の一部として扱える(Array クラスの派生型を生成するのはコンパイラの役目)ので便利ではあるのだけど、どうしても他のコレクションと比べると色褪せちゃう。多次元配列やジョグ配列のように配列でしか利用できないものがあるということと、軽量で高速という点でコレクションと連携を図ることができるという点を押さえておくと便利。コレクションクラスには ToArray メソッドを備えているものがある。コネクションを全走査したいような場合、いったん配列に変換(実際はコピー)したのちに処理することで処理速度を稼ぐことだできる場合がある。ただし、その分のメモリも余計に食うので気を付けて。

下の表は、配列と List<T> を使った for 文と foreach 文のタイムトライアルの結果。

TypeTime
int Array For00:00:00.0005753
int Array ForEach00:00:00.0005874
int List For00:00:00.0012276
int List ForEach00:00:00.0012387

 総じて List<T> の方が処理時間が長くなる傾向にあり、共に foreach が微妙に遅いという結果になる。foreach が使えない程遅いという訳でもないし、LINQ と絡めるようなアプローチなら別の評価基準も考える必要があると思う。

 List<T> の提供する動的配列というのは、要素の追加と削除をサポートしている配列。ただ、頻繁にこれらを行うとパフォーマンスの点で問題が出てくることがある。下の表は List<T> に対して 100,000件の要素を追加した際に要した時間。使用したメソッドは Add と Insert。Add メソッドは要素を最後尾に追加することしかできないけど、Insert メソッドは任意の位置に要素を挿入することができる。Insert 0 と表記しているものはリストの先頭に、Insert n と表記しているものはリストの最後尾(Add と同じ動作)に要素を挿入している。

TypeTime
List Add00:00:00.0018722
List Insert 000:00:02.8639549
List Insert n00:00:00.0012008

ベンチマークの結果は、常にその値が出ることは期待しないでいただきたい。さすがに 3 桁も違うと逆転する可能性はほぼ無いとは思うけどね。リストの途中に要素を追加する場合、追加した項目以降の要素は順送りに移動させられる。その際のオーバーヘッドが Insert 0 の遅い理由でしょう。要素の抜き差しが多くなるような用途なら LinkedList<T> の適用も考えるのもいいかもしれない。


参照 [Programming]

結構長い期間開発に携わっているので、その手の英単語は割りと覚えている方だと思う。ただ、どうしても馴染まない訳ってのもあって、登場するたびに辞書や Web サイトで調べまくってもなかなか納得のいかないものがある。

reference という単語、多くの場合において「参照(する)」と訳してやれば意味は通じると思う。参照型とかリファレンスマニュアルと言うように、日本語訳を当てたり、そのままカタカナ表記で使われたりと、かなりバラバラだったりするけど、意味が解らないようなものにはなっていないと思う。

対して dereference は、、、。意味的には、「参照先のオブジェクトを取得する」が近いんじゃないかと思うんだけど、なぜかこれを逆参照と表記するものが多い。で、これが納得いかない訳の最たるもの。

逆参照なんて言われると、私は逆方向の参照のように感じてしまうのだけど、そのまま受け入れている人も多い。「逆××」という単語で真っ先に思いついたのは DNS や 用例集で使われる 「逆引き」だったんだけど、この場合の「逆」には reverse が当てられているので、文字通り逆方向の参照なんだとイメージできる。

Wikipedia では誤訳だと記載されているけど、ここのタイトルの間接参照だって結構微妙な気がする。

excite は「参照先のデータを取得」で統一?

weblio も「参照先のデータを取得」で統一?。まあ、アクセスするなんて訳もあるけど、、、それは access と何が違うのって感じ。

それ以外の訳で多かったのは「参照外し」とか、、、外してないよね?参照は残ったままだもん。

自分の思考過程のなかで dereference が出てくる場合はあまり問題にならないのだけど、他人の言葉の中に「逆参照」が出てくると、一度 dereference に変換してやらないと混乱しちゃう。しょっちゅう使う単語でもないので未だに慣れない。


確率 Again [Programming]

プログラミング規模の大小に拘らず、ファイル操作が必要になるシチュエーションは結構ある。ファイルの形式は様々なものがあるだろうけど、他のアプリケーションと共有することを考えると、XML やら CSV やらの(ライブラリによるサポートが期待できる)テキストファイルっていうのが手軽でいい。

 テキストファイルは決して万能ではなく、いくつかの難点も抱えているので、他の選択肢を排除してしまうのは得策ではないと思う。状況によってはバイナリファイルを使う事や、データベースとの連携も必要なら考慮すべきかな。

今回のネタでは、あまり形式は関係ない。単にファイルであればいいのだけど、テキストファイルと限定してしまったほうがイメージがしやすそうだったからそうしたまで。

ここに、とあるアプリケーションが吐き出したテキストファイル(ログのようなものを想像して頂ければいい)がある。このファイルを読み込んで整形する小さなツールを考える。普段だと PowerShell あたりで処理しちゃうところなんだけど、今回は C# を使ってアプリケーションとして構築したい。

ファイルを読み込むために作成するコードは殆ど議論の余地がないだろう。インデントの好き嫌いとか、命名規約を無視したらこんなコードを記述する人が多いんじゃないだろうか。

using (var s=new FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.Read)) {
//
// 処理
//
}

掻い摘んで要点を挙げるなら

  • ファイル操作中に例外が発生した際に、ファイルが開きっぱなしになるのを防ぐには using ステートメント使用の一択
  • 既存のファイルを読むだけなので、ファイルモードは Open
  • 読み込み限定なので、ファイルアクセスは Read
  • 処理中に書き換えて欲しくないので、共有モードは Read

実際のところ、これで殆どの場合はうまくいく。まあ、殆どというのは全部じゃないっていう意味でもあるんだけど、、、。

ここで例外が吐かれる(FileStream オブジェクトの構築に失敗する)条件はいくつかある。ただ、path の示すパス情報が不正である場合や、ファイルが見つからないというような、使用条件が満たされていないといった類のものを除くと、検討しないとならないものは多くないはず。ただ、この例外はあまり対人スキルが高くないのか、普段はあまり人前に姿を現さない。納品直前の深夜とか、デモンストレーションのクライマックスに差し掛かる時とか、、、あまり、逢いたくない時にかぎって赤バッテンを伴い姿を現す。理屈が解らないまま実装してしまったツケが、時間のない時に返ってくるのはよくある話?

プログラミングには不具合がつきものだという意見がある。とは言っても見つけてしまった不具合を放置するわけにもいかず、「これは仕様です」と突き放すこともできないことだってある。さすがに赤バッテンを仕様と認めるマネージャはいないと思うけどね。そして、潰すと決めたからには、事象を再現させることがデバッグの第一段階になるはず。再現手順さえ突き止められれば、ソースコードを虱潰しに洗うなり、野生の勘に頼るなりしながら、原因を探す第二段階に突入していく。ところが、人見知りの激しい不具合は、事象の再現のために仕掛けられた様々なトラップを嘲笑うかのように姿を隠してしまうことがある。普段と様子が違うことを認識しているように。

対処の厄介な例外というのは、大抵は複数の条件が整った時にのみ現れると相場が決まっているように思う。そして、条件が整う確率は極めて低いことが多い。稀に、この低い確率を数だけでカバーしようとする輩がいる。「確率 100万分の 1 なら、1 秒間に 1000 回テストすれば 20 分も動かしておけばヒットする。」という理屈(本人に聞いた♪)らしいが、条件が整わないなら時間の無駄だと思うんだけど。


今回の件の落としどころは、共有モードの指定の仕方にある。それ以外の検討はまず不要。

上記のコードを使用した場合に発生する厄介な例外というのは System.IO.IOException というもの。MSDN Library を見ればその概要は解るけど、どうも要領を得ないという感じは否めない。実際に例外を起こして、そのメッセージを見るのが一番解りやすかった。

別のプロセスで使用されているため、プロセスはファイル 'パス' にアクセスできません。 
パスの部分には実際のパスが入ります。

 メッセージを見ると解る通り、同時に同じファイルを使用した場合に発生する例外であり、それは共有モードの指定が誤っているという事になる。ちなみにプロセスというのが若干引っかかるんだけど、実は同一プロセス内の共有の場合でも同じメッセージになる。

また、同一ファイルを共有する処理が全て FileAccess.Read を指定している場合、この例外は発生しない。誰かが同時に書き込みをするような場合に発生する可能性が出てくる。この同時が事象の発生確率を下げる要因。通常の処理でファイルを開きっぱなしにすることはまずない。必要になったら開き、不用になれば閉じる、、、なので、開いている時間が極端に短く、複数の処理で同時にファイルをアクセスしようなんてことは起きない、、、滅多に。

解決方法はこれ。左記の要件をリプレイスしよう。

  • 処理中に書き換えを許すので、共有モードは ReadWrite 

自分は読み込みしかしないのに ReadWrite の共有を許すのは違和感があるかもしれない。ドキュメントにもあまり詳しくは記述されていないようなので以下のように説明を付けてみます。

 登場するのは読み書き処理を実行する RW と、読み込み処理を実行する RO。

  1. RW が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは RW、共有モードは Read に設定する。
  2. RO が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは Read、共有モードは Read。これは失敗するケース。
    この際に既に別のストリームが開かれており、ReadWrite が指定されていることが解る。共有モードに Read ではこれを認められないので IOException となる。
  3. RO が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは Read、共有モードは ReadWrite に設定する。
    この際、既に別のストリームが開かれており、ReadWrite が指定されていることが解る。共有モードが ReadWrite なので処理は成功する。自分自身が要求するのは読み取りだけだけど、他人が書き込むことは許容するという広い心を持つことが無駄な例外を避けるコツともいえる。

WR と RO の起動順序が逆転する可能性も考慮する必要がある。

  1. RO が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは RW、共有モードは Read に設定する。これは失敗するケース。
  2. RW が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは ReadWrite に設定したいのだけど、共有モードが Read のため IOException となる。
  3. RO が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは RW、共有モードは ReadWrite に設定する。こっちは成功するケース。
  4. RW が起動し、ファイルアクセスのためにストリームを開く。ファイルアクセスは ReadWrite、共有モードは Read を設定する。
    ちなみに、このままでは、もうひとつ ReadWrite アクセスする処理が同時に走ると、そこで IOException になる。

いかがでしょう?

ファイル操作を無理やり競合させて動作検証するために作ったコードがこれ。これだったら確率なんか気にせずにすむはず。先ほどの例外のメッセージは catch の処理を適当に書き換えて出力させてもの。

using System;
using System.IO;

namespace app {
class Program {
delegate void Test(string path, Test test);

static void ReadOnly(string path, Test test) {
try {
using (var s=new FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
test(path, NoOperation);
}
}
catch (Exception e) {
Console.WriteLine("ReadOnly: "+e.GetType().ToString());
}
}

static void ReadWrite(string path, Test test) {
try {
using (var s=new FileStream(path,
FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) {
test(path, NoOperation);
}
}
catch (Exception e) {
Console.WriteLine("ReadOnly: "+e.GetType().ToString());
}
}

static void NoOperation(string path, Test test) { }

static void Main(string[] args) {
string path="./test.txt";
using (System.IO.File.Create(path)) { }

ReadWrite(path, ReadOnly);
//ReadOnly(path, ReadWrite);
}
}
}

鶏が先 [Programming]

ここに 2 つのライブラリがある。ともに Visual Basic で記述されてた実用性のないサンプルプログラム。ひとつがこれ。

Public Class Lib1
Public Sub Test1()
Dim lib2 As New Lib2()
lib2.Test2()
End Sub

Public Sub TestA()
Console.WriteLine("TestA")
End Sub
End Class

そして、もうひとつがこれ。

Public Class Lib2
Public Sub Test2()
Dim lib1 As New Lib1()
lib1.TestA()
End Sub
End Class

装飾は極力なくしたつもり、、、なので、Visual Basic に不慣れな方でも数分眺めていたら、何を意図したものか解っていただけるのではないでしょうか。

Lib1 のメンバー Test1 が、Lib2 のメンバー Test2 を参照していて、Lib2 のメンバー Test2 が Lib1 のメンバー TestA を参照している、相互参照とか循環参照という状態を作り出している。 Test2 が Test1 ではなく TestA を参照しているのは、そうしないと実行時にスタックオーバーフローを誘発してしまい(無限再帰)論点がぶれてしまいそうだったから。

この 2 つのライブラリ、(一応)ビルドして実行することが可能。Lib1 のインスタンスを構築して Test1 を呼び出してやれば、巡り巡って TestA を呼び出すことが可能。

Lib1 を含むライブラリをビルドするためには Lib2 を含むライブラリが必要だし、その逆もまた真。ヒントだけ言うとしたら、なんとかして、一方のライブラリを作り上げてしまうことが必要、、、嘘でもいいから。タイトルに挙げたのはコレ、動かない卵ではなく、取り敢えずでも動く鶏が必要。

このような状態のライブラリが、本当に必要なのかは解らないし、保守業務ははっきり言って困難を極める。それでも使いたいっていう人が少なからずいるのには、ちょっと呆れてしまう。はっきり言ってこれは、バッドノウハウ。一応、Visual Studio でもビルドできるようだけどねぇ。


縛りプレイ [Programming]

コーディング規約という名の縛りプレイ、プロジェクト毎、グループ毎、発注元毎といろいろあるようだけど、結構理不尽なのもある。

  • goto 禁止
    goto 文を規制しているところは結構多い。goto 文は一切禁止と定めているところや、条件付きで認めるというところもあるけど、あまり推奨するところはない感じ。
    普通にコード書いていると滅多に使いたくなることもないけど、C# だとちょっと微妙なことがあるね。C# では switch 文の中でフォールスルーが認められないため、特定の case 文に遷移したいときには goto 文を記述しないとならない。ここでの goto 文は、使わなくてもなんとかなるけど、コードが膨れ上がるのが難点。
  • do 文禁止
    C# や C++ では、後評価の繰り返しを do 文と呼んでいる。Visual Basic では Do ~ Loop While となるもの。
    Do 文では、Do 文の中に記述されたステートメントを最低でも 1 回は実行される。使用頻度は大して高くはないと思われる。私は Do 文をトリックに使うことが多く普通の用途で使うことはまずないんだけど、これを禁止されてしまうと for あるいは while 文を使ってループを考えないといけなくなるのが面倒。
  • while 文禁止
    C# や C++ 特有の話になる。
    C# や C++ の for 文は、while 文のスーパーセットになっている。なので、while 文で記述できていたものを for 文で書き換えるのは難しいことではない。数が多いと手間ではあるけど。
  • 無限ループ禁止
    C# では for(;;) { 略 } のように書くのが一番手っ取り早い無限ループ。多くの場合、終了条件が全くないわけではなく、ループ内で脱出条件を満足すると break するような記述が多いんじゃないだろうか。
    規則に合わせるために、無駄に条件を付加してしまうと、コードは見苦しくなるのが常。
  • マクロ禁止
    C++ では #define 文を使ってマクロを記述することができる。MIN や MAX のように関数のように扱うことが可能なマクロも存在している。既存のマクロの使用を禁止してしまうとかなり弊害が出そうだけど、「作るな」というのであればなんとかなる!?
  • テンプレート禁止
    C++ のテンプレートは超の字がつくほど高機能。C# や Visual Basic でジェネリックと呼ばれる機能が近いのだけど、テンプレートとジェネリックでは自由度が全く異なる。確かにテンプレートを使い倒すと STL のように何がしたいんだか訳の解らんコードになることもあるけど、完全に規制されてしまうのも寂しい感じがする。
  • 拡張 Method 禁止
    .NET Framework では LINQ と呼ばれる機能が提供されている。要は、コレクションの中から条件にマッチするものを探してくるためのものなんだけど、LINQ で遊ぼうと思ったら拡張 Method の使い方を覚えるのはほぼ必須。標準的に備わっているものだけでもある程度事足りるんだけど、いろいろ覚えてくると自分でも作って見たくなっちゃう。
    メンテナンス性があまりよくないので進んで使ったりはしないのだけど、ダメって言われると使いたくなる。
  • var 禁止
    C# や Visual Basic では型の推論をしてくれるようになっている。Visual Basic の場合、推論の有無にかかわらず変数の宣言は Dim ステートメントを使うのだけど、C# では推論を効かせたいときに var を使う。
    推論は型を指定しないで使うわけではなく、コンパイラが文脈から型を割り出してくれるというもの。dynamic 型とは異なり、コンパイル時に型が決定する。
    昔々、Variant 型を乱用して痛い目に逢った連中が、「それは危険だからよせ」と言っているように聞こえるのは私だけだろうか。

と、まあ、あまり大勢に影響のなさそうなものから、思考の妨げになりそうなものまであるけれど、個人的にはあまりきつく縛らないで欲しいと思う。


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。