AIと実践するVibe-Driven Development:第1回「"イイ感じ"で進捗表示して!」
こんにちは!株式会社ネクスタのエンジニア、bikです。
僕はこれまで、サーバーサイドの処理は同期的に書くことがほとんどでした。しかし、最近のWeb開発では非同期処理が当たり前。正直、ちゃんと理解できているか自信がありませんでした。
「このままではマズい。何か実践的なテーマで、非同期処理を深く学んでみよう!」
そう決意した僕が選んだお題は、「時間のかかる処理の進捗表示」。これなら、バックグラウンド処理、クライアントとの通信など、非同期処理の勘所がたくさん詰まっていそうです。
そして、今回の学習パートナーとして、GoogleのAI**「Gemini 2.5 Pro」**を相棒に選びました。やりたいことの雰囲気(Vibe)を伝え、AIと対話しながら実装を進める「バイブコーディング」で、僕がどこまで成長できるのか。
これは、そんな僕のリアルな学習と格闘の旅路の記録です。
すべての始まりは、一つの"学習テーマ"だった
まず僕がGeminiにぶつけたのは、こんなVibeでした。
僕: 「非同期処理の学習として、時間のかかる処理の進捗をプログレスバーで"イイ感じ"に表示する機能を作ってみたいんだけど、どう始めたらいいかな?」
Gemini: 「素晴らしい学習テーマですね!ユーザー体験の向上にも繋がります。方法はいくつか考えられますが、リアルタイム性を最優先するならSignalR、シンプルさを重視するならAPIポーリングが良いでしょう。」
技術選定、そして4つの試練
僕: 「リアルタイム性が高い方がUXは良いはずだ。まずはSignalRの実装案を見せてくれ。」
Geminiはすぐに、SignalRハブの作成、Program.cs
へのサービス登録、クライアントでの接続といった、実装に必要な手順とコードを提示してくれた。しかし、僕たちの前には4つの試練が待ち受けていました。
▼ 試練1:.NETのバージョン不一致
最初のコンパイルから早速エラーの洗礼を浴びた。「System.MissingMethodException
」。Geminiに聞くと、プロジェクトのターゲットフレームワークとNuGetパッケージのバージョンが一致していないことが原因だと即答。僕が不注意で新しいバージョンのパッケージを参照していたのだ。バージョンを修正し、最初の関門を突破する。
▼ 試練2:HttpClient未登録
コンパイルは通った。しかし、今度は実行時エラーだ。「InvalidOperationException: ... no registered service of type 'System.Net.Http.HttpClient'
」。DIコンテナへの登録が必要か。Geminiの助言通り、Program.cs
にbuilder.Services.AddHttpClient();
を追加する。基本的な「お作法」が抜けていた。
▼ 試練3:クライアントの切断
ついに処理が動き出した!…かに思えたが、バックグラウンド処理が通知を送るタイミングで、サーバーのコンソールにNullReferenceException
が表示され、処理がクラッシュした。
僕: 「今度はNullReferenceException
だよ…。あれ~~?何で何だろう??」
Gemini: 「クライアントがページを閉じるなどして、通知を送る相手が既にいなくなっている可能性があります。SendAsync
を呼び出す前に、クライアントがnull
でないことを確認するチェックを追加してください。」
これは単なる設定ミスじゃない、非同期処理の難しさだ。クライアントはいつでもいなくなる可能性がある。その前提でコードを書かなければならない。
▼ 試練4:ページ離脱時の謎エラー
これで完璧なはずだった。しかし、ページを離れるタイミングで、RemoteJSRuntime
に関連する別のNullReferenceException
が稀に出力されることに気づいた。Geminiに聞くと、コンポーネントの破棄処理とBlazorフレームワークの処理が競合している可能性があるという。await
を外し、「投げっぱなし」で破棄処理を開始することで、ついにすべてのエラーを乗り越えた。
戦いの果ての「戦略的選択」
画面の上を滑らかに進む青いバーを見て、僕は一度、思考をリセットするためにコーヒーを淹れた。動いたことは間違いない。しかし、一歩引いて考えてみる。
僕(心の声): 「SignalRの仕組みは、Hub、接続管理…と、複数の構成要素が連携して動いている。単体で見れば一つ一つは難しくないけど、全体としては仕組みが少し複雑だな。これを僕らのアプリに組み込むと、この機能だけが少し特殊な作りになってしまいそうだ。将来のメンテナンス性を考えると、色々と不都合が出てくるかもしれない…。」
僕は、技術的な優劣だけでなく、プロジェクト全体を見渡して判断を下した。
僕: 「Gemini、ありがとう。SignalRは動いたよ。でも、今回の要件と僕らのアプリの現状を考えると、よりシンプルで堅牢なポーリング方式の方が、現時点では最適な選択だと判断した。こちらの実装で進めよう。」
これは失敗ではない。戦略的選択だ。動くものの中から、プロジェクトにとって最も価値のある道を選ぶ。それこそがエンジニアリングの本質だ。
AIの限界、そして人間の気づき
しかし、ポーリング方式の実装中、あるエラーで僕たちは完全に手詰まりになってしまう。何度Geminiに聞いても、同じような間違った回答を繰り返すのだ。
僕(心の声): 「ハッ…!そうか、Geminiが学習したデータの中に、古い書き方(PostJsonAsync
)と新しい書き方(PostAsJsonAsync
)が混在していて、混乱しているんだ。これはAIには解けない。人間が気づくしかない問題だ!」
僕はGeminiの提案を無視し、自分の直感を信じてコードを修正した。そして、エラーは消えた。AIは最高の相棒だが、最後の最後で真実を見抜くのは、ディスプレイの前に座る僕たち人間の観察眼と経験なのだ。
そして、ようやく動いたポーリング方式のコードを眺めているうちに、僕は次の課題に気づいてしまう。
僕: 「Gemini、この実装だと、ユーザーがページを移動したら_taskId
の情報は消えちゃうよね?つまり、進捗が追えなくなるんじゃ…?」
Gemini: 「その通りです。素晴らしい気づきです。次はその課題、状態の永続化に取り組みましょう。」
僕たちの冒険は、まだ始まったばかりだ。
次回予告
消えゆく進捗。それは、エンジニアの心の脆さか。
魂の記録を、データベースに刻むことはできるのか。
だが、黒い影がシステムを覆う。サーバー、沈黙の時。
次回、「そのタスク、サーバーが落ちても死なないの?」
Discussion