書籍概要

良いコード/悪いコードで学ぶ設計入門
―保守しやすい 成長し続けるコードの書き方

著者
発売日
更新日

概要

「ITエンジニア本大賞2023」技術書部門で大賞受賞!

本書は,より成長させやすいコードの書き方と設計を学ぶ入門書です。

システム開発では,ソフトウェアの変更が難しくなる事態が頻発します。コードの可読性が低く調査に時間がかかる,コードの影響範囲が不明で変更すると動かなくなる,新機能を追加したいがどこに実装すればいいかわからない……。

変更しづらいコードは,成長できないコードです。ビジネスの進化への追随や,機能の改善が難しくなります。

成長できないコードの問題を,設計で解決します。

こんな方におすすめ

  • コードの設計スキルに興味がある人
  • 日々,悪いコードと向き合っていて改善したい人
  • より良いコードを書きたい人

サンプル

samplesamplesamplesample

目次

1 悪しき構造の弊害を知覚する

  • 1.1 意味不明な命名
  • 1.2 理解を困難にする条件分岐のネスト
  • 1.3 さまざまな悪魔を招きやすいデータクラス
  • 1.4 悪魔退治の基本

2 設計の初歩

  • 2.1 省略せずに意図が伝わる名前を設計する
  • 2.2 変数を使い回さない,目的ごとの変数を用意する
  • 2.3 ベタ書きせず,意味のあるまとまりでメソッド化
  • 2.4 関係し合うデータとロジックをクラスにまとめる

3 クラス設計 ―すべてにつながる設計の基盤―

  • 3.1 クラス単体で正常に動作するよう設計する
  • 3.2 成熟したクラスへ成長させる設計術
  • 3.3 悪魔退治の効果を検証する
  • 3.4 プログラム構造の問題解決に役立つ設計パターン
  • Column 種類の異なる言語と本書のノウハウ

4 不変の活用 ―安定動作を構築する―

  • 4.1 再代入
  • 4.2 可変がもたらす意図せぬ影響
  • 4.3 不変と可変の取り扱い方針

5 低凝集 ―バラバラになったモノたち―

  • 5.1 staticメソッドの誤用
  • 5.2 初期化ロジックの分散
  • 5.3 共通処理クラス(Common・Util)
  • 5.4 結果を返すために引数を使わないこと
  • Column C#のoutキーワード
  • 5.5 多すぎる引数
  • 5.6 メソッドチェイン

6 条件分岐 ―迷宮化した分岐処理を解きほぐす技法―

  • 6.1 条件分岐のネストによる可読性低下
  • 6.2 switch文の重複
  • Column クソコード動画「switch文」
  • 6.3 条件分岐の重複とネスト
  • 6.4 型チェックで分岐しないこと
  • 6.5 interfaceの使いこなしが中級者への第一歩
  • 6.6 フラグ引数

7 コレクション ―ネストを解消する構造化技法―

  • 7.1 わざわざ自前でコレクション処理を実装してしまう
  • Column 車輪の再発明
  • 7.2 ループ処理中の条件分岐ネスト
  • 7.3 低凝集なコレクション処理

8 密結合 ―絡まって解きほぐせない構造―

  • 8.1 密結合と責務
  • Column クソコード動画「共通化の罠」
  • 8.2 密結合の各種事例と対処方法
  • Column クソコード動画「継承」

9 設計の健全性をそこなうさまざまな悪魔たち

  • 9.1 デッドコード
  • 9.2 YAGNI原則
  • 9.3 マジックナンバー
  • 9.4 文字列型執着
  • 9.5 グローバル変数
  • 9.6 null問題
  • 9.7 例外の握り潰し
  • 9.8 設計秩序を破壊するメタプログラミング
  • 9.9 技術駆動パッケージング
  • 9.10 サンプルコードのコピペ
  • 9.11 銀の弾丸

10 名前設計 ―あるべき構造を見破る名前―

  • 10.1 悪魔を呼び寄せる名前
  • 10.2 名前を設計する―目的駆動名前設計
  • 10.3 設計時の注意すべきリスク
  • 10.4 意図がわからない名前
  • Column 技術駆動命名を用いる分野もある
  • 10.5 構造を大きく歪ませてしまう名前
  • Column クソコード動画「Managerクラス」
  • 10.6 名前的に居場所が不自然なメソッド
  • 10.7 名前の省略

