こんにちは、UPSIDERでエンジニアをしている太田です(@Hide55832241)。
この記事では、エンジニアがAIエージェントを活用し、AIによるコードの自動生成によって実装工数を削減できるのかを考察します。
近年 GitHub Copilot や Cursor 、 Cline などのAIコーディングエージェントが登場しています。
こうしたツールの普及により、本当にコーディング業務の生産性は向上するのか?
日々開発するエンジニアとして、主観的な視点で考察します。
本記事では、エディタなどでAIコーディングエージェントを活用し、エンジニアが自らコードを自動生成させるケースに焦点を当てて考察します。
非エンジニアによる活用や、自律的にタスクを実行するようなツール(例: Devin 、 OpenHands 、 Goose など)は対象外とします(ただし考察の中には共通する部分もあると思います)
AIを使用したコード生成
今回は、簡単なFormのフロントエンド実装を題材にします。
実際のプロダクトで検証した内容がベースとなっています。
以下のFormコンポーネントを例として取り上げます。
ワイヤーフレーム
Form仕様
入力項目
slug | 項目名 | 種類 | 必須/任意 | 検証 | 選択肢 | デフォルト値 | 備考 |
---|---|---|---|---|---|---|---|
name | カード名 | text | 任意 | 最大40文字 | - | 親コンポーネントから指定した任意の値 | - |
monthly_limit_amount | 限度額(月) | number | 必須 | 整数、最小: 1,000、最大: 100,000,000 | - | 親コンポーネントから指定した任意の値 | - |
enabled | 利用制限 | boolean | 必須 | 親コンポーネントから指定した任意の値 | - | - |
その他項目
項目名 | 種類 | 用途 | 備考 |
---|---|---|---|
更新ボタン | Form Submitボタン | カード情報を更新する | - |
(参考)自分で実装した場合
- 所要時間: 約25分
- ファイル数: 約20
- コード量: 約500行
- クオリティ: 100%(自己評価)
タスクのみを指示しAIに生成させる
仕様を doc.md
に記載し、AIに以下のように指示したケースを検証します。
doc.md
# **Form仕様** ## **入力項目** | slug | 項目名 | 種類 | 必須/任意 | 検証 | 選択肢 | デフォルト値 | 備考 | | --- | --- | --- | --- | --- | --- | --- | --- | | name | カード名 | text | 任意 | 最大40文字 | - | 親コンポーネントから指定した任意の値 | - | | monthly_limit_amount | 限度額(月) | number | 必須 | 整数、最小: 1,000、最大: 100,000,000 | - | 親コンポーネントから指定した任意の値 | - | | enabled | 利用制限 | boolean | 必須 | 親コンポーネントから指定した任意の値 | - | - | | ## その他項目 | 項目名 | 種類 | 用途 | 備考 | | --- | --- | --- | --- | | 更新ボタン | Form Submitボタン | カード情報を更新する | - |
@doc.md の仕様のFormコンポーネントを作成してください
※ CursorのAgentモード(Claude 3.7 Sonnet)の使用を想定します
※ SSRは行わず、React、React Hook Form、Zodを使用したプロダクトです
生成結果
生成されたコードには、以下のような多岐にわたる点で修正が必要でした
useController
やregister
を使うべき箇所で、watch
やsetValue
を使用している- ラジオボタンを期待する箇所でチェックボックスを使用している
- コンポーネントを不要に定義し直している
- 未指定にすべき箇所に空文字を渡している
FormContext
のdisabled
を参照せず常にfalse
にしている- ディレクトリ構成が期待と異なる
- 一部項目にエラーメッセージが設定されていない
- 存在しないmutation keyを使用している
- 汎用エラーメッセージを使わない
- 汎用Formスキーマを使わない
- 汎用コンポーネントが活用されていない
修正が必要なコードの例
// useControllerやregisterを使うべき箇所で、watchやsetValue を使用している // NG const FormFieldEnabled = () => { const { setValue, watch } = useFormContext() const value = watch('enabled') return <RadioGroup value={value} onChange={(e) => setValue('enabled', e)} /> } // 期待するコード (簡略化しています) const FormFieldEnabled = () => { const { control } = useFormContext() const { field } = useController({ control, name: 'enabled' }) return <RadioGroup {...field} /> } // 汎用Formスキーマを使わない/汎用エラーメッセージを使わない // NG export const FORM_SCHEMA = z.object({ name: z.string().max("150文字以内で入力してください").optional(), ... }) // 期待するコード export const FORM_SCHEMA = z.object({ name: OPTIONAL_STRING_SCHEMA({ columnName: FORM_FIELD_LABEL.name, additionalSchema: z.string().max(NAME_VALIDATION.maxLength, { message: MESSAGE_MAX_LENGTH( FORM_FIELD_LABEL.name, NAME_VALIDATION.maxLength ), }), }), ... })
何度か試行を重ねましたが、生成結果にはかなりのランダム性がありました。
初回の生成でそこそこ使えるコードが出力されれば運が良い方だと感じます。
正直、最初は「雑な指示でもここまで作れるのか」と感心しました。
しかし結果的には作り直しレベルで修正の必要なケースが多く見られました。
試行回数がまだ十分とは言えませんが、この規模のタスクに対して実装に関する具体的な指示をしなかった場合、「完全に納得のいくコード」が生成されたケースは、今のところ一度もありません。
これらのことから全く実装について指示せず、簡単なFormコンポーネントレベルのコードを生成させることは生産性の向上に繋がらない、むしろ生産性が悪化する可能性もあると感じます。
実装について指示する
次に実装について指示したケースを検証します。
今回はよりスコープを絞り、Formスキーマのみを生成させる例を具体的なコードとともに取り上げます。
期待するコード
import * as z from 'zod' import { MESSAGE_MAX_LENGTH, MESSAGE_NUMBER_MIN, MESSAGE_NUMBER_MAX, MESSAGE_NUMBER_INT, } from '@/consts/validationMessage' import { OPTIONAL_STRING_SCHEMA, REQUIRED_NUMBER_SCHEMA, REQUIRED_BOOLEAN_SCHEMA, } from '@/lib/zod/form' const MONTHLY_LIMIT_AMOUNT_VALIDATION = { min: 1_000, max: 100_000, } as const const NAME_VALIDATION = { maxLength: 40, } as const export const FORM_FIELD_LABEL = { name: 'カード名', monthly_limit_amount: '限度額(月)', enabled: '利用制限', } as const export const FORM_SCHEMA = z.object({ name: OPTIONAL_STRING_SCHEMA({ columnName: FORM_FIELD_LABEL.name, additionalSchema: z.string().max(NAME_VALIDATION.maxLength, { message: MESSAGE_MAX_LENGTH( FORM_FIELD_LABEL.name, NAME_VALIDATION.maxLength ), }), }), monthly_limit_amount: REQUIRED_NUMBER_SCHEMA({ columnName: FORM_FIELD_LABEL.monthly_limit_amount, additionalSchema: z .number() .int({ message: MESSAGE_NUMBER_INT(FORM_FIELD_LABEL.monthly_limit_amount), }) .min(MONTHLY_LIMIT_AMOUNT_VALIDATION.min, { message: MESSAGE_NUMBER_MIN( FORM_FIELD_LABEL.monthly_limit_amount, MONTHLY_LIMIT_AMOUNT_VALIDATION.min, '円' ), }) .max(MONTHLY_LIMIT_AMOUNT_VALIDATION.max, { message: MESSAGE_NUMBER_MAX( FORM_FIELD_LABEL.monthly_limit_amount, MONTHLY_LIMIT_AMOUNT_VALIDATION.max, '円' ), }), }), enabled: REQUIRED_BOOLEAN_SCHEMA({ columnName: FORM_FIELD_LABEL.enabled, additionalSchema: z.boolean(), }), })
- 各項目のスキーマ、エラーメッセージは共通定義されたものを使用する必要がある
additionalSchema
で定義する条件には、明示的なエラーメッセージを設定する必要がある- 定数は直書きせず、使い回せるように定義する
なぜスキーマを直書きしないか
たとえば数値入力の場合、z.number()
と書けば済むように見えるかもしれませんが、実際には多くの処理を組み込んだ汎用スキーマを定義して使っています。
以下は、実際のコードに近い一例です。
const REQUIRED_NUMBER_SCHEMA = < Input extends number, Def extends z.ZodTypeDef, Output extends number, >({ additionalSchema, }: { additionalSchema: z.ZodSchema<Output, Def, Input> }) => z .string() .transform((v) => { // 全角->半角変換、カンマ除去、単位除去など return formattedValue }) .refine((v) => !!v, { message: '入力してください' }) .transform((v) => { if (v === '') return z.NEVER return Number(v) }) .pipe(z.number({ invalid_type_error: '数値を入力してください' })) .pipe(additionalSchema)
実装に関する指示を与えない結果
スコープをFormスキーマに絞ったのでもう一度、タスクのみを指示しAIにコード生成させた結果を簡単に記載します。
Formスキーマに絞った場合、Form全体を生成させるより質が高いコードを生成されるケースが増えました。
ほぼ修正の必要のないコードが生成されることもありました。
しかし依然としてランダム性が高く、期待するコードが生成される確率は高くありません。
Formスキーマのような小さいタスクですら期待するコードが生成されないと、複数の実装工程でランダム性を受け入れる必要があるため生産性向上には繋がらない、もしくは非常に限定的であると判断します。
実装に関する指示を与えた結果
以下のドキュメントを追加します。
form_schema.md
# Formスキーマ ## ルール ### 1. `src/lib/zod/form.ts` に定義した共通スキーマを使用すること 共通スキーマの命名ルール: {REQUIRED/OPTIONAL}_{TYPE}_SCHEMA **例** - 必須入力の文字列用スキーマ: REQUIRED_STRING_SCHEMA - 任意入力の数値入力用スキーマ: OPTIONAL_NUMBER_SCHEMA ### 2. additionalSchemaの各項目にはエラーメッセージを設定すること **例** ```typescript REQUIRED_STRING_SCHEMA({ ..., additionalSchema: z.string().max(100, { message: MESSAGE_MAX_LENGTH(...) }), }) ``` エラーメッセージは `src/consts/validationMessage.ts` に定義したメッセージを使用すること ### 3. 文字数制限などの制約で使用する値は定数定義して使用すること **例** ```typescript const RANGE = { max: 100 } REQUIRED_STRING_SCHEMA({ ..., additionalSchema: z.string().max(RANGE.max, { message: MESSAGE_MAX_LENGTH(RANGE.max) }), }) ``` ## 出力例 ```typescript const NAME_VALIDATION = { maxLength: 100 } as const export const FORM_SCHEMA = z.object({ name: REQUIRED_STRING_SCHEMA({ additionalSchema: z.string().max(NAME_VALIDATION.maxLength, { message: MESSAGE_MAX_LENGTH(NAME_VALIDATION.maxLength)}), }) }) ```
以下の指示でコード生成します。
@doc.md の仕様のFormコンポーネントを作成してください @form_schema.md に従ってください
生成されたコードは、確実に質が上がっていました。
試行回数が十分ではありませんが検証した際には、修正の必要のないコードを生成する確率が7割、多少の修正は必要だがおおむね許容できるレベルのコードが3割、使えないコードが生成されたのはゼロです。
この結果から工夫をすれば質の高い結果を得られることがわかりました(得意なタスクとそうではないタスクで結果に差は出ると思いますが)
あらゆる工程でこのレベルのコード生成ができれば生産性向上が見込めそうです。
シミュレーション
実装に関する指示をAIに与えることでコードの質が向上し、その結果修正にかかる工数が減る可能性があることがわかってきました。
では、 どの程度の工数削減が見込めるのか? ざっくりとした前提ではありますが、シンプルな条件でシミュレーションしてみます。
以下は、 「似たような実装が何度も発生する場合に、AIの活用がどれほど工数削減に寄与するか」 を試算したものです。
AI導入による効果が出る条件を把握するために、工数比較しました。
数値はあくまで仮定に基づくものであり、実運用とは差異があることをご承知おきください。
前提
- 初回実装は人間が行う
- 類似実装がある場合、汎用的なAI指示を作成
- 以降の実装はAIに任せる
- 人間による実装工数
- 人間による初回実装: 30
- 人間による類似実装: 10 (初回実装の3分の1)
上記を前提として以下の組み合わせでシミュレーションをします
- 初めてAIに指示する際に今後の類似実装でも使用可能な汎用的な指示を作成する工数を30 (人間による初回実装と同程度)、10 (人間による類似実装と同程度) とした場合の2パターン
- 人間による類似実装の工数に対し、AIが実装した場合の工数を2 (5分の1)、1 (10分の1) とした場合の2パターン
汎用的な指示の作成工数 | AIによる実装工数 | |
---|---|---|
仮定1 | 30 | 2 |
仮定2 | 30 | 1 |
仮定3 | 10 | 2 |
仮定4 | 10 | 1 |
それぞれAIを使用せずに実装した場合に比べて、どれほどの工数削減が見込めるのかシミュレーションします
人間による実装工数
まとめると以下のようになります
類似実装回数 | 仮定1 | 仮定2 | 仮定3 | 仮定4 |
---|---|---|---|---|
1 | 工数が増加 | 工数が増加 | 工数が増加 | 工数が増加 |
2 | - | 減少 | - | 減少 |
3 | - | - | - | 2分の1 |
4 | - | 2分の1 | 減少 | |
5 | 減少 | - | - | 3分の1 |
10 | 2分の1 | 3分の1 | 5分の2 | 5分の1 |
人間が実装した場合と比較した類似実装回数ごとの実装工数
汎用的なAI指示の作成に大きな工数が必要な場合、AIによる類似実装の効果が出るのが遅く、類似実装が少ない場合は工数の削減は見込めなさそうです。
汎用的なAI指示の作成にあまり工数が必要ない場合、AIによる類似実装の効果が出るのが早く、類似実装が少なくても工数の削減を見込めそうです。
AIによる類似実装のパフォーマンスが高い場合、類似実装が増えるにつれ大きな工数の削減が見込めそうです。
しかしこれらはあくまで机上のシミュレーションです。
もっと生産性向上が見込めると考える場合以下のようなことも考えられるでしょう。
- 初回実装もAIに行わせる
- 指示の作成の工数を削減できる
逆に以下のような様々な懸念点も考えられるでしょう。
- 類似実装といってもタスクごとに様々な要件や規模があり、すべてのタスクで質の高いコードが生成できるとは限らない
- タスク全体のコードを生成できるとは限らないので、生産性の向上は限定的
所感と限界
様々な考察をしましたが実際どうなのか?
正直なところ、現時点ではまだ明確な答えは出せません。
今回の検証や考察はエンジニア業務の中でも「実装」に焦点を当てたごく一部の側面に過ぎません。
また実装に限っても、AIがすべてを代替できる状況とは言い難いです。
たとえば、UI実装のような視覚的・感覚的な要素は、AIにとっては依然として難易度が高い分野であると感じます。
デザインシステムや実装レベルのデザインファイル、レイアウトコンポーネントの整備などにより精度は向上が見込めると考えられます。
MCP等によるデザインファイルへのアクセスなどにより生産性の向上を見込めるかもしれません。
しかしそれらが整っていない環境でモデルの精度やツールの性能だけに頼るのはまだ難しいように感じます。
特に曖昧な指示に対しては今後モデルの性能が向上してもある程度のランダム性を伴うことが予想されます。
またAIが生成したランダム性の高いコードを確認して何度もやり直すことは、とても労力を伴うように感じています。
慣れの問題もあるかもしれませんが、AI縛りでコーディングをすると1日の終わりには想像以上の疲れを感じることがありました。
おわりに
AIによるコード生成には、現時点でも十分な可能性が感じられます。
しかし使い方によっては生産性を下げてしまう可能性もあると感じています。
実装について詳細に指示する方法はコードの質は高くなるかもしれませんが、それ自体の工数とメンテナンスが伴います。
とはいえ、使い方次第では確実に開発効率を引き上げられるポテンシャルがあるとも感じています。
AIによるコード生成には大きな可能性を感じながらも、今はまだ試行錯誤を重ねている段階です。
これらのことはあくまで現時点で感じていることです。
AIモデルや周辺の技術の進化は想像以上に早く、このような状況になることは数年前に全く予想もしていませんでした。
今後どのような状況になるかわかりませんが、AIを活用したエンジニアリングの可能性に期待し、今後も試行錯誤を重ねながら向き合っていきたいと思います。
We Are Hiring !!
UPSIDERでは現在積極採用をしています。 ぜひお気軽にご応募ください。
カジュアル面談はこちら!
Culture Deckはこちら