コーディングに意識する設計・品質の言語化メモ

enumura
·
公開:2026/1/2
  • オブジェクト思考を意識

    • データとそれを操作するメソッドを集約する

      • そうしないと重複メソッドがいたるところに散見されるようになるから

    • クラス内でバリデーションを書いて早期リターン

    • 横断的関心事は集約する

      • アプリケーションにおいて各層を横断して使うもの(logger、エラー処理)は集約して書いてOK

    • 関心ごとの分離

      • utilsクラスみたいなふわっとしたものは、開発が進むにつれて肥大化する傾向がある

        • 読み解くのが難しくなるので、目的に応じてクラスを分割する必要がある

    • 肥大化したクラスの整え方

      • 全体の処理を関心ごと別に、インタフェース部分とロジック部分に分離していく

        1. 処理の目的ごとにインタフェース(関数)を設けて、その処理の結果得られる値(return値)を考える。その値を得るために必要な情報を引数(インターフェース関数の引数)に持つ

        2. そしてその引数から関数の結果をえらるためのロジックを内部に書いてカプセル化する

        3. そのインタフェースを元あったらクラスに記載しリファクタ完了

    • if文のネスト解消

      • 条件部分と実行部分に分ける

        • 条件部分:まとめて書く

          • 早期リターンで返す

          • elseは見通しが悪くなるので書かない

        • 実行部分:まとめて書く

        • 条件を反転させて1つのif文に抜き出して、ネストを1つずつとく

          • 元:3つのXXの時YYするみたいな3層のネスト

          • 新:「!XXの時」というように1つずつのif文の条件を反転させて1つのそう気バリデーションのif文として切り出す。それを最初に並べる

            • これで見通しの悪い多重ネスト構造を解消する

      • フラグ引数の使用

        • 読み手の認知負荷が高くなる

          • そのメソッドの内部まで見に行かないといけないから

    • switch文できをつけること

      • 同じを条件のロジックは1箇所にまとめること

        • 重複コードが出来やすいのがswitch文の特徴であり、仕様変更時にそれらを全て把握してやるとなると保守性が悪いため

    • for文の中にif文パターン

      • jsでいうmap()、some()、filter()で実装できないか検討した方がいい

        • そっちの方がバグが入る隙間なく、冗長な書き方を回避できる

      • for文の中にif文がネストするパターン

        • break, continueを使って早期にループを抜けつつ、ネスト構造を解消できないかを検討

    • リスト、配列の操作

      • それらの変数値とメソッドを準備したクラスを作ってカプセル化する

        • リストと配列を作成するクラスだけ作っても、それを操作するメソッドを書いてなかったらクラス外の任意の処理タイミングで同じロジック複数登場してしまう(処理の重複)。これをなくすためにクラス内でリストの値生成とメソッドの実装までやった方が良い場合あり

          • メソッド内でスコープ外のインスタンス変数であるリスト、配列を操作するのは副作用(破壊的な変更・?)のため回避したい

            • 対策:メソッド内で新規にリスト、配列を生成しそれに変更を加えてreturnするようにするえ

  • 引数

    • 引数のパターンを統一する

      • 引数は入力値として受け渡すのが普通。そこに関数の出力値となるような引数があると、そのパターンも考慮して読まないといけないので認知負荷が高くなる

        • 参照型と出力型の引数が混在すると、そのメソッドのロジックを読む時の見通しが悪くなる

    • え引数が多い場合

      • インスタンス変数を使うようにする

        • そのクラスの概念に注目して本当に必要な引数だけもらうようにし、残りのヘンスはそのクラス内で生成する。そのインスタンス変数をクラス内で使うことにより引数の数は減らせる

  • bool値

    • if文とか条件の反転に「!」を使うことがあるが、〜でないと考え直す負荷がある。それは認知負荷が高い

      • 逆の意味を持つ変数を準備し置き換える

        • 例:「!isValid」 → 「isInvalid = !isValid」と置き換える

  • DRY(Don’t Repeat Yourself)の原則

    • 何でも共通化させればいいというわけではない

      • 目的を基準に考える。目的単位で関心ごとは分離させる。目的が違うなら共通化させるべきでない。

        • やりすぎると複数の処理が1つの共通化した処理に依存するので、結果として密結合を生んでしまう。そうなると、後で仕様変更があった時に影響範囲が大きくなるのでメンテナンス性が悪いから

  • YAGNIの原則

    • YAGNI = You Ain't Gonna Need It = 今は必要ないでしょう

      • 実際に必要になったときに実装する方針

        • デッドコード(デメリットが多いからという意味)になってしまう可能性が高いから

  • マジックナンバーの使用

    • マジックナンバー:コードを読んでいて突如出てくる意図がわからない数字のこと

      • コードないの各所でその数値のベタ書きがあると、仕様変更の際に全部守成しないといけなく見落としを生む可能性がある

      • 意図側からない

        • 対策:意図がわかる変数名をつけて宣言して使う

  • 何でも文字列型にしない

    • 意図が分かりにくいし、都度適切な型に変更するロジックも必要になりバグを生む可能性があるため

  • グローバル変数(とそれ同等のもの)

    • どこかでも参照、変更ができるので影響範囲がでかい

      • そのためローカル変数と比べてバグとなる可能性が高い

        • 変数に限らずそれと同等の動きをするものは同じように考える必要がある

          • 対策:カプセル化

            • データとそれを操作するためのメソッドを準備したクラスなり関数を作って影響範囲を小さくする。これにより変更が容易になる

  • nullの発生について

    • nullが生まれるような設計だと、nullチェックのための条件分岐が必要になりコードの見通しが悪くなる。またチェック漏れでバグに繋がる

      • 対策:そもそもnullが生まれないような設計をする

        • 何もない状態を表現するなら"KARA"定数みたいに専用の定数を準備する

  • プロジェクトの進め方

    • 設計はガチガチに最初は作りすぎない

      • どうせ後で修正することになるから。たった1回の設計で終わることはない

        • コード書いて初めてわかることもある

        • 設計と実装のサイクルを回して課題認識できることもある

          • ただし設計ルールはチームで合意形成が最初に必要

  • 命名について

    • 意図が曖昧な命名はしない

      • 意味の幅がでかい命名はあらゆるロジックを惹きつけてしまうため

        • 関心ごとの分離ができていない状態になり、肥大化したクラスは開発生産性をていかさせることに繋がる

          • 対策:どういう目的を達成したいのかがわかる命名にする

    • 名前とロジックが対応することに価値がある

      • プログラム構造を大きく左右することになる

        • 1度命名して終わりではなくて継続的に見直すことが必要

    • リファクタリング

      • そのクラスの目的は何か、何を持って目的達成と言えるのか、そもそも全然別の概念が含まれてる可能性がないかを確認する

        • ポイント:修飾言葉

          • 単にUserというわけではなく、ログイン済みのユーザ、ログイン後のユーザ、みたいに名詞の意味を具体的に修飾してる言葉に注目する。この違いで関心ごとの分離をしやすくなる

        • メソッドは可能な限り1語の動詞で命名がわかりやすいが、省略が必要になる場合は省略せずのほうがいい

    • あまり良くない命名

      • 意図がわからない命名(例:User01)

      • 技術用語由来の命名(例:Memory)

      • その場しのぎの命名(例:hoge, tmp , fuga)

      • データクラスになるような命名

        • データを操作するメソッドが定義されてないように見えるから

    • 境界づけられたコンテキスト

      • 業界ごとにことなるドメイン知識がある。そのため同じ言葉でもダブルミーニングになったり意図した意味で読んでもらえない場合がある

        • 文脈でクラスを分けて設計する。各文脈を異なる関心ごととして認識して分割する

  • コメント

    • そのロジックの目的は何か、仕様変更の時に何に注意すればいいかを書くようにする

  • クラス

    • クラス内のメソッドは、クラス内のインスタンス変数に結果を代入する

      • クラス外の変数に代入したり、クラス外の変数の値に変更を加えるのは避ける

        • getter, setter:はあまり使わない方がいい

          • 外のクラスの中のインスタンス変数に変更を加えたり、取得する手段が増えるため

    • 一貫性のないクラス

      • 一貫性がある:目的達成のために最低限のデータ要素が揃ってる

      • 一貫性がない:ノイズとなるようなデータがオプションのような感覚で詰められてるクラス

        • 最初は一貫性のあるクラスであっても、仕様変更を繰り返すうちに一貫性がなくなってしまうケースは多い

    • 1つの目的に対して1つのクラス

      • これが単一責任の状態

  • コマンドとクエリの分離

    • 同じデータに対してデータの取得とその値の変更を行うメソッドは目的が違うのが混じってるので2つに分けた方がいい

      • 変数の値だけを返すような関数でもOK

  • リファクタリングの仕方

    • 新規開発:まずは動くコードを作る

      • コードをプッシュする前、PRをだす前に見直す

        • ある関数を書く場合

          • まずは書きたい関数の全体像を書く

          • 要件にあったテストを書いて動かし失敗を確認

          • ごちゃってもいいのでテストを通す(動く)ソースを書く

          • それを修正していく。修正の都度テストが通ってることを確認

          • 修正完了のながれ

    • リファクタと要件を満たした機能の修正、追加を一緒にしない

      • あとからcommit、prを見て何をしていたのかゴチャって見にくいから

    • 小さく修正して都度コミットする

      • でかい変更を入れてテスト通して失敗するとエラーの原因をおいにくい

        • 小さく修正してテスト通してコミットするとエラーの原因にすぐ気づける。回り道に見えそうだが多分こっちの方が早い

    • リイファクタをする前に必要ない機能がないかを見ておく

      • 見る全体量を可能な限り減らして考える負荷を下げたいため

  • スキルとは

    • 再現性を持って実現できること

      • 同じような問題に直面した時に、再現性を持って技術を使って解決できること

  • ビジネスへの影響

    • 変更容易性が低い→開発速度が低下→アウトプットが出しにくい→ソフトウェアとしての成長が遅い→プロダクトが収益を生みにくい→ビジネスに悪影響

  • 課題とは?

    • 課題 = 理想(知っておかないといけない) - 現状

  • ソースの品質を計測

    1. ソースの行数

    2. 循環的複雑度

      • ソースコードの"構造的な複雑さ”を数値として測ったもの

        • pythonだとradonというものがある

          • ロジックの重さ(複雑さ)を定量評価できる

    3. テストのカバレッジ

  • デッドコード

    • 何らかの仕様変更で到達可能になった時にバグになるので基本削除