プログラマはソースコード分割の神になれるか?
身内が回してきたツイートを見て一言「若いな」と返してしまった。
クソコード動画「共通化の罠」 https://twitter.com/MinoDriven/status/1127539251761909760
軽く後悔しながら悶々としていたら、 コード分割についてある程度まとまりつつあったので、ついで記事にしてみる。
コードの分割境界をテストする
予めコードの分割境界を見極めるのははっきり言って”無理”だ。
例えばWebフレームワークでは「Controller」「Service」「Model」といった層に分けての実装を推奨しているものがあるが、 わずか50行のService+サブクエリが10個もあるModelの組みあわせと、1000行を超えるService+サブクエリすらないModelの組みあわせを同列に扱えるときは?Serviceに1個のサブドメインのみが含まれる場合と大小入れ子含め20個を超えるサブドメインが含まれるコードを、適切で統一された切り分け方で分解できるか?
結果は、巨大な神クラスがそびえたつか、小分けにしすぎてどのクラスがどのクラスと依存しているか理解も一苦労するコードが生まれるのがオチだろう。
もちろん例外はあるが、例えば実装している領域に精通していて今作り出しているソレに起こりえる外的圧力と変化を事前察知できるマジプシーじみた超越者か、神たるプロダクトオーナーでないといけない。
センスのない我々が、そんな英知に近づくことはできるのだろうか? 少なくとも、”どちらの方向に進めばよいか”を知る地道な検証手段はとれるはずだ。
ただし、亀の甲を火炙りしてみたくなる*1ぐらい、定性的で主観捉え方によって答えがかわる領域でもある。 どうか、真に受けない程度で読んでいただければ幸いだ。
テストをシミュレートする
コードや関数を分割するからには、上位の流れとなる呼び出し元と、下位の詳細な実装があるはずだ。 (Webシステムなら、最初に処理を受けるActionやControllerと、細部実装であるServiceやDaoに分割するなど)
この2つを、個別にテストしたとき、それぞれ違ったテストになるだろうか? もし、どちらも同じような条件下で同じような操作でしかテストできないとき、その2つのコードは同じレイヤに存在している疑いが強い。
「SQLと、純粋なJavaのコードで分けている」場合でも、それはSQLで表現しているかJavaのコードで表現しているかの違いでしかなく、アプリケーションの汎化・特殊化といった文脈からすると同じレイヤであるかもしれない。
インプット・アウトプットの新規軸・断面を被害妄想する
転変地異を引き起こす仕様変更は、だいたい下記のようなパターンだ。
- 汎用化している処理に特殊な結果を付与したい(アクセスログ解析において、特定のキャンペーンコードをクエリパラメータに持つ場合に別途その軸で分析できるようにしたい)
- 汎用化している処理に一部例外で大きく処理の有無やしかたを切り分けたい(キャンペーンコードの場合のみ、PV数の実績から除外したい)
- ある処理にたいして、その処理そのものは類似ではあるが、それ以外の色々な業務規則が異なるドメインまで拡張する(正社員の勤怠管理が主だが、外部派遣している社員の勤怠も管理したい)
これらの変化が起きたとき、おそらく考えたインタフェースは少なからず変更が発生する。 しかしインタフェースが領域や流れに沿って忠実に定義されているのあれば、自然な拡張をもって変更を受け入れられることが多い。
- アクセス解析はGROUP BY 集計ありきで実装し、マートとして事前集計しておく。
- アクセスログの母数となるベース実績を事前にフィルタ集計する工程を追加し、集計前後でモデルをあまり変更しないDecoratorパターンのような実装を導入する。
- (これは本当に無理ゲーだが)勤怠の蓄積、締め・集計、給与査定など各ステップごとに実績データ・モデルを疎結合にしておく
設計の検証について不可欠な大事な考え方だが、 もし後輩が現在の設計の不慮に気づき相談をされても、実装コストやスコープの拡大が目に見えたとき、たいてい私はキツく返してしまうだろう。
設計にあれこれ考えて不注意に処理のパラメータを増やすと、そのコードが何を目的をしていたのか分からなくなり、更に分割をしがちだ。 更に、要件外の汎化を想定してまた抽象的なパラメータの形式を想定する。 悲しいことに、コーディングや設計に使える時間は有限であり、膨大にふくらんだインタフェースは貴重で楽しい時間を食いつぶしてしまうことになる。
もしこんな被害妄想にとらわれてしまったら、一度我に返ってほしい。
仕様変更は、スコープ外の要因が入り込んだ結果であり、スコープに対する姿勢やリスクの評価はプロジェクトそれぞれ。 足元を見ず行き過ぎると、痛いしっぺ返しを受けるだろう。
実装パターンに当てはめて評価する
あれこれとリスク評価したのち、一歩下がってパターン実装に当てはめてみるとうまくいくものが見つかることが多い。
- 連続して処理を流し、共通のデータモデルがどんどん変化していく → メソッドチェーン、Decorator、Context
- 処理の流れが一方通行であり元の処理に戻ることがない → Subscribe
- 大量の引数をオプション的に指定する → 連想配列をパラメータとして渡すインタフェース、可変長引数、JSON・XML
実装中に結びつけるには、実装パターンをひたすら学習するしかなく、縦割りも横割りも多種多彩。
今流行っているフレームワーク・言語の新構文を学習するだけでもいくつか習得できるので、直近興味のある分野でひとまず手を動かしてみると、自然に身につくかもしれない。
人は神になれるか
色んな文献から流用してきたが、 ご存知のとおり、こんな知識にあふれているはずの世のシステムは、たいてい負債をかかえている。
プログラマは所詮人で、万象から思いつく要求をモデル化するにあたり 教科書を清書するだけで答えができるのはいかにも都合が良すぎるし、オープンソースコミュニティさえもが血と汗と半ば狂気に浸る道理がない。
それでも、設計してみてほしい。 イケると思ったコードで実装してみて、いざリスク発露から蹂躙されたあとは、 領域に対する素晴らしいモデルがあなたの中に見出されるはずだ。
ソフトウェアに身を置くのであれば、どうか、実現し伝え目に見える形で還元してほしい。 素晴らしいアイディアは、たとえどんな強権のもとで進言を無視されるものであっても、 振り返ると必ず光って誰の心にもとどまり思い出されるものだ、と、知的な創造プロセスのあるべきと思う。
最後に
やっぱり説教になってしまった。
「リファクタリングっていうのをすれば負債が解決するんだ」という根も葉もない予算投下の実例を見てきたのもあり、バズワードの上っ面だけでコロコロいくのが嫌いとかいう、やっぱり老人臭くなっているかもしれない。寝る前にコードかこう