SOLID原則をまとめてみた Part1 ~SOLID原則とはなんぞや編~

はじめに

はじめましてorこんにちは!
ecbeing2年目、R&D部門所属の浦…改め蓑代です。
※結婚して名前変わっちゃいました

これまでは、Dockerの記事やスクラムに関する所感記事、JavaScriptライブラリに関する記事を書きました。
blog.ecbeing.tech

そして今回…というより本シリーズでは、泣く子も黙る『Clean Architecture』本を参考に…。
www.amazon.co.jp ソフトウェア設計の5つの原則として名高い「SOLID(ソリッド)原則」についてまとめていこうかと!

…既にSOLID原則をご存じの方は、「すごく難しい話題をまとめるんだな」と察していただけましたかね…?

SOLID原則は「原則」と名が付いているように、非常に概念的な話が多く。
※具体的な実装の話ではなく、もっと上位の概念の話が多い…という感じです

実は筆者は新卒のころにSOLID原則について学ぶ機会があったのですが…。
趣味でプログラミングしてた程度の知識しか持ってなかったからか、さっぱり身につかず。
そんな状態でSOLID原則を見ても、2週間後には「SOLIDのSってなんだっけ…」となるくらいに頭から消え去っちゃいました。

しかし、そんな筆者も今年で2年目(とちょっと)。
様々なプロダクトにプログラマとして関わってきた今の筆者なら、以前よりもSOLID原則の事が分かるようになる…と淡い希望を抱き、まとめてみようと決心した次第です。

後、社内の凄腕アーキテクトの青木さん(めっちゃすごい)が、こんなことを話してくれ…。
※筆者の要約なので、これをまんま話したわけではないです

世の中の様々なアーキテクチャを学ぶと、新技術を学ぶときに「その技術がどんなアーキテクチャを採用したか」が分かるようになる。
例えばMVVMを知っていれば、Vue.js製のプロダクトを見た時に「画面上へどんなデータが出力されるのか」はViewをたどれば見つける事が出来る。
「どこのフォルダやファイルにどの様な処理が書いているか」が、その技術を知らなくても推測できるようになるのだ。(ここ重要)
十分な知識と良く練られた良質なソフトウェアであれば、僅かなコードを見るだけで何のアーキテクチャを採用したのかが分かるようになり…。
そこまでのレベルにたどり着くと、新技術のinput速度が圧倒的に早くなる。

