sironekotoroの日記

Perl で楽をしたい

「良いコード/悪いコードで学ぶ設計入門」第1章〜第6章のまとめ #ミノ駆動本

一旦まとめ

7章を目前に、どの内容をどこで学んだんだっけ・・・?というのを自分なりにまとめておきたくなりました。

コード書いたからか、結構覚えているなーという感じです。

あと、本を先に進めたい!という気が勝っておざなりにしてるところがあるなぁ、と。

普遍的な変数のところですね。

Perl の場合には Readonlyconstant を使うのですが・・・ううん(顔を背ける)

さて、7章以降もがんばってくぞー

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

  • 弊害とは

    • コードを読み解くのに時間がかかる
    • バグを埋め込みやすくなる
    • 悪しき構造がさらに悪しき構造を誘発する
  • 意味不明な命名

    • 技術駆動命名
      • 変数名に int, flag などプログラミング用語やコンピュータ用語に基づいた名前
      • 内容や用途に基づいてない名前
    • 連番命名
      • Class00, method01 など
  • 何重にもネストしたロジック

  • 巨大なネスト
  • データを保持するだけのクラス
    • データを扱うメソッドがない
    • 重複コード(重複メソッド)がコードのあちらこちらに書かれてしまう
    • 修正漏れが発生する可能性
    • 可読性の低下
    • 生焼けオブジェクト
      • 未初期化状態のオブジェクトが利用できてしまう
        • 当然利用しようとすれば undef になる
    • 不正値の混入
  • 悪魔退治の基本

第2章 設計の初歩

  • 省略せずに伝わる名前を設計する
  • 変数を使いまわさない、目的ごとの変数を用意する
    • 再代入を避ける
  • 意味あるまとまりでメソッド化する
    • サブルーチン化か
    • 種類の異なる処理をメソッドにまとめる
    • だらだらと書かない
    • 理解のしやすさを優先に、行数や変数名の文字数が増えることを厭わない
  • 関係し合うデータとロジックをクラスにまとめる
    • データクラスの問題でも書かれていた「いろいろなところに類似のロジックが書かれる」問題

第3章 クラス設計

  • オブジェクト指向は、ソフトウェアの品質向上を目的とする考え方の一種
  • 適切なクラス設計により、保守や変更が容易になる
  • 頑強なクラスの構成要素
    • インスタンス変数
    • インスタンス変数を不正状態から防御し、正常に操作するメソッド
    • 基本的にインスタンス変数とメソッドはワンセット、一つのクラスに入れる
      • 例外はある
    • 不正値チェックをクラス内で行うことができる
      • データしか持たないデータクラスだとできない
  • 自己防衛責務を持たせる
    • 一つ一つのクラスが完結している
      • NG:他のクラスによる初期化が必要
      • NG:データ入力を他のクラスにしてもらう
  • 成熟したクラスへ成長させる設計術
    • コンストラクタで確実に正常値を設定する
      • インスタンス変数に格納する前に、正常値であるかをチェックする
      • 不正値だったらガード節で即エラーを出しインスタンスを作成させない
  • 計算ロジックをデータ保持側に寄せる
  • 不変で思わぬ動作を防ぐ
    • 変数の上書きができると思わぬ副作用を招く
    • final, const Readonly 使おう
  • インスタンス変数を変更したい場合は、インスタンスごと作り直す
  • メソッド引数やローカル変数も不変にする
  • 「値の渡し間違い」を型で防止する
    • 引き数をプリミティブ型にしない
      • Int 型ではなく、Money 型などで渡す
    • 同じ型同士で処理するように、メソッドの引数の型チェックを行う
  • 現実の営みにないメソッドを追加しない

    プログラム構造の問題解決に役立つ設計パターン

  • 完全コンストラク
    • 生焼けオブジェクトを作らせない
      • コンストラクタで生成する時点でしっかり引数入れて引き数チェックもする
  • 値オブジェクト
    • 値の概念そのものをクラスとして定義する
  • 「値オブジェクト」+「完全コンストラクタ」はオブジェクト指向設計の最も基本形を体現している構造の一つ

第4章 不変の活用

  • 再代入を避ける
    • 不変にする
    • 引数も不変にする
  • 関数による可変インスタンスの操作
    • 主作用(関数が値を受け取り、値を返す)以外の副作用が出ないようにする
    • データを引数で受け取る
    • 状態は変更しない
    • 値は関数の戻り値として返す
    • インスタンス変数を不変にしておくことで、副作用の余地をなくす
      • 変えようとするとエラーになるから
      • 意図して変えたい時はちゃんとメソッド作る
  • 不変と可変の取り扱い方針
    • デフォルトは不変
    • スコープが局所的なケースのみ可変
      • ループカウンタなど
    • 可変の変数で状態を変更する時は、状態変更のみ発生するように設計する
      • 副作用がないようにする
    • コード外とのやり取りは局所化する

