これ何
良いコード悪いコードで学ぶ設計入門を読んで大事だなと思ったことを忘れないようにメモしておく。
良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方:書籍案内|技術評論社
個人的にはリーダブルコードよりも読みやすく、全てのコードを書く人におすすめできる本だと思いました。
各章ごと
3:クラス設計
- クラスは単体で作動するように設計する
- 自己防衛責務がある
- ベストプラクティス
- よい設計パターン
大事なこと:データとデータ操作ロジックを一箇所にまとめておくこと(凝集性が高い状態)。必要な操作だけを後悔すること。
# example: 物体検出問題において正解データとなるbounding boxデータを表現するクラス class BoundingBox: def __init__(self, xmin: int, xmax: int, ymin: int, ymax: int, label: str): if not self._are_valid_positions(xmin, xmax, ymin, ymax): raise Exception("Invalid positions.") if len(label) == 0: raise Exception("Empty string is passed as a label name.") self.xmin = xmin self.xmax = xmax self.ymin = ymin self.ymax = ymax self.label = label return def _are_valid_positions(self, xmin: int, xmax: int, ymin: int, ymax: int) -> bool: # 座標のバリデーション if xmin < 0 or ymin < 0: return False if xmin >= xmax or ymin >= ymax: return False return True def get_bbox_area(self) -> int: # bboxの面積を返すメソッド # クラスに関連する処理はクラスのメソッドとして提供することで凝縮性を高める return (self.xmax - self.xmin) * (self.ymax - self.ymin) def slide_bbox(self, stride_x: int, stride_y: int): # bboxを移動するメソッド # データと同じ場所に操作するメソッドを定義することで凝集性を高める # 副作用を防ぐために、クラス変数を上書きするのではなく新しいインスタンスを作成する return BoundingBox( self.xmin + stride_x, self.xmax + stride_x, self.ymin + stride_y, self.ymax + stride_y, self.label )
4:不変の活用
- 再代入をできるだけ許さない
- 可変であることで生じること
6: 条件分岐
- 条件分岐が入れ子になっているといいことはない
- 早期returnは良いsolution
- switchが色々なところに実装されると、変更に弱いコードになる。
- switchするのは一箇所にまとめる
- 条件が増えていくのであればinterfaceとして共通する構造を定義するのがよい
- 種類ごとに切り替えたい機能をinterfaceのメソッドとして提供するのがよい
- interfaceの型で分岐処理をしたいときはinterface側に実装する
- フラグ引数(0ならこれ、1なら違う処理、みたいな関数の引数)
- 関数を分けましょう
7: コレクション
- 言語がコレクションに対する処理を提供している場合は自前で実装しない。
- ループ中に条件分岐が多くある場合には早期continueやbreakを活用する
- コレクションに関する実装が散らばってくるときにはFirst class collectionを検討する(カプセル化)
- コレクション型のインスタンス変数と、それらを不正状態から防御し正常に制御するためのメソッドを提供する。
@dataclass class Member: name: str hp: int # PartyはFirst Class Collectionとして機能する class Party: def __init__(self, members: List[Member]): self.members = members def add_member(self, new_member: Member) -> Party: # self.membersを上書きするのではなく、新しいPartyインスタンスを返却する new_members = copy.deepcopy(self.members) new_members.append(new_member) return Party(members=new_members)
8:密結合
- 単一責任の原則(クラスが担う責任はただ一つにするべき)を強く意識しておくことが疎結合性を担保するコツ
- 重複コードを恐れない。ほとんど同じコードであっても責務や概念が異なる場合にはコードを分割することは悪いことではない。
12:メソッド
- 他のクラスのインスタンス変数を変更しない。 変更するのは自身のインスタンス変数のみに絞る。
- コマンド・クエリ分離(Command-Query Separation:CQS)の原則を守る
- 状態の取得及び状態の変更のどちらかを責務とするメソッドにする。どちらも同時に行わない。
- 引数が多くなりそうなら別クラスにまとめることを考える
- 概念ごとにクラスに分割すれば引数が多くなりすぎることがなくなるはず
- 戻り値
- プリミティブ型で返すよりも独自の型を定義して返す方が安全
- エラーは戻り値で返すのではなく例外をthrowする
- 負数などでエラーを表現しない