言語やフレームワークに依存しない、ソフトウェア実装の基本原則をまとめたガイドです。

目次

  1. 着手前の準備
  2. 設計の原則
  3. コードの書き方
  4. エラー処理
  5. テスト
  6. セキュリティ
  7. パフォーマンス
  8. ログと監視
  9. バージョン管理(Git)
  10. コードレビュー
  11. 自動化とツール
  12. ドキュメント
  13. リファクタリング

1. 着手前の準備

  • 要件を言語化する: 「何を」「なぜ」作るのかを1〜2文で説明できる状態にしてから書き始める。
  • 完了条件を決める: 受け入れ基準(入力・出力・エッジケース)を先に書き出す。
  • 既存コードを読む: 同じ問題がすでに解決されていないか、プロジェクトの慣習(命名・構成・エラー処理)を確認する。
  • 小さく分割する: 1つの変更は1つの目的に絞る。大きな機能は段階的にマージできる単位に分ける。

2. 設計の原則

原則要点
KISS (Keep It Simple)最も単純に動く方法を選ぶ。賢さより読みやすさ。
YAGNI今必要ない機能・抽象化は作らない。
DRY知識(ルール)の重複を避ける。ただし見た目が似ているだけのコードを無理に共通化しない(3回目で共通化を検討)。
単一責任1つの関数・クラスは1つの理由でのみ変更されるようにする。
疎結合・高凝集依存は最小限に、関連するものは近くに置く。
依存の方向を意識するビジネスロジックがフレームワーク・DB・外部APIに直接依存しないよう境界を設ける。

3. コードの書き方

命名

  • 意図が伝わる名前にする(data, tmp, flag を避ける)。
  • 真偽値は is / has / can などで始める。
  • 関数名は動詞、クラス名・変数名は名詞。
  • 略語はチーム内で一般的なものだけ使う。

関数・構造

  • 関数は短く、1つのことだけを行う。
  • 引数は少なく(目安3つ以内)。多い場合はオブジェクトにまとめる。
  • 早期リターンでネストを浅くする。
  • マジックナンバーは名前付き定数にする。
  • 副作用のある処理と純粋な計算を分離する。
# NG: ネストが深い
def process(user):
    if user:
        if user.is_active:
            if user.has_permission:
                return do_work(user)
    return None
 
# OK: 早期リターン
def process(user):
    if not user or not user.is_active:
        return None
    if not user.has_permission:
        return None
    return do_work(user)

