← すべての投稿

よりよいコードを生成するために、コードを理解する

FiboCode を作るなかで、自分のプロジェクトを理解できているかが気になるようになった。自然言語プログラミングや曖昧な要件、アーキテクチャを通じた AI との協働について考えたこと。

自分のプロジェクトなのに、よくわからない。その感覚が私は好きではない。

要件を出したのは自分だし、機能も自分が欲しかったものだ。AI がコードを生成したあと、動くことも確かめた。それでも、その機能がどのモジュールを通るのか、なぜその実装なのか、別の箇所を変えたら何に影響するのかと聞かれると、うまく答えられないことがある。

FiboCode を作るなかで、このことがますます気になるようになった。

もちろん、AI にはもっとコードを書いてほしい。でも、このプロジェクトはこれからも作り続けたい。今日の機能ができれば、明日にはまた別のアイデアが浮かぶ。変更のたびに、ほかが壊れませんようにと祈りたくはない。

A を直すと B に問題が出る。B を直すと、A に新しいバグが出る。そうなると、症状をまた AI に送り、次の説明と修正を待つことを繰り返しやすい。

けれど何度か繰り返すうちに、不安は大きくなる。個々の変更には筋が通っているように見える。それを全部合わせたとき、システムはどうなっているのだろう。

そこを理解したい。この問いを考えるうちに、コードの理解と生成は、最初に思っていたより深くつながっている気がしてきた。

自然言語プログラミングから考えたこと

機械語からアセンブリ言語、そして高級言語へ。私たちは抽象化を重ねてきた。

以前は自分で処理していた細部の多くを、道具に任せられるようになった。少ないコードで複雑な機能を表せるし、毎回いちばん下から始めなくてもよい。

今はさらに一段上がり、自然言語で要件を伝えて、AI にコードを生成してもらえる。

一見すると表現方法が変わっただけにも見える。でも、見過ごせない違いがあると思う。

アセンブラやコンパイラは、明確な規則に従ってコードを変換する。プログラムにバグがないわけではないが、変換そのものが私の意図を推し量る必要はない。

モデルは、それを推し量らなければならない。

私の言葉、見えているコード、文脈から、何をしたいのかを推測して実装を選ぶ。同じ一文から違う結果が出ることもある。結果が毎回同じでも、私がうまく伝えられなかった要件を、ずっと実装しているだけかもしれない。

あとで読んだ Birgitta Böckeler の記事にも、この違いが書かれていた。大規模言語モデルは推論によって出力するので、自然言語のコンパイラとしてそのまま扱うことはできない。自分が考えていたことに近かった。

では、何ができるだろう。

要件を明確にし、規約を残し、大事な振る舞いをテストする。それぞれ、ずれをいくらか減らせるはずだ。設計でモジュールの連携や関連するクラス、メソッドがわかっていれば、AI にも実装のよりどころが増える。

ただ、文書はコードと合っている必要がある。とうに変わった設計を詳しく説明している文書は、何もないより困る場合もある。

これらで生成の不確実性をすべて消せるとは思わない。それでも、その場でモデルに推測してもらうことは減らせる。

自分が何を変えたいのか、本当にわかっているだろうか

そう考えていくと、別の問題に行き当たる。人も要件を明確に説明するのが難しい。

コードをよく知らないときは、なおさらだ。

画面にボタンを増やしたいとか、ある操作を簡単にしたいとか、そこははっきりしているかもしれない。でも、既存のどのモジュールに入れるのか、どんな処理を通すのか、どの振る舞いを残すべきかは、わかっていないことがある。

そんな状態で「ほかの機能に影響させないで」と何度伝えても、AI はどう解釈すればいいのだろう。

たとえば「ここを統一して」は、表示をそろえる意味かもしれないし、データ処理をまとめる意味かもしれない。解釈によって変更範囲は大きく変わる。

自分でも範囲を説明できなければ、モデルは見えているものから補うしかない。想定より多く変更したり、私の知らなかった約束事を迂回したりするかもしれない。

変更後に問題が見つかり、また直してもらう。変更が積み重なるにつれ、最初の要件、途中で加えた制約、もともとのコードのつながりが、全部そのまま文脈に残っているとは限らない。

最後には、部分的には動いても、前の問題を直すための継ぎ当てだらけになるのではないか。もう AI しか触りたがらず、人は手を出しにくい。そんなコードが心配だ。

こうしたコードを見ると AI slop という言葉を思い出す。しかも、再び AI に任せても、モデルがシステム全体を理解しているとは限らない。次の説明が出てきても、私たちが理解できたことにはならない。

