トップ 追記

日々の破片

Subscribe with livedoor Reader
著作一覧

2009-07-04

_ 産み出す文化と保持する文化

相変わらずSOAPとRESTについて考える。もちろん理由もあるのだが、第三者の目で眺めて興味深いからでもある。

たとえば、WADLを眺めて、なんじゃこりゃと感じる。

LLとHL(そういえば、HLとは言わないな)があって、LLの人は既にハッピーなのだが、そこにHLの人がやってくる。なぜかは知らないがLLの人に「型宣言がないなんて信じらんない」とか言い出したりしてそこらじゅうにシグネチャを埋め込ませようとする。

いや、そんなものなくても困らないから、と言っても聞く耳を持たない。まるでイヌイットに海水パンツを売りに来たり、南洋諸島にオイルヒーターを売りに来たりする商社マンのようである。どうやら、それがなければならないらしい。もっと適切なのは、バナナがなりまくっているフィリッピンの小島のひとつに、バナナ架けを売り込みに来た商社マンであろう。さあ、君たち、バナナ架けを買い給え。これがあればバナナは黒くならない。

いや、それが必要な場合は知っていて、とミンダナオの人が答える。今度日本へホームステイするから、そのときはお土産に買ってくよ、とか。

でも商社マンは納得しない。今、ここになければならないし、今、ここで利用しなければならないのだよ。坊や。なぜなら定義言語があれば、自動でクライアントを作れるじゃないか。

いや、別に自動でクライアントが作れなくても、裏庭で新しいバナナをもいでくれば済むから。というか、食いきれないほどもがないし。とミンダナオの人は答える。

それはおかしい。と商社マン。ストックしなければだめだろ? 文明というものは銀行とともにあり、流通とともにあり、どちらもストックだ。

そうは言ってもねぇと、目の前のバナナをもいで食いながら、仮にもぎ過ぎて黒くなったら、別に、また書けば済むじゃん。というか、そのためのLL。

そこで考えるに、HLでリファクタリングというのは、文化的な垣根を破壊する作業なのかも(同一性の問題についても考え方が異なる。ダイジェストが同じでなければ同じプログラムではないという考えと、機能が同じであれば同じプログラムという考え方。何をもって同一とみなすかというのも考え方の相違だから理解しあうのは難しいのだろうな)。だけど、垣根はあまり壊れないよね、というよりも、壊れる垣根はそもそも垣根の役に立っていないし。


2009-07-03

_ 手ぬぐいと納税者は絞れるだけ絞れと行政官は言った

流れよわが涙、と警官は言った (ハヤカワ文庫SF)(友枝 康子/フィリップ・K・ディック) 流れよわが涙、と警官は言った (ハヤカワ文庫SF)(友枝 康子/フィリップ・K・ディック)

昨日書いた後悔先に立たずの公開だが、ぶくまこめで「。与える情報を絞るのではなく、与えた情報を絞らせるように設計すべき。」に対する反応があって、それも正しい反応だと思った。もっとも2行あるのは例示のあやなので、ちょっとそこに突っ込まれてもとは思うが。

たとえば、次に示すBase64EncoderクラスのAPIは何がなんでもおかしい。

public class ComponentInfo {
    public byte[] getGuid() { ... }
    public byte[] getCpuInfo() { ... }
}
...   
public class Base64Encoder {
    public String encodeGuid(ComponentInfo info) { ... }
    public String encodeCpuInfo(ComponentInfo info) { ... }
}

これはどう考えてもあり得ないでしょう。こんなメソッドの決め方をしていたら、一体、どれだけメソッドが必要となるか。正しくは、

public class Base64Encoder {
    public String encode(byte[] data, int offset, int length) { ... }
    public String encode(byte[] data) { ... }
    ...
    public Reader encode(InputStream stm) throws IOException { ... }
}

とすべきだ。クラス名もBASE64エンコーダだし。

