Read Book: A Philosophy of Software Design
読んだ本:Ousterhout, John. A Philosophy of Software Design
よいソフトウェアデザイン、コードとは何か、ソフトウェア開発で常につきまとうテーマです。 経験を通じて直観的な感覚は養われていくのですが、この本はその点を言語化し、体系化を試みており、自分の知見の整理に役立ちました。 実体験を踏まえ、印象に残った点をまとめます。
この本のテーマは大まかに2点で、1) ソフトウェアの複雑さとは何か、なぜよいデザインが重要かについてと、 2) デザインを向上する技法についてです。 2章が前者で、3章以降は後者の説明に充てられています。自分の感想もこの2点に分けてまとめます。
ソフトウェアの複雑さについて
著者はソフトウェアの複雑さを、ソフトウェアの構成要素のうち、システムの理解及び変更修正の難度を上げる全ての要因と定義しています。 これは個人的にも納得感のある定義でした。ソフトウェアエンジニアの役割とは、実世界の変わりゆく要求・課題に対して、 計算機を活用することで解決に導くことであると考えるので、システムの変化への適応性は本質的に重要な要素と思います。
また、より多くの時間開発者の目に触れる箇所ほど、複雑さを増す弊害が大きいと述べられています。 この点は、特に仕事としてチームで開発していると強く感じるところです。 コードを注意深く読まないと挙動が理解できないソフトウェアは、チームの生産性に悪影響を与えます。 より多くの開発者の目に触れるであろうソフトウェアほど、よいデザインを作ることのレバレッジが大きくなります。 さらに、後ろの章でも触れられていますが、ソフトウェアデザインは開発者にとっての利便性より、 コードを読み、利用する人にとっての複雑さを重視するべきというのも大事なポイントです。 一般的に開発期間と運用期間では、後者が大半を占めるわけですし、その間多くの人がデザインを目にする場合、 各個人が理解にかかる時間を短くする方が、オリジナルの開発者の時間を節約するより効果が大きいです。
複雑度が高いシステムの弊害として、以下の3点が挙げられています。
- Change amplification
- Cognitive load
- Unknown unknowns
コードを読んだり変更修正する際にこうした影響を受けるわけですが、最初の2つはまだましで、コードの理解や変更に 余計な時間がかかることが主なデメリットと思います。(それ自体もバグを生む遠因となり得るので決してよくはありませんが。) 一方で、”Unknown unknowns”については、理解を妨げるだけでなく、変更の影響範囲が特定できなくなるため、 容易に破壊的な変更を埋め込んでしまうようになります。コードに触れる人が想定できないような挙動は、可能であれば排除し、 避けられない場合もドキュメント等で十分な注意喚起をするべきです。
ソフトウェアデザインを向上する技法について
3章以降は、よいデザインを作り上げるための技法の、トピック別での解説になっています。 数が多いので、ここでは印象の強かった箇所のみ取り上げます。
Working Code Isn’t Enough
本の中では、Strategic (戦略的な) ProgrammingとTactical (急場しのぎの) Programmingという対比で論じられていますが、
スピード感を求められる現場では、往々にして多少の複雑さを許容して、短い期間での開発を要求されることがあります。
ビジネスの都合上ある程度は避けられないのですが、一旦ソフトウェアに入り込んだ複雑さは大体想定よりも早い段階で悪影響を及ぼします。
自分の意見としては、一時しのぎのコードを書かざるを得ない場合は、併せてリファクタリングの計画も立てておくべきと思います。
複雑さは積もり上がっていくので、時間が経てば経つほど、リファクタリングが大変になります。
また、経験的に複雑なコードは作者であっても、すぐに意図がわからなくなりがちです。
最初のコードを書いた記憶が鮮明なうちに直した方が効率も良いと思います。
さらに、そのリファクタリングは可能な限りオリジナルの開発者が実施するべきです。
リファクタリング前の複雑なコードのままでは、引継ぎのコストも高くなるため非効率です。
鉄は熱いうちに打てで、早いうちに、小さく、繰り返しリファクタリングするのが最も効率的なのではないかと思います。 複雑なコードを放置するリスクは非常に高いのですが、機能追加要求などみかけ上優先度の高そうなタスクはいくらでもあるので、 落ち着いたらいつか直そうくらいの感覚でいると直す日はやってこないです。
16章と19章からですが、以下の2つのフレーズはよく的を得ているとおもいます。
Ideally, when you have finished with each change, the system will have the structure it would have had if you had designed it from the start with that change in mind. (p.136)
the increments of development should be abstractions, not features. (p.154)
安全にリファクタリングするための原則として、機能開発とリファクタリングを同時に実施するべきではないという考え方もありますが、 個人的には外部仕様に影響がない範囲で、かつレビューが大変になりすぎない程度であれば混ぜてしまっても良いと考えています。 機能でなく、ソフトウェアの構造上の変化を変更単位と考えると筋が通ります。 先ほど述べた通り、リファクタリングを遅らせるほど、複雑さは積み上がり、リファクタリングが大変になりがちです。 インクリメンタルに小さなリファクタリングを実施する方が、見通しよくコードが書けると思います。
自分のこれまでの仕事で、既存サービスを統合や分離するタスクがありましたが、これも単にコードを統合、 分離する作業ではないです。 そのタスクが発生している時点で、システムに対する要件が変化しているわけなので、 デザインのレイヤで変化を織り込む必要があり、するとコードのリファクタリングも必要になると思います。
Modules Should Be Deep
Classitis may result in classes that are individually simple, but it increases the complexity of the overall system. Small classes don’t contribute much functionality, so there have to be a lot of them, each with its own interface. (p.26)
これは単純に反省ですが、自分も長いクラスやメソッドがあったときに、処理の流れを明確にする意図で、 処理の単位毎に、クラス・メソッドの分離をよくやっていました。 分離すると元のクラス・メソッドはシンプルになるんですが、詳細な動作を追うときに別の箇所に飛ばないといけなくなり 全体としてはかえって見通しが悪くなってしまいます。 いままで漠然と、長いクラス・メソッドは読みにくいといった先入観があったのですが、長さは大きな問題ではないと認識を改めました。
Better Together Or Better Apart?
メソッドをまとめる・分ける際の指標として、情報が共有されているか、まとめることでインターフェースがシンプルになるか、 コードの重複があるか、一般的なケースの処理か特定のケースの処理か、といった観点が解説されています。 この9章では、おそらく単一モジュールであるということが前提になっていると想定します。
複数のモジュールやマイクロサービスを抱えるチームでの開発では、モジュール・サービスの分割をどうするかという点も 課題になると思います。 うまく言語化できないのですが、上記の要因以外にも、チームの規模やリリース頻度、組織構成などといった要因も絡みそうです。 こうした大規模システムデザインについて、まとまった文献があるのかは気になります。 思いつく範囲だと、Conway’s Lawやマイクロサービスなどの キーワードが関係するんでしょうか。
まとめ
ソフトウェアデザインについて簡潔にまとまっており、ページ数もそれほど多くないので読みやすかったです。 新たな観点が得られた他にも、今まで感覚的に理解していた内容が言語化され、語彙を増やす意味でも学びがありました。