単一目的の人々に要注意
純粋すぎて痛い
単一責任原則(SRP)は、あまりに理にかなっているがゆえに判断力をくぐり抜けてしまうアイデアの一つだ。
一つのことをやれ。それをうまくやれ。モジュールを集中させろ。コードに変更理由を与えろ。良い助言だ。
ところが、誰かがその助言をものさしに変えて、5行を超える関数はコードの臭いだと宣言し始める。
問題はSRPではない。問題は「小さいこと」を「凝集性」の代わりとして扱うことだ。
そうなると、あなたは「単一目的主義者」に出会ったことになる。モジュール性について間違ってはいないが、有用な境界を最大限の断片化と混同している開発者たちだ。

I. その下にある有用なアイデア
フォームにチェックボックスを一つ追加するだけで、本来は1ファイルだけ変更すれば済むはずなのに、8ファイルを5ディレクトリにまたがって変更する…… React/Redux、お前のことだ。
SRPを判断力をもって適用すれば役立つ。単一の概念的なタスクに焦点を絞ったコードユニットは理解しやすい。テストは適切な境界で振る舞いを対象にできる。明確なモジュールのおかげで、システムの一部を変更する際にアプリケーション全体を巻き込まずに済む。
古典的なUnixの例でさえ、スローガンが示唆するよりも実用的だ。lsはファイルを一覧表示するが、同時に opendir、readdir、closedir、stat のような呼び出しを調整している。有用な単位は可能な限り最小の操作ではない。有用な単位は、タスクを解決する最小の首尾一貫したものだ。
元のUnix哲学は構成とシンプルさについてであり、すべてを単一の関数やファイルに還元することではない。
その区別は重要だ。「一つの責任」は「一行の振る舞い」と同じではない。
II. 過剰な抽象化:シンプルさが混沌に変わるとき
うちのアーキテクトは5行超の関数はすべて「コードの臭い」と言い張っている。私たちのコードベースは今や無知な絶望の臭いが微かに漂っている。
失敗モードは、すでに一週間を台無しにした後で簡単に見つかる。
コードベースはファイルが増えたが、形状は失われた。すべてのヘルパーにヘルパーがいる。すべての概念が、プロダクトの意味ではなく技術的な役割に基づいて名前付けされたフォルダに分割されている。チェックボックスを追加するには、コンポーネント、フック、セレクタ、アクション、リデューサー、定数、テストフィクスチャ、そして主にインポートパスが罪悪感を持たないようにするためのバレルエクスポートに触れる必要がある。

その純粋さは何をもたらしたのか?
- ファイルシステムの撒き散らし: ソースディレクトリが無数の小さなファイルで悪夢のような風景に咲き乱れ、その多くはひとつの、悲しいほど孤独な関数だけを抱えている。ナビゲーションは洞窟探検と化す。
- 依存関係の絡まり: インポートとエクスポートの網が異常に密で、実行を追跡するには大きなホワイトボードと、その機能に見合う以上の忍耐が必要になる。一度だけインポートされるファイルが、まるで再利用可能なふりをしてそこに座っている。
- テストの裏切り: テストは脆く、極めて特化した衛兵と化し、微細な実装詳細を守る。関数シグネチャを変えてみろ? 何十ものテストが古い壺のように粉々になる。テストスイートは安全網から地雷原へと変貌する。
- 速度の消失: 単純な変更が複数ファイルにまたがる修正叙事詩に転移する。新規開発者のオンボーディングは、今週
UserProfileコンポーネントが実際にどこにあるのかを見つけるために地図とコンパスを渡す数週間を要する。この「整理」の重みで前進は地質学的スピードに減速する。
私は、たった100行の機能が15以上のファイルに解剖され、それぞれが1つか2つの関数を含む「純粋な」小さな天使であるコードベースの深淵を見つめてきた。その混乱を頭の中で保持しようとする認知負荷の爆発半径は、分離による理論上の利得を完全に打ち消す。シンプルになったのではなく、ただ散らばっただけだ。
III. 完全性の代償:開発者への影響
私たちは機能を出荷するよりも、ファイル構造と命名規則の議論に多くの時間を費やしている。これがアジャイルなのか?

