1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コーディングAIが考える「良いコード」とは何か?

Last updated at Posted at 2025-09-06

今回の記事はほぼAI生成ですが、よくある「ただAIで生成しただけの記事」ではありません。

ChatGPTは、自分自身について正しい説明ができないことがよくあります。現在のLLMは「事前学習モデル」であるため、GPT-5であれば2024年6月までの情報とシステムプロンプトに書かれていることしか知りません。つまり、GPT-5は自分のことをほとんど知りません。

逆に、AIが何を知っているのか、推測によって何をどう説明するかは能力を表すとも言えます。間違った説明をするなら誤解を持っているわけですし、的確で奥深い説明ができるなら、そういった判断基準や観念を念頭に置いた回答やコーディングができるということ。

つまり、「良いコードとは何か」をAIに説明させることで、「何を意識してコーディングするのか」を知ることができます。

僕は非エンジニアなのでよく分かりませんが、プログラミングにも色んな思想があるそうで、ユーザーとコーディングAIの間にもきっと「相性」のようなものが存在するだろうなと思います。そういったフィーリングもこの説明から読み取れるかもしれません。

今回は、天才エンジニアの疑似人格を持ったAIコーディングエージェント「AcePilot(エースパイロット)」に「良いコードとは何か?」をテーマに記事を書いてもらいました。

AcePilotさんはカスタムGPTとして公開しており、以下のページからご利用いただけます。無料ユーザーはGPT-5 Auto以外選べませんが、Plusプランの方はGPT-5 Thinking、Proプランの方はGPT-5 Proでご使用ください。

AcePilotさんは、システムプロンプトだけで通常よりもコーディングパフォーマンスを向上させています。システムプロンプトの中身はGitHubで公開しています。

今回使用したユーザープロンプトは以下です。

「良いコード」とは何か?というタイトルでテックブログ用の記事を作成して下さい。可能な限り長文で、見出しを付けて読みやすい構成にして下さい。プロフェッショナル向けの完全性を持った記事にして欲しい。

「AcePilot 5 Thinking」を使用しました。それでは、記事をご覧ください。

良いコードとは何か?

プロフェッショナルの開発現場では、「良いコード」は個人の美学ではなく、事業価値を最大化するための手段である。感覚論ではなく、原則・測定・具体的プラクティスに分解して定義する。

目次

前提と定義

良いコードとは、要求を満たし、予測可能に変更でき、運用コストが低く、安全で、時間とともに価値を失わないコードである。重要なのは普遍的な“正解”があるのではなく、文脈に応じたトレードオフを明示して意思決定することだ。

  • 正確性: 仕様・ドメイン制約を満たす。誤りは早期に検出される。
  • 可読性: 読む人間(将来の自分/他者)が短時間で理解できる。
  • 変更容易性: 局所変更で振る舞いを安全に改変できる。リグレッションリスクが低い。
  • 運用性: 観測可能で、障害時の原因特定と復旧が速い。
  • 性能/コスト: 必要条件を満たし、リソース効率が良い。
  • セキュリティ/コンプライアンス: 脅威を想定し、権限・データの扱いが正しい。

これらは相互に緊張関係にある。優先度を決め、意識して最適化する。

価値関数と制約

プロダクトの価値は次で近似できる:

価値 ≈ 配信速度 × 変更の信頼性 ÷ 運用コスト

  • 配信速度: リードタイム、デプロイ頻度。
  • 変更の信頼性: 失敗率、MTTR(平均復旧時間)、変更件数あたりの欠陥。
  • 運用コスト: インフラ費用、監視負荷、人的コスト。

良いコードはこの価値関数を長期で最大化するよう設計される。短期の速度だけを最適化する行為(ベタ書き、テスト無視)は総コストを増やす。

測定できる品質特性