11 コメント ―保守と変更の正確性を高める書き方―

  • 11.1 退化コメント
  • 11.2 コメントで命名をごまかす
  • 11.3 意図や仕様変更時の注意点を読み手に伝えること
  • 11.4 コメントのルール まとめ
  • 11.5 ドキュメントコメント

12 メソッド(関数) ―良きクラスには良きメソッドあり―

  • 12.1 必ず自身のクラスのインスタンス変数を使うこと
  • 12.2 不変をベースに予期せぬ動作を防ぐ関数にすること
  • 12.3 尋ねるな,命じろ
  • Column クソコード動画「カプセル化」
  • 12.4 コマンド・クエリ分離
  • 12.5 引数
  • 12.6 戻り値
  • Column メソッドの名前設計
  • Column staticメソッドの扱いに注意

13 モデリング ―クラス設計の土台―

  • 13.1 邪悪な構造に陥りがちなUserクラス
  • 13.2 モデリングの考え方とあるべき構造
  • 13.3 良くないモデルの問題点と解決方法
  • Column クソコード動画「Userクラス」
  • 13.4 機能性を左右するモデリング

14 リファクタリング ―既存コードを成長に導く技―

  • 14.1 リファクタリングの流れ
  • 14.2 ユニットテストでリファクタリングのミスを防ぐ
  • 14.3 あやふやな仕様を理解するための分析方法
  • 14.4 IDEのリファクタリング機能
  • 14.5 リファクタリングで注意すべきこと
  • Column Railsアプリのリファクタリング

15 設計の意義と設計への向き合い方

  • 15.1 本書はなんの設計について書いたものなのか
  • 15.2 設計しないと開発生産性が低下する
  • 15.3 ソフトウェアとエンジニアの成長性
  • 15.4 課題を解決する
  • 15.5 コードの良し悪しを判断する指標
  • Column クラスを分割すると読みにくくなる?
  • 15.6 コード分析をサポートする各種ツール
  • Column シンタックスハイライトを品質可視化に利用する
  • 15.7 設計対象と費用対効果
  • 15.8 時間を操る超能力者になろう

16 設計を妨げる開発プロセスとの戦い

  • 16.1 コミュニケーション
  • 16.2 設計
  • 16.3 実装
  • 16.4 レビュー
  • 16.5 チームの設計力を高める

17 設計技術の理解の深め方

  • 17.1 さらにステップアップするための設計技術書紹介
  • Column バグ退治RPG『バグハンター2 REBOOT』
  • 17.2 設計スキルを高める学び方
  • Column C#と長き旅,そして設計への道

サポート

ダウンロード

本書のサンプルファイルを圧縮ファイル形式でダウンロードできます。
適宜展開してご利用ください。

(2022年8月8日最終更新)

ダウンロード
sample_2022-08-05.zip

正誤表

本書の以下の部分に誤りがありました。ここに訂正するとともに,ご迷惑をおかけしたことを深くお詫び申し上げます。

(2022年8月31日最終更新)

P.37 3.4.2のオブジェクトに関する説明の部分

Moenyクラス
Moneyクラス

(以下2022年6月7日更新)

P.27 3.2.1のリスト3.4

IllegalArgumentException()やNullPointerException()のメッセージの内容がわかりづらいものでした。正しくは下記のようなメッセージが望ましいです。


class Money {
  // 省略
  Money(int amount, Currency currency) {
    if (amount < 0) {
      throw new IllegalArgumentException("金額には0以上を指定してください。");
    }
    if (currency == null) {
      throw new NullPointerException("通貨単位を指定してください。");
    }

    this.amount = amount;
    this.currency = currency;
  }
}

P.33 3.3のリスト3.18


import java.util.Currency;

class Money {
  final int amount;
  final Currency currency;

  Money(final int amount, final Currency currency) {
    if (amount < 0) {
      throw new IllegalArgumentException("金額には0以上を指定してください。");
    }
    if (currency == null) {
      throw new NullPointerException("通貨単位を指定してください。");
    }

    this.amount = amount;
    this.currency = currency;
  }

  Money add(final Money other) {
    if (!currency.equals(other.currency)) {
      throw new IllegalArgumentException("通貨単位が違います。");
    }

    final int added = amount + other.amount;
    return new Money(added, currency);
  }
}

P.39 Column 種類の異なる言語と本書のノウハウにおけるRubyのコード(リスト3.19)