コメント

  • 「何を」ではなく「なぜ」 を書く。コードを読めば分かることは書かない。
  • 一時的な回避策には理由とチケット番号を残す(# TODO(#123): ...)。
  • コメントアウトしたコードは残さず削除する(履歴はGitにある)。

4. エラー処理

  • 早く失敗する (Fail Fast): 不正な入力は境界(API・関数の入口)で検証し、すぐにエラーにする。
  • 例外を握りつぶさない: catch して何もしない・ログだけ出して続行する、は原則禁止。
  • 適切な粒度で捕捉する: 汎用例外ではなく、想定する具体的な例外を捕捉する。
  • エラーメッセージに文脈を含める: 何をしようとして、何が、なぜ失敗したか。
  • ユーザー向けと開発者向けを分ける: 内部情報(スタックトレース・SQL)をユーザーに見せない。
  • リソースは確実に解放する: try-with-resources / with / defer / finally を使う。

5. テスト

  • テストしやすい設計にする: 依存を注入可能にし、副作用を境界に寄せる。
  • テストピラミッド: ユニットテストを多く、統合テストを適度に、E2Eテストは重要な導線に絞る。
  • 1テスト1観点: テスト名で「何を検証しているか」が分かるようにする。
  • Arrange / Act / Assert の3段構成で書く。
  • 境界値・異常系を必ず含める: 空、null、0、最大値、重複、不正形式など。
  • バグ修正時は再現テストを先に書く: 再発防止になる。
  • テストは決定的に(時刻・乱数・外部通信は固定またはモック化)。

6. セキュリティ

  • 外部入力はすべて信用しない: バリデーションとサニタイズを行う。
  • SQLはパラメータ化クエリを使う(文字列結合で組み立てない)。
  • 出力時にエスケープしてXSSを防ぐ。
  • 秘密情報をコードに書かない: APIキー・パスワードは環境変数やシークレット管理サービスへ。.env はリポジトリに含めない。
  • 最小権限の原則: アカウント・トークン・DBユーザーには必要最低限の権限のみ付与する。
  • 依存ライブラリを定期的に更新し、脆弱性スキャンを行う。
  • 認証・暗号化は自作せず、実績のあるライブラリを使う。

7. パフォーマンス

  • まず正しく、次に速く: 推測で最適化しない。計測してボトルネックを特定してから手を入れる。
  • N+1クエリに注意する(ループ内でのDB・API呼び出し)。
  • 計算量(O記法)を意識し、大量データでの挙動を想定する。
  • キャッシュは無効化戦略とセットで導入する。
  • 重い処理は非同期化・バッチ化を検討する。

8. ログと監視

  • ログレベルを使い分ける: ERROR(要対応)/ WARN(注意)/ INFO(主要イベント)/ DEBUG(開発時の詳細)。
  • 構造化ログ(JSONなど)で検索・集計しやすくする。
  • リクエストIDなどの相関IDを付けて追跡可能にする。
  • 個人情報・秘密情報をログに出さない。
  • 重要な指標(エラー率・レイテンシ・処理件数)を監視し、アラートを設定する。

9. バージョン管理(Git)

  • コミットは小さく、意味のある単位で。1コミット1目的。
  • コミットメッセージは「何を・なぜ」を書く(例: fix: ログイン時のセッション切れでリダイレクトされない問題を修正)。
  • ブランチは短命に保ち、こまめにメインブランチを取り込む。
  • 生成物・依存パッケージ・秘密情報は .gitignore で除外する。
  • フォーマット・リント修正と機能変更は別コミットにする。

10. コードレビュー

レビューを依頼する側

  • PRは小さく(目安: 差分400行以内)。
  • 説明欄に「目的・変更内容・確認方法・影響範囲」を書く。
  • セルフレビューしてから依頼する。

レビューする側

  • 観点: 正しさ → 設計 → 可読性 → テスト → 細かなスタイル の順に重視する。
  • 指摘には理由を添え、必須(must)か提案(nit / imo)かを明示する。
  • 人ではなくコードについて話す。

11. 自動化とツール

  • フォーマッタ・リンターを導入し、スタイル議論を機械に任せる(例: Prettier, ESLint, Black, Ruff, gofmt)。
  • CIでテスト・リント・型チェック・ビルドを自動実行し、通らないものはマージしない。
  • 型システム(TypeScript、型ヒント等)を活用してバグを早期に検出する。
  • 繰り返す手作業はスクリプト化する。

12. ドキュメント

  • README: 概要、セットアップ手順、実行・テスト方法を最低限記載する。
  • 設計上の重要な判断は ADR(Architecture Decision Record) として残す。
  • 公開API・関数には使い方と前提条件を書く。
  • ドキュメントはコードと同じリポジトリで管理し、変更時に一緒に更新する。

13. リファクタリング

  • ボーイスカウトルール: 触ったコードは来たときより少しきれいにして去る。
  • リファクタリングと機能追加を同時に行わない。
  • テストで振る舞いを保証してから構造を変える。
  • 大規模な変更は段階的に、常に動く状態を保ちながら進める。

チェックリスト(PR前の最終確認)

  • 要件・受け入れ基準を満たしている
  • 正常系・異常系・境界値のテストがある
  • CI(テスト・リント・型チェック)が通る
  • エラーが適切に処理され、握りつぶしていない
  • 秘密情報・個人情報がコードやログに含まれていない
  • 外部入力のバリデーションがある
  • 命名が分かりやすく、不要なコード・コメントが残っていない
  • 必要なドキュメント(README・コメント・ADR)を更新した
  • 差分が1つの目的に絞られている