0
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が書いたコードは「動く」。でも「直せる」のか——律速は「書く」から「読む・直す」に移った

0
Last updated at Posted at 2026-10-02

AIにコードを書かせると、だいたい動く。テストを書かせれば、だいたい通る。では、そのコードは半年後、別の担当者が安全に変更できるだろうか。

この問いに即答できないなら、見ているのは「生産性が上がった」という錯覚かもしれない。生産性が上がったのではなく、コストが後ろ倒しになっただけ、ということがある。しかも厄介なのは、ボトルネックの場所そのものが「書く」から「読む・直す」へ移っていることだ。書く速度がいくら上がっても、そこは速くならない。

「動く」と「直せる」は別の軸

ソフトウェアの価値は、書いた瞬間ではなく、変更され続ける期間の総和で決まる。ある時点で正しく動くコードでも、仕様変更・バグ修正・機能追加のたびに安全に手を入れられなければ、その都度コストがかかる。これを俗に「技術的負債」と呼ぶ。負債という比喩がうまいのは、借りた瞬間は得をしているように見えて、返済(あとからの手直し)のタイミングで利息付きの請求が来る、という時間差があるからだ。

AIがコードを書く場合、この時間差がより露骨になる。AIは「与えられた入力に対して正しい出力を返す」ことには強い。しかし「このコードは半年後の自分や他人にとって読みやすいか」「変更の影響範囲が追いやすいか」は、AIにとって最適化の対象になっていないことが多い。発注側がそれを明示的に求めない限り、AIは"動く"を満たした時点で手を止める。

具体例:一見正しい関数

次のような処理を頼んだとする。「注文リストから、キャンセルされていない注文の合計金額を計算して」。AIはこう返すかもしれない。

def calc(orders):
    total = 0
    for o in orders:
        if o["status"] != "cancelled":
            total += o["price"] * o.get("qty", 1)
    return total

これは要件通りに動く。テストケースを2〜3個投げても通る。しかし、半年後にこの関数を変更する立場で読むと、いくつも引っかかる。

まず命名。calc は何を計算するのか、関数名からは分からない。呼び出し元を読まないと意図が追えない。次に責務の混在。「キャンセル除外」というビジネスルールと、「合計金額の算出」という計算ロジックが同じループの中に埋め込まれている。将来「返金済みの注文も除外したい」という要件が来たとき、このifの条件を書き換えるべきか、別の関数に分けるべきかの判断材料が、コードのどこにも残っていない。

さらに o.get("qty", 1) という一行。数量が未指定なら1件として扱う、という暗黙の前提がある。これは意図的な仕様なのか、単にAIが欠損値を適当に埋めた結果なのか、コードを読むだけでは区別できない。半年後に「数量なしの注文はエラーにすべきだった」と判明したとき、この一行が静かに紛れていたことに気づくまで時間がかかる。

動いている。テストも通る。けれど、変更の起点になる「なぜこうなっているか」という情報が、コードの外に出てしまっている。これが"保守できない"コードの典型的な形だ。バグではないのに、変更しようとした瞬間に事故る。

もう一つ、別の種類の例を挙げる。「外部APIを呼んで、失敗したら3回までリトライして」と頼むと、AIはこう書くことがある。

def fetch_with_retry(url):
    for _ in range(3):
        try:
            return call_api(url)
        except Exception:
            continue
    return None

要件通り動く。リトライも入っている。だが、ここでは「どんな例外でも握りつぶして次に進む」という判断が、コード中に静かに埋め込まれている。ネットワークのタイムアウトのようにリトライすべき失敗と、認証エラーのようにリトライしても無駄な失敗が、同じ except Exception で一括りにされている。呼び出し元は None が返ってきたとき、それが「リトライし尽くした末の失敗」なのか「別の理由で呼ばれなかった」のかを区別できない。これもテストは通る。通るテストが、品質を保証しているわけではないという、よくある落とし穴だ。

AIがこうなりやすいのは悪意や能力不足ではなく、最適化している対象が違うからだ。AIは「指示された入出力を満たす」ことに向けて出力を生成する。例外の分類や、将来の変更可能性を見越した設計判断は、指示されなければ最適化の対象に入らない。これは人間のジュニアエンジニアが陥る失敗とよく似ている。

だが一点だけ、AIの出力には人間のジュニアのコードと違う厄介さがある。量が多く、インデントも変数名も一見整っていて、コードレビューの目が思わず素通りしてしまう体裁をしていることだ。見た目の洗練度と、設計判断の浅さは、AI生成コードにおいてはしばしば一致しない。

律速が「書く」から「読む・変える」に移った

AI以前、開発の時間の多くは「書く」作業に割かれていた。だから書く速度が生産性のボトルネックだった。AIが実装を安く速く供給できるようになった今、律速は「そのコードを読んで、意図を理解し、安全に変更する」作業に移っている。コードレビューにかかる時間、仕様変更時に影響範囲を調べる時間、テストが仕様を表しているか確認する時間——これらは書く速度が上がっても短縮されない。むしろ、AIが生成するコード量そのものが増えれば、読む・レビューする・変更する対象が増え、この工程の負荷は上がる。

ここで非自明な転回が起きる。AIが実装を安く供給する時代に相対的に価値が上がるのは、「書く力」ではない。AIの出力に対して、命名・責務の分離・テスト・境界(インターフェースや前提条件)を与えて「読んで直せる形」に整える判断力だ。先の例で言えば、calc を calculate_active_order_total のような意図の読める名前に直し、キャンセル判定を別関数に切り出し、「数量未指定は1件とみなす」という暗黙の前提をコメントかテストケースとして明文化する——この一手間こそが、AIには委任しにくく、人に残る仕事になる。

受け入れ判断の基準を変える

AIが出したコードをそのままmergeするかどうかを、「動いたか」で判断するのはもう粗すぎる。代わりに問うべきは、「このコードは、今の自分以外の誰かが、半年後に安全に変更できる状態か」だ。この基準は特別なものではない。レビューで一度は聞いたことのある問いを、AI時代に意識的に当て直すだけだ。具体的には次の4点を見る。

  • 関数名やクラス名が、処理の手順ではなく意図を表しているか。
  • 1つの関数やクラスが、複数の責務(判定ロジックと計算、取得と加工など)を抱え込んでいないか。
  • 境界値・エッジケース(0件・null・極端な値など、入力側に起因するケース)が、テストとして言語化されて残っているか。
  • 暗黙の前提(例外の握りつぶし、デフォルト値の補完、副作用の有無など、実装側の判断に起因するもの)が、コードの外に漏れたまま放置されていないか。

これらに引っかかるなら、動作確認が通っていても、その場で直す。命名を変える、責務を分ける、見落としていたケースをテストに書き起こす——この一手間は数分で済むことが多いが、やらなければ半年後に何倍もの時間で取り返すことになる。AIに任せる範囲が広がるほど、この受け入れ基準を自分の中に持っているかどうかが、チームの保守コストを決める。

「書く力」はAIが代替しやすい。「読んで直せる形に整える判断」は、今のところ人に残っている。どちらに時間を投資するかは、もう選択の余地がある問いではなくなってきている。

設計や可読性にまつわる定番書籍が、実際にどれだけ読まれ・薦められているかはデータで確かめられる。気になる人は下記を覗いてみてほしい。

0
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
0
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?