JavaScriptを有効にしてください

良いコード/悪いコードで学ぶ設計入門

 ·   ·  ☕ 13 分で読めます  ·  🐯 ブシトラ

設計本についてはいくつか読んでいるが、ミノ駆動さんの書籍ということで
興味が出たので読む。新たな知識をメモしていく

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

  • 低凝集 → ロジックやデータが分散し、バラバラになっていることをさす
  • 生焼けオブジェクト → 初期化しないと使い物にならないクラス、未初期化状態が発生しうるクラスのこと

データクラスが引き起こす障害

  • 重複コード
  • 修正漏れ
  • 可読性低下
  • 未初期化状態
  • 不正値の混入

2章 設計の初歩

基本的な考え方

  • 省略せずに意図が伝わる名前を設計する
  • 変数を使い回さない、目的ごとの変数を用意する
    • 再代入ダメ。変数は使い回さないこと。
  • ベタ書きせず、意味のあるまとまりでメソッド化
    • 一つのロジックに、複数の処理を含ませない
  • 関係し合うデータとロジックをクラスにまとめる
    • 例としてhitPointをクラス化。

3章 クラス設計

オブジェクト指向設計の基本の解説

  • 悪魔に負けない頑強なクラスの構成要素
    • インスタンス変数
    • メソッド

上記2つを兼ね備えたクラスが悪魔退治の武器になる。なぜか?↓
2章のアンチパターンが発生するから。

インスタンス変数を書き換えるのではなく、イミュータブルにすること。
現実の営みにはないメソッドを追加しないこと。

完全コンストラクタ↓
この場合、operandの両方とも設計後に変更しないことが担保できるという話(setterないので)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Money
  ALLOWED_CURRENCY = [:yen]
  attr_reader :amount, :currency

  def initialize(amount:, currency:)
    if amount < 0
      raise ArgumentError.new('金額は0以上を入力してください')
    end

    if currency.nil?
      raise ArgumentError.new('通貨単位を入力してください')
    end

    unless ALLOWED_CURRENCY.include?(currency.to_sym)
      raise ArgumentError.new("#{currency}は許可されていない通貨単位です")
    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(amount: added, currency: @currency)
  end
end

# minus_money = Money.new(amount: -1,  currency: 'yen')
# NG 金額が マイナス
# nil_currency_money = Money.new(amount: 100,  currency: nil)
# NG 通貨単位がnil

money = Money.new(amount: 100,  currency: 'yen')
money.add(Money.new(amount: 500, currency: 'yen'))
# OK

確かにだいぶ堅牢↑
定数を外部に切り出したり、複数指定考慮もいるかもだが、上記までやればだいたい事足りそう

4章 不変の活用

可変(ミュータブル)と不変(イミュータブル)の話。

  • 変数を再代入するのはダメ🙅‍♂
  • 引数を変数として扱って、再代入するのはダメ🙅‍♂
1
2
3
4
def cal_money(price:)
 price = price * 10
 # 引数は餌にして使って変数として使うな 
end
  • インスタンスを使い回すのダメ🙅‍♂

5章 低凝集

メソッド内の凝集度の話。

  • 凝集度

    • モジュール(クラス)内におけるデータとロジックの関係性の強さを表す指標
  • FactoryMethod使おう

    • 初期化処理が膨大になるようなら別のファクトリクラスを検討

オブジェクト指向の基本に基づき、設計をすること
ユースケースに対応する事柄である横断的関心事であれば、共通化してもいい

  • 引数を変更して出力する出力引数はNG
1
2
3
4
5
6
7
def add(a, b, sum)
  sum = a + b
end
sum = 0
add(3, 5, sum)
puts sum  # 出力結果: 0
# 最後のsumはaddメソッドとは別
  • メソッドで引数が多いものは低凝集になりがちなので注意せよ
  • プリミティブ型執着もしないように。
  • メソッドチェインはデメテルの法則に違反するので、NG