この例のBase64Encoderクラスのようなオブジェクトは、呼び出しの末端に来て、この中で別のオブジェクトをコンポジションしていることもなければ、他のオブジェクトをラップすることもない。このタイプ(つまりはユーティリティだ)のオブジェクトが実装すべきAPIは、絞りに絞ったものとすべきだ。具体的には、プリミティブとせいぜいjava.langパッケージのオブジェクトとその配列。もっとも、上の例ではjava.ioのオブジェクトを含めているが、ケースバイケースではそれもありだろう。

しかし、それは逆に言えばファサードのような他のオブジェクトのコンポジションクラスであれば、絞れば良いということではない、ということだ。

次のクラスは、情報を収集して、最後にレポートを出す。

public class CompoenentInfoSet {
    /**
     * {@link com.example.foo.ComponentInfo1}オブジェクトのGuidをレポート用に設定する。
     * @param guid 対象クラスのCompoenetInfoが返すGuidの値。
     */
    public void setRawId(byte[] guid) { ... }
    /**
     ....
    public void setHash(int hash) { ... }
    ...
    public String report() {
        ...
    }
}

利用するコードは以下となる。

....
ComponentInfo ci = new ComponentInfo(this.getClass()); // と何気なくnewを使って自滅するのであった
...
CompoenentInfoSet set = newCompoenentInfoSet();
set.setRawId(ci.getGuid());
set.setHash(this.getClass().hashCode());
...

ここで、CompoenentInfoSetクラスのAPIは一見すると妥当なように見えるかも知れない。が、それは違う。

この時点では、RawIdとして与えるのはCompoenetInfo#getGuidで、Hashとして与えるのは、selfが属するクラスのhashCodeかも知れない。

しかし、5年後の夏には、GuidだけでCompoentInfoを示すのでは困るかも知れない。そのために、拡張が必要となったとしよう。

public class ExtendedCompoenentInfoSet extends ComponentInfoSet {
    public void setCpuInfo(byte[] cpuinfo) { ... } // 追加

もちろん、5年前には想像もしていなかったことだ。

で、それはそれとして、元のプログラムも現役で稼働しているのだから、同じく継承クラスを作って、でも、基本的な処理は元のクラスと同じだから、と見てみると

....
ComponentInfo ci = new ComponentInfo(this.getClass()); // と何気なくnewを使って自滅するのであった
...(10行程度のフラットな処理)
CompoenentInfoSet set = getCompoenentInfoSet();
set.setRawId(ci.getGuid());
set.setHash(this.getClass().hashCode());
...(その他20行のフラットな処理。しかし最後にはデータベースへレポートを保存するような二度と書きたくはないようなコードもある)

幸いにも、ComponentInfoSetは、ファクトリメソッドパターンのインスタンスで生成するようになっている。が、残念、使えない。どうやって、新しく追加したsetCpuInfoメソッドを呼べば良い? ファクトリメソッドで割りこめても、cpuInfoの元ネタのci変数にファクトリメソッドからアクセスすることは不可能だ。

かくして、コピー&ペースト再利用となり、メンテすることになるコード資産が倍増した。

もし、以下のようであったらどうだろうか?

public class ComponentInfoSet {
    /**
     * {@link com.example.foo.ComponentInfo1}オブジェクトが持つGuidをレポート用に設定する。
     * @param ci 対象クラスのCompoenetInfoのインスタンス。
     */
    public void setComponentInfo(ComponentInfo ci) { ... }
    /**
     ...
    public void setClassObject(Class c) { ... }
    ...
    public String report() {
        ...
    }
}

つまり、ComponentInfoSetクラスは、別に呼び出し側がわざわざオブジェクトからバイト列を抜き出してやらなくても、自分がどの情報を利用して何をするかは知っている。だからこそ、元々のメソッド名も、setRawIdだったり、setHashCodeだったりしていたわけだ。

これはDRY原則に通じる。すべてのオブジェクトが何をするかの詳細を知っている必要はない。詳細はシステムの末端(ただし、冒頭で示したようにユーティリティなどの「機能」特化オブジェクトは除く)のオブジェクトが知っていれば良い。知識をあまねく分散させたり中央集権でやるのではなく、知識(というよりもそれを利用して果たすべき責務)を適切に分配することが重要だ。

このクラスを利用するコードは元のコードから次のように変わる。

....
ComponentInfo ci = new ComponentInfo(this.getClass()); // と何気なくnewを使って自滅するのであった
...
CompoenentInfoSet set = newCompoenentInfoSet();
set.setComponentInfo(ci);
set.setClassObject(this.getClass());
...

一方、ExtendedComponentInfoクラスは次となる。

public class ExtendedComponentInfoSet extends ComponentInfoSet {
    /**
     * {@link com.example.foo.ComponentInfo1}オブジェクトが持つGuidとCpuInfoをレポート用に設定する。
     * @param ci 対象クラスのCompoenetInfoのインスタンス。
     */
    public void setComponentInfo(ComponentInfo ci) { ... }
    ...
    // もちろん、reportメソッドの実装なども変わる。
}

