見出し画像

良いコード 悪いコードで学ぶ 設計入門(書籍:勉強メモ :2023/10/27)

◆はじめに

プログラミング歴が長くなってきてもコードのクオリティが低い人はそれなりに居ると感じる。基本的にプログラミングは独学に頼る事が多いのと「仕様がカバーされた動くコードが正義」になりがちで、可読性・保守性を失うだけでなく、コーディングした範囲に脆弱性が入り込んでしまう事が多い。
自分自身のブラッシュアップもそうだが、人に教えることが出来るようになる事をエンジニアは目指すべきだとは思う。という思いで噂になっている本書を手に取りました。

◆基本情報

出版:2022年5月12日 初版
著者:仙場 大他(せんば だいや)
発行所:株式会社技術評論社
https://www.amazon.co.jp/dp/4297127830

◆構造の弊害

・悪しき構造がさらに悪しき構造を誘発する
・条件分岐のネストは理解を困難にする
・データしかもたないクラスと計算ロジッククラスが分離されていると計算ロジックを何度も実装してしまう事が多発する。データとロジックがバラバラになっているのを低凝縮と言う。データベースのマッパーにロジックを埋めたくなるのはコレがある。

◆設計

・省略せずに意図の伝わる名前を設計する。
・変数は使いまわさずに目的ことに用意する。
・関係し合うデータとロジックをクラスにまとめる。
・クラス単体で正常に動作するように設計する。
・コンストラクタで正常値を設定する。(不正値かバリデーションする)
・値オブジェクトとして値をクラスとして扱うことによりロジックを高凝縮にする。
・可変(ミュータブル)不変(イミュータブル)を適切に設計する。
・関数(メソッド)は「引数を受け取り値を返す」以外に「状態を変更する」事がある
・関数の影響範囲を限定する(データや状態を引数で受け取る・状態を変更しない・値は関数の戻り値とする)
・状態の変更は新しいインスタンスを生成する。
・凝縮度に影響がない場合にstaticメソッドを使ってもいい。(ログ出力やフォーマット変換など)
・コンストラクタをprivateにしてファクトリメソッドで目的別に初期化を呼び出す(コストラクタに対する初期値セット/引数の注入をファクトリメソッドにして切り替える)
・commonやutilといった「共通」を思わせるクラスは関係無いロジックが雑多に実装されてしまう事になる。
・関数の引数(入力値)は変更しない。別の変数に入れる。(可読性が低下する)
・引数が多すぎる。プリミティブ型(標準の型)を多用するなら意味のある単位ごろにクラス化する(データクラス)
・クラスのインスタンス変数はprivateにして直接は触らせない。どこからえも値が変更できると、どこで値が変更されているのか調査しきれなくなる。メソッドを経由させる。

◆条件分岐

・if文の中に数十~数百行の処理が実装されていると理解するのに時間がかかる。
・早期returnでネストを解消する。
・elseも早期returnでifの連続にすると可読性があがる
 if(){}
 else if () {}
 else if () {}
 ↓↓↓↓↓↓
 if(){return;}
 if(){return;}
 if(){return;}
・switch文は可読性を低下させやすい
・interfaceを定義してimplementsする事でクラスに同じ型を与えて機能の切り替えを簡単にする。(同じインスタンス・メソッド名を使えるようにする)
・switch以外の処理を切り替える方法を考える(同じ条件のswith文が同じプロジェクトに書かれがち)
・interfaceは型判定を減らさせるために実装する(型判定が必要な処理はinterfaceから見直す)
・フラグ引数(true、false)を機能を分岐させるために使うなら違うメソッドを作成する。
・ループ処理中の条件分岐ネストは早期continue(早期break)で解消する。

◆絡まって解きほぐせない構造(密結合)

