書籍「良いコード/悪いコードで学ぶ設計入門」より「見逃してはならない重要ポイント」
スポンサーリンク

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

ITエンジニア本大賞2023で大賞を受賞し、多くのエンジニアから「数年前の自分に渡しておきたい本」と絶賛されている書籍「良いコード/悪いコードで学ぶ設計入門 保守しやすい成長し続けるコードの書き方」の改訂新版が発売されました。

特に重要だと思われるポイントや見逃してはならないポイントをいくつか厳選してまとめます。

こんな人におすすめ

こんな方におすすめです。

  • これから本格的に規模の大きいプログラムを書くことになる中級者を目指す人
  • 人に見せても恥ずかしくないようなコードを勉強したい人
  • ネットの情報やAIの情報をきちんとつなぎ合わせることができるようになりたい人

特にウェブの検索やAIが生成したコードやそれらをつなぎ合わせたプログラムなどが増加することが見込まれます。するとそれを正しくきれいな形に保ち、品質をよくするスキルというのはまだまだ人間に求められます。

きちんと正しい判断ができるようになるためにも、良いコード/悪いコードを理解しておくことが大切です。

第1章:悪しき構造の弊害を知覚する - 「意味不明な命名」の排除

まずは、書籍の紹介の挿絵にも使われている悪いコード定番のものから紹介します。

良いコードを書くための第一歩は、「悪しきコード」がもたらす弊害を認識することから始まります。
悪しきコードとは、読み解くのに時間がかかり、バグを埋め込みやすく、さらに悪い構造を誘発するものです。その最たる例の一つが「意味不明な命名」です。

上のとおり、クラス名である MemoryStateManager、メソッド名の changeIntValue01、updateState02Flag、変数のintValueのようにその技術用語由来にのみ注目された命名を技術駆動命名といいます。

このような名前ではその真の「目的」が不明瞭になり、設計そのものに疑問ができコードの意読性を損なう可能性があります。

同様に、クラス名のClass001、メソッド名のmethod001のように番号付けで命名するものを連番命名といいます。しかしこの数字に意味は全くないため、悪しき手法の代表です。

このような命名は、コードを読み解く時間を大幅に増やし、結果的にバグの温床となります。
プログラミングの分野では、「名前重要」という座右の銘があるほど、適切な名前付けが機能の成功を左右すると言われています。

ポイント1

意味不明な命名をはじめとした、条件分岐の入れ子構造によるネストの嵐、重複コード、修正漏れ、可読性低下、未初期化、不正値の混入などバグを生み出し、開発生産性を低下させる悪魔退治の基本を知ること


第8章:条件分岐 - 迷宮化した分岐処理を解きほぐす技法

先ほども少し触れた条件分岐の入れ子構造によるネストの嵐について詳しく見ます。

コードの可読性を著しく低下させ、「迷宮化」した処理につながるのが、深くネストした条件分岐です。
この問題を解決するための強力なテクニックが紹介されています。

深いネストと文の重複の解消する方法のひとつに、早期returnがあります。

早期return

条件分岐のネストを解消するために、早期returnガード節を積極的に使用することが強く推奨されています。
これにより条件とロジックが切り離され、コードが読みやすく、変更しやすくなります。

// 生存しているか判定
if (0 < member.hitPoint) {
  // 行動可能かを判定
  if (member.canAct()) {
    // 魔法力が残存しているかを判定
    if (magic.costMagicPoint <= member.magicPoint) {
      member.consumeMagicPoint(magic.costMagicPoint);
      member.chant(magic);
    }
  }
}
if (member.hitPoint <= 0) return;
if (!member.canAct()) return;
if (member.magicPoint < magic.costMagicPoint) return;

member.consumeMagicPoint(magic.costMagicPoint);
member.chant(magic);

interfaceを使った機能の交換

switch文が多くの場所に重複して現れると、新しい条件が追加されるたびに多くの場所を修正する必要があり、保守性が低下します。
これを解消するために、interface(インターフェース)を活用したデザインパターンが紹介されています。

本書で紹介されているとおりの魔法の処理を例にすると、Fire(火)、Shiden(紫電)、HellFire(地獄の業火)といった異なる魔法において、それぞれの名前やダメージをSwitch文で分岐して処理するのではなく「インターフェース」を実装する独立したクラスとして定義することでSwitch文のないスッキリとしたコードにすることができるということです。

