コードと世界の「境界線」に立つのが今一番面白い
AIによって、コーディングの大部分が置き換えられました。
正直、最初は寂しかった。
自分の手で書くという、一番楽しい作業が失われたと感じたからです。
でも最近は、Claude Codeが期待と違うものを返してきたとき、「よし、来たな」と思っている自分がいます。
はじめに
先週、その「よし、来たな」を1日に3回感じた出来事がありました。
同時に、エンジニアとして積み上げてきたものの価値を、改めて感じた瞬間でもありました。
本記事では、その3つの瞬間を追体験しながら、AI時代に「人間が立つべき場所」について考えてみます。
以下の内容は、実体験をベースとして記事向けに調整したものです。
ある日の出来事: キャッシュで3回、手が止まる
「コンポーネントキャッシュで改善して」
あるとても遅いページがありました。
商品が大量に並び、DOMが巨大なページです。
調べてみると、Next.jsのサーバーサイドレンダリングがボトルネックになっています。
これは、Next.jsのuse cacheでコンポーネントをキャッシュすることで、簡単に改善できそうです。
Claude Codeに「このページのDOMが巨大で遅いから、コンポーネントキャッシュで改善して」と投げてみます。
// 生成されたコード
async function Page(props: Props) {
"use cache"
cacheLife('hours')
const items = await fetchItems()
const recommendedItems = await fetchRecommendedItems(props.userId)
return (
<div>
<ItemList items={items} />
<RecommendedList items={recommendedItems} />
</div>
)
}
1回目: 「あれ、待てよ」
Claude Codeの出力を見て、手が止まります。
async function Page(props: Props) {
"use cache"
// キャッシュ期間がhours?
cacheLife('hours')
このページの商品は在庫や価格がリアルタイムで変わります。
キャッシュ期間がhoursでは長すぎる。
AIは、このデータがどんな性質を持っているか知りません。
「在庫がリアルタイムで変わる」「価格が頻繁に更新される」といったドメインの知識は、コードには書かれていないからです。
「気づけてよかった」
2回目: 「あ、これダメだ」
追加のプロンプトでminutesに修正させます。
コードを見て、また手が止まります。
// propsにuserId…?
async function Page(props: Props) {
"use cache"
cacheLife('minutes')
const items = await fetchItems()
// ユーザーごとに出し分けている
const recommendedItems = await fetchRecommendedItems(props.userId)
PropsにuserIdが入っています。
ユーザーごとに表示する商品を出し分けるのに使われているようです。
Next.jsのuse cacheは、Propsの値ごとにキャッシュキーを生成する仕様です。
ということは、ユーザーごとに別のキャッシュが作られてしまいます。
これでは、キャッシュヒット率がほぼゼロになります。
最悪、メモリの枯渇でサーバーがダウンしかねません。
AIはコードの意図は理解できます。
でも、そのコードがシステム全体でどう振る舞うかまでは見通せません。
「気づけてよかった」
「よし、これで大丈夫」
userIdが必要なのはrecommendedItemsだけです。
コンポーネントを分離して、itemsだけキャッシュするよう指示します。
async function Page(props: Props) {
return (
<div>
<CachedItems />
<RecommendedItems userId={props.userId} />
</div>
)
}
// itemsだけキャッシュ
async function CachedItems() {
"use cache"
cacheLife('minutes')
const items = await fetchItems()
return <ItemList items={items} />
}
// recommendedItemsはキャッシュしない(userId依存)
async function RecommendedItems(props: Props) {
const items = await fetchRecommendedItems(props.userId)
return <RecommendedList items={items} />
}
「よし、これで大丈夫…本当に?」
3回目: 「...本当に大丈夫?」
何かを見落としている気がする。
コードだけ見ていても答えは出ない。
こういうときは、コードの外に想像力を広げてみます。
「このデータは、どこから来ている?」
APIから先のことを何も考えていなかったことに気がつきます。
このページの商品一覧は、別チームが管理するAPIから取得しています。
もしそのAPIでA/Bテストをしていたら?
fetchItemsの先にあるAPIのコードを確認してみます。
// 別チームが実装したAPI
export async function getItems(): Promise<Item[]> {
const items = await fetchItemsFromDB()
// A/Bテスト: 並び順のアルゴリズムを検証中
if (Math.random() > 0.5) {
return sortByPopularity(items)
} else {
return sortByNewest(items)
}
}
やはり、A/Bテストが入っていました。
気づかずにリリースしていたら、AかBのどちらかがランダムにキャッシュされ、テスト結果が汚染されるところでした。
「気づけてよかった」
まだ「境界線」に立てる
3つの瞬間すべて、AIは気づけませんでした。
どれも人間が立ち止まったから防げたものです。
でも、AIも「知識」は持っていました。
Next.jsのキャッシュの仕組みも、A/Bテストの概念も知っています。
足りなかったのは、「今、その知識を使うべきだ」という判断でした。
では、その判断はどこから来たのか。
振り返ると、3回とも同じことをしていました。
「コードの中」だけでなく、「コードの外」を見ていたのです。
- 在庫や価格がリアルタイムで変わるというドメイン知識
- キャッシュが分散したときのシステム全体への影響
- 別チームがA/Bテストを入れているかもしれないというコードの外の世界
コードと外の世界の「境界線」
AIはコードを高速に読み書きできます。
でも、コードの外にある文脈は見えていません。
だから、気づける人間がいなければ、間違った方向に高速に進んでしまいます。
コードと外の世界の間には「境界線」があります。
そこに立てるのは、現実世界に生きている人間だけです。
「境界線」に立つ面白さ
AIによってコードを書く楽しさが失われた、と思っていました。
でも、境界線に立つ面白さはまだ残っています。
なぜなら、あらゆる状況に有効な銀の弾丸はないからです。
現実世界には、時間、コスト、サーバーリソース、ビジネス要件、他チームの都合など、様々な制約があります。
これらをすべて解決する万能な正解はありません。
「パフォーマンスを上げたいけど、開発コストは抑えたい」
「キャッシュを効かせたいけど、リアルタイム性も必要」
こうしたトレードオフは、状況によって最適解が変わります。
だから人間が境界線に立ち、判断を下す必要があります。
将来のことはわからないですが、今はまだ、コードと世界の境界線上にコーディングの面白さが以前と変わらず存在しているように思います。
むしろ、AIで高速にコードが生成されるようになった分、気づきの機会は増えたかもしれません。
「よし、来たな」
AIの出力を見て「待て」と手が止まった瞬間。
頭の中で何かが引っかかる、あの感覚。
「あ、これ見落としてる」と気づいたときの、静かな高揚感。
それは、境界線に立った瞬間です。
コードと外の世界の間で、制約を翻訳し、判断を下す。
積み上げてきたものがあるから、その瞬間に気づける。
気づけるから、AIを高速エディタとして使いこなせる。
だから最近は、Claude Codeが期待と違うものを返してきたとき、「よし、来たな」 と思っている自分がいます。
過去を懐古するのではなく、人間の役割はプロダクトを作ることに変わったと割り切るのでもなく、その狭間。
コードと世界の「境界線」に立つのが、今一番面白い。
Discussion