日進月歩が激しいこの業界で、爆速input力を身につけられればエンジニアとしてものすんごい強みになりそうだなと思い。
筆者もそうなりたい!と強く思ったというのも動機です。
((というかこっちが本命

その取っ掛かりにまず、SOLID原則を学んで行こうかなと。

…序章が長くなってしまいました…。
早速本題、「SOLID原則をまとめてみた」に入っていきましょう!

SOLID原則とは

まずは「SOLID原則」という言葉の説明から。

SOLID原則は、オブジェクト指向プログラミング分野を前提とした「ソフトウェアの5つの原則」を覚えやすくするための用語です。
「オブジェクト指向プログラミング分野が前提」とあるように、当該の話は関数型プログラミング等の他分野には当てはまらない原則となりますのであしからず。

同じくソフトウェアの原則である「GRASP」とは異なります。
こちらは「クラスやオブジェクトに責務を割り当てる方針を導くパターンや原則」なので、SOLID原則が指す方向性よりも狭いものになっています。

すっごい蛇足ですが、このSOLID原則を提唱したRobert C. Martin氏は『Clean Architecture』や 『アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技』、『Clean Code アジャイルソフトウェア達人の技』など、素晴らしい著作物の生みの親です。

そんなSOLID原則が掲げる5つの原則は、それぞれ下記の様に呼ばれます:

  • Single Responsibility Principle:単一責任の原則(SRP)
  • Open/closed principle:オープン/クロースドの原則(開放閉鎖の原則)(OCP)
  • Liskov substitution principle:リスコフの置換原則(LSP)
  • Interface segregation principle:インターフェース分離の原則(ISP)
  • Dependency inversion principle:依存性逆転の原則(DIP)

それぞれの原則が意味するものはこうです。

  • Single Responsibility Principle:単一責任の原則
    • 1つのクラスは1つだけの責任を持たなければならない
    • ソフトウェアの仕様の一部分を変更したときには、それにより影響を受ける仕様は、そのクラスの仕様でなければならない
  • Open/closed principle:オープン/クロースドの原則(開放閉鎖の原則)
    • ソフトウェアのエンティティは拡張に対して開かれていなければならないが、変更に対しては閉じていなければならない
  • Liskov substitution principle:リスコフの置換原則
    • プログラムの中にある任意のオブジェクトは、プログラムの正しさを変化させることなく、そのサブクラスのオブジェクトと置換できなければならない
  • Interface segregation principle:インターフェース分離の原則
    • 汎用的な目的のインターフェイスが1つだけあるよりも、特定のクライアント向けのインターフェイスが多数あった方がよりよい
  • Dependency inversion principle:依存性逆転の原則
    • 具象(具体的な物)ではなく、抽象に依存しなければならない

これらの原則は、ソフトウェアをより理解しやすく、より柔軟に、よりメンテナンス性の高いものにするために考案されたものです。

どうしてSOLID原則が生まれたのか

ここからはSOLID原則の理論を提唱した論文(Design Principles and Design Patterns - by Robert C. Martin)を追いながら、「どうしてSOLID原則が生まれたのか」を深堀りしていきましょう。

ダメなソフトウェア設計の4つの原因

当該論文によれば、ダメなソフトウェア設計の原因には次の4つがあるとのこと:

  • Rigidity-剛性
  • Fragility-脆弱性
  • Immobility-不動性
  • Viscosity-粘性

…名前だけじゃようわからんですね…。
1つ1つ詳しく見ていきたいと思います。

Rigidity-剛性

「Rigidity-剛性」は、変更や修正が困難であることを指します。

例えばで言うと、1つのモジュールを変更したいのに、影響範囲が大きかったり調査が難航することで数週間もかかってしまう状態です。
これにより、修正コストの大きさからエンジニアだけでなく、マネージャー職、経営者ですらシステムの変更を恐れる様になり(剛性)、やがてそれは経営方針にも悪影響を及ぼす…かもしれません。エライコッチャ…

Fragility-脆弱性

「Fragility-脆弱性」は、剛性と密接に関わるものとして挙げられています。

先に挙げた例の様に、1つのモジュールを変更するだけで影響範囲が色んな箇所に散ってしまうと、十分な影響範囲の調査なしの修正によりシステムが壊れてしまう可能性が出てきます。
また、壊れやすさが上がると、時間の経過とともにシステムが壊れる可能性も高くなる…とのこと。

これが脆弱性が指すものです。まぁこんな状態のシステムなんてメンテできませんよね…。

この悪影響はそのまま、顧客やシステムの経営者からすらの「エンジニアに対する信頼度」の低下につながるとのこと…
悪いコードにより信頼度が低下するなんて、いやぁゾッとしますねぇ…😱ギャー
それこそ、「せっかく優秀なエンジニア雇ったのに、改修後の他範囲への影響率が下がらないじゃないか!高いお金出して雇ってるのに!」なんて事になる未来もちょっと見えるような…。ウヒー

Immobility-不動性

「Immobility-不動性」は、「作成したソフトウェアを他のプロジェクトに再利用できない事」を指します。

これは「1つのモジュールに色んな処理が複合されている場合」を考えてみるとわかりやすいかもです。
この状況下で「そのモジュールの一部の処理を他プロジェクトで再利用したい」事を考えてみましょう。

…察しの良い方はもうお分かりかと思いますが…。
こう言ったモジュールでよくあるのが「そのモジュール内でしか使用しない前提で処理を書かれていること」や「依存する処理が多すぎること」です。
この様なモジュールでは、一部の処理だけを移行できず「モジュールを丸ごと移行しないと使用できない」ケースになることがほとんどです。

ただ、モジュールを丸ごと移行して動かすなんてことは大概の場合はできないはずです…。
モジュールを丸ごと移行した後に待ち受けるのは「そのモジュールを自分たちのプロジェクト用に書き換えること」。
再利用性なんて0に等しい環境が出来上がっちゃうのです…開発コストが上がるのは言わずもがな。

ちなみに、こちらの不動性につながるもの…と言っていいのかですが…。
私たちのチームでは、チーム独自に「新規プロダクトを作成する際の雛形フレームワーク」が存在します。
ReviCoや今後ローンチされるとあるプロダクトも、実はこちらのフレームワークを元に作られたものなのです。

また、雛形のフレームワークは絶えず進化を遂げています。
雛形のフレームワークが使用している.NET Coreのアップデート対応だったり、細かな改善だったり…。

さらに、雛形フレームワークの進化内容をReviCoや他プロダクトに取り込むコストは結構控えめであるため…。
すでにローンチされているプロダクトであったとしても、NET Coreのアップデート対応だったり、細かな改善だったりを雛形フレームワークを元とした少なめコストで実現できると言う流れが生まれているのです!
不動性の逆、再利用性が高いと言えるんじゃないかなと。

Viscosity-粘性

「Viscosity-粘性」は「設計」や「環境」の影響で、開発に無駄に時間がかかっちゃう事を指します。

例えばデザインパターンの1つMVVMに沿ってきっちりと作成されたプロダクトにおいて、完全新規に1画面を作成する改修を考えてみましょう。
MVVMで1画面を作成するには、新しくViewModelやView、Modelを作成しなければなりません。
((大体は既存のコピペで済むかもしれませんが…

ただ、もしこれが「既存の画面にちょこっと処理を追加するだけで実現できるなら?」
MVVMに沿わないかもしれませんが、圧倒的な速度で実装が完了するはずです。
つまりこれが、デザインパターン…「設計」の影響によって無駄に時間がかかっちゃうことです。

一応補足すると、粘性はデザインパターンを否定しているものではないと思います。
要は「適材適所ではないデザインパターンを採用したせいで、無駄に開発コストを上げない様にしようね」って事だと思います。
堅牢なシステムであればそれ相応のデザインパターンを、プロトタイプ開発であればそれ相応のデザインパターンを採用しろって事なのかなぁと。

個人的な所感としては…。
「既存の画面にちょこっと処理を追加する」だけで本当に改修が終わるならそれでもいいかもしれませんが、まぁ大体そんなわけないですしね。
「以前追加してくれたあの画面に、こういう機能もつけて!」なんて要望が出てきたら…きっとその時は、新規に1画面を作成しなかった自分を呪うでしょうね。

「環境」による開発に無駄に時間がかかっちゃう事についても触れましょう。
ここで指す環境とは「エンジニアの開発環境」を指します。

例えばエンジニアに渡されるマシンが低速で、1回ビルドするにも非常に長い時間がかかっちゃうと…。
ビルド時間を少しでも削減するために、デザインパターンの観点から見ると正しい修正をしたくても、先に挙げた様な小手先の修正(既存の画面にちょこっと処理を追加する)だけで済ませたくなっちゃうのではないでしょうか。

これはエンジニアへ渡されるマシンだけでなく、デプロイ時のビルド用サーバーやソースコード管理システムにも同じことが言えるかと。
1回のデプロイにうん何時間もかかる様な環境や、1回のcommitやpushに数分もかかる様な環境では、デプロイやcommit/pushするのに尻込みする風土が生まれちゃう様になっちゃうかと…。

そうなってしまうと「エンジニアの開発効率」だけでなく「システムのリリース頻度」も下がり、結果的に「たまにしかリリースされないプロダクト」ができてしまいます。
日進月歩な世界で、更新頻度が遅いプロダクトは遅かれ早かれ置いてけぼりになるでしょう…。

本当の原因

ここまで見てみると、先の章で述べた4つの特性によってソフトウェア設計が悪くなっちゃう…と思いますが。
当該論文には、これら4つの特性よりももっと根本的な原因について示唆しています。

それは…初期段階では想定していなかった形で要件が変わること

「何を言ってるんだ、要件なんて変化するものやろ」と思いの方もいるかもしれません。
(筆者も論文読んでて思いました)

ここで言いたいのは「初期段階では想定していなかった形で要件が変わり、元々のデザインパターンにはそぐわない修正を余儀なくされる」ことです。
もしくは、初期段階でアサインしていた「採用したデザインパターンに詳しい方」がいなくなり…。
全く別の「採用したデザインパターンに詳しくない方」が既存デザインパターンを無視して改修した結果、元々のデザインパターンにはそぐわない修正となったパターンもあるかもしれません。

いずれにせよ、これらの変更により「プロダクトが採用するデザインパターン」と「要件」がそぐわなくなり…。
「なんでこんなデザインパターンを採用したんだ」という形にまでたどり着いちゃうかもしれません。
ここまでくると、リファクタリングを検討してもいいかもしれないですね…。

どんな変更が設計をダメにするのか

ここまでは「設計がダメになっちゃう要因」について見てきました。
次に論文では、「設計がどんな変更でダメになっちゃうのか」を記述してあります。

どんな変更でダメになるのか…論文では「モジュール間の不適切な依存関係にある」と述べています。
これだけじゃちょっと分かりにくいので、自分なりに解釈してみます。

恐らく「モジュール間の不適切な依存関係にある」というのは…。
例えばMVVMを採用したプロダクトが、度重なる変更によりModel層にViewModel層に関わる記述やView層に関わる記述が増えてしまうと言う事なのかなと。

これにより「View層に対し改修を加えるならView層のロジックだけを見ればいいはずが、Model層の改修もやらなくてはいけない」状態になってしまいます。
様々な要件によってやむを得なくそうなってしまったかもしれませんが、この状態がいい状態とは決して言えませんよね?

そしてさらに…。
当該論文では、これを解決するために「依存性を区切る仕切りを作成し、管理することで依存関係を適切に正すことができる」とあります。
その後「この仕切りとモジュールの依存性を管理するために、SOLID原則が必要である」と続くのです。
SOLID原則は、この話の流れで出てくるということですね…。

…ここは正直筆者もよくわかってないのですが…。
きっとSOLID原則を深く理解した後、もう一度これを読むと分かるのかなと。

現段階の自分では「世にある様々なデザインパターンの様に、各レイヤー層を要件に合わせて正しく区切り管理すること」なのかなと感じてますが…SOLID原則を深く理解した後の自分はどう解釈するでしょう、楽しみです!

お次はこのSOLID原則の1つ1つを詳しく見ていきましょう…と思いましたが、記事が長くなりすぎるので今回はこの辺で。

おわりに&次回記事に続く…

今回の記事では、SOLID原則の概要やその出自についてまとめてみました。
まさかSOLID原則自体で1Partになっちゃうとは驚きです…。
((SRPも今Partに入れれると思ったのですが…

次回記事では、SOLID原則のSの部分「Single Responsibility Principle:単一責任の原則(SRP)」についてまとめていこうかと!
今Partでは具体的なソースコードは書いていきませんでしたが、今後見ていく5つの原則については簡単なコードも合わせながら見ていこうかなと思います。

ちなみに余談ですが… 「はじめに」でちょこっとお話に出た青木さんは、過去にこんな記事を書いてます
blog.ecbeing.tech blog.ecbeing.tech

どれも素敵な記事なので、タイトルにある技術に興味のある方はぜひ!

それでは~!

~ecbeingでは基礎的な技術も深堀りまとめてくれる、発信力のある若手エンジニアを大募集中です!~ careers.ecbeing.tech

〜〜参考記事〜〜