uhyo/blog

書評『良いコード/悪いコードで学ぶ設計入門』

2022年5月19日 公開

皆さんこんにちは。今回は、2022年4月30発売の『良いコード/悪いコードで学ぶ設計入門』を読み終わったので、書評という形で感想と紹介を述べたいと思います。筆者はもともと技術書を読まず「ネットでいいやん」派だったのですが、このたびTypeScript入門書を出版したこともあり、それを過去の話として葬り去るべく技術書を読んでいくことにしました。せっかくなので、読んだ技術書の感想等を紹介します。

おことわり: この記事では、「筆者」とはこの書評を書いた人を指し、『良いコード/悪いコードで学ぶ設計入門』を書いた人のことは「著者」と呼びます。また、この記事の内容はすべて筆者の個人的な見解であり、本の内容や本を読んで得られる知識について何らかの保証をするものではありません。

筆者について

筆者はフロントエンドエンジニアで、TypeScriptとReactを専門としています。業務では何だかんだで設計の番人的な立場もやっており、TypeScriptとReactの設計にはうるさいです。

Javaはほとんど書いたことがありません。小学6年生の時にpublic static void mainが覚えられなくて挫折しました。そのあとは就活のコーディングテストでちょっと書かされたくらいです。調べたらOptionalというのがあったので使ってみたら「変わった書き方をするね」と言われました。

筆者の設計観

自分語りしないで早く本を紹介しろという声が聞こえてきそうですが、書評はそれ自身も読み物として面白いほうがよいと聞いたのでまずは筆者の設計観を簡単に紹介します。

筆者の考えでは、究極の設計スキルとは論理的思考能力です。どんな場合にも通用する具体的かつ汎用的な設計論というのはありません。何らかの前提(例えば使用言語)とその上に達成したい目的(開発効率の改善など)があり、その間をちゃんとした根拠をもって繋ぐのが設計です。前提が違えば具体的な設計論は当然異なり、異なる前提でも汎用的に使えるスキルは論理的思考しかありません。

例えば、設計とは制約を与えることだとも言われますが、それは制約によってプログラムを読み書きする人の挙動を操作し、その結果として目的を達成するものとして説明できます。「なぜその制約によって人の挙動を操作できるのか」ということは、前提条件と推論によって説明できなければいけません。

そのためか、筆者は名前のついた設計パターンを覚えることにあまり興味がありません。というより暗記が苦手です。GoFのデザインパターンは3つくらいしか言えませんし、クリーンアーキテクチャがどういうものなのか具体的に説明できません。何とかパターンというのは数学で言う定理のようなもので、論理的な推論のパターンに名前を付けたものです。たまに大学受験などで「公式を覚えなくてもその場で導出できれば良い」という話を聞きますが、そういう感じです。名前が付くということはそれだけ便利であることの証ですが、暗記できなくても導出できればよいのでしょう。つまり、究極的には前提に立ち返って論理的思考を重ねて目的を達成できるのであれば、それが正しい設計になると思っています。逆に言えば、定理を使うならばその証明ができるほうが、より原理を理解しており設計力が高いと言えるでしょう。

それゆえに、筆者の考えでは、設計力を成長させるというのは、おおよそ論理的思考能力を成長させることに相当します。前提と目的を正しく把握し、両者の道筋を作れるようになることです。論理的思考というとあまりに汎用的すぎますが、実際のところある程度プログラミング領域に特化したものにはなるでしょう。例えばインターフェースという概念を理解するとか、あるいは前提として使用言語の特性を把握するための、言語そのものに対するメタ的な分析といったことが必要です。また、実践的には「カプセル化」とか「責務の分離」のような汎用性の高い“定理”を身に着けることも設計力の強化につながるでしょう。

したがって、設計の指南書に対しても筆者は上記のようなことを期待します。ただ、それを突き詰めるとコードが出てこない本になりそうですが(そして本書の17.1「さらにステップアップするための設計技術書紹介」によればそのような本もあるようですが)、入門でそれはハードルが高いのでしょう。本書のようにコードがわんさか出てくるほうが入門者にとっては望ましいのだと思います。その中にも“論理”のエッセンスがあるととても良いと思います。

では、前置きは以上にして本題に入ります。

総評

設計の大海へと漕ぎ出す最初の一歩。良いコードを書けているか自信のないJava使いにおすすめ。

自分の本や他の本への感想を眺めていると、技術書は一口に入門書と言っても“基礎”タイプと“実践”タイプの2種類があるのではないかと最近思っています。この本は“実践”タイプの入門書です。つまり、一番土台となる基盤の理論だけをじっくりやるのではなく、基盤から応用までをカバーした本であるということです。ここでの応用とは、実際にプロダクションコードを改善できるようになるということです。その代わり、本当の基盤部分を手厚く教えてくれるわけではありません。基礎ばかりを突き詰めるのではなく、多くの実例を見て設計改善を学び練習するドリルのような本です。

“基礎”の入門書と“実践”の入門書を4段のピラミッドに例えた図。“基礎”の入門書は一番下の段だけを100%カバーしている一方、“実践”の入門書はすべての段を16分の7ずつカバーしている。
“基礎”の入門書と“実践”の入門書のカバー範囲の違い

