/note/tech

Why Go is an Ideal Language for AI-Assisted Software Engineering

要約:

■ 1. 書く時代から読む時代への転換

  • ソフトウェア工学の根本的な変化:
    • かつては大半のコードを人間が手で書いていた
    • 現在はAIコーディングアシスタントやエージェントに大量のコード生成を任せる
  • 人間に残る役割:
    • 生成されたコードを読み、整理し、意図どおりに動くか検証する
    • AIは全体文脈の把握が限定的なため、システムアーキテクチャ設計は人間が担う
      • サービス間の境界設計、本番環境の安全性と信頼性の担保も人間の責務
  • 生産性指標の変化:
    • 従来は言語の生産性を「書きやすさ」で測っていた
    • エージェントが数秒で数百行の構文的に正しいコードを生成する以上、人間の記述速度は重要でない
    • 重要なのは書かれた後のレビュー、検証、保守
  • AIはチームメイト:
    • やや無鉄砲ではあるが、AIは同僚である
    • 最も重要なのはチームとしてどう協働するか

■ 2. ソフトウェア工学のための言語設計

  • Goの出発点:
    • チーム開発の観点こそが、20年以上前にRob Pike、Robert Griesemer、Ken ThompsonがGoogleでGoを作った動機
    • 他言語が機能を急速に追加しロジック表現の多様化を志向する中、Goは言語設計をソフトウェア工学に奉仕させる方向を選んだ
  • ソフトウェア工学とプログラミングの違い:
    • プログラミングはコードを書いて実行し問題を解くこと
    • ソフトウェア工学は他者と協働し、時間とともに進化する永続的システムを設計し実装する営み
    • プログラミングはソフトウェア工学の一部にすぎない
  • ソフトウェア工学に資する言語設計の要件:
    • 言語だけでなく、開発ライフサイクル全体を覆うツールを備えたエンドツーエンドのプラットフォーム
    • チーム全体が同じ方法で構造化、整形、テストできる、意見を持った単純さ
    • 今日書いたコードが10年後も動き、10年後も良いコードであり続ける強い互換性保証
    • チーム規模に応じてスケールする、グローバルな依存関係管理を備えた強いエコシステム
    • 全体に織り込まれた合理的で堅牢なセキュリティの考慮とツール
  • 基盤の意義:
    • これらの要素はスケーラブルで長期的な協働の土台となる
    • 原作者が去った後も何年も保守可能なシステムを構築できる
    • AIがチームに加わった今、この基盤の重要性はこれまで以上に高まる

■ 3. プラットフォームとしてのGo

  • 言語ではなくプラットフォーム:
    • Goは当初から開発ライフサイクル全域に接点を持つ堅牢なツールチェーンを同梱してきた
    • 標準で整形ツール、テストフレームワーク、依存関係管理、高度なセキュリティツールを提供する
    • 複雑な外部フレームワークを不要にする包括的な標準ライブラリと相まって、比類ない一貫性の基準線を与える
  • AIと人間のニーズの一致:
    • これらの機能は元来人間のために作られたが、AIと人間のニーズは驚くほど似ている
    • 外部検証なしに反復的なリファクタリングを命じられたAIは、人間の手作業と同様に性能が急速に劣化する
    • 初回が95%正しくても、反復ごとに誤りが累積しコンテキストウィンドウを汚染する
      • 精度が下がる一方でトークンコストは増大する
    • Goではエンドツーエンドのツールチェーンを活用し、より速く、安く、確実に高品質で安全かつ正確なコードを生成できる
  • エコシステム全体の一貫性:
    • 大多数のGo開発者が同じコアツールを使うため、コミュニティ全体が一体となって前進する
    • ランタイム、IDE、パッケージエコシステムを横断して主要な言語拡張を一斉に無理なく採用できる
    • 標準ライブラリはプログラムロジックのばらつきを減らし、反復的で予測可能なイディオムを促す
      • 開発者にもAIにも理解しやすい構造的均一性を生む
    • この均一性は大規模コードベースの保守を助けるだけでなく、LLMにとってより清潔で標準化された学習データを生み出す

