虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

悪いコードをやっつけよう!『良いコード/悪いコードで学ぶ設計入門』を読んで

こんにちは。虎の穴ラボのH.Kです。
5月の大型連休で社内の技術書購入を支援する制度を利用して購入した『良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方』を読みました。

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

とても良い本でしたので、本書の横断的な概要と、その中で良かったところや気になったところをご紹介していきます。

どんな本か

一言で表すなら「やりたいことをコードに落とし込む際に気をつけるべきポイントを、豊富なサンプルコードの対比ユニークでわかりやすい命名で示した入門書」です。

基本情報

タイトル 良いコード/悪いコードで学ぶ設計入門
サブタイトル 保守しやすい 成長し続けるコードの書き方
著者 仙塲大也
発行日 2022/04/30(紙版)
発行 技術評論社
ISBN 978-4-297-12783-1
紹介ページ 良いコード/悪いコードで学ぶ設計入門(技術評論社)

私が感じた本書の立ち位置

オブジェクト指向らしいコードを書く、またはコードにしていくための手引書だと感じました。
プログラミングの入門書よりは難しい内容ではあるものの、入門書の内容をしっかり理解できていれば十分読み進められるものになっています。
「オブジェクト指向とは何か」というところにフォーカスした説明はあまりありませんが、この本の「良いコード」を目指していけば設計手法としてのオブジェクト指向への理解が深まっていきます。
オブジェクト指向に関しては本書のレビュー者に『現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法』などで知られる増田亨氏が入っていることからも安心して学んでいけると思います。

前提

  • 設計とコーディングは不可分である
  • 「悪いコード」=>「良いコード」の構成
  • サンプルコードはJava

設計とコーディングは不可分である

ドメイン駆動開発、やや厳格なオブジェクト指向の文脈が濃いのだと感じます。オブジェクト指向ではモデル(オブジェクト)をコードでどう表現するか考えることが設計になります。コードを考えることが設計なので、当然不可分です。これはEvans氏の『エリック・エヴァンスのドメイン駆動設計』にも通じる考え方です。しかし伝統的なSIerの開発における設計は多くの場合、そうではないと思います。手続き型に近い考え方で開発が行われ、モデルとプログラムが直結していないので、関係性を事前に考えておく必要があり、この工程を設計と呼んでいることが多いです。
そのため、この本における「設計」は「設計ではない」と感じる人もいるかと思います。
15章にて本書における「設計」とは何か、なぜ「設計」をするのかが記載されています。「設計ではない」と感じる人も目的については共感できることが多いはずです。「設計」か「設計ではない」かに囚われず、「良いコード」を学べる本ですので、「設計とコーディングは不可分である」に共感できない場合も、ぜひ読んでいただければと思います。

「悪いコード」=>「良いコード」の構成

本書は「悪いコード」が示され、悪い箇所を指摘し、「良いコード」に改めていく構成になっています。
そのため「悪いコード」、「良いコード」それぞれがある程度読める必要があります。
難しい実装や一般的ではないメソッドを使っている部分はないですが、プログラミング自体の初学者だと少々大変かと思います。

サンプルコードはJava

上記の通り、難しいコードはないので他言語を使っている人でも問題ありません。
オブジェクト指向型の言語と呼ばれるものであれば概ね同じように適用できます。
もちろん、RubyだとEnumないからどうしようなど、読み替えや取捨選択が必要なところはあります。

おすすめする読者層

  • ある程度書けるが、実装方法に自信がない
  • 読みにくいコードがありリファクタリングしたいが、どうすれば良いかわからない人

著者について

Twitterで「問題のあるコードから発生する問題」をコミカルに動画で紹介していることで有名なミノ駆動氏が著者です。
動画で紹介された数々の「悪いコード」に対する著者の回答としても読めるので、著者の動画のファンの方にもおすすめできます。

構成について

大きく3つの段階にわかれています。

  • 1〜2章:入門
  • 3〜12章:実践
  • 13〜17章:発展

1〜2章:チュートリアルステージ

序盤の1〜2章は言わばチュートリアルステージとなっています。
これからどのような敵(悪いコード)が出てくるか、もっと言えばどのような対象を「悪いコード」と呼んでいるのか説明している章とも言えます。
この1〜2章について、詳細な敵の倒し方は後続の章に説明を任せているものが多いので、中級者以上の方は読み飛ばしてもよいかもしれません。

3〜12章:本編

この中盤の章はRPGで言えば本編です。様々な敵を見つけて、それらに都度対応していきます。
具体的にどのように「悪いコード」を「良いコード」にしていくかが豊富なサンプルコードとともに書かれています。
このあたりは『テスト駆動開発』のように、段階を追って逐一サンプルコードを併記して改善を行っており、非常にわかりやすいです。
またGoFのデザインパターンに準拠している部分も多く、コーディングのパターンを増やすことにも役立ちます。

13〜17章:やりこみ世界

発展となるこの段階は世界を自由に旅をして、根本から解決していく、最終章となります。
PS版ドラクエⅦで言えば飛空石入手後の世界です。(Ⅶは私が一番好きなドラクエシリーズです)
この章までに培ってきたことを駆使して「悪いコード」と戦っていきます。
また、一通りの情報を押さえた上で、改めて「設計」を定義付けし、本書に書かれていない改善への足がかりを作っています。

良かったところ

