DanLevy.net

単一目的の人々に要注意

純粋すぎて痛い

単一責任原則(SRP)は、あまりに理にかなっているがゆえに判断力をくぐり抜けてしまうアイデアの一つだ。

一つのことをやれ。それをうまくやれ。モジュールを集中させろ。コードに変更理由を与えろ。良い助言だ。

ところが、誰かがその助言をものさしに変えて、5行を超える関数はコードの臭いだと宣言し始める。

問題はSRPではない。問題は「小さいこと」を「凝集性」の代わりとして扱うことだ。

そうなると、あなたは「単一目的主義者」に出会ったことになる。モジュール性について間違ってはいないが、有用な境界を最大限の断片化と混同している開発者たちだ。

ソフトウェアアーキテクチャにおける暴力
あらゆるところにコンポーネント

I. その下にある有用なアイデア

フォームにチェックボックスを一つ追加するだけで、本来は1ファイルだけ変更すれば済むはずなのに、8ファイルを5ディレクトリにまたがって変更する…… React/Redux、お前のことだ。

SRPを判断力をもって適用すれば役立つ。単一の概念的なタスクに焦点を絞ったコードユニットは理解しやすい。テストは適切な境界で振る舞いを対象にできる。明確なモジュールのおかげで、システムの一部を変更する際にアプリケーション全体を巻き込まずに済む。

古典的なUnixの例でさえ、スローガンが示唆するよりも実用的だ。lsはファイルを一覧表示するが、同時に opendirreaddirclosedirstat のような呼び出しを調整している。有用な単位は可能な限り最小の操作ではない。有用な単位は、タスクを解決する最小の首尾一貫したものだ。

元のUnix哲学は構成シンプルさについてであり、すべてを単一の関数やファイルに還元することではない

その区別は重要だ。「一つの責任」は「一行の振る舞い」と同じではない。

II. 過剰な抽象化:シンプルさが混沌に変わるとき

うちのアーキテクトは5行超の関数はすべて「コードの臭い」と言い張っている。私たちのコードベースは今や無知な絶望の臭いが微かに漂っている。

失敗モードは、すでに一週間を台無しにした後で簡単に見つかる。

コードベースはファイルが増えたが、形状は失われた。すべてのヘルパーにヘルパーがいる。すべての概念が、プロダクトの意味ではなく技術的な役割に基づいて名前付けされたフォルダに分割されている。チェックボックスを追加するには、コンポーネント、フック、セレクタ、アクション、リデューサー、定数、テストフィクスチャ、そして主にインポートパスが罪悪感を持たないようにするためのバレルエクスポートに触れる必要がある。

この無限の作業パターンから逃れられない
あらゆるところにコンポーネント

その純粋さは何をもたらしたのか?

私は、たった100行の機能が15以上のファイルに解剖され、それぞれが1つか2つの関数を含む「純粋な」小さな天使であるコードベースの深淵を見つめてきた。その混乱を頭の中で保持しようとする認知負荷の爆発半径は、分離による理論上の利得を完全に打ち消す。シンプルになったのではなく、ただ散らばっただけだ。

III. 完全性の代償:開発者への影響

私たちは機能を出荷するよりも、ファイル構造と命名規則の議論に多くの時間を費やしている。これがアジャイルなのか?

乱雑さが芸術の域に達している
乱雑さが芸術の域に達している

この病的な断片化は、単なる美的問題ではない。開発者の注意の使い方を変えてしまう。

生産性の消失: 技術的負債は忘れろ。これは強迫的なディレクトリのネストによって蓄積された組織的負債だ。小さな変更ごとに抽象化の層を掘る考古学的発掘が待っている。時間はcd ..grepのブラックホールに消えていく。

テストの税金: テストスイートは自信を与える代わりに摩擦の源となる。些細なリファクタリングで壊れたテストの修正に何時間も溶ける。それらのテストは、検証すべき微細な詳細にあまりに密結合しすぎている。

認知負荷: 人間の脳が扱える、互いに関連のない情報の断片の数には厳しい限界がある。開発者にプログラムの流れを散らばった十数のファイルから組み立てさせるのは、理解を積極的に妨げ、自信を持った変更を難しくする。

IV. 実用主義を受け入れる:実践的な代案

私は、関連する二つの関数を同じファイルに入れることを提案した。部屋の反応は、私がステージング環境を削除すると提案したかのようだった。 — 更生中の純粋主義者より

脱出ハッチはSRPを放棄することではない。答えは、それを適切な意味のレベルで適用することだ。

実践では次のようになる:

目的は博士論文に値する理論的な完全性ではなく、同僚(そして未来の自分)が建物に火をつけたいと思わずにナビゲート、理解、修正できるコードを作ることだ。

時には、これはファイルが50行ではなく200行になることを意味する。時には、関数がデータの取得とわずかな変換の両方を扱う。時には、クラスが二つの責任を持ち、それらが非常に密結合であるため一緒に住むべきである。システム全体を扱いやすくするのであれば、おそらくそれが正しい判断だ。

実践的な問いかけに集中し続けよ:

V. 結論:凝集性があり保守しやすいコードの育成

単一責任の原則は便利なツールである。しかし、コードベースを原子の塵にまで粉砕することを命じるものではない。他のツールと同様、その価値は使う人の判断に依存する。

だから、三行を超える関数に容赦なく戦争を仕掛ける「Single-Purpose People」に出くわしたら、一呼吸置け。あの12ファイルのチェックボックスを思い出せ。

我々の仕事は、理論的に完璧なスノーフレーク関数を構築することではない。動作し、問題を解決し、次に触る人を罰しないソフトウェアを構築することだ。

実用的であれ。結果に集中せよ。完全な純粋性の追求が、保守可能なコードの敵になってはならない。あなたの正気とチームの速度は、それにかかっている。

¹ 皮肉なことに、最下層で「真の単一目的」を達成するには、そのすぐ下に計り知れない複雑さを隠す必要がある。

² ここで言っているのは概念的な純粋性、つまり関数が論理的に「一つのこと」だけを行うべきという考え方だ。これを、副作用のない「純粋関数」という関数型プログラミングの概念と混同してはならない。別物ではあるが、関連する場合もある。