ジュニアエンジニアが実践したい、プロンプトより大切な「下ごしらえ力」
1. はじめに
AIがコードを書いてくれる時代になりましたね。
Claude CodeやCodexといったコーディングエージェントが次々と登場して、プロンプトを入力すれば勝手に実装を進めてくれる。カスタムコマンド等を整備すれば、自分でブランチを切って、コードを書いて、テストまで追加してくれる。ジュニアエンジニアの私からすると、自分がエンジニアとして発揮できる価値について考えさせられる瞬間が正直あります。
もちろんソフトウェアの発展に貢献することが大事なので、私は最初
「AIに入力するプロンプトを磨くことで、より効率良く開発できる。結果としてソフトウェアの発展に貢献できる」
と考えていました。より具体的に、より詳細に指示を書けば、AIはもっと良いコードを出してくれるはずと考えて、プロンプトエンジニアリングの記事を読んだり、英語でプロンプトを書くと効率がいいと聞いて実践してみたりしました。とにかく「AIへの伝え方」を工夫することに注力していたように思います。
しかし、ある程度AIと開発を重ねるうちに気づいたのは、大事なのは「プロンプトの書き方」よりも、「AIにわたす前の準備」だったということです。どれだけ丁寧にプロンプトを書いても、そもそも自分の中で「なにを作りたいのか」が曖昧だと、出てくるコードも曖昧になる。逆に、渡す前にしっかり整理しておけば、プロンプト自体はシンプルでも良いコードが返ってくる。これに気づいてから、AIがぐっと良いコードをアウトプットしてくれるようになりました。
私はこの「渡す前の準備」を「下ごしらえ」と呼んでいます。この記事では、ジュニアエンジニアの自分がAIとの開発を通じて学んだ「下ごしらえ力」について書いていきます。
2. なぜ、AIに任せてもしっくりこないのか
AIにコードを書いてもらうと、動くものは出てきます。エラーも出ないし、指示した要件は満たしている気がしているが、あとから見ると微妙なことが多い。
よくあるのはこういうパターンです。「レビュー投稿機能を作って」というと、以下のようなコードが返ってきます。
def create
# バリデーションをここでやる
if params[:comment].blank?
render json: { error: 'コメントは必須です' }, status: :unprocessable_entity
return
end
if params[:rating].to_i < 1 || params[:rating].to_i > 5
render json: { error: '評価は1〜5で入力してください' }, status: :unprocessable_entity
return
end
# 画像アップロード処理もやる
image_url = nil
if params[:image].present?
uploaded_file = params[:image]
filename = "#{SecureRandom.uuid}_#{uploaded_file.original_filename}"
filepath = Rails.root.join('public', 'uploads', filename)
File.open(filepath, 'wb') { |f| f.write(uploaded_file.read) }
image_url = "/uploads/#{filename}"
end
# レビュー作成だけやりたいのに!
review = current_user.reviews.build(
restaurant_id: params[:restaurant_id],
comment: params[:comment],
rating: params[:rating],
image_url: image_url,
scene_tag_id: params[:scene_tag_id]
)
if review.save
render json: review, status: :created
else
render json: { errors: review.errors.full_messages }, status: :unprocessable_entity
end
end
動くには動きますが、バリデーション、画像アップロード、レビュー作成が全部1つのメソッドに入っていますね。単なるレビュー投稿の話なのに、50行超えのcreateアクションは、あとから読む人にとっては負荷が高いです。
- 1つのメソッドに複数の処理が詰め込まれている
- どこでなにをしているのか、責務の境界が曖昧
- ある箇所を直そうとすると、別の箇所に影響が出る
など、「動くけど、あとから触りづらいコード」になりがちです。
AIが普及している今、ジュニアエンジニアとして目指すべき姿は「AIを使いこなし、品質の高いコードを今までより早く生み出すこと」なのに、AIと同じレベルでいては、「ジュニアレベルなんてAIで十分」と言われてしまっても仕方がないですよね。
振り返ってみると、しっくり来るコードとそうでないものの差は、AIの性能ではなく私の渡し方の問題でした。AIは「実装して」と言われたことを実装するのは得意ですが、「どこで区切るか」「なにをどこに持たせるか」といった設計判断はこちらが示さないと確実なものになりません。
3. 下ごしらえとは何か
私も上記のようなコードを書いて、PRをcloseしてしまうことが多かったのですが、
転機となったのは先輩から紹介していただいた、以下の記事です。
私はあまり設計に関するイメージが湧いていなかったのですが、AIにやらせていたのは「手順」をなぞっているだけだったのか、という気付きを得ました。
大場さんの発表では、設計を「逆算」として捉える考え方が紹介されています。まず完成した機能を思い浮かべて、「それにはなにが必要か?」を順々に考えていく。詳しくはぜひ元の発表を見ていただきたいのですが、私はこれを参考に、次の3つの問いを意識するようになりました。
- 完成像は?
- そのためには、どんなデータ(処理)が必要か?
- それを整理すると、どの区分に分けられるか?
コードを書く前にこの3つを整理しておくことが、私の考える「下ごしらえ」です。
4. では、下ごしらえしてみよう
この考え方を、AIとの開発に取り入れてみました。
まず自分で逆算する
AIに投げる前に、まず3つの問いに対して答えを出しておきます。
さっきのレビュー投稿機能で言えば、こんな感じです。
完成像は?
まず「この機能が動いているところ」を想像します。
- ユーザーがレビュー投稿画面を開いている。
- 星をタップして評価を選ぶ。
- コメントを入力する。
- 写真を添付する。
- 投稿ボタンを押す。
- 「投稿しました」と表示されて、レビュー一覧に自分の投稿が並ぶ。
こんな感じで、画面の動きをひととおり頭の中で再生してみます。
バックエンド開発のみの場合は少し難しいですが、図示する等で整理してみてください。
そのためには、どんなデータ(処理)が必要か?
完成像を眺めながら、実現に必要な要素を洗い出します。
- 星の評価を受け取る
- コメントを受け取る
- 画像を受け取って保存する
- 評価は1~5の範囲か確認する
- コメントが空になっていないか確認する
- 全部OKならレビューをDBに保存する
ここではまだコードを意識せず、必要な要素を列挙するくらいの解像度で構いません。
それらを整理すると、どんな関心事に分けられるか?
並べたものを眺めて、関心事ごとにグルーピングします。
-
「評価が1~5か」「コメントが空ではないか」は入力値のチェック
-
「画像を受け取って保存する」は画像の取り扱いの領域
-
「レビューを保存する」はレビュー本体の領域
と区切りがつきます。
先輩にレビューを依頼する
ここで、先輩にレビューを依頼してみてください。ドメインとエンジニアリング、両方の分野で経験がある方からレビューをもらうことで、考慮できていなかった要素や、もっと詰められる要素について解像度が上がると思います。
解像度が上がったら、AIに実装を任せる
下ごしらえで分けた関心事を、そのままタスクの単位にしてAIに渡します。
- 「入力値のバリデーションを実装して」→ PR作成 → レビュー
- 「画像アップロード処理を実装して」→ PR作成 → レビュー
- 「レビュー保存のコントローラーを実装して」→ PR作成 → レビュー
一度に「レビュー投稿機能を作って」と渡すと、AIは全部まとめて1つのメソッドに書いてしまいます。一方でタスクを切って渡すと、頭のコンテキストが絞られる。コンテキストが絞られると、「これはコントローラーじゃなくてモデルに書くべきだな」という判断が自然と浮かんできます。
下ごしらえの段階で「なにを作るか」を分解しておくことで、結果的に責務の分離されたコードに近づいていきます。最初から完璧な設計を指示する必要はなくて、タスクを小さく切ること自体が、正しい設計への道筋になります。
たとえば、先ほどの例をこの流れで進めると、最終的にはこうなります。
# app/models/review.rb
class Review < ApplicationRecord
belongs_to :user
belongs_to :restaurant
belongs_to :scene_tag, class_name: 'Tag', optional: true
validates :comment, presence: true
validates :rating, presence: true, inclusion: { in: 1..5 }
end
# app/services/image_uploader.rb
class ImageUploader
def self.upload(file)
return nil unless file.present?
filename = "#{SecureRandom.uuid}_#{file.original_filename}"
filepath = Rails.root.join('public', 'uploads', filename)
File.open(filepath, 'wb') { |f| f.write(file.read) }
"/uploads/#{filename}"
end
end
# app/controllers/reviews_controller.rb
def create
image_url = ImageUploader.upload(params[:image])
review = current_user.reviews.build(
restaurant_id: params[:restaurant_id],
comment: params[:comment],
rating: params[:rating],
image_url: image_url,
scene_tag_id: params[:scene_tag_id]
)
if review.save
render json: review, status: :created
else
render json: { errors: review.errors.full_messages }, status: :unprocessable_entity
end
end
createアクションは15行程度になりました。バリデーションを直したければモデルを見ればいいし、画像処理を直したければサービスを見ればいい。どこになにがあるかが明確になっています。
5. 「下ごしらえ力」を身に着けて、AIを使いこなそう
正直、「設計」という言葉には苦手意識があり、設計書を書くスキルや、ドメイン知識がないとそもそも成り立たないのではないか、と思っていました。「経験の浅い私にはあまり関係のないシニアエンジニアの領域なので、早くそこに到達できるように頑張らないとな」くらいに捉えており、ある意味諦めていたのかもしれません。
しかし今は、
「設計とは、実装に入る前に「なにを作るか」の解像度を上げる作業なのだ」
と少し違う捉え方をしています。周りの人が理解しやすいドキュメントに起こす必要は必ずしもなく(それがアウトプットとして求められているなら別ですが)、頭の中でもいいしメモ書きでもいいので「自分がなにを作ろうとしているのか」を明確にするプロセスそのものが設計なんだと、今は考えています。
解像度が上がっていると、AIが出してきたコードを見る目が変わります。関数の責務やデータの流れについて、自分の中で答えを持っている状態になり、AIの出力が意図に沿っているかどうかを判断することができます。
逆に解像度が低いままだと、「動いているかどうか」しか見えず、動いてはいるけどなにかが違う、しかしどこが違うのかわからない、といったようなバイブコーディングの悪い側面を感じることになってしまいます。
AIが実装を肩代わりしてくれる時代になったことで、「判断できる状態を自分で作る」という工程の価値がより鮮明になりました。コーディング速度でAIに勝つのは難しいですが、「何を作るべきか」を考え、成果物を評価するプロセスには、ジュニアエンジニアでも貢献できる余地があります。この点に気づけたことが、最近の最も大きな収穫です。
Discussion