内容には「early returnを心がけよう」のようなとても基本的なものもあり、「設計」の定義が広くない人だと「こんなのは設計ではない」と言いたくなるかもしれません。筆者としては、しっかりと定義された目的(コードの読みやすさ)を目指す論理的な営みはもはや設計と言ってよいと思うし、もしこれが「設計」でなかったとしても、「設計」に通じる道であることは確かだと考えます。その意味で「大海へと漕ぎ出す最初の一歩」を総評としました。

どのような読者におすすめ?

設計能力というのは本来言語に依存しないものですが(本書の「はじめに」やP38のコラムにもそのようなことが書いてあります)、この本はJavaもしくはそれにかなり類似したパラダイムを持つ言語を使う人が読んだほうがよいと思います。

本書は対象読者をオブジェクト指向プログラミング言語の使用者としていますが、実際のところ内容はかなり「クラス設計」に重きを置いています(第3章冒頭)。その理由は、おそらくJavaのようなクラスベースの言語においてはクラスがカプセル化に用いられる主要なスコープだからでしょう。これはあらゆる言語に通用するわけではなく、例えば筆者が使うTypeScriptでは、どちらかといえばクラスのスコープよりモジュールスコープや関数スコープがカプセル化の手段として重要であり、「モジュール設計」などが必要になってきます。また、OCamlもオブジェクトを名に冠する言語ですがモジュールによる抽象化が強力です(さすがにこれは意地悪な例ですが)。

本書では書名の通り、悪いコードの例が多分に出てきます。そして、さまざまなテクニックでそれらをサクサクと解決していくのが本書の醍醐味です。クラスベースの言語を前提としている以上、解決策としてクラス設計が出てくる場面がかなり多くあります。悪い言い方をすれば、クラスベースでない言語においては同じ解決策を直接は活用できません。

もちろん、設計を学ぶにあたって重要なのは個々の具体的な言語機能を使いこなすことではなく、それによって何が達成できるのか理解したり、あるいはカプセル化のような汎用的な設計の道具を理解したりすることです。その意味で、たとえサンプルがJavaで書かれていたとしても、そこからエッセンスを取り出して自分が使う言語に当てはめるのが理想的な学習者の在り方でしょう。

しかし、この本を必要とするような入門者にとってはそれはハードルが高いというのが筆者の考えです。まずは本書に関するプラクティスを実践してみるだけで精一杯でしょう(だからこそ、著者は論理で説き伏せるばかりでなく、このように多くのコードで説明する構成を選んだのだとも思います)。

本書にはJava以外の言語に応用可能な部分も多くありますが(変数の命名の章など)、Javaのようなクラスベースオブジェクト指向を前提とした記述も多くあります。そのため、本書を最大限活用するには読者がJava使いである必要があります。本書に書かれているテクニックを他のパラダイムに翻訳しながら理解するのは困難性が数段上がります。

筆者は読者が軽快に問題解決を体験できることが本書の特徴かつ重要な点だと考えており、Java以外のパラダイムの使い手にとってはその軽快さを低減させる要因になってしまうと思います。

裏を返せば、Javaを書くのであれば、あるいはJava特有の部分は潔く切り捨てるのであれば、設計について全然何も分からなかったとしても本書は有用でしょう。本書からは、設計の重要性がぜんぜん理解されない現場での著者の苦労が時折にじみ出てきます(16.5「チームの設計力を高める」など)。その経験からか、本書は本当に初歩の初歩から読者を設計の道へ導くように書かれています。良いコードとは何かまだつかめていないという初心者レベルの人でも、本書によって得るものがかなりあるはずだと思います。

この本を読んで何を得られるか

この本は全17章もあるだけあって、さまざまな場面に対応できる幅広いテクニックを取り扱います。目次が公開されているのでそれを見るとだいぶ雰囲気が掴めるでしょう。

内容はかなり基礎的で、本格的な設計書という趣ではありませんが、むしろそれが本書の強みだと感じられます。本書において重要なのは、本書を読んで読者が「良いコードを書けば開発生産性を改善できる」という体験が得られることだと感じました。その体験を得た読者が設計の世界へ足を踏み入れるのです。その意味で、この本はまさに設計への入り口、設計の入門書と呼ぶにふさわしい一冊でしょう。

15.3「ソフトウェアとエンジニアの成長性」15.4「課題を解決する」に書かれているように、悪いコードがすでに存在している状況でそれを改善しようと動くのは簡単なことではありません。その状況を打破するのが本書の最大の目的なのではないかと考えられます。それを裏付けるかのように、第16章は設計の世界へ一歩踏み出した人をサポートする内容になっています。

以上のことから、この本においてはとにかく設計の経験がない読者でも設計の良さを体感できることが重要です。それゆえ、全体を通して「悪い設計の弊害」が多く語られているのは、普通のことかもしれませんが本書のよい点だと感じました。設計へのとっつきやすさを保ちつつ、設計の意味を読者に伝えられるからです。