・単一責任の原則(クラスが担う責任は、たったひとうに限定すべき)
・密結合ではなく、それぞれが独立してる構造「疎結合」を目指す
・概念が異なるモノをDRY原則によって無理に結合すると密結合になる。DRY原則は概念単位で考える。
・継承はよっぽど注意をして扱わないと危険(スーパークラスはサブクラスを気にせず変更されるためサブクラスは壊れやすい)
・継承より委譲が推奨される。
・なんでもPublicで宣言すると密結合を生み出しやすくなる。可視性を適切に制御する。
・関係しないモノ同士が同じクラスにならないようにクラスを分離する。(依存しあわないなら別のクラスにする)
・privateメソッドが多いクラスは単一責任ではなく多くの責務を持っていることが多い。責務の異なるメソッドは別々のクラスに分離する。
・高凝縮を目指すと密結合に陥ることがある(同じ責務だろうと1か所にコードを集めてしまう)
・表示責務と表示以外の責務は、それぞれ別のクラスに分離する。
・データクラスはデータの置き場所として巨大化しがち。ユースケース以外のデータは持たせないで別でデータクラスを作る。
・責務ごとにクラスを分離する。単一責任の原則を遵守する。

◆設計の健全性を保つ

・YAGIN原則(実際に必要になったときのみ実装せよ)
・変数は影響範囲が最小になるよう設計する。
・nullを返さない、nullを渡さない、nullを変数に代入しない。(null安全)
・try~catchしていても処理をせずに例外の握り潰しに注意する(バグとなる。握りつぶさせない)
・目的駆動名前設計、目的や意図を読み取れる名前を付ける
・複数にまたがるユースケース(例えばECサイトにおける「商品クラス」)は巨大化する。「関心の分離」関心事、ユースケースや目的、役割ごとに分離し、商品クラスを予約品・注文品・在庫品・発送品など関心事に分離して複数のクラスに再設計する。
・商品クラスのような大雑把な名前は避ける。
#「common」「util」なども、そこにロジックが集まりやすい
・関心の分離は「ビジネス目的」を名前として表現することがポイント
・具体的で、意味範囲が狭い、目的に特化した名前を選ぶ
・金額→請求金額、消費税額、延滞保証料、キャンペーン割引料金、etc
・ユーザー→アカウント、個人プロフィール、職務経歴、etc
・ユーザー名→アカウント名、表示名、本名、法人、etc
・商品→入庫品、予約品、注文品、配送品、etc
・ビジネスの目的に合う名前を付ける
・疎結合高凝集になっているか点検する。複数の意味を持っていたら分解する。関連個数を計測する。
・「要注意会員」は「貸出延滞回数」と「図書汚損回数」が一定値を超えている人が・・・みたいなのが出てきたら要注意。ロジックがベタ書きで謎仕様になっている可能性がある。適切なメソッドを設計する。
・技術駆動命名(コンピュータ用語由来:Int、Memory、Flag、etc)だけでは意図がわかりにくくなる。
・ロジック構造をなぞった命名(ロジックを説明するようなメソッド名)は、何をしたいのかメソッド名から伝わらない。実現したい目的の理解が不十分だと付けてしまう事が多い。
・意図や目的がわかる命名をする。
・MemberManagerクラス→巨大化する名前→関心事と責務(なにをしたいのか)を単一責任の原則でメソッドをクラスにして分離する。
・Controllerも他のクラスへリクエストパラメータを渡す責務に限定する。分岐ロジックを実装しないようにする。
・carクラスを作ると配送先・注文など複数のコンテキスト(「文脈」「状況」)が集まってくる事になる。パッケージを分離した設計が必要。(配送パッケージ-car 販売パッケージ-car)
・マジックナンバー 定数化していないで埋め込まれた その意図や意味が書いた人にしかわからない数字。
・連番命名 xxxx001 xxxx002 など連番を付けていく命名規則

◆コメント

・コードと比べてコメントはメンテナンスされにくい。
・古くなったコメントは嘘をつきはじめる。
・挙動をなぞるだけのコメントは退化しやすい。
・意図や仕様変更時の注意点をコメントにする
・可読性の悪いロジックを補足説明するコメントは、ロジック自体の可読性をあげる。

