以前の私は、少し挑発的に「Codexにとって最も相性のいい言語はTypeScriptだ」と表現していました。今なら、もう少し正確に言います。私が普段行っている本番向けのフロントエンド/フルスタック開発では、TypeScriptを使うとCodexを最も制御しやすく、検証しやすく、安心して使いやすいと感じています。
重要なのは「TypeScript」という名前そのものではなく、制御可能性です。AIコーディングエージェントは、もっともらしいコードを書くことには非常に長けています。しかし本番開発で必要なのは、それより厳しい条件です。既存の契約に合っていること、振る舞いを壊さないこと、リファクタリング後も整合性が保たれること、そして前提が間違っていたときに早く失敗してくれること。TypeScriptは、こうした制約をプレーンなJavaScriptよりもはるかに多く、機械が読める形で表現できます。
Codexがコードベースから本当に必要としているもの
良いプロンプトは重要です。ただし、コンテキストを与えるのはプロンプトだけではありません。コードベース自体が、変更を理解しやすくすることも、逆にモデルに推測を強いることもあります。実務で強いのは、重要な前提がコード上に現れ、しかも自動で検証される環境です。
- 関数シグネチャは、何を受け取り、何を返してよいかを明示します。
- interfaceやtypeは、ドメインオブジェクトの形を暗黙知のままにしません。
- Union型やenumは、取り得る状態を狭めます。
- コンパイラは、誤った前提の多くを編集直後に具体的なエラーへ変換します。
AIエージェントがアプリケーション全体を完璧に理解していなくても、契約を破った瞬間にコードベース側が反応してくれるなら、かなり安全に前へ進めます。フィードバックループが短いほど、自信満々の推測が入り込む余地は小さくなります。
型は単なるドキュメントではなく、実行可能なコンテキスト
良いTypeScriptは人間に設計意図を伝えるドキュメントとして役立ちます。AIを使った開発では、さらに強い価値があります。そのドキュメント自体を機械的に検証できるからです。たとえば、次のようなdiscriminated unionを考えます。
type PaymentResult =
| { ok: true; receiptId: string }
| { ok: false; code: "declined" | "timeout" };
function getReceiptId(result: PaymentResult) {
if (result.ok) return result.receiptId;
return result.receiptId;
// TypeScript: Property 'receiptId' does not exist
// on the failure branch.
}JavaScriptでは、同じミスが実行時にその分岐を通るまで自然に見えてしまうことがあります。TypeScriptなら、マージ前にその前提を拒否できます。Codexにとって、このエラーはエディタ上の赤線ではありません。「どの前提を誤解したのか」をかなり具体的に示すフィードバックです。
だから私は、Record<string, any>のような広すぎる型より、ドメインを正確に表す型を好みます。型が現実を正しく表現しているほど、コードベースは人間にもAIにも有用なコンテキストになります。
コンパイラがミスをフィードバックループに変える
私が信頼しているのは「Codexにコードを書かせて、そのまま採用する」やり方ではありません。反復を前提にします。
task → inspect → edit → typecheck → lint → test → review diff地味な流れですが、だからこそ強いです。意味のある変更をするたびに、環境が返事をします。型エラーは契約の破損を見つけ、lintは別の種類の問題を拾い、テストは型では表現できない振る舞いを確認します。そして最後のdiffは人間がレビューします。
巧妙なプロンプトは最初の回答を良くしてくれるかもしれません。一方、強い検証ループはすべての試行を改善します。本番開発では、この差のほうがはるかに重要です。
大規模なリファクタリングほどTypeScriptの価値が出る
小さく独立した修正なら、ほとんどのコーディング支援ツールで対応できます。難しいのは、変更が何十ファイルにも伝播するケースです。ドメインのフィールド名変更、APIレスポンスの変更、コンポーネントpropsの厳格化、Union型の分割、古い抽象化の置き換えなどです。
TypeScriptでは、破壊的変更を入れると、影響を受ける前提がエラーとして表面化します。コンパイラが「古い契約をまだ使っている場所」の地図を作ってくれるようなものです。Codexはその一覧を一つずつ修正でき、各パスのあとに再び検証できます。
もちろん、これだけで大規模リファクタリングが自動的に正しくなるわけではありません。ただし、影響範囲が見えるようになります。緩い型付けのコードベースでは、見落とした依存関係が「開発中に誰も開かなかった画面のバグ」として初めて発覚することがあります。この差は実務では大きいです。
TypeScriptでも守れないもの
TypeScriptはガードレールであって、正しさの証明ではありません。防げない失敗も多くあります。
- 間違ったビジネスロジック: 型が完全に正しくても、計算や条件分岐そのものが間違っていることはあります。
- 実行時の入力: API、DB、フォーム、外部サービスが、コンパイル時の想定と異なるデータを返すことがあります。
- 不足したプロダクト文脈: コンパイラは、なぜそのフローが存在するのか、どのエッジケースがユーザーにとって重要なのかを知りません。
- セキュリティやアーキテクチャ: 型が通っていても、認可、キャッシュ、DB境界、インフラ設計が正しいとは限りません。
そのため、runtime validation、テスト、ログ、監視、レビューは依然として必要です。AI支援開発で危険なのは、typecheckが通ったことを「機能が正しい」と勘違いすることです。
私がAI支援の変更で使っている進め方
- 目的を狭く定義する。 何を変えるのか、何を変えないのか、完了条件は何かを明確にします。
- 編集前にCodexに調査させる。 既存の型、呼び出し元、テスト、隣接モジュールを読むことは、長いプロンプトより有益な場合があります。
- 最小の一貫した変更を優先する。 無関係なコードまで“親切に”書き換えるより、焦点の合ったdiffのほうが検証しやすいです。
- すぐにtypecheckとlintを実行する。 構造上のミスは、変更がまだ小さいうちに出したいからです。
- 関連テストを実行し、振る舞いが変わるならテストを追加する。 型は契約を、テストは振る舞いを検証します。
- 最後のdiffは自分で確認する。 命名、設計、重複、エッジケース、そして元の問題を本当に解決しているかを見ます。
JavaScriptで十分な場面もある
これはJavaScriptが悪いという話ではありません。小さなスクリプト、使い捨てのプロトタイプ、単純な自動化、共有状態がほとんどないコードでは、TypeScriptの構造化コストに見合わないこともあります。テストと規約が非常に強いJavaScriptプロジェクトなら、AIエージェントも十分に高い精度で働けます。
私の主張はもっと限定的です。コードベースが大きく、長寿命で、多くの人が触るようになるほど、機械的に検証できる制約の価値は上がります。そして、それはAIが作るミスの修正コストが高くなるタイミングとも重なります。
私がCodexとTypeScriptを組み合わせる本当の理由
TypeScriptがCodexそのものを賢くするわけではありません。悪い推測をコードベースが許しにくくなるのです。
ここが最も重要です。本番ソフトウェアで欲しいのは、単に創造的なエージェントではありません。間違えた瞬間に継続的にフィードバックを返すシステムの中で動くエージェントです。型、コンパイラエラー、lint、テスト、人間のレビューが組み合わさって、そのシステムを作ります。
だから私が「TypeScriptはCodexと非常に相性がいい」と言うとき、それは言語への信仰ではありません。速く、機械的に検証できるフィードバックの話です。曖昧さを減らし、影響範囲を小さくし、リファクタリングを安全にし、検証を真面目に回すことで、AI支援開発の速度をより信頼できるものにできます。