具体的な設計の内容としては、ネストを深くしない・命名をきちんとするといった一番基本的なことから、クラスの責務の意識・疎結合高凝集といったレベルまでを扱います。一つ一つのテクニックについて、ただテクニックを覚えるのではなく、どのような理由で何を改善するのかを書くように努めており、スムーズに読めると感じました。

また、最近話題の「値オブジェクト」の取り扱いもあります。著者はかなり細かく分ける派のようですね。例えば「税抜価格」と「税込価格」を別々にしたり(P38)、「商品価格」と「配送手数料」を別々にします(P263)。自分はデータと業務ロジックは分離させる派なので、値オブジェクトにデータ自身の制約だけでなく業務ロジックを混ぜるようなやり方(P222など)はあまり好きではありません。まあ、最初に書いた通り設計に唯一の正解は無いものですから、読者が将来十分に設計力を成長させたときに自分の頭で考えられればそれで良いのです。具体的な細かい内容を議論することにはそこまで意味がありません。

この本に書かれていないこと

“実践”の入門書の宿命として、本書は論理という基盤的な部分について体系的な取り扱いがあまりないように見えます。もちろん、著者が論理を軽んじているというわけではなく、記述の端々から論理性が感じられるようにはなっています。しかし、それは本書の説明に根拠を持たせるための論理性であり、論理の重要性そのものを読者に伝えることを主とはしていません。

具体的には、どうしても本書の内容は様々なトピックの列挙となり、ストーリーをあまり感じませんでした。それゆえ、現場で使えるテクニックは多く身につきますが、それらの根底に流れる本質的な洞察へと読者を導いていないと感じました。そのほうが“実践”的なのでそれが悪いというわけではありませんが、筆者のように論理を重視する立場の人には小粒な話題の連続に見え、もしかしたらあまり楽しめないかもしれません。

また、「前提は何か」を読者に考えさせる構成にはなっていないと感じました。本書ではクラスベースオブジェクト指向を大前提に置き、その上で設計について議論する場面が多くあります。そのため、そこで読者が身につけられる設計(先ほどの言葉で言えば“定理”)はクラスベースの世界の中だけで成り立つ論理です。

例として、1.3.5では「生焼けオブジェクト」が悪いコードの例として登場します。これは、正しく初期化されていないためインスタンス変数がnullとなってしまっているオブジェクトのことです。これを防ぐことは、Javaでは設計と言えますが、null安全な言語では設計ではありません。なぜなら、生焼けオブジェクトを防ぐことは言語仕様の一部であり、生焼けオブジェクトが生まれる余地が無いからです。

このように、何が設計で何が設計でないのかすら、前提によって異なります。本来、具体的な方法論にはつねに前提が付きまといます。理想的には、次のように公理(設計の前提条件)を葉とする証明図が形成されるべきです。この例では、前提2については本書の説明で公理までたどり着くことができますが、前提1については1.3.5の説明では「この設計はJavaの言語仕様から来る問題に対処するためのものである」という本質を認識するのは難しいでしょう1

  • 方法論: コンストラクタでちゃんとインスタンス変数を初期化する(1.3.5)
    • 前提1: Javaでは生焼けオブジェクトができてしまう
      • 公理: Javaの言語仕様
    • 前提2: nullを保持しうるクラスは良くない(1.3.5)
      • 前提2-1: バリデーションを至るところで行うのは保守性が悪い(1.3.6, 9.6)
        • 前提2-1-1: 保守性の悪いコードはバグを生み開発効率を低下させる(15.2, 15.3)
          • 公理: 開発効率を上げたい

この記事の前半で、本書をJava以外を使う読者が自分の言語に翻訳しながら理解することは難しいと述べましたが、それはクラス設計に入れ込んだ説明であればあるほど、Javaの言語仕様を暗黙の前提とした記述が増え、そのような暗黙の前提を読者に認識させる機会が減ってくるからです。結果として、読者が異なるパラダイムに直面したときに本書から得た知識だけで太刀打ちするのは難しいでしょう。具体的には、特にクラスベースへの依存が大きい3〜8章が該当します。6.2「switch文の重複」や6.3「条件分岐の重複やネスト」などに顕著です。そのような設計がJavaの言語仕様を前提としたものである以上、Javaの言語仕様に対して批判的な穿った考察をもう少し盛り込んでくれても良かったというのが感想です。

まとめ

本書は、まさに設計への入門書と呼ぶにふさわしいコンセプトを持った本だと感じました。初歩的な内容ではありますが解説がとても丁寧で、初心者を設計の道へ誘おうとする工夫が感じられます。

ただ、どうしてもJavaの上の話という印象が強いです。この本のTS版が欲しいです(575)。

80点(星4の上限くらい)です。書名や「はじめに」から感じられる印象よりもJava依存が強く感じられたことと、筆者の個人的な好みから少しずれていることを加味してこの点数です。

※点数は筆者の主観によるものです。

おまけ: 発見した誤植

P351 誤「ESlint」正「ESLint」 👮‍♂️


  1. 一応、null安全性については9.6.2「null安全」で説明がありますが、1.3.5とは関係のない文脈です。