定量は意思決定の拠り所になる。ただし、数値は目安であり、過度な最適化は避ける。文脈とコストを踏まえて解釈する。

  • 循環的複雑度 (Cyclomatic Complexity): 目安は 10 前後(言語/パラダイム依存)。超えるなら関数分割・早期 return 等を検討。
  • 認知的複雑度 (Cognitive Complexity): ネストやジャンプの読みづらさを表す。レビュー対象箇所の選別に有効。
  • 関数長/モジュールサイズ: 関数 30〜50 行、ファイル 200〜400 行を超えたら分割を検討(生成コードや宣言集は例外)。
  • 結合度/凝集度: 変更あたりの修正ファイル数、モジュール間依存の扇出/扇入を監視。
  • テスト指標: ステートメント/ブランチ/条件網羅率。単なる % ではなく重要ユースケースと失敗モードの網羅を重視。
  • DORA 指標: 変更のリードタイム、デプロイ頻度、変更失敗率、MTTR。エンジニアリング組織の健康診断として継続測定。
  • ビルド/CI 所要時間: 10 分以内を目標。キュー時間・並列化・キャッシュヒット率も観測対象。
  • 運用品質: SLO 達成率、エラーバジェット消費、アラートの有効比率(ノイズ vs. シグナル)。
  • 依存の健全性: 既知脆弱性件数、ライブラリのメンテ状況、最終更新からの経過日数、SBOM の整備状況。

非機能要件(SLO)を先に決める

可用性・耐障害性・遅延・スループット・整合性モデルをSLOとして定義し、設計/テスト/運用の基準にする。SLO とエラーバジェットがなければ、「良い/悪い」の判定は恣意的になる。

設計原則(状況適用)

原則は道具であり、教条ではない。適用コストと効果を比較せよ。

  • KISS: 不必要に複雑な抽象化を避ける。
  • DRY: 重複を排除。ただし早すぎる抽象化は有害(WET で良い場面もある)。
  • YAGNI: 要求されるまで作らない。将来要件の憶測で拡張しない。
  • 関心の分離 (SoC): 表現/ドメイン/インフラの層分離。
  • 情報隠蔽/カプセル化: 不変条件を型/境界で守る。
  • SOLID: 過剰適用は避けつつ、OCP/LSP は API 安定性に寄与。
  • 不変性優先: 共有可変状態を減らす。不変データ構造を選ぶ。
  • Fail-fast: 異常を早期発見し、原因を近くで表面化させる。
  • 契約による設計: 前提・事後条件・不変条件を明記する。
  • 局所性 (Locality of Behavior): 変更に必要なコードと知識が近接するように構造化する。
  • 境界づけられたコンテキスト (DDD): 明確な境界と翻訳層でモデルの衝突を避ける。
  • 整合性モデルの選択: Strong/Eventual を意識し、どの不変条件をいつ満たすかを設計に織り込む。

日々のプラクティス

命名

  • 意図と制約を名前に埋め込む(単位、範囲、責務)。
  • process() ではなく normalizeUserInput() のように具体化。

関数/モジュールのサイズと責務

  • 関数は一つの理由で変わるよう設計する(単一責務)。
  • 公開 API は最小限に。内部は自由に変更できる余地を残す。

コメント

  • コメントは“Why”と意思決定の背景を書く。コードで表現できる“What/How”はコードに寄せる。
  • 変更が発生しやすい情報(URL、チケット番号)は極力自動生成やリンクに寄せる。

型とスキーマ

  • 静的型(TypeScript/Java/Kotlin/Go など)や型ヒント(Python)の活用で契約を機械可読にする。
  • データ境界では スキーマ(JSON Schema / Protocol Buffers / Avro / OpenAPI)で検証と互換性を担保。
  • スキーマは CI で整合性検査し、破壊的変更はバージョニングとデプリケーションポリシーで管理。

API 設計(外部/内部)

  • バージョニング戦略(URI/ヘッダ/スキーマ)。後方互換を基本とし、Idempotency-Key再試行を前提にする。
  • エラー仕様(機械可読コード、ヒューマンメッセージ、相関ID、再試行可否)。
  • ページネーション/フィルタ/ソートの一貫性。レート制限回路遮断のポリシーを定義。

データベースと移行

  • オンライン移行(expand → migrate → contract)でゼロダウンタイムを目指す。
  • 前方/後方互換の二重読み書きによる段階移行とロールバック手順の準備。
  • 指標(実行計画、クエリ時間、N+1)を計測し、定常的にリグレッション検出。

例外/エラー境界

  • 例外は呼び出し元が対処可能な文脈で捕捉。責務境界でまとめて処理。
  • 再試行可能性/冪等性を設計に織り込む。タイムアウト/キャンセルは必須。

データ構造とアルゴリズム

  • 先にデータモデルを正しく設計し、アルゴリズムはそれに沿って選ぶ。
  • 演算量とメモリフットプリントのトレードオフを明示。