もし、こうであれば、5年後には、単にnewComponentInfoSetファクトリメソッドをオーバーライドして、ExtendedComponentInfoSetクラスのインスタンスを生成して返すように変えたクラスを作れば、すべての作業が完了する。コードの重複も生まれない。既存の処理に対する影響はゼロ。もし、データベースをいじくるところで問題が出たりしたら、その時にはじめて、本格的に元のクラスのコードのコピー&ペーストなりを行えばよい。

たったひとつの冴えたやり方は、ケースバイケースにいくつものやり方から適切な方法を選択して設計することだ。

たったひとつの冴えたやりかた (ハヤカワ文庫SF)(ジェイムズ,ジュニア ティプトリー/浅倉 久志/ジェイムズ・ティプトリー・ジュニア) たったひとつの冴えたやりかた (ハヤカワ文庫SF)(ジェイムズ,ジュニア ティプトリー/浅倉 久志/ジェイムズ・ティプトリー・ジュニア)

2009-07-02

_ 5年後に後悔しないJavaプログラムの書き方

ここ数日、死ぬほど後悔しまくっているので、あらためて(というのは、数年前にも一度後悔しまくって、そのときの知見はあらかた処方箋とかコーディングの掟に書いているからだが)後悔しないための書き方をいくつか紹介する。

とにかく、ファクトリメソッドパターンを使うこと。

これは本当に重要。しかも簡単でありながら効果は絶大。

だめな例。

