なぜ、SOLIDの単一責務の原則(SRP)は理解しにくいのか?
はじめに
単一責務の原則(SRP)は、SOLIDの中でも最も有名でありながら、最も誤解されやすい原則の一つです。名前だけ見ると、いかにも分かりやすそうです。単一責務という四文字は、いかにも「一つのことだけをやれ」と言っているように見えます。ところが現場では、この原則ほど人によって解釈がぶれるものもあまりありません。
ある人は、SRPを「一つのクラスは一つのことだけをするべきだ」と理解します。ある人は「変更理由が一つであるべきだ」と理解します。さらにある人は「一つの関係者にだけ責任を負うべきだ」と理解します。どれも全くの外れではないのですが、どれか一つだけを掴むと、すぐに話が崩れます。ここに、分かりやすそうで分かりにくいという、この原則特有の厄介さがあります。
しかもSRPは、コードの見た目だけでは判断できません。短くて綺麗なクラスがこの原則を守っているとは限りませんし、少し大きめのクラスでも、十分にこの原則を満たしていることはあります。見た目の小ささと、設計の正しさが一致しないのです。初学者ほど、まず見た目から判断したくなるので、ここで最初のつまずきが起きます。
結局のところ、この原則が難しいのは、「どう書くか」ではなく「なぜ変わるか」を見ろと要求するからです。しかもその「なぜ」は、現在の機能一覧ではなく、将来の変更圧力に関わります。本稿では、変更理由、言葉の罠、抽象、時間軸、実務判断という観点から、なぜSRPがここまで理解しにくいのかを整理します。
出発点は機能ではなく変更である
SRPが理解しにくい第一の理由は、多くの人が設計を機能分解として学ぶのに対して、この原則は最初から変更分解の発想に立っていることです。設計教育の初期では、入力、計算、出力といった処理の流れで物事を分ける癖がつきます。これはこれで自然ですし、アルゴリズムを考えるうえでは有効です。しかしSRPは、その見方をいったん横に置けと言います。何をするかではなく、何の都合で書き換えられるかを見ろというのです。
この考え方の源流には、デイヴィッド・パーナスのモジュール分解の議論があります。パーナスは、処理手順に沿って分解するのではなく、将来変わりそうな設計判断を隠すように分けるべきだと考えました。ここで重要なのは、モジュールの境界が手順ではなく、変更の局所化を基準に引かれていることです。つまり、モジュールとは実行順序の箱ではなく、変更の波及を止める壁なのです。
その流れを、後にロバート・C・マーチン(以降:アンクルボブ)が「一つの変更理由」という形で整理しました。彼が言っているのは、クラスの仕事数を数えろという話ではありません。クラスがどの都合で書き換えられるのか、その変更軸を見ろという話です。ここで視点が一段ずれます。機能一覧を見ていた人にとっては、急に見ている地図が変わるわけです。
たとえば、税額計算、合計計算、印刷整形という三つの処理が一つのクラスにあるとします。機能分解の目では、三つの仕事が見えます。しかしSRPの目では、税制変更、通貨規則変更、帳票レイアウト変更という三つの変更要因が見えます。前者は現在の処理一覧です。後者は未来の変更圧力です。人間は前者を見るのは得意ですが、後者を最初から自然に見るのは苦手です。ここに、SRPの最初の難所があります。
「責務」という言葉が人を迷わせる
SRPが誤解されやすい第二の理由は、そもそも責務という言葉が危ういことです。日常語としての責務は、担当作業や役目に近く読まれます。そのため、多くの人は「責務が一つ」という言葉を見た瞬間に、「一つのクラスは一つの仕事だけを担当するべきだ」と解釈してしまいます。この解釈は分かりやすいのですが、かなり危険です。なぜなら、SRPが見ているのは仕事の数ではなく、変更理由のまとまりだからです。
ここで起きる典型的な誤解は、「一つのことだけをするクラスを作ればよい」という短絡です。すると、少しでも処理が増えるたびにクラスを割り、少しでもメソッドが増えるたびに別クラスへ逃がすようになります。見た目は確かに整います。けれども、同じ理由で一緒に変わるべき処理まで分離されると、今度は保守のたびに複数箇所をまたぐことになります。これは責務を整理したのではなく、語感に引っ張られて設計を崩しただけです。
さらに厄介なのは、「変更理由」自体もまた、機械的な概念ではないことです。仕様変更は分かりやすいです。では、バグ修正はどうか。外部ライブラリ更新はどうか。構造改善はどうか。ここに明快な線引きはありません。だからこそ、定義文だけを見て単純なルールとして覚えようとすると、すぐに手が止まります。SRPは、法律の条文のように綺麗に運用できるものではなく、設計判断のための道具だからです。
つまりこの原則は、名前があまり良くありません。責務という言葉が仕事数を連想させる一方で、実際には変更理由や変更主体を見なければならないからです。入口のラベルと中身の視線がずれている。これだけで、原則としての難度はかなり上がります。SRPが分かりにくいのは、内容が深いからだけではなく、名前が親切そうで不親切だからでもあります。
答えはコードの外にもある
SRPがさらに理解しにくいのは、この原則がソースコードの中だけで完結しないからです。アンクルボブが後年強調したのは、SRPは結局人の問題だということでした。給与計算、労働時間報告、データ保存が一つの従業員クラスに入っている例を考えると、コードだけ見れば「どれも従業員に関する処理だからまとまっている」と見えるかもしれません。しかし現実には、それぞれ別の関係者の要求によって変わります。
会計部門は給与計算に関心を持ちます。運用部門は労働時間報告に関心を持ちます。基盤担当はデータ保存の方式に関心を持ちます。これらは全部「従業員」という言葉の下に置ける処理ですが、変更要求の出どころは別です。すると、一つの要求に応えて修正したコードが、別の要求側から見れば余計な副作用になることがあります。ここで問題なのは、メソッド数でもクラス長でもありません。変更要求の出どころの混線です。
この時点で、SRPは純粋なコード作法ではなくなります。クラス図だけ見ていても半分しか分かりません。誰がどの理由でこのコードを触るのか。どの部門がどの変更を気にするのか。どの要求が同時に来るのか。このような情報は、ソースコードそのものには書かれていません。つまりSRPは、コードの構造を見ながら、同時に組織と運用も見なければならない原則なのです。
ここが、継承やインタフェースの話より難しいところです。継承なら継承関係を見ればよいですし、依存関係なら依存グラフを見ればある程度議論できます。しかしSRPは、コードの外の都合が正解の一部になっています。そのため、教科書で定義だけ覚えても、実務で急に迷いやすいのです。SRPは、コードの中に半分、コードの外に半分ある原則だと言ったほうが近いです。
抽象の高さを揃えないと議論が霧になる
SRPの議論がすぐに噛み合わなくなる理由として、抽象の高さが人によってずれることも大きいです。たとえばボールペンは、会計事務所でも法律事務所でも芸能事務所でもパン屋でも使われます。利用場面だけ数えれば、いくらでも増やせます。だからといって、ボールペンが多責務だとは言えません。なぜなら、その便益は「文字を書く」という一つの抽象にまとまっているからです。
逆に、スマホのような製品は明らかに多機能です。電話、カメラ、決済、地図、ゲーム、通信など、便益の種類が複数あります。これを製品レベルで見れば多責務的です。しかし内部でアプリや基本ソフトのサービスが適切に分離されていれば、モジュールレベルでは十分にSRPに沿っています。つまり「多機能な製品であること」と「多責務なモジュールであること」は全く同じではありません。どの高さで見るかによって、話が変わるのです。
ソフトウェアでも同じことが起きます。並べ替え処理は会計でもゲームでも基本ソフトでも使われますが、責務は並べ替えです。ボールペンと同じです。一方で、便利屋のような管理クラスが、入力受付、計算、保存、通知、表示整形まで全部抱えているなら、それは利用場面の多さではなく、抽象の混線の問題です。重要なのは、用途の数ではありません。同じ抽象の下にまとまっているかどうかです。
だからSRPは、機能数を数える原則ではありません。抽象の高さを揃えずに議論すると、「それは多責務だ」「いやそうではない」という不毛な言い争いになりやすいです。見ているレベルが違うからです。SRPが理解しにくいのは、この原則が「責務をいくつに分けるか」より先に、「何を一つの責務として数えるのか」というさらに厄介な問いを含んでいるからです。
時間軸が入るので静止画では読めない
SRPが本質的に難しい最大の理由は、これが時間軸を含む原則だからです。現在のコードの形だけでは、この原則の適否は十分に分かりません。そのコードが今後どれくらいの期間使われるのか、どのくらいの頻度で変更されるのか、誰がどの理由で触るのかという情報まで含めて、初めて輪郭が見えてきます。未来の変更はソースコードの中には書いていません。だからSRPは、静的な構造だけ見ても掴みにくいのです。
たとえば一回限りのデータ変換スクリプトなら、読み込み、整形、出力を一つのファイルに押し込めても大きな問題にはなりません。どうせ一度使って終わりであり、他人が保守せず、将来の仕様変更も来ないなら、変更理由は実質一つだからです。この場合に、SRPを教科書通りに厳格適用して細かく分割するのは、ほとんど過剰設計です。時間軸を無視した善意は、ここで無駄になります。
反対に、同じ処理が社内共通基盤になり、数年単位で使われ、複数部署がそれぞれ別の要求を出し始めると、事情は一変します。単発スクリプトの感覚で一つのクラスに押し込めていたものが、後から変更の衝突点になります。つまりSRPは、その場限りか長期保守かで、同じ構造への評価が変わる原則です。ここを分かっていないと、「全部分けるべきだ」と「別に分けなくてよい」の間を、状況抜きで振り子のように揺れるだけになります。
SRPは静止画ではなく、長時間露光で見るべき原則です。同じ被写体でも、露光時間が違えば見えるものが変わります。短時間で見ればただの点に見えるものが、長時間で見ると軌跡になります。SRPも同じです。その瞬間だけ見れば一つにまとまって見えるコードが、数年分の変更履歴まで含めて見ると、複数の軌跡が重なった危険な交差点に見えることがあります。この比喩を抜くと、この原則の本質はかなり伝わりにくくなります。
どこで分けるべきかは実務の判断軸で見る
SRPが分かりにくいもう一つの理由は、「壊れるかどうか」と「分けるべきかどうか」が一致しないことです。たとえば税制変更では税額計算だけ、帳票変更では印刷整形だけが変わるとして、互いを壊さないなら、当面は困りません。実務感覚として、「今壊れていないのだから、別に一緒でもいいだろう」という判断はかなりまともです。ここを無視して理屈だけで分割を強制すると、かえって現場は設計原則を嫌います。
しかし、SRPが見ているのは現在の故障有無だけではありません。別々の変更要因が同居していると、将来の修正で思わぬ巻き込みが起きやすくなります。問題は「もう壊れているか」ではなく、「別の都合を巻き込みやすい配置か」です。つまり、これは故障診断というより事故予防の話です。壊れてから問題視するのでは遅いが、壊れていないだけで放置してよいとも限らない。この中間に、SRPの判断の難しさがあります。
ここは実務上、三つの軸で見るとかなり整理しやすくなります。第一に変更頻度です。頻繁に変わるものほど、分ける価値が高まります。第二に影響範囲です。少しの変更で広い範囲に波及するなら、分離の優先度は上がります。第三に担当者分離です。同じ人がまとめて面倒を見るのか、別チームや別部門がそれぞれ触るのかで、衝突確率は大きく変わります。この三つが同時に大きいなら、分けるべき可能性はかなり高いです。
要するに、「壊れていないから大丈夫」と「別軸なら即分割」は、どちらも雑です。SRPを実務で使うなら、変更頻度 × 影響範囲 × 担当者分離という掛け算で考えるのがかなり有効です。頻度が低く、影響が狭く、担当者も同じなら、同居でも大きな問題にはなりにくいです。逆に、よく変わり、影響が広く、担当者も分かれているなら、分けない理由のほうが薄くなります。この具体性がないと、SRPはただの標語で終わります。
小ささを目指すとむしろ外す
SRPの誤解が最も分かりやすく害になるのは、これを小さいクラスを作る原則だと思い込む場合です。「一つのことだけをする」と覚えてしまうと、少しでも処理が増えたら分ける、少しでもメソッドが増えたら別クラスにする、という方向へ走りやすくなります。見た目は確かに整いますし、教科書的にも綺麗に見えます。しかし、その分割が変更理由と一致していなければ、設計としてはむしろ悪化します。
実際には、同じ理由で一緒に変わるべき処理まで細切れにされると、変更のたびに複数のクラスをまたいで追いかける羽目になります。コードの棚は綺麗に並んでいるのに、変更の動線はぐちゃぐちゃという状態です。これは典型的な失敗です。SRPが求めているのは小ささではなく、変更のまとまりの見えやすさなのに、サイズだけを目的にしてしまったからです。
本来、同じ理由で変わるものは集めたほうがよいのです。凝集が高まり、修正箇所が追いやすくなります。逆に、違う理由で変わるものが混ざると、結合が増えて危険になります。SRPは、結局この凝集と結合の調整です。小さいこと自体には価値はありません。変化の軸が揃っていることに価値があります。ここを外すと、「綺麗だけど保守しにくい」という、いかにも現場で嫌われる設計が出来上がります。
人は整った棚を見ると安心します。しかしソフトウェア設計で本当に重要なのは、棚の数ではなく、どの変更要求がどの棚に閉じ込められているかです。SRPは、見た目を綺麗にする原則ではありません。変更要因の混線を減らす原則です。この視点を失うと、小さいクラスを量産して満足するという、かなり滑稽な状態に陥ります。
まとめ
SRPが理解しにくい最大の理由は、名前が簡単そうなのに、中身が全く簡単ではないことです。責務という言葉は仕事の数を連想させますが、実際に見なければならないのは変更理由のまとまりです。この時点で、入口と本体の間にずれがあります。多くの誤解は、ここから始まります。
さらにこの原則は、コードだけの原則ではありません。誰がどの理由で変更を要求するのかという関係者と組織の問題が入り、しかもそのコードが単発で終わるのか長期保守されるのかという時間軸まで入ってきます。したがって、静的な構造だけ見て白黒をつけようとしても、うまくいきません。クラスの大きさを数えて安心するのも、ここで外れます。SRPは、コードの中だけで完結する綺麗な法則ではなく、現実の保守と運用に強く結びついた原則です。
そしてこの原則は、抽象の高さにも依存します。ボールペンのように多用途でも単一責務なものはありますし、スマホのように多機能でも内部が適切に分離されていれば問題ないものもあります。何を一つの責務と数えるかは、どの抽象段階で境界を引くかによって変わります。つまりSRPは、機能数やクラス長ではなく、抽象境界と変更軸の整合を見極める原則です。
結論として、SRPは「一つのことだけをせよ」という単純な教訓ではありません。変更設計、抽象設計、保守設計を同時に要求する、かなり重い原則です。だからこそ読むだけでは分かった気になりやすく、実務に持ち込むと急に難しくなります。SRPは静止画ではなく長時間露光で見るべき原則であり、その意味で、理解しにくいのは欠陥ではなく、この原則が最初から現実の時間を相手にしているからなのです。
Discussion