6章 条件分岐

  • 条件分岐のネストは悪of悪
    • 早期returnしよう
    • else や elsif も 早期returnしよう
  • switch文(case)が増えたらまずはまとめることを意識
    • 型でinterface毎にクラス設定して、分岐処理を抽象レイヤーで処理
  • ストラテジーパターン使って、処理を切り替えることが大事
  • Policyメソッドを使うのもいい
  • フラグ引数はアンチパターン
    • 何が起こるかを読み手に強要している
      • true/false でメソッドを分けるようにすること

クソコード動画「switch文」解説

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

  • 車輪の再発明しないこと(例では、JavaのanyMatch)
    • 車輪の再発明の下位互換を四角い車輪の発明という
  • ループ処理の条件分岐ネストは悪なので、早期continue(Rubyならnextか)やbreakでネスト解消しよう
    • 基本的にifの中で処理が散らばれば、早期 breakできないか考える
  • コレクション処理が複雑になったらカプセル化させる
    • (ファーストクラスコレクション)[https://qiita.com/gashiura/items/999a8c36e47a07fa4b27]
      • 対象の配列に対する処理を全て集約したクラスになります。別名コレクションオブジェクト

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

  • ソフトウェアにおける責任とは、「ある関心事について、不正な動作にならないよう正常に動作するよう制御する責任」
    • 単一責任
  • 同じ処理でも、ビジネス目的にあわせた設計にする
    • 例: 夏季限定割引300円/通常割引400円。 仮に共通化したとして、夏季限定が円ではなく%割引になったら破綻する
  • 安易な継承は推奨できない
    • いちいちサブクラスがスーパークラスに依存しているため、気にしてあげないといけない
      • 継承ではなく、コンポジション構造を使うこと
  • 影響スケッチを使う。Jig
  • public/protected/private を意識して使おう

つまり,privateは自分からしか見えないメソッドであるのに対し
て,protectedは一般の人からは見られたくないが,仲間(クラスが
同じオブジェクト)からは見えるメソッドです.
protectedは例えば2項演算子の実装にもう一方のオブジェクトの状
態を知る必要があるか調べる必要があるが,そのメソッドをpublic
にして,広く公開するのは避けたいというような時に使います.

  • スマートUI
    • フロントに表示ロジック以外がまじり込んでいること
  • 巨大データクラス
    • 集約させた邪悪ななんでもクラス
  • トランザクションスクリプトパターン
    • メソッド内に処理がダラダラと長く書き連ねている構造
  • 神クラス
    • 1クラスに何千何万行のロジックを持った神、神は神でもよくない神

クソコード動画「共通化の罠」
クソコード動画「継承の罠」

すべて、責務ごとにクラスを分けることで解決する

第9章 設計の健全性を損なうさまざまな悪魔たち

  • デットコード
  • YAGNI原則
  • マジックナンバー
    • 定数にしようね
  • グローバル変数
    • 使うなら、影響範囲を必ず小さくすること
  • nullチェック
    • null を返さない/渡さない 設計にする
  • 例外握り潰し
    • ログを残す!!
  • メタプロ濫用
    • 仕様を変えるような変更は悪
  • 技術駆動パッケージング
  • 銀の弾丸

第10章 名前設計

目的駆動名前設計 → 存在じゃなくて目的から名前を設計するのだ。クラスは多目的でなく、単目的!!

  • 関心の分離
    • ユースケースや目的、役割毎にクラス分割が必要
  • 目的不明オブジェクト
    • さまざまな目的に使われやすいクラスやモデル
  • 目的駆動名前設計
    • 可能な限り具体的、意味範囲が狭い、特化した名前
      • 会社の事業的にどういう目的を達成したいのか
    • 存在ベースではなく、目的ベースで名前を考える
      • 例: 金額だと抽象的、請求金額、消費税額、延滞保証料、キャンペーン割引料金 etc
    • どんな関心事があるか分析する
    • 声に出して読んでみる
      • ユビキタス言語で、職種超えて会話して齟齬なくす
    • 利用規約を読んでみる
      • 取り扱いやルールが厳密なので、そこからクラス設計できるケースも有る
    • 違う名前に置き換えられないか検討する
      • 例: ホテルの顧客 → 支払者/宿泊者 の意味になってしまう。 宿泊者と支払者に変えたほうが良い。
    • 疎結合高凝集になっているか点検する
  • 技術駆動命名
    • 技術ベースでの命名 (int/memory/method etc) はダメ
  • 驚き最小の原則
    • インターフェイスを、使う人が想像し易い形に設計すること
  • CQRS

クソコード動画『カプセル化 Mk-II』 で考える 上手くカプセル化できない理由
クソコード動画 Managerクラス

第11章 コメント

  • 退化コメント
    • コメントが嘘ついてるパターン
  • コメントで命名をごまかす
  • 引数/返り値 のコメントは◯

意図や仕様変更時の注意点を読み手に伝えること

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

  • 必ず自身のインスタンス変数を使うこと
    • 完全コンストラクタとnilガード
  • インスタンスを可変にせず、予期せぬ動作を防ぐような設計にすること (4.2.5参照)
  • 尋ねるな、命じろ
    • 他クラスを気にしたりいじったりするメソッド構造は悪。デメテルの法則に違反する
  • コマンド・クエリ分離
    • 下記のようなコードは、取得と変更を行ってしまっているので、badではなくgoodのように関心をわける
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Hoge
  # bad
  def gain_and_get_point(point)
    point += 10
    point
  end

  ## good
  def gain_point(point)
    point += 10
  end

  def get_point
    point
  end
end
  • 引数は不変にすること
  • フラグ引数は渡さない
  • nilを渡さない
  • 引数は限りなく少なく
  • エラーは戻り値ではなく例外スローすること

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

  • モデリングでattributes詰めすぎた場合

    • Userなのに、 ClientとWorkerの情報をUserが持ってしまっている構造 is evil
  • システムは目的達成手段である。モデルはシステムの構成要素である。

    • 特定の目的達成のために最低限考慮された必要な要素を備えたものがモデル
      例: 商品モデルがあったとする。注文時は、商品モデルは売値や在庫を気にするが、発送時にはサイズや重量が目的となる。
  • 現物世界での物理的存在と、情報システム上のモデルが1:1になるとは限らず、1:多になるケースがある

モデルにいびつさを感じた場合、以下を検討する

  • モデルが達成しようとする目的をすべて洗い出す
  • 目的それぞれに特化してモデリングし直す
  • 目的名前駆動設計にもとづき、さらに見直す
  • モデルに目的外の要素が入り込んでいる場合、さらに見直す

モデリングの仕方がUserクラスの負債化をまねく分割設計で爆死しないための2つの考え方

クソコード Userクラス

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

  • ネストをearly return
  • 値の代入のタイミングは、本当に必要な時のみ
  • ベタ書きロジックは、しっかりとロジック化する
  • if !hoge みたいなことしない

安全にリファクタするには、まずそのロジックをユニットテストしてから!これ絶対
テストコードを用いたリファクタの流れ

  1. あるべき構造の雛形クラスをある程度作る
  2. 雛形クラスに対してテストコードを書く
  3. テストを失敗させる
  4. テストを成功させるための最低限のコードを書く
  5. 雛形クラス内部でリファクタ対象のコードを呼ぶ
  6. テストが成功するよう、あるべき構造へロジックを少しずつリファクタする
  • 仕様があいまいな場合は、まず仕様をみたすための仕様化テストを書く
    • メソッドの挙動を明らかにするためのテスト
  • 試行リファクタリング
    • リファクタしちゃって、見通しを分析してから可読性、ロジック仕様理解、デットコードを確認する手法
  • 機能追加とリファクタリングを同時に行わないこと

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

  • 設計とは、課題を効率的に解決するしくみづくりのこと
    • ソフトウェアにおける設計とは、ソフトウェアの品質特定の向上を促進するしくみづくり

システム・ソフトウェア製品における品質特性

とあるが、本書は「変更容易性」について書いた本である

  • 設計しないと開発生産性が下がる どうなるか↓

  • バグを埋め込みやすくなる

  • 可読性が低下する

  • 木こりのジレンマ

  • 一生懸命仕事した感覚が残って生産性は悪いまま

  • レガシーコードは資産の負債である理由↓ エンジニアの成長を妨げる

    • レガシーコードに人は引きずられやすい
    • レガシーコードは高品質設計を妨げる
      • 納期により設計改善を諦めることもある…
    • レガシーコードは開発工数を増大させる
  • 課題を解決する

    • 知覚容易な課題と、知覚困難な課題がある
    • 理想形を知ってはじめて課題を知覚できる
  • コアドメインに絞ってリファクタすること (ビジネスとのトレードオフ)

    • システム内で最大の価値を付加すべき場所
    • 価値があり重要で、費用対効果が最大の箇所
    • 競争優位性があり、差別化が図られ、ビジネス上優位に立つポイント

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

開発プロセスの話

  • コンウェイの法則 → システムを設計する組織は、そのコミュニケーション構造をそっくりまねた構造の設計を生み出してしまう
  • 逆コンウェイの法則 → ソフトウェアのto be を設計し、そこからソフトウェア構造に最適な組織編成をする作戦
  • 粗悪なコードはきれいなコードを書くより常に遅い
  • 割れ窓理論とボーイスカウトの法則
    • 一度汚くなると汚れる
    • 通った道は(コード)きれいにね
  • 既存コードを信頼しない。踏襲しない。
  • ジョシュアツリーの法則 → 人は名前を知った途端、それが見えるようになる。逆に、名前がなければ(知らなければ)、それが見えない。つまり、名前を知ることで存在を知る。
  • コードレビューで最重要視なのは、敬意と礼儀
  • 設計を布教するにも、協力が必要

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

他の本の紹介

インプット2:アウトプット8にすべし

全体の感想

  • 2章 → 関係し合うデータをクラスにまとめて、低凝集を避けるアプローチは常に頭にいれることが大切
  • 3章 → (final Money Other) みたいな感じで、 const 型 引数 とできるのいいなぁ..静的羨ましい
    • 完全コンストラクタ意識もっと強める。無駄に setter 使うのは避けること
  • 4章 → Rubyは非常に可変性の高い言語であることを受け入れ、単純に規律を定める方が、より現実的。
  • 5章 → UtilityCommonという悪魔のメソッドが2万行になったことがある。と上司が言っていたのを思い出した
    • メソッドチェイン使いがちになるときにはその前で early return とかしてあげないとバグが頻出する
  • 6章 → Rubyでインターフェイスを実装するのは難しそう? Ruby3のRBSならそれっぽいことができるっぽい
  • 7章 → ファーストクラスコレクション意識したい
  • 8章 → 共通化の罠気をつける。
    • 気軽に継承するのは辞めて、コンポジションを使う
  • 9章 → パッケージは、設計パターンではなく、ビジネス概念で切るべきである。
  • 10章 → 商品クラスみたいな目的不明オブジェクトを体験したことがあるのでつらみがわかる
    • クラスの処理を口頭で説明する必要がある場合危険信号だな。。(クラス設計し直す)
  • 12章 → メソッド単位の設計をきれいにすることで、低凝集構造を避けることができると改めて思った。この章は、何度も読み直したい
  • 13章 → 目的ベースでモデリングすることを呼吸できるようにしたい。1に目的2に目的じゃ
  • 14章 → リファクタリングはボーイスカウトで通ったらきれいにするべきだが、機能追加とはわけて行うことを再認識。
    • テストや仕様不明なときは、仕様化テストが有効なの知見になった
  • 15章 → 設計をするべき不吉な臭いを感じ取れるエンジニアになりたいね
  • 16章 → 心理的安全性が独り歩きすることはよくある。本当の心理的安全性とは時には厳しい言葉を投げかけても壊れない関係性のことだと思う。
    • Done is better than Perfect ではあるが、設計をせずに実装すると、いつか尻拭いをすることになる

全体を通じて、とても読みやすく学びが多かった。
実際の設計する際にまた読みに戻ってきたい

共有

busitora
著者
ブシトラ
エンジニア