■ 4. 可読性の優先

  • 書きやすさより読みやすさ:
    • 開発者はコードを打ち込む時間よりも既存コードを読む時間の方がはるかに長い
    • この設計哲学は、賢さより単純さを重んじ、他言語が称賛する構文的魔法を明確に拒む文化として現れる
    • Gopherはチームの誰が書いたコードか判別できないことを愛しており、すべてが同じ見た目になる
  • AI時代における効果の増幅:
    • かつて個々の開発者は構文の簡潔さ、暗黙の型付け、プロトタイピングを加速する巧妙な近道を好んだ
    • エージェントのエルゴノミクスと人間による検証ループが求めるのは正反対の予測可能性、明示性、厳格な構造
    • ボトルネックは生成から検証へ完全に移行する
    • 同じロジックを十数通りに表現できる言語では、AIは断片的で行き当たりばったりな構文の寄せ集めを生成する
      • 人間のレビュアーにとって意図の解読は消耗を強いる作業になる
  • 揺るぎない一貫性による解決:
    • 組み込みのgofmtが単一の標準書式を強制する
    • 複雑な抽象を意図的に制限した言語設計を提供する
    • シニアエンジニア、ジュニア、LLMのいずれが書いてもコードは同じ見た目になる
    • 構文が完全に予測可能なら、幻覚によるAPI呼び出し、論理的欠陥、脆弱性を素早く発見できる
    • この標準化はオープンソースのGoエコシステムにも及び、モデルは標準化されたデータで学習する
      • より少ない試行で正しく慣用的なGoコードを生成できるようになる
  • 人間に明快な言語はAIにも明快:
    • AIがコード生産量を加速させ続ける中、可読性への傾倒がシステムのスケールを可能にする
    • 理解し、検証し、安全に保守する能力を失わずに済む

■ 5. 信頼性

  • 静的型システムが第一の防衛線:
    • LLMは構造的境界とファイル横断の型整合性でしばしば失敗し、幻覚的なプロパティや潜在バグを生む
    • Pythonのような動的型付け言語では、こうした幻覚は基本的な構文検査をすり抜ける
      • 特定の本番ワークロード下で実行時に初めてシステムを落とす
    • Goではコンパイラが即座にこれらの誤りを弾く
    • 存在しないメソッドの使用、誤った型の受け渡し、未初期化の変数はコンパイルを通らない
  • 高速コンパイルとの相乗効果:
    • GoのコンパイルはJava、C#、Rustなど他の本番品質のコンパイル言語より桁違いに速い
    • エージェントは構文エラーや型エラーを自ら反復的に修正する効率的な自己修正ループを回せる
    • 人間の同僚がレビューする前に構文的に正しいコードが届く
  • 電池付属の哲学とサプライチェーン:
    • LLMは学習データに依存するため、古い、放置された、あるいは悪意ある第三者依存を提案しがち
    • 包括的な標準ライブラリが、最適化され安全で公式に保守されたパッケージへAIを自然に誘導する
    • サプライチェーン脆弱性の攻撃面を劇的に減らし、コードベースを軽量で保守しやすく保つ
  • 外部依存が必要な場合の完全性保証:
    • あらゆるGoプログラムに取り込まれた全モジュールのチェックサムとキャッシュ済みコピーが記録される
      • チェックサムデータベースとモジュールミラーが中間者攻撃を防ぐ
      • 依存が消失したり密かに改変されるリスクを排除する
    • 脆弱性データベースとgovulncheckが既知の脆弱性を追跡し、脆弱なシンボルを呼び出すコードを指摘する
    • 実際に呼び出している関数の脆弱性のみを提示するためノイズが少なく、行動に直結する
      • 人間のレビュアーもAIも正確にパッチを当てられる
  • テストとファジング:
    • 組み込みのテストフレームワークとネイティブのファズテストが継続的検証の標準化された厳格な砂場を提供する
    • 外部ツールの寄せ集めに頼らず、ネイティブのツールチェーンで堅牢なテストを書き実行できる
    • ファジングはプログラムへの入力を継続的に操作しバグを発見する自動テスト手法
    • 隠れた境界値バグを露出させ、AIは予測不能なランダム入力に対し自らのロジックを反復的に堅牢化できる