enum MagicType {
  fire,
  shiden,
  hellFire
);

// 魔法で攻撃する
void attack(final MagicType magicType) {
  switch (magicType) {
    case fire :
      name = 'ファイア';
      costMagicPoint = 2; // 消費MP
      attackPower = 20 + (int)(member.level * 0.5); // 攻撃力
      break;
    case shiden : 
      name = '紫電';
      costMagicPoint = 5 + int(member.level * 0.2);
      attackPower = 50 + (int)(member.agility * 1.5);
      break;
    case hellFire :
      name = '地獄の業火';
      costMagicPoint = 5 + int(member.level * 0.2);
      attackPower = 200 + (int)(member.magicAttack * 0.5 + member.vitality * 2);
      break;
    default:
      throw new IllegalArgumentException();
  }
  ... //魔法によるダメージ計算
}

ここ以外にも同じようなswitch-case文が発生するためキレイなコードではない。

final Map<MagicType, MagicAttack> magicAttacks = Map.of(
  MagicType.fire, new Fire(member),
  MagicType.shiden, new Shiden(member),
  MagicType.hellFire, new HellFire(member),
);

interface MagicAttack {
  String name();
  int costMagicPoint();
  int attackPower();
}

class Fire implements MagicAttack {
  ...
}

class Shiden implements MagicAttack {
  ...
}

class HellFire implements MagicAttack {
  ...
}


// 魔法で攻撃する
void attack(final MagicType magicType) {
  final MagicAttack usingMagicAttack = magicAttacks.get(magicType);
  
  showMagicName(usingMagicAttack); //魔法名の表示
  ... //魔法によるダメージ計算
}

かなり説明を省略していますので、詳しくは本書を確認もらえればと思います。

要するにswitch-case文で分岐ごとに計算している処理を、MagicAttackという共通の形を持った、Fireクラス、Shidenクラス、HellFireクラスの中に任せて、実際に魔法攻撃をするattackメソッドの中では、魔法の種別だけがわかれば、そのクラスのインスタンスを生成して、インスタンスが持っている設定値(消費MPや攻撃力など)で中間の計算をし、最終的なダメージ計算だけをすればよいように書くことができます。

これにより全く分岐が発生することなく、スッキリとした処理を書くことができるようになりました。
そして魔法が増えた場合にも、簡単に対応できるというメリットも大きいです。

このように、interfaceを使ってまとめて機能を取り換える設計をストラテジパターン(Strategyパターン)と呼びます。

改訂新版では、このインターフェース設計についての説明が大幅に強化されており、「分岐を書きそうになったら、まずinterface設計」という考え方が、オブジェクト指向初心者が実践的なイメージを持つ上で非常に有効だと強調されています。

また本書ではもう一つ、同じくinterfaceを使ったポリシーパターンというものも紹介されており、中級者への第一歩として重要な要素だと書かれています。

ポイント2

分岐を書きそうになったら、まずinterface設計


第11章:名前設計 - あるべき構造を見破る名前

命名はプログラミングにおいて非常に重要であり、書籍全体を通して何度もその重要性が強調されています。

本書では基本的な考え方として、目的駆動名前設計をあげています。

目的駆動名前設計とは、その名のとおり、名前から目的や意図が読み取れることを重視する設計のことです。

目的駆動名前設計の重要性

設計への影響

名前の付け方一つで、その後のコードの設計が大きく変わることがあります。

具体的で狭い意味範囲の名前

単に「animal」ではなく、「dog」や「bird」のように、可能な限り具体的で、意味範囲が狭く、特化した名前を選ぶべきです。
これにより、その変数やクラスが何を表現しているのか、どのような目的を持っているのかが明確になります。

存在ベースではなく目的ベースで考える

単にデータが存在するからという理由ではなく、そのデータが「どのような目的」で使われるのかを考えて名前を付けます。

例として「このフラグが立っているときのUserは要注意会員」や「この行のpriceは新品価格で、次の行のpriceは中古価格」のように、形容詞で違いを表現している場合、それぞれを独立したクラスとして設計するチャンスだと述べられています。
例えば、Userクラスの中にisSuspiciousというフラグを持つのではなく、SuspiciousUserという新しいクラスを作ることで、そのユーザーが「要注意会員」であるという目的が明確になり、関連するロジックもそのクラスに集約できます。