良かったところは、細かいところだとたくさんありますが、大きいところだと以下の2点です。

  • 平易な表現
  • サンプルコードが豊富

細かいところではDomainクラスの利用推奨やUtilクラスの非推奨など、より良いオブジェクト指向への指針が豊富に紹介されている点が良かったです。他にも開発で遭遇しがちな、nullの取り扱いやExceptionのハンドリング、メタプログラミングへの戒めまで記載されていることも、すぐに実践で活かしていけそうで良いです。
このことから、本書の内容をしっかり理解していけば、入門書から一歩進んだ学習ができると感じました。

平易な表現

技術書にありがちなわかりにくい表現が避けられており、純粋に読みやすいです。
このブログも「構成について」のところをRPGに例えて説明しましたが、本書でもサンプルコードはゲームをモチーフにしています。
業務のようなコードではなく、HPの管理や発動する魔法の選択といった、誰でも想像しやすいものがサンプルとして示されているので、プログラミングの基礎があれば業務経験不要で読めるのは良いところです。
また、「悪いコード」への命名も本書の内容を噛み砕くのに役立っていると感じました。

サンプルコードが豊富

前述の通り「悪いコード」=>「良いコード」というふうに記載がなされており、リファクタリング途中のソースコードも掲載されているため、修正がとても具体的です。
サンプルの各「悪いコード」にはどこが悪いのか、どういったときに困るのかというところが詳細に説明されているので、大規模な開発を経験していなくても、発生しうるバグのパターンを学ぶことができます。

気になったところ

6章:Switch文の重複(Switch文からMapへの書き換え)

Switch文をMapに書き換える記載があります。
書籍内で記載されているコードより、かなり簡略化したコードで紹介させていただきます。
前提として以下のような魔法に関する実装があります。

enum MagicType {
  ice,
  fire
}

interface Magic {
  int damage();
}

class Fire implements Magic {
  @Override
  public int damage() {
    // 本来は固有の計算ロジック
    return 5;
  }
}

class Ice implements Magic {
  @Override
  public int damage() {
    // 本来は固有の計算ロジック
    return 10;
  }
}

これらの魔法を使うのにMapに格納するとSwitch文がなくなり「良いコード」になるとされています。

class MagicWand {
  final Map<MagicType, Magic> magics = new HashMap<>();

  MagicWand() {
    magics.put(MagicType.fire, new Fire());
    magics.put(MagicType.ice, new Ice());
  }

  public int damage(MagicType type) {
    return magics.get(type).damage();
  }
}

// 使うときは以下のようになる
MagicWand magicWand = new MagicWand();
System.out.println(magicWand.damage(MagicType.fire));
System.out.println(magicWand.damage(MagicType.ice));

Java 14以降が使える場合、Switch式で書いたほうが良いと個人的には考えています。
理由としては、網羅性が担保されるためです。

class MagicHand {
  final Ice ice;
  final Fire fire;

  MagicHand() {
    fire = new Fire();
    ice = new Ice();
  }

  public int damage(MagicType type) {
    return useMagic(type).damage();
  }

  private Magic useMagic(MagicType type) {
    return switch (type) {
      case fire -> fire;
      case ice -> ice;
    };
  }
}

// 使うときは以下のようになる
MagicHand magicHand = new MagicHand();
System.out.println(magicHand.damage(MagicType.fire));
System.out.println(magicHand.damage(MagicType.ice));

もう少し細かく説明します。
Mapを使ったコードの場合、Mapへのputを忘れる可能性が考えられます。
例えばMagicTypeに新しくthunderが追加されたとします。
Thunderクラスも新しく作り、これで準備できた!と実行するとNullPointerExceptionが発生します。
これはmagics.get(type)でnullが返却されることによるものです。
対して、Switch式はケースを網羅していないとコンパイルエラーになります。すなわち、MagicTypethunderを追加した時点でコンパイルエラーとなりコード修正が強制されます。 もちろん、先にコンパイルエラーだけ直そうとして、switchにcase thunder -> null;を追加してしまえば、メンバ変数にThunderインスタンスを追加し忘れる不具合は発生します。
しかし、最初の実装者とは別の人がThunderの魔法を追加しようとする場合、コンパイラーが修正箇所を示してくれるのは心強いと思います。
LTSであるJava 17が出たとは言え、Java 14以降の利用率が高くないので、書籍としてはMapを使用したテクニックの紹介で十分かと思います。
「自分ならこう書くなぁ」というのを考えながら読むことが大切です。

まとめ

今回買って、とても良かったと感じています。
以下のような利点を感じています。

  • オブジェクト指向への考え方がより強固なものになった
  • 一般的な「良いコード」について考えるきっかけとなった
  • ドメイン駆動の考え方などについて振り返るきっかけとなった

非常に実践的な内容ですが、難しくなくとっつきやすいように工夫して書かれている本だと思いました。
本書内でも書かれていますが、ソフトウェア開発に「銀の弾丸」はありません。
紹介しきれていませんが、16章にはレビューの仕方やチームでの改善の仕方の指針まで載っていますので、 学んだ情報を共有し、チームの技術力向上、技術負債の抑制を目指していきたいです。また、各コードについて社内で輪読して「自分ならこう書く」を聞いてみるのも楽しそうですね。

読んでいて、ためになる上に楽しい本です。ぜひ皆さんも手に取ってみてください。

P.S.

採用情報

■募集職種
yumenosora.co.jp