呼び出し元駆動開発
テスト駆動開発(TDD)という考え方がある。
仕様漏れしてるテストをつくる→満たすようにつくる→リファクタリング
を繰り返す手法で、テストを作るのが最初にくることで仕様ベースで動作が保証されたコーディングができるのがメリットに挙げられている。
だが自分はこれに別の利点を見出した!
開発の方向が定まっていることで「何から手を付けるか問題」が解消されるのだ!
プログラミング何から手を付けるか問題
特定の仕様を実装する、というときに書かないといけないことは熟練してくるほどぱっと、膨大に思いつく。
たとえば特定のインスタンスをポリモーフィズムを担保しつつプールしたい場合。
インターフェース、適当な継承クラス、プールのためのリストとそれを管理するクラス、ついでに初期化処理と追加処理と必要なら削除処理でイテレータとかゲッタとかもほしい。そしてメンテナンスのためにカウンタとかデバッグ表示とかも入れることになる。
と、いうのがぱーっと脳内で弾けるとものすごーくやる気が下がる。めんどくさい。
ここで面倒になるのは、どれから最初に手をつけようか、という吟味する時間が生まれどこから始めても結局スムーズにはいかないのではないか、なんていう脳内全探索をしているからだ。結果的にどの探索も銀の弾丸にはならず、それで疲れた状態からそれなりの手順だなあと絶望しながら作業をやる気なく始めることになる。
呼び出し元駆動開発
そこで呼び出し元駆動開発(YDD:YobidashimotoDrivenDevelopment)だ。この開発手法ではまず呼び出し元を書く。
例えば、
var pool = new InstancePool<ElementObject>();
pool.Add(new ElementObject("Hoge"));
とか書く。当然これは該当クラスを作っていないのでエラーになる。このエラーを無くすようにコーディングしていく。
該当クラスでも使う要素とかクラスとか関数があるはずだ。そういったものも一旦ないのに呼び出す。そうして呼び出し先がないことを除けば機能の実装が終わり、上層の呼び出し元のエラーも消える。エラーが下層に移動したような状態になる。
これを繰り返せばいずれ下層に行きつけばエラーは消える。
たまに、呼び出し元が間違っていることがある。そのときはすこし戻って引数やらキーワードやらを直す。
これが呼び出し元駆動開発だ!
勘違いしないでほしいこと
テスト駆動開発みたいな雰囲気はあるが、この開発手法はテスト駆動のよく言われる利点を一切引き継がない。
テストではないので動作の安全性を担保しない。ただしコンパイルエラーによって静的な整合性は担保する。
仕様を網羅的に書いて気づきを得ることはない。ただし必要なぶんだけ書けばよいので書く量が楽。
メンテナンス時の関心の分離はできない。ただし書くときに関心の分離ができて十分なカプセル化が行われたメンテナンス性の高い構造を実現しやすい。
そしてこれは初心者向けではなく、開発が十二分に大変だということがわかって千里眼をもち、しかし闇雲さがなくなったがためにいつまでたっても手をつけられず実際の能力を発揮できないプログラマーのための手法である。どうすればメンテナンス性が高くポリモーフィズムを活かして構造化できるか、そういった大まかな構造が予めわかっていないとそもそも使うことができない。
ただしこれを使えば、少なくても個人開発にありがちな
「おいプログラマ、こういった仕様のプログラムを書けい」
「ひ~、結構シンプルに書けるはずなのに後々のメンテナンス性も考えるとファイルも分けないといけないし階層も深くて大変だよ~~~」
という状況ですぐ動ける。なぜなら考えればいいのは呼び出し元。仕様に近いところから書いて、構造が破綻していればインテリセンスが自動的に注意してくれる。インテリセンスを秘書として、脳みそをちっさくして無心で書ける。
とりあえずこれでしばらく開発してみます。
効果報告①
2023/05/06ゲーム開発は見た目がある。呼び出し元からダミーを呼び出して開発することで、とりあえず進んでいる感は出る。
見せかけだが、その見せかけすら出せない手法よりもずいぶんやる気が出る。
それと、脳内のイメージを実体にすることができるので想像力のメモリを節約できる。
問題は、この手法に慣れていないので一々己をしばかないと思い出せないことだ。まあ訓練で治るじゃろ
効果報告②
2024/02/17IDEの機能を使えばかなり効率化できる。
VisualStudioを使っているのだが、ctrl+.でクイックアクションができて、これが非常に相性がいい。
たとえば未知の関数を呼び出すとローカルに自動的に関数を作り出す。
未知のクラスを使うと同じ名前空間ではあるものの、クラスファイルを作成してクラスを作成、そのうえ必要要件を満たした状態にまでしてくれる。
(たとえばstringとintを受け取るコンストラクタを呼び出しておけばそういった形式のコンストラクタがすでに生成されている)
ローカル変数も勝手に作り出してくれるので、これで自動生成されてフィールド同士の順番が追加順になっている状態については容認することにした。
上記の無心で書ける、というのは仕様がわかり切っているときだけだ。
実際のところフローをもとにどういったデータを運んでいかないとだめか、保持しなければならないかといった問題に目が向くようになり、より本質的なところを考えるようになった。今までそんな余裕がなく無心で走っていて効率が悪かったのだと思うが、余裕ができた。
この前も簡単なステートマシン的なものを自作したのだが、4つのクラスにまたがる複雑めな設計だったものの根本的な問題を起こさず素早く実装できた。
(前だったら一日つぶしていたが数時間で実装できた)
それはそれとして思ったのは、呼び出し元を書く場合には内部にどういったフローを書くのか思い浮かべる必要はあるということだ。
引数で何が必要なのか、そもそもどうやって関数やクラスを切り分けていくのかというところを決定するには内部で何をするのかを理解していないと全く話にならない。
なのでやはり経験は必要。そして呼び出し元駆動開発は作業こそ呼び出し元駆動だが、思考のフローはとりとめのないあちこちに飛び回るものだということをここに書き記す。
もしかしたら次回の効果報告で、「思考のフローはこうまとめろ!」みたいなことを言ってるかもしれない。