■ 6. 保守性

  • Day 2以降が真の評価軸:
    • 可読性は本番投入までを、信頼性は本番稼働の継続を支えるが、真の尺度は運用開始後の保守性
    • コードベースは生きた系であり、自然に劣化し技術的負債を蓄積し、要件変化への適応を迫られる
    • 人間だけが作者だった時代、保守負担は予測可能な運用コストだった
    • 自律的AIエージェントが数百のプルリクエストを生成しサービス全体を気まぐれに書き換える今、進化速度とアーキテクチャの漂流の危険は激増する
  • 互換性の約束:
    • Goにおける互換性は利便性ではなくセキュリティ上かつ運用上の必須要件
    • 15年前にGo 1.0向けに書かれたコードは最新のツールチェーンで無変更のままコンパイルされ動作する
    • 後方互換性を決して壊さないと約束しており、Go 2.0は永遠に存在しない
    • コンパイラとランタイムの改善に伴い、アップグレードして再コンパイルするだけでコードも良くなる
  • 運用上の可搬性:
    • Goはシステム依存ゼロの単一静的バイナリに直接コンパイルされる
    • AIエージェントがマイクロサービスの立ち上げやスクリプト実行などシステム管理者として振る舞う場面が増えている
      • この自己完結的な設計の重要性はかつてなく高まる
    • コンパイラはOSやアーキテクチャを跨いだクロスコンパイルに対応する
      • 複雑なビルドシステムなしに必要な全ターゲット向けのバイナリを構築できる
  • アーキテクチャの漂流への対抗:
    • 大規模なリファクタリングと近代化のための決定論的ツールを組み込みで提供する
    • 公式言語サーバーgoplsと、モダナイザーの概念を導入した新生go fixが該当する
    • モダナイザーは古いコードパターンを最新のイディオムと言語機能へ決定論的に更新し、コードの均一性を保つ
    • 規模の面では自分のコードだけでなくGoエコシステム全体を前進させる
      • ライブラリやオープンソースプロジェクト、第三者コードベース間の均一性を維持する
    • 標準化されプラットフォームに直接組み込まれているため、AIエージェントも安全に活用できる
      • パッケージの再構成、依存関係の管理、技術的負債の整理をコードベースを壊さずに行える
  • 本番環境まで届く保守性:
    • ランタイムはプロファイリングと実行トレースを標準で備え、負荷時の挙動を深く可視化する
    • コンパイラはプロファイル誘導最適化をネイティブに支持し、実運用のプロファイルから高度に最適化されたバイナリを生成する
    • AIが統括するデプロイパイプラインと組み合わせると、閉ループの最適化サイクルが成立する
      • 本番データを自動的にコンパイラへ還流させ、システムを再構築し最適化できる

■ 7. 結論

  • 言語選択の重要性はむしろ増している:
    • 開発者が書くコードが減る以上、言語選択の重要性が増すのは一見直感に反する
    • コード生成をAIに委ねると、ボトルネックは書く速さからレビュー、検証、保守の厳密さへ完全に移る
  • 従来型言語の限界:
    • 緩いプロトタイピングや巧妙で暗黙的な近道を優先してきた言語は、断片的なエージェント出力の重みの下で安定を保てない
  • Goの適合性:
    • Goは初日から大規模かつ長期的な協働の課題を解くために設計された
    • 読み手優先の明快さ、本番運用への即応性、プラットフォーム全体の一貫性を備える
    • AIという同僚の高速な出力を、信頼性、保守性、システムの完全性を犠牲にせず吸収する決定論的なガードレールとなる
  • AIは最も新しいチームメイト:
    • 極めて生産的な貢献者だが、成功には強力なガードレールを要する
    • Go上に構築することは、単にコードを書くことではない
      • 人間とAIが本番システム上で安全に協働し反復できる、堅牢で自己修正的な基盤を築くこと

■ 8. 導入手順

  • go.devのインストール手順に従い最新リリースのGoをダウンロードする
  • AntigravityなどVisual Studio Code系IDEを使う場合はVS Code向け公式Go拡張機能を導入する
  • 明示的な指示または事前読み込みのスキルを通じて、エージェントにGoツールチェーンを使うよう指示する
  • エージェントにGoで新しいアプリを書かせる