だから、要件を正確に伝えるには、まずプロジェクトのおおまかな姿を知る必要があると感じるようになった。

最初から細部をすべて理解する必要はない。でも、自分が求める変更がどこに入るのかは知っておきたい。

アーキテクチャの層で AI と話したい

AI が生成したコードを一行ずつすべて確認することを、AI を使う前提にしたいわけではない。

プロジェクトが複雑になれば、人の集中力にも限りがある。まず骨組みを知りたい。中心となるモジュールとその責務、機能の入り口、処理の流れ、データや状態が変わる場所だ。

高級言語の実装の上に、システムを理解するための層をもう一つ整理したい。ひとまず「アーキテクチャ層」と呼んでいるけれど、名前にはあまりこだわっていない。

大事なのは、その層が実際のコードに対応していることだ。

モジュールを見たら実装を探せる。機能の流れを見たら、通るクラスやメソッドがわかる。振る舞いを調整したくなったら、つながりをたどって、見るべき場所に行ける。

そうすると、AI と具体的な話ができる。

たとえば、ある操作が共通のタスクキューを通ると知っていれば、追加する機能もその経路を使えるか相談できる。AI が状態管理をもう一組作ったら、既存のものではなぜだめなのか、あとで両者が影響し合わないかを聞ける。

そういうことを考えていなければ、動く実装を受け取って、そのまま受け入れていたかもしれない。少なくとも今は、余計に複雑になっていないかを早めに考えられる。

こうした比較で、自分のコードを書く力も上がると思う。

実現方法がいくつもあるなかで、このプロジェクトはなぜこれを選んだのか。きれいな抽象化に見えても、変更のたびにあちこち触るなら、本当に役立っているのか。素朴なモジュールなのに、何度要件が変わってもほかを巻き込まないなら、何がよかったのか。

時間をかけて考える価値がある。自分なりの判断ができるようになれば、AI に調整してほしいところを伝えやすくなるし、別の提案も理解しやすくなる。

もちろん、私のアーキテクチャ理解が間違っていることもある。図のつながりはコードに戻って確かめる必要がある。影響しないと思った機能も、テストや実際の動作で確認する。重要な状態や複雑な境界に出会ったら、実装をもう少し深く見る。

たいていはどこを見ればよいかわかり、まだわかっていない場所も把握できる。少しずつ、そうなりたい。

FiboCode のコード分析機能を作っている理由も、そこにある。

コード、文書、関係図をつなげて、見慣れない実装から骨組みをつかみ、一つの機能の流れをたどれるようにしたい。その理解を持って AI と変更を相談できれば、少し安心できる。

Unmesh Joshi も What Is Code? で、よい抽象化や型、テスト、明確な規則が、モデルによるコードの理解や変更の手がかりになると述べている。私もこの方向に共感する。システムへの理解を、次の変更でも使える形で、できるだけプロジェクトに残しておきたい。

コードを読む時間も、やはり残しておきたい

AI は書くのが速い。つい次の作業も任せたくなる。

機能が次々に完成して、プロジェクトは進んでいるように見える。それでも、終わったあとに自分は何を理解できたのかが気になる。

要件を出して結果を受け取るだけで、複雑になってから大量の修正や作り直しをするなら、節約した時間をまた使ってしまうかもしれない。モデルの呼び出し費用も、一つの実装を覚え直す手間もかかる。

その時間の一部を使って、落ち着いてコードを読みたい。

一度にたくさん読む必要はない。なぜこのモジュール分割なのか、なぜこの機能にこの手順が必要なのか、今回 AI が使った知らない方法は何か。わからなければ質問し、コードで根拠を探し、少し変えて自分の理解が合っているか試せばよい。

資料を探すなかで、Anthropic の学習実験を知った。未知のプログラミング課題を AI と完成させた参加者は、直後のテストで、手書きのグループより平均点が低かった。小規模な実験なので長期的な能力の変化まではわからないが、作業のあとで本当に学べたのかを気にかけるようになった。

そうした理解を少しずつ積み重ねたい。似た設計に出会ったときに見当がつき、AI の提案にも根拠のある質問ができるようになりたい。

FiboCode がそこをうまく支えられるかは、まだ探っていく必要がある。でも、コード分析を作りたい理由の大きな部分は、確かにここにある。

これからも AI とコードを書きたい。そしてプロジェクトが大きくなっても、どう動いているのかを自分で説明できるようでありたい。


この考えを簡単に図にすると:

関連して読んだ資料