あけましておめでとうございます、になるはずだったのですが、後から読んだ『Googleのソフトウェアエンジニアリング』の方を先に記事にしたので新年2本目の更新です。
さて、本題。最近のお気に入りポッドキャストであるe34.fmで激賞されていた『A Philosophy of Software Design』を読みました。初版は2018年に出ていて、今回は2021年に出た第2版を読みました。
どんな本?
書籍のテーマはソフトウェアの複雑さです。複雑さとは、システムを理解したり変更したりするのを困難にさせるものだと定義されています。そして、その兆候は、単純な変更がたくさんのコードの修正を強いてしまうことや、認知負荷(『Team Topologies』でも出てきましたね)が高まってしまうことなどに表れますが、最も深刻なのが「未知の未知」の発生だとされています。未知の未知は、バグの混入に容易に繋がってしまう一方で、定義上その存在に気づくことができないのでとても厄介です。
書籍では、このような複雑さを減らすためのさまざまな設計原則を紹介しています。このように書くと、設計原則のカタログのように見えてしまいそうですが、論述の展開は体系的で、さすが研究者の書いた本だという印象があります。
印象に残った箇所
Deep Modules と Shallow Modules
この本について語るときにはしばしば言及される部分ですが、モジュールをDeep/Shallowという軸で語るのは新鮮でした。Deepなモジュールの方がいいというのが本書の主張です。この区分は第4章で出てくるのですが、後続の章でも繰り返し言及される(「こうするとモジュールがDeepになるよ」などの形で)枠組みです。
インタフェース | 機能(Functionality) | |
---|---|---|
Deep | 少ない | 多い |
Shallow | 多い | 少ない |
※インタフェースが「多い」というのは、メソッドの多さや、あるメソッドの引数が多いことなどを指します。
第4章。抽象の話や、Deep modules(小さなインタフェース、多くの機能)とshallow modules(大きなインタフェース、小さな機能)の話。重要な点を(誤って)取り除いてしまう抽象の結果としてのobscurity。
— こま (@koma_koma_d) 2021年12月30日
General-purpose と Special-purpose
第6章くらいから出てくる軸で、「目下の必要を満たすよりも少しばかり(somewhat)一般的な目的に使えるように設計しましょう」というのが主張です。
第6章で、目下の必要を満たすよりも少しばかりgeneral purposeなモジュールを作りましょうという話をしてて、メソッドを少なくできないか考えてみようね、代わりに引数が増えるのは物事をシンプルにできていないのでダメだよ、といわれてて、せやなぁとなってる。
— こま (@koma_koma_d) 2021年12月30日
この軸に関連して書かれていたこととして、ある処理(コード)がgeneral-purposeなのかspecial-purposeなのかを考えて、それらが混ざらないようにしましょうというアドバイスや、パフォーマンスに関する章(第20章)でのクリティカルパスからspecial-purposeな処理を取り除くとよいですよ、というアドバイスは印象に残りました。
コメントについて
「コメントを必要とするようなコードにしない」というのがよく言われることですが、この本では「そうはいってもコメントじゃないとできないことはあるでしょ」という立場からどういうコメントを書くのがいいのかを論じています。
第10章、コメントについての章だけど、最後でまたUncle Bob が批判されている。良いコードがコメントの必要性を減らすのは確かだけど、コメントが必要なことはコードの質の低さを意味するわけじゃないでしょう、と。
— こま (@koma_koma_d) 2021年12月31日
個人的には有益だと感ずるアドバイスが多かったコメントについての論述でしたが、中でも印象に残っているのは、「コードよりも詳細を説明するコメントか、コードよりも抽象的なことを説明するコメントか」という区別です。コードの繰り返しに過ぎないようなコメントを書かないというのはよく言われることですが、詳細度という尺度をおいて、どっちの方向に寄せたコメントを書いているのかを意識するというのはとても役立ちそうな気がしました。
また、コメントを後で書くのではなく、設計の手段として最初に書きましょうという話がされていたのも印象に残りました。後から書こうとすると、コメントが実装の繰り返しになってしまったり、そもそもコメント書かなかったりということが起きがちですが、先にコメントを書くことでそれは設計の活動の一部となって楽しくなるし、抽象を考えることで設計の改善にもつながると言われています。
「インクリメントは機能ではなく抽象であるべき」
第19章では、オブジェクト指向(特に継承)、アジャイル開発、ユニットテスト、テスト駆動開発、デザインパターン、getter/setter といったトピックについて論じています。
よく聞く話も多かったのですが、アジャイル開発について論じているところで「the increments of development should be abstractions, not features」という記述があったのは印象的でした。インクリメンタルな開発をするときに、どのように設計を考えるかというのは大きなトピックですが、このような形でのアドバイスは初めてみました。続きの文章では、「抽象の必要に気づいたら、時間をかけて少しずつ作るのではなく、一気に設計しろ」と言っていて、あくまで「必要に気づいたら」ベースではありますが、開発対象の単位として抽象を主役として取り上げようという方向性は納得度はあるものでした。
アジャイルについての節で、the increments of development should be abstractions, not features と言っている。あとの箇所で、抽象は少しずつ作るんじゃなくて一気に設計しろと言っているので、機能をインクリメントとして作っていく中で抽象を作るんじゃないぞ、ということを言っているかな。
— こま (@koma_koma_d) 2022年1月1日
全体的なスタンスについて
以上、印象に残った箇所をいくつか取り上げて紹介しました。個別のアドバイスについては「なるほどそういう考え方ができるのか」と思うところも多かったのですが、全体としては「今まで慣れ親しんできたのと結構違うスタイルだな」という印象も強く残りました。
大クラス主義?
上記のような印象を特に感じたのは、小さいクラスをたくさん作ることに全体的に否定的である点です。この書籍の中では、たくさんの小さいクラスを設けることは利用者にとってはインタフェースの多さ、ひいてはshallowなモジュールに繋がってしまいやすいものとされています。また、大きなクラス(メソッド)の方がファイルを行ったり来たりせずに一覧できるのでみやすい、という点にも言及しており、分割に対しては謙抑的なスタンスをとっています。
基本スタンスは「メソッドやクラスはまぁデカくてもええやん、みやすいし」という感じで、子タスクを切り出してもいい条件として、親子のそれぞれが独立して理解できる(反対側も読まなくても理解できる)ことを挙げている。
— こま (@koma_koma_d) 2021年12月31日
もちろん、「一つになっていた方が読みやすいんだから分割なんてするなよ」という雑な議論はしておらず、具体的な事例も交えつつ「こういうケースだとこういうデメリットがあるので分割しない方がいい」という丁寧な議論をしているのですが、自分は個人的には、責務の小さなクラスを多く作って組み合わせることで機能を実現する文化に親しんできて、分割前提でものを考えることが多かったので、(反発するわけではないですが)引っかかりながら読みました(それでこそ読む価値があるとも言えます)。
このような議論を展開するときには、著者はしばしばJavaの標準クラスライブラリを取り上げて批判します。Javaの標準クラスライブラリの一部に設計的にイケてないところがあるという話はしばしば耳に入ってくるので、言語の文化の話でもないのかもしれないですが、Ruby の特徴として「大クラス主義」というのが挙げられることがあるようで、文化(あるいは言語としての設計思想)の違いもあるのかなと思ったりしました(妄想)。
大クラス主義 クラス設計において一つのクラスにさまざまな機能を盛り込む方針。 Ruby の Array は、配列、リスト、タプル、集合、スタック(LIFO)、キュー(FIFO)などの機能を兼ね備えており、大クラス主義的と言える。
Ruby用語集 (Ruby 3.3 リファレンスマニュアル)
いや、これは多クラス主義でしょう。大クラス主義は結果的に少クラス主義につながります。
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/14664
私個人の印象ですが、小クラス主義で直交性が高いのは指数関数的な複雑さの原因になりやすいと思ってます。逆に大クラス主義でメソッドが多いのは線形の複雑さですね。私の好みはいうまでもないでしょう。
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/14675
ちなみに「大クラス主義」は海外では聞かない概念のようです。
英語で「大クラス主義」ってなんて表現するんだろうね。海外で聞いたことのない概念だけど。
— Yukihiro Matz (@yukihiro_matz) 2016年6月21日
なお、Java に近いコミュニティだと、『現場で役立つシステム設計の原則』の増田さんが以下のように「小クラス主義」と「大クラス主義」に言及していました。これは業務アプリケーションのバックエンドの設計の文脈で、書籍で出てくるUnixや言語の標準ライブラリの設計とはだいぶ前提としているものが違いそうなので、この文脈で「大クラス主義」「小クラス主義」という用語を用いることがどれだけ適切なのかは不明ではあるのですが。
なお、 id:snoozer-05 に「この大クラス主義ってのがRubyistじゃない自分にはよくわからんのですが」と聞いたところ、「『研鑽Rubyプログラミング』を読むといいんじゃないか」と言われました。確かに、関連する話が書かれているようなので、後で読んでみようと思います。
第 2 章「役に立つ独自クラスを設計する」では、いつ独自クラスを定義すべきか、 独自クラスへの SOLID 原則の適用、クラス設計でクラスを大きくすることとクラスの数を多くすることのトレードオフを扱います。 (『研鑽Rubyプログラミング β版』まえがき)
研鑽Rubyプログラミング β版www.lambdanote.com
とまぁ、妄想を書きましたが、真面目な(そして実践的な)話に戻ると、この書籍の主張している Deep なモジュールの優位性、そしてそのようなモジュールを実現するために過度のクラス分割を避けるという方針は理解できるものです。あるモジュールがあまりにたくさんのクラスをインタフェースとして外部に公開すると、クライアントの認知負荷は増大します。そのため、モジュールの設計者としては、最小のインタフェースで(少しばかりgeneral purposeな)機能を提供することを考えるのは重要だと思います。
一方で、あるモジュールのインタフェースを固定した上で、そこの内部で(外部に公開されない)クラスやメソッドの分割をどれだけするのかという点については、この節の冒頭に貼ったツイートで自分が書いているように、条件付きでサブタスクの分割を認めていて、納得がいくものです。たとえば、意味のあるまとまりを持ったコードを private メソッドに切り出すことは外部のインタフェースには影響を与えませんが、public メソッドの本体を読もうとしている人にとっては(ライブラリのクラスやモジュール内の他のクラスと同様に)所与のインタフェース、抽象を提供することになり、効果的である場合も多いでしょう(何も考えずに private メソッドに切り出すなよ、はその通り)。
第9章末尾(関数の長さについて)と第12章末尾(コメントについて)の2箇所にわたって、Uncle Bobこと Robert Martin (の、『Clean Code』における主張)が批判されていたのは印象的でした。プロレスですね。
第9章の最後ではUncle BobのClean Codeが批判されていて、長いからという理由だけでバラすんじゃないと言われている。長さ(length)よりもモジュールの深さ(depth)の方が重要で、まず深くしてから短くできないか考えろと言っている。
— こま (@koma_koma_d) 2021年12月31日
MITアプローチ?
もう一つ、「おっ」と思った点として、以下の一節があります。
it is important for a module to have a simple interface than a simple implementation. (p.61)
(実装もインタフェースもシンプルであるに越したことはないという前提の上で)「シンプルな実装よりも、シンプルなインタフェースを」という主張で、いわゆるMITアプローチの立場を表明しているように読めました。
MITアプローチとNew Jersey アプローチについては、ajitofm 6: Worse is Betterの後半(40分くらいから)で id:t-wada さんが言及していたので、知っている人も多そうです。Richard Gabrielのエッセイ「The Rise of Worse is Better」で「MITアプローチ」のデザイン哲学の一つとして挙げられている「簡潔性」についてのスタンスは、まさにこれです。
Simplicity-the design must be simple, both in implementation and interface. It is more important for the interface to be simple than the implementation.
The Rise of ``Worse is Better''
日本語訳: The Rise of "Worse is Better"
上記のエッセイの中で、MITアプローチは「MIT/Stanford 方式の設計」とも言われていますが、まさにこの書籍の著者はスタンフォード大学の教授なので、(非常に雑な結びつけではありますが)一定のなるほど感がありました。
この書籍のスタンスをMITアプローチ(あるいは大クラス主義)の一形態だと断ずることには実益はないのですが、この書籍以外にもさまざまな立場があり、一部対立するように見える部分がある(例えば、MITアプローチと対置されているNew Jerseyアプローチでは、「実装のシンプルさの方がインタフェースのシンプルさよりも大事だ」と主張しています)ことがわかると、一歩距離をとって書籍を読むことができると思います。この意味で、書籍のタイトルが「A Philosophy」と不定冠詞になっているのは良いなと思いました(『A Pattern Language』の A にこだわるアレグザンダーオタクの感想)。
追記
改めてWorse is Better 関係の記事やツイートを読み直していて気づいたのですが、 Worse is Better についての @rui314 さんの記事で、Stanford在学中に "The Rise of Worse is Better" のオリジナルを読まされたとおっしゃっていて、MIT/Stanfordアプローチ といっても現代にまでどこまで引き継がれているかは相当怪しいということが改めてわかりました。
もともとの「悪いほうが良い」エッセイの最初のバージョンは1989年に書かれた。その中では「良いデザイン」はMITやStanfordのスタイルとして言及されている。面白いことに僕はこのデザイン原則のエッセイをStanfordのコンピュータサイエンスの授業で読まされた。どうやらStanfordの授業もこの30年間ですっかり「悪い方向」に変わってしまったようだ。その授業で僕がクラスの掲示板に投稿した文章を多少修正の上で翻訳したのがこのエッセイである。
おわりに
以上、『A Philosophy of Software Design』を読んだよ、というお話でした。
なお、日本語翻訳については、柴田芳樹さんが以前ブログの中で、
私が知る限り、残念ながら、この本は日本語へは翻訳されないようです。
『A Philosophy of Software Design』:柴田 芳樹 (Yoshiki Shibata):SSブログ
と述べられていて、「期待できないのかな?」と思っていたのですが、ドイツ語翻訳(2nd Editionベース)は既に出ているので、もしかしたら出るかもしれません。とはいえ、本文170ページ程度の小さな書籍で、英文もわかりやすかったと思いますので、「英語は苦手だけど読めるようになりたいな・・・」と思っている方にはおすすめです。
関連記事等
著者のGoogleでの講演
e34.fm のホストの deeeet さんのツイートと記事
私の今年の技術書大賞は「A Philosophy of Software Design」https://t.co/JB3pUVHIIE ですここ数年読んだ技術書の中でもぶっちぎりで良かった(時間が許すなら)翻訳したいくらい
— Taichi Nakashima (@deeeet) 2018年11月30日
書こうと思って書かなかったことでMicroservices化以前にできることとしてModule化があり複雑化と闘うためのDeep Moduleという概念を提唱した近年最高の書籍である Philosophy of Sotfware Design https://t.co/YqOadrs96P という本があるので読みましょう. pic.twitter.com/A2ZR1LjO5D
— Taichi Nakashima (@deeeet) 2019年5月22日