気まぐれメモランダム / でたらめフィードバック

『良いコード/悪いコードで学ぶ設計入門』を読むにあたり気をつけたいこと

公開:

諸事情あってITエンジニア本大賞2023技術書部門大賞受賞作の『良いコード/悪いコードで学ぶ設計入門』(仙塲大也、技術評論社)にいまごろになって目を通しました。「設計」にかぎらない幅広いトピックをあつかう点で看板に偽りありですが、その手広さが支持された一因なのでしょう。ステップアップを図りたい初級者にとって有用なアドバイスが得られる本であることは確かだと思います。一方で個人的には見過ごせない記述もまま見られ、手放しでお勧めできる本でもないというのが率直な評価です。いくつかメモします。
(用語は同書を踏襲します)

不変をめぐって

同書の強い主張の一つに「クラスは原則として不変にすること」があります。基本的には同意しますが、その主張のしかたには疑問があります。

まず本書のJavaによるサンプルコードで不変と主張されるクラスの定義は、クラスの宣言でfinal修飾子を指定せず継承を許している点で不完全です。
このあたりは言語の仕様にもよりますが、Javaは継承すれば派生クラスは基本クラスのメソッドのオーバーライドが可能になります(ただしprivateメソッドやfinal修飾子が指定されたメソッドは除く。オーバーライドの例は同書でもリスト8.16で例示があります)。オーバーライドで基本クラスのメソッドやインスタンス変数を参照しないようにすれば、基本クラスの機能をまったく使わずかつ基本クラスのインスタンスと置き換えられる派生クラスが定義でき、その派生クラスのインスタンスは基本クラスのインスタンスの代わりに使えます。つまり不変のクラスを引数とするメソッドに可変のクラスのインスタンスを引き渡せます。
(そんなことをする人はいない、と思われた方には、人間の思考の柔軟性を低く見積もりすぎと申し上げます)
全体を通して堅牢性を主張しながら堅牢にし切れていないクラス定義が呈示されていて不思議に思えます。

(なおJavaでクラスを不変にするには継承を禁止する必要ありという指摘はBloch『Effective Java』が初版の段階で行っています)

「第4章 不変の活用」の「4.2.3 副作用のデメリット」の説明も十分とは言えません。

  • 可変のクラスにおいてメソッドがインスタンス変数を変更するのは自然で「関数の外にある状態の変更」(p.48)ではありません。これを副作用とするのは端的にミスリーディングです((クラスに属さない)関数とクラスのメソッドは等価ではない)
  • 複数スレッドからのアクセス競合による値の変化も副作用として、「同じ結果を得るには、同じ手順で実行するなど、処理の実行順に依存してしまいます」(p.48)と書きますが、複数スレッドでアクセスが競合するようなケースに対して実行順の保証が可能かは不明で、必要になるのはスレッド間の同期です(そうでなければ最低限アトミックな参照・更新の保証)。コードのスレッド安全性はそれだけを独立で議論すべき巨大なトピックで、この記述のように副作用とからめた簡単な触れかたは誤解を招きかねません。不変がスレッドセーフに寄与するのはたしかですが、近年の非同期・並行処理の知見の蓄積を考えると、私見では複数スレッドから同じインスタンスを更新するような構造そのものの解消からまず検討すべきと考えます

型と処理の関係

第5章ではインスタンス変数を使えないので低凝集にならざるを得ないとの理由でstaticメソッドを非推奨としていますが、この主張はずいぶん雑に思えます。

  • 共通の実装の詳細を抽出したprivateメソッドがインスタンス変数を参照しない処理になることはめずらしくない
  • クラスの共通の変数(static変数)をカプセル化(privateに)するにあたりアクセス用のstaticメソッドの用意が適切なケースはある
  • インスタンス変数を参照しなくても型と関係するメソッドは考えられる

わかりやすいスローガンであるがゆえに、型とメソッドの関係を考える機会を奪うという意味でデメリットのほうがおおきいと考えます。
(本件にかぎらず、思考停止を惹起するようなわかりやすいフレーズが多用されているのは気になる点です)

不当なswitch蔑視