class Money
  attr_reader :amount, :currency

  def initialize(amount, currency)
    if amount < 0
      raise ArgumentError.new('金額には0以上を指定してください。')
    end
    if currency.nil? || currency.empty?
      raise ArgumentError.new('通貨単位を指定してください。')
    end
    @amount = amount
    @currency = currency
    self.freeze  # 不変にする
  end

  def add(other)
    if @currency != other.currency
      raise ArgumentError.new('通貨単位が違います。')
    end
    added = @amount + other.amount
    Money.new(added, @currency)
  end
end

P.40 Column 種類の異なる言語と本書のノウハウにおけるJavaScriptのコード(リスト3.20)


function Money(amount, currency) {
  if (amount < 0) {
    throw new Error('金額には0以上を指定してください。');
  }
  if (!currency) {
    throw new Error('通貨単位を指定してください。');
  }
  this.amount = amount;
  this.currency = currency;
  Object.freeze(this);  // 不変にする
}

Money.prototype.add = function(other) {
  if (this.currency !== other.currency) {
    throw new Error('通貨単位が違います。');
  }
  const added = this.amount + other.amount;
  return new Money(added, this.currency);
}

P.109 6.2.7のリスト6.39,魔法「紫電」の値オブジェクト導入版のコードの一部

本来変数valueを使うべきところが数値になっていました。

return new AttackPower(10);
return new AttackPower(value);

下記のコードも参照してください。


class Shiden implements Magic {
  private final Member member;

  Shiden(final Member member) {
    this.member = member;
  }

  public String name() {
    return "紫電";
  }

  public MagicPoint costMagicPoint() {
    final int value = 5 + (int)(member.level * 0.2);
    return new MagicPoint(value);
  }

  public AttackPower attackPower() {
    final int value = 50 + (int)(member.agility * 1.5);
    return new AttackPower(value);
  }

  public TechnicalPoint costTechnicalPoint() {
    return new TechnicalPoint(5);
  }
}

P.172 8.2.4の脚注10番

privateに限らす
privateに限ら

P.215-216 10.2.5のリスト10.1


/** サービス利用料 */
class ServiceUsageFee {
  final int amount;

  /**
   * @param amount 料金金額
   */
  private ServiceUsageFee(final int amount) {
    if (amount < 0) {
      throw new IllegalArgumentException("金額には0以上を指定してください。");
    }
    this.amount = amount;
  }

  /**
   * サービス利用料を確定する。
   *
   * @param salesPrice          販売価格
   * @param salesCommissionRate 販売手数料率
   * @return サービス利用料
   */
  static ServiceUsageFee determine(final SalesPrice salesPrice, final SalesCommissionRate salesCommissionRate) {
    int amount = (int)(salesPrice.amount * salesCommissionRate.value);
    return new ServiceUsageFee(amount);
  }
}

P.323 15.2.5の損失の計算

単純計算で,開発チーム全体で1日あたり3×20=60時間損失が発生していることになりますね。これが1か月ともなると実働日数20日として120時間,1年間となると1440時間も損失が生じます。
単純計算で,開発チーム全体で1日あたり3×20=60時間損失が発生していることになりますね。これが1か月ともなると実働日数20日として1200時間,1年間となると14400時間も損失が生じます。

P.351 16.3.3のツール名

ESlint
ESLint

P.354 16.4.2の脚注6のURL表記

Respectful Code Reviewshttps://chromium.googlesource.com/chromium/src/+/HEAD/docs/cr_respect.md
Respectful Code Reviews https://chromium.googlesource.com/chromium/src/+/HEAD/docs/cr_respect.md

「https://」の前に半角スペースが必要です。

P.379 参考文献の漏れ

下記参考文献が掲載されていませんでした。

『Half baked objects』
著:Kristofer,
URL:https://krkadev.blogspot.com/2010/05/half-baked-objects.html

P.380 索引の表記ミス

ESlint
ESLint

補足情報

P.74 5.5.1 プリミティブ型執着の補足

(2022年6月14日更新)

書籍中ではJavaのStringをプリミティブ型執着の一例として上げていますが,厳密にはJavaのStringはプリミティブ型ではありません。

ここでは,プログラミング言語に備え付けの素朴で汎用的な型のことを指しています。

たとえばC#のstring型なども厳密にはプリミティブ型ではありませんが,プリミティブ型執着の対象です。

商品一覧