public class FooBar {
    private Connection conn;
    ...
    protected void setup() {
        ...
        conn = DriverManager.getConnection(url);
        ...
    }

urlを指定することや、DriverManagerの実装を交換すれば良いだろうと想定していても(というか、Connectionならそういう方法もあり得るが、そうはいかないものも多い)、そのための手間が大き過ぎれば意味がない。

良い例。

public class FooBar {
    private Connection conn;
    ...
    protected void setup() {
        ...
        conn = newConnection(url);
        ...
    }
    protected Connection newConnection(String url) {
        ...
        return DriverManager.getConnection(url);
    }

利用方法

public class FooBarTest extends TestCase {
    FooBar target;
    protected void setUp() throws Exception {
        target = new FooBar() {
            protected Connection newConnection(String url) {
                return new MockConnection();
            }
        }
    }

ファクトリーメソッドパターンを適用しておけば、オブジェクトの生成時に割りこめる。当然だが、効果は絶大。

フィールドはパッケージプライベートにすること。

この問題は比較的すぐに気づいたのだが、いかんせん、本当にインフラ中のインフラになってしまったコンポーネントはごく初期に開発したためにprivateを使い過ぎていて、厄介なことこのうえない。

だめな例。

public class FooBar {
    private Connection conn;
    ...
    protected void setup() {
        ...
        conn = DriverManager.getConnection(url);
        ...
    }

良い例。

package com.example.foo;
public class FooBar {
    Connection conn;
    ...
    protected void setup() {
        ...
        conn = newConnection(url);
        ...
    }

ファクトリメソッドパターンを適用すれば万事解決だと思うとそうはいかないことが稀にある。稀にしかないからこそ、ちゃんと用意をしておいたほうが良い。

利用方法

package com.example.foo;
public class FooBarTest extends TestCase {
    ...
    public void testSpecialCase() throws Exception {
        target.doSomething();
        MockConnection stat1 = (MockConnection)target.getConnection(); // #getConnectionはprotected
        target.conn = new MockConnection();
        target.doSomething();
        assertEquals(stat1.getCondition(), ((MockConnection)target.getConnection()).getCondition());
        ...

ここで、フィールドをprivateにしたままでパッケージプライベートなsetConnectionメソッドを導入するという代替案を考え付くかも知れないが、メソッド呼び出しとフィールドの上書きはユースケースが異なるので、上記のような処理の役には立たない。仮に立つのであればその程度の利用方法なので、あまり考えてもしょうがないとも言える。

セッタメソッドの公開とフィールドの公開の両立が必要な例

package com.example.foo;
public class MoreFooBar extends FooBar {
    void setConnection(Connection c) {
        ...               ← この部分の迂回が必要な状況(開発後1年で必要になるとしたら、それは設計ミスだとは思うが)
        super.setConnection(c);
    }

追記:上に関連してメソッドを(構造化の要領で)機能単位に分割する(サブルーチンのインスタンス化)というのがここに入る。

可能な限りDIコンテナを利用する

これは今となっては当然のことだが。

オブジェクトの利用方法はオブジェクトに任せる

悪い例

protected void foobar(Baz baz) {
    ...
    AR ar = newAR();
    ...
    ar.foo(baz.getBzz());
    ar.bar(baz.getBzz());
    ...
}

良い例

protected void foobar(Baz baz) {
    ...
    AR ar = newAR();
    ...
    ar.foo(baz);
    ar.bar(baz);
    ...
}

arをファクトリメソッドを利用して交換可能にしたにも関わらず、限定的な引数を与えているため、Bazオブジェクトが持つ情報をarは利用できない。その時点ではAPIを狭くした良いインターフェイスと考えていたとしても、処理は精緻化複雑化するものだ。与える情報を絞るのではなく、与えた情報を絞らせるように設計すべき。(で、上の例であればファクトリメソッドをオーバーライドしてARを入れ替えるだけでは済まないので、結局foobarメソッドをすべてコピー&ペーストして2行だけ変更するということになってしまう。これではファクトリメソッドを導入してもそれほど役に立たない)

追記:これについては補足した。

将来を見通すことはできないということを肝に銘じる

3年程度の短期寿命と考えていても、リリースしてしまえば中期、長期にわたってソフトウェアは使われることがあるという2000年問題の教訓は今でも有効だ。

つまり刹那的インターフェイスを避ける。

刹那的インターフェイスというのは、強い制約をもたらすものを指す。上に挙げた例では、new、private、オブジェクトの部分的な引き渡しがそれにあたる。他にもたった一つのfinalメソッドのために、クラスをまるまるコピー&ペーストして別クラスを作らざるを得ないというような例もある。というわけでfinalも極めて刹那的なインターフェイスだ。

ここで挙げた内容は、ミドルウェアやフレームワークの内部のオブジェクトにしか当てはまらないと考えることもできるが、必ずしもそうではない。これらは、プログラムの寿命が長ければ長いほど重要になる。なぜならば、寿命が長いということは、そのプログラムのコードの信頼性が高いということであり、それはつまり元にしやすいということに通じるからだ。

コピー&ペーストと、継承と、コンポジションの3種類の再利用方法があり、いずれも一長一短がある。コピー&ペーストはたまたま発覚しなかったバグが見つかった場合に大きなダメージとなる。コンポジションの場合は狭過ぎるインターフェイスによって拡張に耐えられなくなる。継承は細切れな派生クラスの深いツリーによって手が負えない塊になる危険を持つ半面、長期的に成長するソフトウェアに大しては安定した基盤を提供できる(コンポジションと異なり狭いインターフェイスの問題は、内部に食い込めるだけに抑制できる。結局、コンポジションと継承はどちらも重要なのである)。

Javaプログラミングの処方箋 (Programmer’s foundations)(宇野 るいも/arton) Javaプログラミングの処方箋 (Programmer’s foundations)(宇野 るいも/arton)

大体、ここに書いたことの通りだ。

本日のツッコミ(全1件) [ツッコミを入れる]

_ [HIROKI] [#1・フィードバック法定式に応用するのには、自分的に扱い易い。リーダータイプナのかも知れない・・此処一年以内に、活用..]


2009-07-01

_ 今日(昨日だけど)は期末だったのか

というわけで、るびまも出ているけど(RubyKaigi大特集!)、Firefox3.5が出たので、更新した。

これで、IE7程度の速度(ずーっと起動していた場合の話)になれば良いのだが。

_ 新しい世界にやって来た古い世界の人たち

ローザとコルンゴルトは知っていたが(ハイフェッツの弾いた協奏曲集はお気に入り。映画音楽家としてはローザのムーンフリートは本当に素晴らしい)、ヒンデミットもアメリカの人になっていたのは知らなかった。退廃芸術家とされたのかな。

コルンゴルド&ローザ:ヴァイオリン協奏曲&ワックスマン:カルメン幻想曲 コルンゴルド&ローザ:ヴァイオリン協奏曲&ワックスマン:カルメン幻想曲

バレエ音楽が嫌いなせいもあって、ストラヴィンスキーはアメリカ時代のほうが好きでアゴンは良く聴くと思ったけどアゴンもバレエ音楽だ。プルチネッラを出せばよいのか(これもバレエだ)。

ストラヴィンスキーと言えば、何かの映画で主人公がハリウッドでゆうゆうと暮らすストラヴィンスキーの家を覗きに行って睨まれるというエピソードが描かれていて、それがイメージ通りの偏屈なロシア人そのもののそっくりさんでえらく笑ったのだが、はて何の映画だったろうか。グッドモーニングバビロンかな?

コルンゴルトという生き方は興味深い。ありあまる才能に恵まれて神童、天才の名をほしいままにしていたのに、あるいはそれゆえに時代が変わる足音に鋭敏に反応してしまう。そこで新しい世界に行き、新しい世界に身を投じる。そこでも神となる。しかし、そこで神となっている間に生れ故郷では彼のスタイルは唾棄すべき古臭いスタイルとなっていて、しかも新世界の神というのは旧世界では単なる邪教への改宗者でしかなく、居場所がなくなり身悶えしながら忘れ去られて死んでいく。

続きがあって、新世界でもみんなが忘れ去った後に、こんだ旧世界の新芸術界(つまり映画)の作家たちがその偉大な存在に気づき、復活を遂げる。それがたった100年の間の出来事だ。

という具合でテレピン(本当にそう読むのかなぁ)は知らないけど、コルンゴルトとローザ(ミクロシュ・ロージャが正しいとは思う)は好きなので、New World Composers from the Old Worldを買おうとしたが、ちょっと考えた末、保留。


2009-06-30

_ 補償トランザクション

補償トランザクションの実装はそれなりに難しいわけだが、システム更新番号(あるいはそれに準ずる機能)を利用するのはどうだろうか?

並行処理が行われている時、システム更新番号はすごい勢いで変化していく。たとえば、あるトランザクションの開始時に1000だとして、終了時に1001ということはない。というのは、別のトランザクションが処理している可能性があるからだ。

しかし、そのトランザクションが更新したロウに限定すれば以前の状態を取り出せるはずだ。

実際には、トランザクションの開始時点で取得したとしても、他のトランザクションの更新待ちになる可能性があるわけだから、Oracleに限定すれば、あるロウについて必ず悲観ロックを実行し、それからシステム更新番号を取り出し、そして更新したらどうだろうか。

もし複数のロウを更新するのであれば、最初にすべての対象となるテーブルのロウに対して悲観ロックを実行する。そしてシステム更新番号を取り出し、次にすべてのロウを更新する。

トランザクションの開始時にDAOにロウデータをフィルした状態で用意すればそれなりの単純さで実現できる。

トランザクションのIDとそうやって取得したシステム更新番号をペアで持つ。

と、考えたが、そのトランザクションの処理後に更新されたらだめだ。やはり差分で補償するしか無いのかなぁ。


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|

ジェズイットを見習え