プログラミングをしているとよく聞く話の一つに、DRY原則、Rule of 3などと呼ばれる原則がある。
簡単に言うと、プログラミングでは同じような処理がしばしば繰り返されるが、
だからと言ってソースコードをコピペしまくるのは愚行だ、という主張である。
これは一理ある話である。
コピペでは、そのコードが誤っているときや、後で変更が必要になった時に全ての箇所を変更しなければいけない。
また文字列置換などの工夫があるとはいえ、古典的なコピペは、変更箇所がある場合に名前の誤り、置き換え漏れなどのバグ誘発要因でもある。
しかし一方で、安易に共通化しようとすると、使用される文脈が異なる場合にある個所のみで修正が必要になった時や、
「似ているが完全に同一ではない」場合などに、却ってそれらへの対応が複雑になることがある。
そこで、ここでは、「似ている」=定型的なソースコードが使われる場合の扱い方について、現時点での考えをまとめてみたいと思う。
定義
・定型的なソースコード
意味合いまたは形式が類似しているソースコードのこと。
・共通化
完全に同じものをどこか一か所にまとめること。
スーパークラスのメソッド、staticメソッド、オブジェクト指向言語以前のサブルーチン・関数など。
・抽象化
同じ意味合いのものを「やるよ」とだけは一か所で定義して、具体的な中身は「やるよ」印が付いたそれぞれの場所で記述すること。
interface、abstractメソッドなど。
サブクラスでのオーバーライドが可能なスーパークラスの具象メソッド(virtualメソッド)は、共通化と抽象化の中間的な存在である。
ジェネリックプログラミングなど、戻り値や引数の型によって処理が微妙に変わる場合も、ものによっては中間的と見ることは可能である。
・自動生成
同じ形式のソースコードを、ある規則に従ってコンピューターに生成させること。
エンティティクラスのプロパティとデータベースのカラム名、及びその対応性が分かっているときに、
定型的な処理(DELETE/INSERT/UPDATE、主キーによる検索など)のコードを書きたい場合や、
クラス図からプロパティとメソッドのスケルトンコードに変換したい場合など。
オブジェクト指向の文脈で出てくる共通化・抽象化とは別の観点であり、必ずしも対立する訳ではない。
定型的ソースコードを扱う方法は、主に上記三種類だと考えている。
さて、定義でほぼ答えが出てしまっていると思うが、私の考えはこうだ。
完全な共通化は、完全に(内容のみならず周辺ソースの中での意味付けも)同じ処理のみに、
抽象化はそれ以外の意味合いが同じ処理に、
自動生成は、意味内容によらず、必要なコードの形式が規則的な時に、
それぞれ使用するべきである。
ただし、形式が規則的である場合の特殊な場合が共通化可能な場合であり、
このような場合には自動生成=単なるコピペである。
同様に、共通化可能な場合は、抽象化もその気なら可能だろう。
よって、考慮すべき順番としては、共通化が最上位に、抽象化と自動生成がその次に来る。
殆どのケースで完全に共通で、例外箇所が限定的であれば、共通化と抽象化の中間にあたる方法が望ましい。
抽象化と自動生成では、使用場面が異なるので、その定型的コードが持つ意味合いと形式を把握する必要がある。
同じ意味合いだが内部処理そのものは無論、そのコードの生成規則まで異なっているなら、純粋な抽象化で対処することとなる。
一方で、形式・規則こそ同じだが意味合いはてんでバラバラであるなら、純粋な自動生成の対象となる。
(自動生成を念頭に置いた場合、入力値については、人間にとっての可読性のみならず、機械可読性も重要になるが、それはここでは深入りしない。
多くの点で両者は相関するが、人間可読性のための規則的記述への例外や、人間の手打ちによる規則の範囲内での表記揺れなどは、
機械可読性にとっては忌むべき敵であるとだけ、ここでは言っておく)
だが実際には、意味合いと構造、ともに似ているがどうしても書き換えないといけない個別の箇所がある(プロパティ名など)という
ケースも多く、この場合は抽象化と自動生成の両方が適用できる。
単純な自動生成であればそのための入力値を引っ張ってきて、エディターで正規表現による置換操作を行う「半」自動的手法で事足りるが、
繰り返される場合はそれすらもプログラムにやらせて、できたものをそのまま受け取ることも可能であり、実はその方が長期的には効率的である。
なお、自動生成すべき場合は、結局コード量は多いままで(可読性・保守性への貢献はなく)、
実装の人間側の手間が一時的に減らせるだけなのではないか、という意見もありそうだが、
自動生成の利点は、可読性と保守性の観点でも確かに存在する。
第一に、自動生成コードは規則正しいので、規則に沿うだけで全てを理解できる。
規則正しさゆえに名前が長くなりすぎたり、ゲシュタルト崩壊を引き起こしたりして却って読みにくくなる可能性はあるが、
そのような例外は限定的で、基本的には規則性は可読性の指標となる(でなければ、誰もコーディング規約の類など作るまい?)。
また、生成規則に誤りがある場合でも、誤りそのものが規則的なので、
人間の手打ちによる、よく分からないランダムなエラーよりは余程対処しやすい。
規則的な誤りは、多くの場合、これまた規則的な文字列置換で一斉対処が可能だからである。
これらの理由から、私は定型的コードについては、自動生成も有効な選択肢であり、
自動生成が理論上可能な場合には、その領域の手打ちは可能な限り追放するべきだと考えている。
(この問題は、また別の機会にまとめようと考えている)
話を戻してまとめると、定型的なソースコードの扱い方は、
・共通化:オブジェクト指向以前からのやり方(だがそれでよい時もある)
・抽象化:オブジェクト指向の王道(「完全に」同じではないかもしれない時)
・自動生成:敢えて言うなら機械可読性(検索可能性)指向プログラミング(※)
に大別され、最も強力な共通化でOKの場合はそれ、
そうでない場合は意味合いごとに取り扱う抽象化と形式・規則に基づく自動生成をうまく使い分けるべし、
そしてそもそも自動生成がテーブルにまだないなら積極的に乗せるべし、というところである。
※巨大化しうるプロジェクトのコードは、できる限り人間ではなくコンピューターに検索・加工させろ、またそうしやすいように書け
≒できるだけシンプルな規則で記述ルールと変更ルールを定式化できるように書け、という方針に基づくプログラミング。
これも今回は深入りしないが、機械語に戻れと言っているのではなく、高級言語そのものが持つ人間可読性は前提である。