条件分岐は整理されるに越したことはありませんが、同書のswitchに関する整理は筋が悪いと思います。

  • 「6.2.5 条件分岐を一箇所にまとめる」、「単一責任選択の原則」を持ち出すのであれば、Javaの場合は Enum の定義で管理する値を複数にしたりメソッドを定義したりするほうが原則にかないます。Javaのような高機能な列挙型のない言語でもクラスの定義できるプログラミング言語であればTypesafe Enumパターンでクラスを定義することで同等の表現が可能です
  • 「6.2.6 よりスマートにswitch文重複を解消するinterface」、よくないコードとしてのリスト6.19の例示には無理があると思いますがそれは置いておくとして、図形のような共通の属性が考えられるケースではインターフェースよりも抽象基本クラスのほうが素直でしょう。ここでは「継承は推奨しません」(p.155)とする著者の主張が適切な解法の選択を妨げているように思えます。「この「機能切り替えが簡単になる」のがinterface の大きな利点のひとつです」(p.101)というのは矮小化だと思います
  • 「6.2.7 interfaceをswitch文重複に応用(ストラテジパターン)」 の「switchの代わりにMapで切り替える」でswitchをMapで置き換えるテクニックが紹介されていますが、Typesafe Enumなら列挙とインスタンスの対応付け自体を不要にできます。仮になんらかの値と同じinterfaceを実装する複数のインスタンスの対応付けが必要だとして、判定処理が十分集約されていれば(「コードの不吉な臭い」の「重複したコード」がない状態であれば)その判定方法がifであってもswitchであってもMapであっても本質的な違いはありません。人によってはMapを使ったコードのほうが理解しづらいということもあるでしょう。Mapを使ったほうがよいケースとしては対応付ける値の算出方法が複雑で処理を独立させたほうがよい場合や対応関係を明確にしたいケースなどが考えられますが、いずれにせよ本質的な差ではありません。なおここで定義されているような判定処理はMapのインスタンスの管理も含めてstaticメソッドとして問題ないケースも多いと思います

「「分岐を書きそうになったら、まずinterface設計!」」(p.123)とありますが、型が適切に定義されていれば型にまつわる判定は自然に減るものです(その意味でswitchの多さが適切でない設計を示すことはあります)。分岐を減らすためにinterface定義を検討するのは本末転倒です。

明瞭でない型に対する姿勢

著者はinterfaceを推奨する一方、先に触れたとおり継承を前提としたクラスについては「継承はよっぽど注意して扱わないと危険、継承は推奨しません」(p.155) と否定的です。

たしかに現状のプログラミング言語のオブジェクト指向サポート機能はおおむね乱用を防ぎづらい仕様になっており、その観点から基本的には首肯できる意見です(クラス定義の構文は継承は原則禁止、オプトインで継承可とするほうが望ましいと個人的には考えます)。しかし継承のほうが自然に表現できる型や処理はたしかに存在します。「すぐに密結合に陥」(p.154)るから推奨しない、というのは短絡的でしょう(基本クラスのカプセル化が不十分なときの派生クラスとの密結合は継承固有と言えるでしょうが、それ以外は継承関係でなくとも生じるように思えます)。継承が有用なケースや継承する際の注意点に言及がないのは不親切と言わざるをえません。

またここまで言いながら「第13章 モデリング」の「13.4.2 機能性をイノベートする「深いモデル」」は継承関係の解説が主でinterfaceは登場しません(記述から言って呈示されているクラス図の汎化関係がinterfaceを想定したものとは読みづらい。図6.2では記述されているステレオタイプもここでは記述されていない)。型について=クラスやinterfaceについてどのように考えるのか、設計でどのようにモデリングしてどのように実装に落とし込むのか、説明不足は否めないと思います。

(コードと関係のない)いくつかの注釈

同書は数多くの原則や法則に触れていて後学に資すると思いますが、中にはそのまま受け止めないほうがよいものもあります。

  • 「15.5.5 チャンク」で紹介されている「マジカルナンバー4」、オリジナルは「マジカルナンバー7」でミラーの法則として有名ですが、元となった論文「不思議な数“7”、プラス・マイナス2:人間の情報処理容量のある種の限界」を1956年に発表したジョージ・ミラー自身は「マジカルナンバー7」という表現は修辞的に使っただけと言われます。Yablonski『UXデザインの法則』(オーム社、原著2020年・邦訳2021年)によると、「その後の短期記憶とワーキングメモリの研究で、記憶範囲は「 チャンク」単位で測定しても一定ではないことが明らかになっている」(p.55)とのことです
  • 「16.3.1 割れ窓理論とボーイスカウトの規則」で紹介されている割れ窓理論には批判もあります

なおマジカルナンバーについては私も誤解していたので人のことは言えません。俗説に向き合うのはむずかしいですね。

同書で触れられていないこと

同書では触れられていないトピックで、私見では次の2点の欠落が特に気になります。

  • 契約プログラミング
  • 関数型言語由来のラムダ式と協働するコレクション操作

前者は高品質な設計・実装に有用ですし、後者は複雑なコレクション操作をスマートに解決します(「第7章 コレクション」のサンプルコードの多くはJava Stream APIで別解を出せます)。

同書の記述から察するにどちらも著者の立場とは相容れないものと推察され、そのために触れられなかったのではないかとと思いますが、どちらも優れたソフトウェア開発に資する有用な考えかた・機能ですので、触れられていないのは個人的にはたいへん残念です。

最後に

他にも著者と見解の異なる点はままあるのですが、長くなったのでここまでにします。参考までに他の方の評のリンクを挙げておきます。

最後にひとつ、著者や読者に問いかけておきます。「クソコード」なる言葉を使い動画を公開してアクセス数を誇るような人が用いる「心理的安全性」(p.334の「16.1.3 心理的安全性」で触れられています)という言葉にどれだけ信頼がおけるものとお考えでしょうか?

関連コンテンツ

Pick up work

最近のエントリ

アーカイブ

ブログ情報