◆メソッド

・自身のクラスのインスタンス変数を使うこと(他のクラスのインスタンス変数を変更する構造にしてはいけない)低凝集に陥る。変更したいインスタンス変数を持つクラスに変更メソッドを実装する。
・getter/setterを使うのではなく、そのクラスに何をどう変更するのか命じるようなメソッドを用意する。
・フラグ引数は使わない。フラグ引数でロジックを切り替えるような処理は別のメソッドにする。(ストラテジパターンなどにする)
・nullを返さない。(nullに意味を持たせない)
・エラーは例外をスローすること(戻り値で返さない)

◆モデリング

・Userなど1つのモデルに複数の目的が集まってしまう。(一貫性がなくなる)

◆リファクタリング

・ネストを解消する
・意味のある単位にロジックをまとめる
・条件を読みやすくする(論理否定は読みづらい
・ベタ書きロジックを目的を表すメソッドに置き換える
・ユニットテストでリファクタリングのミスを防ぐ
・機能追加とリファクタリングは同時にやらない
・スモールステップでコミットしていく

◆設計への向き合い方

・「木こりのジレンマ」斧が刃こぼれしてる。旅人は言った「刃を研げば楽に切れますよ」、木こりは答えた「刃を研ぐ時間なんかない!」
・課題を知覚する。課題があるから設計する意識が生まれる。
・知覚困難な課題がある。
・理想形を知ってはじめて課題を知覚できる。
・「変更容易性」は経時変化により表出するのですぐに比較できない。
・コードの良し悪しを判断する指標を作る
・開発プロセスとの戦い(レガシーコードは問題のある開発プロセスが背景にある事が多い)
・コミュニケーションが希薄だと設計品質に問題が生じる
・コミュニケーションに課題があるときは心理的安全性の向上に努める。
・多数決は品質が最低になる(一番レベルの低いところに合わせて基準が作られる事になる)
・割れ窓理論(粗悪なコードの近くに粗悪なコードが書かれて、より悪化していく)
・ボーイスカウト理論(キャンプ場を自分が来た時よりきれいにすること)
・既存コードを信用しない(お手本にしない)
・コーディング規約/命名規約

◆コードレビュー

・設計的な妥当性に重点をおいてレビューする
・コードを書く仲間を尊重し、敬意と礼儀を意識する。
・良くないコードを改善タスクとして積み上げておき、定期的に改善タスクを棚卸する。

◆最後に

書籍のコードはJAVAで書かれてます。私はJAVA言語は専門ではありませんが(PHPが専門のphperです)プログラミングの基礎的な部分は同じで読みやすいと思いますが、他人が書いたコードが読めて修正を入れられるレベルになっていないとサンプルコードを改善していく箇所は理解するのがキツイかもしれません。
それと納得しながら読んでいると10時間くらいかかります(4日に分けて2~3時間ずつ)。同じことも繰り返し出てきます。(それだけ重要ということ?)
コーディングをする組織として全員が知ってもらいたい知識が詰まっているので会社やグループ単位での輪読をオススメしたいです。

・今後、意識しようと思った心に残った事

nullを返さない、nullを渡さない、nullを変数に代入しない。(null安全)
関心事、目的、責務、意図、それぞれ別のクラスにする必要はないか設計を考える(管理=Managerクラスを1つではなく、管理の内訳:登録・変更・サービス実行・・・を別のクラスとして設計する)
コメントは意図や仕様変更時の注意点を記載する。
「変更容易性」は経時変化により表出するのですぐに比較できない。
ダメコードでも早く作る人が現場には居る。指示されたことを早くやるので評価もされていたりする。
品質向上の意識が高い人は指示以外の何かをやっている人として目を付けられる事がある。
勉強会は本の読み合わせ「だけ」をすると効果が低い。アウトプットが大事。

以上、「良いコード 悪いコードで学ぶ 設計入門(勉強メモ)」でした。

この記事が気に入ったらサポートをしてみませんか?