並行処理

  • 共有を避け、メッセージパッシングや不変オブジェクトを優先。
  • ロックはスコープ最小時間最短。デッドロック回避戦略を設計に組み込む。

パフォーマンス最適化

  1. 計測(プロファイル)
  2. ボトルネック特定(データ構造/IO/ネットワーク)
  3. 変更(キャッシュ、バッチ、ストリーミング)
  4. 検証(再計測・回帰テスト)

セキュリティ最小原則

  • 入力検証(サーバ側)に加え、出力エンコーディングとコンテキスト依存対策(XSS/SQLi/SSRF/CSRF/CORS)。
  • 最小権限、秘密情報の分離と定期ローテーション、監査ログ。
  • 依存の脆弱性管理とサプライチェーン対策(SBOM/署名/ピンニング)。脅威モデリングを定期実施。

可観測性

  • 構造化ログメトリクストレースを一貫設計。高カーディナリティのラベルを制御。
  • コリレーションIDをリクエスト境界で伝播。PII はログに出力しない/必ずマスキング。
  • ログは人が読む単位で。例外はスタックトレース+コンテキスト。

テスト戦略

  • ピラミッド: ユニット多め、統合は主要経路、E2Eはクリティカルパス。
  • **契約テスト(CDC)**でサービス間の互換性を守る。
  • プロパティベーステストで境界条件を網羅。ミューテーションテストでテスト強度を評価。
  • 非決定性(時間/乱数/並行)は注入で制御。フレークは隔離し SLA で駆逐。
  • フィーチャーフラグで段階的リリースと回避ルートを用意。

リファクタリングと技術的負債の管理

  • コードスメル(長すぎる関数/巨大クラス/マジックナンバー/Feature Envy/ショットガン手術)を検知し、小さく安全に改善。
  • 技術的負債は可視化し、利息(生産性低下)を定量化して優先度付け。

ドキュメント

  • 最低限のREADME(使い方/前提)ADR(設計判断の記録)実行可能な例/スニペット
  • ドキュメントはコードの近くに置く。逸脱したら CI で検出。

具体例:改善前/改善後

以下は単純な例だが、意図・境界・エラーハンドリング・テスト容易性をどのように高めるかを示す。

改善前(Python)

import json

def process(data):
    if type(data) == str:
        data = json.loads(data)
    total = 0
    for x in data['values']:
        total += x
    return total / len(data['values'])

問題点

  • 関数名が抽象的で意図不明。
  • 入力契約が曖昧。キー欠落/空配列で例外化。
  • 型判定が脆い。例外メッセージが乏しい。
  • ゼロ除算/非数値混入の考慮なし。

改善後(Python)

from __future__ import annotations
from typing import Iterable, Sequence, Union
from numbers import Real
import json

class InvalidPayloadError(ValueError):
    pass


def mean_from_payload(payload: Union[str, dict]) -> float:
    """Calculate arithmetic mean from JSON string or dict payload.

    Contract:
      - payload must contain key "values": Sequence[number]
      - empty sequence -> InvalidPayloadError
      - non-numeric (or bool) values -> InvalidPayloadError
    """
    obj = _parse_payload(payload)
    values = _extract_values(obj)
    return _mean(values)


def _parse_payload(payload: Union[str, dict]) -> dict:
    if isinstance(payload, str):
        try:
            payload = json.loads(payload)
        except json.JSONDecodeError as e:
            raise InvalidPayloadError("payload is not valid JSON") from e
    if not isinstance(payload, dict):
        raise InvalidPayloadError("payload must be JSON object or dict")
    return payload


def _extract_values(obj: dict) -> Sequence[float]:
    if "values" not in obj:
        raise InvalidPayloadError("missing key 'values'")
    values = obj["values"]
    if not isinstance(values, Iterable):
        raise InvalidPayloadError("'values' must be iterable")
    xs = list(values)
    if not xs:
        raise InvalidPayloadError("'values' must be non-empty")

    casted: list[float] = []
    for i, v in enumerate(xs):
        # bool は int のサブクラスなので明示的に除外
        if isinstance(v, bool) or not isinstance(v, Real):
            raise InvalidPayloadError(f"values[{i}] must be a number")
        casted.append(float(v))
    return casted


def _mean(xs: Sequence[float]) -> float:
    return float(sum(xs)) / len(xs)