名前設計の手法
  • 関心事の分析
    • そのコードが「どこに関心があるのか」を分析します。
  • 声に出して話してみる
    • 漠然とした名前では、口頭で説明しようとすると曖昧さや違和感を感じることがあります。声に出して話すことで、より適切な名前に気づくことがあります。
  • 利用規約を読んでみる
    • プロジェクトの利用規約や仕様書に目を通すことで、ビジネス上の具体的な概念や専門用語を把握し、それを名前に反映させることができます。
  • 他の名前に置き換えられないか検討する
    • 複数の候補を考え、最も適切で誤解を招かない名前を選ぶ練習も重要です。

適切な命名は、コードの可読性を高めるだけでなく、チーム内のコミュニケーションを円滑にし、将来の保守や機能追加を容易にするための基盤となります。

ポイント3

名前から目的や意図が読み取れることを重視する設計でる「目的駆動名前設計」を意識する。


まとめ

本書では、は、具体的な「悪いコード」の例とその改善方法を対比させることで、読者が「なぜそのように書くべきなのか」を納得しながら学習できるのが特徴です。

本書で取り扱われていたRPGゲームのコード例は、非常にとっつきやすく意識されたのだとは思いますが、変数名などの命名が長くなってしまい、説明に使う文章量全体が長くなってしまっているのが少し残念なポイントではありました。
しかしながら、一つ一つきちんと読み込んで紐解いていくと、必ず納得感が得られるコードでもあります。

今回取り上げたポイントはいずれも保守しやすく、成長し続けるコードを書くために不可欠な要素です。

これらの原則を意識し、日々のコーディングに少しずつでも取り入れていくことで、チーム開発における生産性向上にも大きく貢献するでしょう。


目次

第1章 悪しき構造の弊害を知覚する
第2章 設計の初歩
第3章 カプセル化の基礎―ひとつにまとめる―
第4章 不変の活用―安定動作を構築する―
第5章 バラバラなデータとロジックをカプセル化する実践技法
第6章 関心の分離という考え方―分けて整理する―
第7章 関心が混ざったコードを分けて整理する実践技法
第8章 条件分岐―迷宮化した分岐処理を解きほぐす技法―
第9章 コレクション―ネストを解消する構造化技法―
第10章 設計の健全性をそこなうさまざまな悪魔たち
第11章 名前設計―あるべき構造を見破る名前―
第12章 コメント―保守と変更の正確性を高める書き方―
第13章 メソッド(関数) ―良きクラスには良きメソッドあり―
第14章 モデリング―クラス設計の土台―
第15章 リファクタリング―既存コードを成長に導く技―
第16章 設計の意義と設計への向き合い方
第17章 設計を妨げる開発の進め方との戦い
第18章 設計技術の理解の深め方


【内容情報】
「あるべき構造」を知り、ソフトウェア開発の問題に立ち向かおう
本書は、より成長させやすいコードの書き方と設計を学ぶ入門書です。筆者の経験をふまえ構成や解説内容を見直し、より実践的な一冊になりました。
システム開発では、ソフトウェアの変更が難しくなる事態が頻発します。 コードの可読性が低く調査に時間がかかる、 コードの影響範囲が不明で変更すると動かなくなる、 新機能を追加したいがどこに実装すればいいかわからない……。
変更しづらいコードは、成長できないコードです。 ビジネスの進化への追随や、機能の改善が難しくなります。
成長できないコードの問題を、設計で解決します。

(こんな方におすすめ)
・コードの設計スキルに興味がある人
・日々、悪いコードと向き合っていて改善したい人
・より良いコードを書きたい人

【著者情報】
仙塲大也(せんばだいや)

青森県出身。
大手電機メーカーからWeb業界へ転身。
アプリケーションアーキテクトとして、リファクタリングやアーキテクチャ改善、若手の設計スキル育成といった、設計全般を推進する業務に従事。
悪しきコードとの戦いの中で設計の魅力に気付く。暇さえあれば脳内でリファクタリングしている。
X(旧Twitter)ではプログラミングの風刺動画を不定期で投稿。

改訂新版 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方 内容紹介より

スポンサーリンク

Twitterでフォローしよう

おすすめの記事