トップ «前の日記(2006-10-07) 最新 次の日記(2006-10-09)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2006-10-08

_ 開発の現場 Vol.6

開発の現場 vol.006(SE編集部)

るいもさんとの連載の2回目。ソース管理編の上(下は次号)です。

_ CSVとString#Split

新人のプログラムにこんなのがあった。(正確に再現する必要はないのでJava,C#混じりかも)
dictionary.Add(data.key, data.foo + "," + ConvertBar(data.bar) + "," + data.baz);
...(幾つかのメソッドが間に挟まる)
try
{
    string d = map[input.key];
    string[] data = d.Split(',');
    if (data[0] == input.Validation_key)
    {
        Command c = new Command("insert into ..." + data[0] + "','" + data[1] ...
        ...
    }
}
catch (NotFoundException e)
{
    throw new ArgumentException(input.key + " not found");
}
...

なぜ、ここでCSVを使うんだ? dictionaryへ元のdataを突っ込めばいいだろ?
と激しく質問。
だって、ConvertBarするし、dataは読み取り専用だからbarのセッタが無いし、……し、……し……
だったら、使う場所でコンバートしたらどうだ?

dictionary.Add(data.key, data.foo + "," + data.bar + "," + data.baz);
...
        Command c = new Command("insert into ..." + data[0] + "','" + ConvertBar(data[1]) ...

これは等価だろ?
確かに……
で、これが等価だということは、

dictionary.Add(data.key, data);
...
    FooData data = map[input.key];
    if (data.foo == input.Validation_key)
    {
        Command c = new Command("insert into ..." + data.foo + "','" + ConvertBar(data.bar) ...
とも等価だよな?
確かに……
で、最初のリストと最後のリスト、どっちが何をインサートしてるか(PreparedStatement相当のを使う/使わないはまた別の問題として)、カラムと整合性が取れているか、わかりやすいか? どっちが簡潔か?
なるほど。

(書いていて思ったが、Webアプリケーションのエスケープ問題と同根だな。なぜか変形した後のデータを保存したくなるらしい――ここでのinsert……は置いておくとして)

_ CSVとString#Split(2)

それからしばらくして見たらCSVが復活していた。
dictionary.Add(data.key, data.foo + "," + ConvertBar(data.bar) + "," + data.baz + "," + currentStatus);
...(幾つかのメソッドが間に挟まる)
try
{
    string d = map[input.key];
    string[] data = d.Split(',');
    if (data[0] == input.Validation_key)
    {
        Command c = new Command("insert into ..." + data[0] + "','" + data[1] ...
        ...
    }
}
...

はい?
いや、ステータスが追加になったんで……
なぜクラスを作らないんだ?
はて?
CSV作ってSplitするオーバーヘッドを考えたらクラスを作ったって同じだろ? (と、インスタンス生成のオーバーヘッドを言ってるのかと思った)
……(ぽかーん)
(あ、まったく考え付かなかったということか)たとえば

internal class FooWithStatus
{
    internal FooData data;
    internal string status;
    internal DataWithStatus(FooData d, string s)
    {
        data = d;
        status = s;
    }
}
...
dictionary.Add(data.key, new FooWithStatus(data, currentStatus));
...(幾つかのメソッドが間に挟まる)
try
{
    FooWithStatus fs = map[input.key];
    if (fs.data.foo == input.Validation_key)
    {
        Command c = new Command("insert into ..." + fs.data.foo + "','" + ConvertBar(fs.data.bar) ...
...
とするだけでも、全然ましだろ?
確かに……
別にクラスだからって、単なる名前つきのフィールドを持ったデータとして使ってもかまわないんだよ。(とりあえずこれでいいや)
なるほど

_ カプセル化

(すでに現実とは異なる展開)

あれ、妙に洗練されてきてるな?

internal class FooWithStatus
{
    FooData data;
    string status;
    internal DataWithStatus(FooData d, string s)
    {
        data = d;
        status = s;
    }
    internal FooData Data
    {
        get { return data; }
    }
    internal string Status
    {
        get { return status; }
    }
}
...
dictionary.Add(data.key, new FooWithStatus(data, currentStatus));
...(幾つかのメソッドが間に挟まる)
try
{
    FooWithStatus fs = map[input.key];
    if (fs.data.foo == input.Validation_key)
    {
        Command c = new Command("insert into ..." + fs.Data.foo + "','" + ConvertBar(fs.Data.bar) ...
...
せっかくのクラスだからちょっとカプセル化を意識してみました。
それ、違うから。
え、フィールドをアクセスしないようにしたんですけど。
まず、第一に、それに意味があるか良く考えてみな。このFooWithStatusといういい加減な名前のクラスはinternalだからこのプログラムのここでしか使わない。別に、プロパティメソッドにする必然は全然ない。だって、privateにするってことは誰かから隠したいということだよな?
ですね。
で、誰かってのは、このプログラム自身ということになるね。
確かに。
ということは隠してるのも隠されてるのもこのプログラムじゃん。それプラスマイナスゼロで、まったく意味無いじゃん。
うう、しかし……
それにカプセル化というのは、そういう意味じゃない。アクセス制御とは関係ない。どうしてもカプセル化したいんだったら、あまり意味はないとは思うが、こうかな……
internal class FooWithStatus
{
    FooData data;
    string status;
    internal DataWithStatus(FooData d, string s)
    {
        data = d;
        status = s;
    }
    internal string Status
    {
        get { return status; }
    }
    internal string Foo 
    {
        get { return data.foo; }
    }    
    internal string Bar
    {
         get { return ConvertBar(data.bar); }
    }
    static string ConvertBar(string s)
    {
        ...
    }
}
...
dictionary.Add(data.key, new FooWithStatus(data, currentStatus));
...(幾つかのメソッドが間に挟まる)
try
{
    FooWithStatus fs = map[input.key];
    if (fs.Foo == input.Validation_key)
    {
        Command c = new Command("insert into ..." + fs.Foo + "','" + fs.Bar ...
...
はあ?
内部にFooDataを持つというのは、利用者にとってはどうでも良い実装詳細に見える。だって利用しているのはfooという文字列、barの変換後の文字列……なわけだろ。
……
さらに、こうだな。
internal class Foo
{
    FooData data;
    string status;
    internal DataWithStatus(FooData d, string s)
    {
        data = d;
        status = s;
    }
    internal bool IsInsertable(string s)
    {
        return data.foo == s;
    }
    internal Command createInsertCommand()
    {
        return new Command("insert into ..." + data.foo + "','" + ConvertBar(data.bar) ...);
    }
    static string ConvertBar(string s)
    {
        ...
    }
}
...
dictionary.Add(data.key, new Foo(data, currentStatus));
...(幾つかのメソッドが間に挟まる)
try
{
    Foo foo = map[input.key];
    if (foo.IsInsertable(input.Validation_key))
    {
        Command c = foo.createInsertCommand()
...
これで、呼び出し側はFooDataなんかについてそもそも意識する必要がなくなった。入力されたキーに一致するFooが存在して、しかもインサートすべき状態ならば、インサートするためのコマンドを作ってもらって、実行する。どのオブジェクトにどの役割を与えるかを決定して分離すること。これがカプセル化ということだ。
なるほど。
ちょっと嘘か。カプセル化した結果は、個々のオブジェクトがそれぞれの役割に応じた処理をするようになること、だ。そのために、必要となる操作とデータをクラス単位にまとめることが、カプセル化。別にprivateかどうかは関係ない。ただ、カプセル化されていれば余分なものにさわる必要がなくなるから、さわる必要がないものをさわれなくするのにアクセス指定が使えるというだけのことだ。

_ ハスケラーにシープラスプラスを説明する

C++ for Haskeller

すさまくじ、おもしろい。と、ひっくりかえるくらにいおもしろい。

関数あたりは笑いを抑えるこたできないなぁ。途中、おれにはわけわかんないが、おもしろ過ぎてさらに最後で大爆笑。

こういうセンスってすげえ。

追記:あろはさんがまとめている

追記の追記:斉藤さんのawk for Haskeller

本日のツッコミ(全2件) [ツッコミを入れる]
_ yowa (2006-10-10 01:50)

追記のリンクがおかしいようです。

_ arton (2006-10-10 02:18)

どうもありがとうございます。直しておきました。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|

ジェズイットを見習え