この記事はクラスター Advent Calendar 2022 1日目の記事です。
クラスターでは主にroom serverのサーバーエンジニアをしている @yoan と言います。
最近タコスにハマってキッチンが油まみれです。
今年の英語の本を12冊(月イチペース)読むという目標を立てました。
12/1時点で7冊と目標到達できなそうですが、アドベントカレンダーにはその中でとても印象深かった「A Philosophy of Software Design」(以後 APoSD
)を紹介したいと思います。
先程調べたところ発売日は昨年の7月なのですが、洋書 Software Design, Testing & Engineering Books
の売れ筋ランキングで現在も1位になっていました。
未翻訳ですがたくさんの方が日本語で記事を書かれていますのでご存知の方もいると思います。
APoSDはソフトウェアの複雑性の定義を出発点に複雑性を減らす設計や方針を述べられた本なのですが、個人的に好きなのは「郷に行っては郷に従え」のような泥臭い(失礼)アドバイスもあり単にソフトウェア設計だけではなくチームでの開発も視野に入った本で非常に親近感が湧きました。
(筆者は現在スタンフォード大学で教授をやられていますが、もともとソフトウェア関連で起業されていた方で商業用の開発経験も反映されているのかなーと勝手に思っています)
本記事ではAPoSDを多くの方に興味を持ってもらいたく、私自身が感銘を受けたポイントやすぐに真似できそうなところに絞って紹介したいと思います。
複雑性とは
本書では複雑性を下記のように定義しています
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the systems
DeepL先生に訳してもらうと
複雑さとは、ソフトウェアシステムの構造に関するもので、システムの理解や変更を困難にするものである
となります。
複雑性が高いことで様々なデメリットを受けます
- 小さい改修にも多大な労力が必要 (change amplification)
- 理解するのが難しい (cognitive load)
- 改修による副作用がないと言い切れない (unknown unknown)
また複雑性は作者より読者に現れると書かれています。
日常でも自分はシンプルにかけたと思っても、レビュワーからコメントをもらったり、数カ月前の自分のコードが読みにくいといった経験は皆さん経験されてると思います。
複雑性が高いソフトウェアは修正箇所が増える、実装期間が長くなる、副作用が出るといった状況を引き起こし、結果開発工数や開発期間の増加の原因になります。
Module should be deep
ではどのようにソフトウェアを設計するとよいのでしょうか?
筆者はdeep moduleを心がけることを紹介しています。
deep moduleとは
モジュールを簡略化した図が下記になります。
(個人的にAPoSDの象徴的な図だと思ってます)
縦を機能(Functionality)横をインターフェース(Interface)とした四角形でモジュールを表しています。
そして機能が多く(深く)インターフェースが少ない(短い)モジュールをdeep module、その逆をshallow moduleと呼んでいます。
それではなぜdeep moduleが良いのでしょうか?
インターフェースはモジュールの機能を抽象化してユーザーに利用してもらいます。
APoSDでは抽象化を下記のように定義しています。
An abstraction is a simplified view of an entity, which omits unimportant details.
DeepL先生で訳してもらうと、
抽象化とは、ある実体を、重要でない細部を省いて簡略化したものである。
となります。
抽象化することで重要でない部分が省かれるので認知負荷が下がります。
つまり適切に抽象化されたインターフェースは利用者の認知負荷の低い複雑性の低いモジュールとなります。これが高機能だったらdeep moduleだと言えます。
例えば圧縮形式のdeflateやzstdも解凍・圧縮といったインターフェースがあるだけでそのアルゴリズムを意識する必要はありません。
本書で紹介されているdeep moduleの最たる例としてUNIXのfile ioが挙げられており、インターフェースはopen, read, write, lseek, closeなどです。
これに対し機能としてストレージはHDDかSSDか、ファイルシステムはなにか、メモリにキャッシュするかなど様々なレイヤーで機能が存在しています。もちろんユーザーはそんなこと考えなくてもファイルが読み込めます。
個人的に面白かった点
クラスは小さくあるべきと教わります。
railsにおけるfat controllerや神クラスなど一つのモジュールが肥大化する例はよくありますし、それを分割する(large -> small)ことで例えばテストがしやすくなるので小さいクラスの良さは理解できます。
しかし著者はこの議論を一歩進めて、1つのlarge moduleを単純に分割しただけでは無数のshallow moduleができるだけでインターフェースが増えるので複雑と述べています。
この話を読んで数年前railsを開発していた当時、rubocopのメソッドの最大行数を警告する項目で議論した事を思い出しました。
当時自分は「短い = シンプルだからいいのでは?」と考えていました。
しかし結果として関数名の命名に悩む、無駄な継承により責務の曖昧なクラスが増え自身の記憶キャパを超え毎回コードを参照するといった事をしていました。
メソッドを横に分割するだけでは不十分で、縦に抽象化を増やすことで認知負荷を下げることなのだと学びました。
そう考えるとある程度規模のあるシステムには必ず層があります。
ネットワークのOSI参照モデルやCPU > OS > アプリケーションといったレイヤーです。
レイヤが別れていると責務も明確になるので、説明も楽ですし新しい概念が生まれたときも対応しやすいです。
おわりに
APoSDでもdeep moduleのあとレイヤーの話が続いて行きます (chapter 7. Different Layer, Different Abstraction など)
もし興味がありましたらぜひご購入を検討してください!