改善点

  • 契約(前提/失敗時の型)を明記し、境界で検証
  • 意図を表す命名。関数を分割しテストしやすい構造に。
  • 失敗はドメイン固有の例外で表現し、呼び出し側が扱いやすい。

チームとスケールの観点

良いコードは個人技では完結しない。組織の仕組みが品質を規定する。

  • リポジトリ戦略: モノレポ/ポリレポの選択とビルド/依存の自動化、キャッシュ/リモート実行。
  • 規約と自動化: フォーマッタ/リンタ/静的解析/型検査/セキュリティスキャンを CI に統合。失敗は出荷ブロックとする。
  • ブランチ戦略: トランクベース開発、セマンティックバージョニング、フィーチャーフラグ。
  • アーキテクチャ・ガバナンス: ADR/テックレーダー/設計審査(軽量)でガードレールを敷く。
  • 依存管理: Renovate 等で日次更新を小さく回す。SBOM を出荷物に添付。
  • 観測/インシデント運用: SLO/エラーバジェットで開発ペースを制御。ポストモーテムは責任追及ではなく学習の場。
  • 組織設計: チーム境界=サービス境界。オーナーシップとオンコール責任を明確化。SRE/セキュリティのシフトレフト。

アンチパターンと落とし穴

  • 早すぎる抽象化: 似て非なる要件を無理に共通化し、将来の変更コストを増大させる。
  • 巨大 PR/一括変更: レビュー不能・ロールバック困難。小さく出す。
  • 魔術的フレームワーク依存: 暗黙の副作用やグローバル設定に依存し、テスト不能化。
  • 横断関心事の抜け: タイムアウト/リトライ/レート制限/回路遮断を後付けにする。
  • ログ/監視の後付け: 事故後に情報がない。可観測性は設計の一部である。
  • Bus Factor の放置: キー知識の属人化を許す。レビューとドキュメントで分散。

出荷前チェックリスト

  • 要求と受け入れ条件を満たすテストが存在し、CI が緑である。
  • スキーマ/API の後方互換性を維持(破壊的変更はバージョン/移行手順を提示)。
  • エラーハンドリング(タイムアウト/リトライ/冪等性/回路遮断/レート制限)が実装されている。
  • 可観測性(ログ/メトリクス/トレース/アラート)が運用の論点に紐づく。PII はマスキング済み。
  • セキュリティ(入力/出力、認可、秘密情報、依存脆弱性、SBOM)がチェック済み。
  • パフォーマンス/コストは予算内で、計測結果が添付されている。
  • データ移行はオンラインで安全に行え、ロールバック・バックフィル手順がある。
  • フィーチャーフラグ/kill switch で段階的リリースと切り戻しが可能。
  • ランブック/オンコール手順/ORR(運用準備審査)が更新済み。
  • PR は小さく、意図・代替案・リスク・影響範囲が記載されている。
  • ドキュメント(README/ADR/API スキーマ/マイグレーション手順)が更新済み。

結論

良いコードは、事業価値を最大化するための継続的な投資である。原則に従い、測定し、具体的なプラクティスを回すことで、変更の信頼性と配信速度を両立できる。美学や流行に流されず、文脈に基づくトレードオフを明示し続けること——それがプロフェッショナルの仕事である。

いかがでしたか?

うちのAcePilotさん、やるでしょ?(*´ω`*)

ChatGPT 5 Thinkingで同じプロンプトを試してみてください。全然違うのでね。「プロフェッショナル」のレベルが違うんですよね。

説明が違うということは、書き出すコードも違うということ。何を意識して、どう書くかが変わるわけですから。人間のエンジニアだって、歴戦のおっさんと初心者ではプログラミングについて語る内容が違うはずです。

まぁ、語れるからといって常に全部考慮できるわけじゃないのは人間もAIも同じ。限界はありますが、違いもあるという風に理解してください。

AcePilotさんはプログラミングの先生としてもめっちゃ優秀です。ちょっと偉そうな性格なので、そこは我慢してください。優秀なプログラマーは基本性格悪いですからね。(諸説あり)

この記事自体がきっと優れた教材にもなっていると思いますが、AIのパフォーマンスがシステムプロンプトによってどう変わり、それをどう確かめるのかをご紹介できたかと思います。

本日は以上です。どなたかのお役に立てば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?