この病的な断片化は、単なる美的問題ではない。開発者の注意の使い方を変えてしまう。
生産性の消失: 技術的負債は忘れろ。これは強迫的なディレクトリのネストによって蓄積された組織的負債だ。小さな変更ごとに抽象化の層を掘る考古学的発掘が待っている。時間はcd ..とgrepのブラックホールに消えていく。
テストの税金: テストスイートは自信を与える代わりに摩擦の源となる。些細なリファクタリングで壊れたテストの修正に何時間も溶ける。それらのテストは、検証すべき微細な詳細にあまりに密結合しすぎている。
認知負荷: 人間の脳が扱える、互いに関連のない情報の断片の数には厳しい限界がある。開発者にプログラムの流れを散らばった十数のファイルから組み立てさせるのは、理解を積極的に妨げ、自信を持った変更を難しくする。
IV. 実用主義を受け入れる:実践的な代案
私は、関連する二つの関数を同じファイルに入れることを提案した。部屋の反応は、私がステージング環境を削除すると提案したかのようだった。 — 更生中の純粋主義者より
脱出ハッチはSRPを放棄することではない。答えは、それを適切な意味のレベルで適用することだ。
実践では次のようになる:
- 原子ではなく凝集に注力: 一緒に変更されるもの、概念的に属するものをまとめよ。モジュールがユーザー認証のいくつかの関連側面を扱っても構わない。おそらく、ログイン状態に関連する関数をそれぞれ持つ6つの別々のファイルより優れている。
- 近親者はまとめよ: 明白で具体的な利益——たとえば、決して来ない仮説上の未来ではなく実際の真の再利用性——がない限り、関連コードを分割するな。近接性は理解にとって重要だ。
- 現実に従え: 機能の純粋性に関する抽象的な理想³ではなく、アプリケーションの実際の機能とワークフローに基づいて整理せよ。この構造は、
Feature Xを理解し修正するのを容易にするか、困難にするか? - 生身の人間を忘れるな: 哀れな開発者を思い出せ。コードを扱うのに必要な頭の中でのジャグリングを最小限にする組織は何か? 人間の理解のために最適化せよ。
- 重要なものをテストせよ: 適切な境界で振る舞いを検証するテストを書け。すべての小さな関数の内部配線に密接にハンダ付けされたテストではなく。カバレッジ率のパフォーマンス劇場ではなく、自信を目指せ。
目的は博士論文に値する理論的な完全性ではなく、同僚(そして未来の自分)が建物に火をつけたいと思わずにナビゲート、理解、修正できるコードを作ることだ。
時には、これはファイルが50行ではなく200行になることを意味する。時には、関数がデータの取得とわずかな変換の両方を扱う。時には、クラスが二つの責任を持ち、それらが非常に密結合であるため一緒に住むべきである。システム全体を扱いやすくするのであれば、おそらくそれが正しい判断だ。
実践的な問いかけに集中し続けよ:
- 新しい人が迷わずに動けるか?
Xを変更しても、無関係なYを壊さずに済むか?- このテストは実際にその機能が動作するかどうかを教えてくれるか?
- 私たちは価値を出荷しているのか、それとも単にフォルダを並べ替えているのか?
V. 結論:凝集性があり保守しやすいコードの育成
単一責任の原則は便利なツールである。しかし、コードベースを原子の塵にまで粉砕することを命じるものではない。他のツールと同様、その価値は使う人の判断に依存する。
だから、三行を超える関数に容赦なく戦争を仕掛ける「Single-Purpose People」に出くわしたら、一呼吸置け。あの12ファイルのチェックボックスを思い出せ。
我々の仕事は、理論的に完璧なスノーフレーク関数を構築することではない。動作し、問題を解決し、次に触る人を罰しないソフトウェアを構築することだ。
実用的であれ。結果に集中せよ。完全な純粋性の追求が、保守可能なコードの敵になってはならない。あなたの正気とチームの速度は、それにかかっている。
¹ 皮肉なことに、最下層で「真の単一目的」を達成するには、そのすぐ下に計り知れない複雑さを隠す必要がある。
² ここで言っているのは概念的な純粋性、つまり関数が論理的に「一つのこと」だけを行うべきという考え方だ。これを、副作用のない「純粋関数」という関数型プログラミングの概念と混同してはならない。別物ではあるが、関連する場合もある。