プログラマの三大美徳に怠惰というのがあるが、これを単なるサボりではなくアーキテクチャとしての怠惰を実現するための方法について書きたい。
この方法は、よく知られている常識的な方法であるにもかかわらず、あまり語られる事がない。
勤勉なプログラマの書くプログラムは、どこか他の場所で起きた変更に伴って連鎖的にゴチャゴチャと手元のコードを変更する必要があるため、一年中コードを変更してるし、しょっちゅう止まる。勤勉なプログラマの間ではそれが当然だと思われているが、これはプログラマの美徳に反する。
筆者個人のエピソードも交えつつ、色々と考えたい。
1.イテレータとぶん回し
昔々、プログラマになりたて(私のキャリアのスタートはいわゆるITドカタだった)の頃の話。
業務系システムのゲンバではこんな感じのコードが沢山書かれていた。
hMap.set("storeCode")=result.get("sotoreCode")
hMap.set("orderId")=result.get("orderId")
hMap.set("actionDate")=result.get("actionDate")
//以下プロパティの数だけ続く
これは、業務系システムインテグレーションがCobolのような世界から流れてきたという歴史的経緯があったのだろう。どこかで属性が一つ追加になれば、コードを連鎖的に変更する必要がある。事実、皆が瑣末な変更作業に日々追われていた。
スマホ登場以前の日本のSIのゲンバはだいたいこんな感じで、僕もあまり疑問に思わなかった。無知だったのである。
しかし、ある日こんな書き方ができることを発見した。
for( keyName: result.getKeys()){
hMap.set(keyName)=result.get(keyName)
}
なるほど???プロパティみたいなものもデータとして扱えるんだ、という気づきを得た私は、ここに何か本質的なものがある、という直感を持った。
しかし、この頃の僕はモノを知らずこの衝撃を言葉にする事ができなかったが、これはイテレータによる集合論的プログラミングの一種だと今なら言える。
1年後、リフレクションの存在を知り静的なものもデータとして扱えることに驚き発狂した。これについては、あとで詳しく。
2.バリデータの啓示
同じころの同僚で、WebアプリのValidator(入力値検証)を実装してる人がいた。
当時はWebアプリケーションフレームワークの普及前で、入力値検証をコードとして書くことが一般的だった。1の話を見てもそのコードの雰囲気はわかると思う。if文を書き連ねるあのスタイルである。
同僚が書いたプログラムはこんな感じの雰囲気だった。今風にちょっと翻訳して書くが本質は一緒だ。
validateSetting = {fields{
fieldName="userID", length="10", type:Number ,,,,
これが、とある一画面のロジック(かなしい)として書かれていた。
「ちょっと、マニアックなことをしたよ」と同僚は恥ずかしそうに誇らしそうに言った。これは宣言的プログラミングとか呼ばれるものだが、そんな言葉も我々は知らなかった。
これは、今ではメタデータとか呼ばれる感じのもので設定ファイルとしてJsonで書かれたりする、ありふれたものだ。
しかし、当時のデジタルドカタ領域ではこのような発想のものは少なく、私は啓示をうけた。
つまり
- 一つ一つのオブジェクトに対してプログラミングしない(ピストルで戦わない)
- 機能を抽象化してデータとして「ぶん回す」ことでソフトウェアを構成する(マシンガンをぶん回せ)
「ぶん回し」というのは、集合演算の渦巻きのことだ。
3.お役所の生真面目な精神はDSLを産む
もうちょっとマシな仕事についたころ、とあるスマホアプリのバックエンドを作っていた。お役所のレガシーシステムから色々な種類のデータをダウンロードして自分達のデータベースに入れる必要が出てきた。
古の謎プロトコルでやってくるデータの説明はPDFドキュメントとして提供され、分厚い「インターフェース仕様書」には300ほどのインターフェースがあり、全て繰り返し許容で固有のデータ型を持った固定長ファイルという感じだった。偉大なるレガシー。
この頃の私は、Scalaという関数型言語を使っており、柔軟な型システムの可能性を感じていた。
インターフェース仕様書は、役人らしき生真面目さで書かれており例外的な記述が一切見当たらなかった(すごい)
そこで私は思い立って、Scalaのアノテーションと型の組み合わせを使ったインナーDSLのプロセッサを開発し、固定長ファイルを静的な任意の型へマッピングする仕組みを作った。インナーDSLはpdfドキュメントと100%互換のセマンティクスを持ち、これをスイスイ定義すると、その仕組みは100%正しく動作し、インターフェースの変更にあたってプログラムの変更は不要であった。
これは戦略的怠惰である。
4.設定ファイルだってぶん回せる
ここまでの話は、わりと色々なミドルウェアとかフレームワークでお馴染みと考え方、つまり汎用的なソフトウェアの作り方の話である。コンポーザブルなシステム、柔軟なソフトウェア、バグらないソフト、拡張性のあるソフト、修正不要であらゆる場面で動作するプログラム、これらはこのように設計される。
たとえば、そのようなミドルウェアの一つにEmbulkがある。これはETLのためのミドルウェアであり、テーブルとそのフィールドのメタデータを設定ファイルに書く事でデータベース間の連携を可能にするものである。
あるECサイトのDWHを構築するときEmbulkを採用した。しかしテーブルが沢山ありこれを書くのはメンドくさいな、と思った。なので、設定ファイルを動的に生成する仕組みを作ってこれを回した。2でバリデータの話をしたが、これはもう一段上のメタレベルの話である。データベースのinformation_schemaからテーブル名とフィールドを取り出して、二重ループ的に処理する、という30行ぐらいのスクリプトを書いた。型の変換で難しいところは、これもISの情報でうまく処理を分岐していい感じになった。
updateやdeleteを許可してるテーブルかそうでないか、これはとても重要であった。データが変更される可能性のあるテーブルは、reolaceAllしか更新手段がないが、更新が無いテーブルであれば、差分を追加するだけで良い(append)方式である。
このECサイトは有名な汎用ミドルウェアで稼働しており、後者の方式のテーブルの全てに更新タイムスタンプが存在しないという真面目な設計になっていたため、更新方式というETLで重要な要件にも簡単に対応できた。 これで設定ファイルを10000行ほど書かなくてよくなった、数ヶ月かかると言われた仕事は一週間ほどで終わってしまった。
そして、このやり方は、元のデータスキーマに変更があっても動的なメタデータに依存し追従するので、数年運用してもプログラムの変更は不要であった。
怠惰は美徳である。
5.ゲームエンジンと「〇〇ゲームツクール」
ゲームエンジンのようなものを作ってた事がある。
大規模なゲーム開発では高度で大規模な分業体制が必要となり、ジャンル特化のゲームエンジンが活躍した。
これは、単なるソフトというより、あるゲームエンジンの上で動作するDSLをゲームデザイナー(レベルデザイナー)が書く事でゲームを開発する方法論である。ベタな要件⇨開発、みたいなプロセスではなく、ゲームジャンルを抽象化したゲームエンジンとDSL仕様が先にあり、具体的な要件がそこで動くイメージだ。
そう、これはゲームデザイナーのための「代替的プログラミング環境」であった。抽象性は、プログラマ以外の人にもゲーム開発の手段を与え高度な分業を可能にした。
6.抽象は集合演算に、具体は外部化せよ
現代の多くの柔軟なソフトウェアがそうであるように、修正しなくて良いプログラムは、プラグラマブルな仕組みを持ってることが多い。
動的モジュール結合の技術がこれを可能にする。ゲームなんかで、複雑でAdhoc(瑣末な具体)はこのような仕組みとしてDSLに組み込まれることが多い。つまり、DSLに、関数オブジェクト/ラムダ/動的ロード可能なインプリメンタ/コールバック関数/準同型、etc のような形式が埋め込まれ、ここがロジックの拡張ポイントとなるわけである
関数型の世界では、データもロジックも同じ、と言い伝えがある。
インターフェイスをimplする事は、そのプログラムを「ある型の集合の一つのインスタンス」として扱うことを意味しプログラムが様々な場面で使われる可能性を与える。
関数オブジェクトやラムダ式、コールバック関数、任意のインプリメンタは、抽象集合論的メタプログラミングの渦巻きの中に具体性を挿入する仕組みである。
リフレクション/マクロ/型変数/バイトコードエンジニアリング/information_schema/eval etc,,,は、オブジェクトレベルとメタレベルを繋ぐジャンプ台のような機能を持つ。
現代の多くの柔軟なソフトウェアは全てこのよう機構を備えている。
格言
-
修正に弱いプログラム< 修正に強いプログラム<修正しなくて良いプログラム
-
抽象化せよ、本質でない瑣末な具体性はオブジェクトで表現し挿入せよ
-
一階層上にジャンプしあらゆるものを集合演算としてぶん回せ
-
一見違うものの中に同じものを見つけ怠ける方法を考えよ