第5章 低凝集

  • static メソッドを誤用しない
    • static メソッドはインスタンス変数を利用できない
      • このため、第3章の「頑強なクラス」の要件を満たさない
    • static がついていないだけで、同じ問題を抱える(インスタンス変数を用いない)クラスも作成可能だが、もちろん作らない
    • staticメソッドは、凝集度に無関係なものに利用する
      • ログ出力
      • フォーマット変換
      • ファクトリメソッド
  • 初期化ロジックの分散
    • コンストラクタを公開すると、さまざまなところで利用され、メンテが大変になる
    • コンストラクタをプライベートメソッドにすることで、外部から直接インスタンス生成させない
      • 内部でだけインスタンス化できる
      • インスタンス化したものを返す
      • static なファクトリメソッドが生きる
        • データと結びついていない(結びついている必要がない)
        • インスタンス化しなくても利用できる
  • 共通処理クラス
    • common, util などと名付けられるクラス
    • 低凝集になりやすい
    • static メソッドが入り込みやすい
    • 第2章でいうところの「意味のあるまとまり」でまとめられるべきメソッドが、共通処理クラスに入れられる
      • クラス設計の基本に立ち返る
    • 横断的関心ごとを共通処理クラスにする
      • static クラスにしても良い
      • ログ出力
      • エラー検出
      • デバッグ
      • 例外処理
      • キャッシュ
      • 同期処理
      • 分散処理
  • 結果を返すために引数を使わない(?)
    • もっといい言い換えがありそう。
      • 「出力引数」、直感的じゃない。
    • インスタンスと数値を渡し、インスタンスをその数値によって変更する例
    • そのインスタンスに変更メソッドとして実装すべき
      • クラスは、データとそれに関するメソッドをまとめるものという基本に立ち返る
    • メソッドの中身を確認しないとわからないような名前づけや処理にしない
  • 多すぎる引数
    • 引数が多い = 処理する内容が多い
      • 適切なクラス設計ができていない可能性
    • プリミティブ執着しない
      • コード重複が生じやすい
      • 適切なクラス(の型)を渡す
        • その型に必要なインスタンス変数やメソッドは、その型に集約され、高凝集になる
    • 意味のある単位ごとにクラス化する
  • メソッドチェイン
    • 似たようなコードが量産される原因の一つ
    • デメテルの法則「利用するオブジェクトの内部を知るべきではない」
    • 「尋ねるな、命じろ」
      • 他のオブジェクトの状態を尋ねない。他のオブジェクトの状態に応じて呼び出し側が判断をしない
      • 命じられた側で判断する
      • 詳細なロジックは、呼ぶ側ではなく、呼ばれる側に実装する

第6章 条件分岐

  • 条件分岐のネストによる可読性低下
    • 早期 return で解消
      • 条件と実行の分離
      • 条件の追加が容易になる
      • else 句をなくすことも可能
  • switch 文の重複
    • 条件分岐は同じだが、返り値だけ異なる switch 文が量産されやすい
      • 量産されることで、仕様変更・条件追加・条件削除時の修正漏れが生じやすい
      • switch 文は増えやすい
    • 条件分岐は1箇所にまとめる
    • interface を使い、スマートに重複を解決する
      • 同名のメソッド(例:area)を引数で渡すクラスに実装しておく
      • 呼ぶメソッドは area() で共通
      • ダックタイピング
      • Perl だと Mo[o|u]se::role
      • interface は利用するクラスに共通のメソッドがあることを要求するので、実装漏れにも対処できる
  • 条件分岐の重複とネスト
    • ポリシーパターンで対処
      • 条件の部品化、部品化した条件の組み替え
      • 条件ごとのクラスを作、(例:GoldCustomer, SilverCustome)条件判定するメソッドを実装する(例:ok)
      • okメソッドを持っているクラスを集約する interface を作る
      • GoldCuster クラスでは、すべての条件を満たす、SilverCustomer クラスでは2つの条件を満たす、などの条件で実装する
      • switch や if 文を使わずとも、条件分岐が可能に
  • 型チェックで分岐しない
    • interface を実装しても、実装したメソッド側で型による分岐をしたのではもったいない
      • 条件分岐削減の役に立っていない
    • if や switch の代わりに interface が使えないかを考える
  • フラグ引数
    • メソッド側で処理を分岐するためにつける引数
    • 型オブジェクトを渡し、interface を実装して共通のメソッドで処理させる