UPSIDER Tech Blog

AIによるコード生成を活かすためのタスク分割

こんにちは、UPSIDERでエンジニアをしている太田です(@Hide55832241)。
AIによるコード生成が進化する中で、私たちはそれを開発現場でどのように実用的に活かせるかを模索しています。
本記事では既存コード資産を活かしたAIコード生成における、効果的なタスク分割のアプローチを紹介します。

タスク分割せずにコード生成する

以下のFormコンポーネントを例としてAIにコード生成させてみます。
※この記事ではUIについて細かく書きませんが、既存のコンポーネントを使用し修正の必要がないコードが生成されることが正しいものという前提とします。

意図に近いコードが生成されることもありますが、品質にばらつきがあり、ランダム性の影響も見られます。
一度の指示で完全に期待したとおりのコードが生成される確率は低いです。
また追加で指示を与えて修正できるかと言われると必ずしもそうではないように感じます。
指示して修正がうまくいくこともある一方で、いっそ削除して再生成した方が早い場合も多いと感じます。
このような背景から、AIが生成した間違いを含むコードをAIに修正させたり、人が引き継いで修正するのは必ずしも効率的ではないように感じます。

コード生成のランダム性例

上振れした場合(理想のコード)

// Formスキーマ
// ✅️ 共通定義した汎用スキーマを使用し、アプリケーション内で統一されたふるまいが考慮されている
export const FORM_SCHEMA = z.object({
  name: OPTIONAL_STRING_SCHEMA({
    columnName: FORM_FIELD_LABEL.name,
    additionalSchema: z.string().maxLength(VALIDATION.name.maxLength)
  }),
  monthly_limit_amount: REQUIRED_NUMBER_SCHEMA({
    columnName: FORM_FIELD_LABEL.monthly_limit_amount,
    additionalSchema: z.number().min(VALIDATION.monthly_limit_amount.min).max(VALIDATION.monthly_limit_amount.max)
  }),
  enabled: REQUIRED_BOOLEAN_SCHEMA({
    columnName: FORM_FIELD_LABEL.enabled,
  }),
})

// Form型
// ✅️ スキーマから推論したinputとOutputの型を定義する
export type FormInputSchema = z.input<typeof FORM_SCHEMA>
export type FormOutputSchema = z.output<typeof FORM_SCHEMA>

// FormContext Hook
// ✅️ 定義した型を使用し、InputとOutputの型をそれぞれ指定する
export const useUpdateCreditCardFormContext =
  useFormContext<typeof FormInputSchema, unknown, FormOutputSchema>

下振れした場合

// Formスキーマ
// ❌ zodを生で使用する
// ❌ エラーメッセージが定義されない(独自に定義されることも)
// ❌ 数値入力はIMEやカンマ除去などが考慮されず、コンポーネントとふるまいが不一致となり正常に動作しない
export const FORM_SCHEMA = z.object({
  name: z.string().optional(),
  monthly_limit_amount: z.number().min(1000).max(100000000)
  enabled: z.boolean()
})

// Form型
// ❌ outputの型しか定義しない
export type FormSchema = z.infer<typeof FORM_SCHEMA>

// FormContext Hook
// ❌ 定義した型を使用しない
// ❌ inputの型を指定しない(検証後の型しか得られず、Formの入力に関連するデフォルト値の考慮などができない)
export const useUpdateCreditCardFormContext =
  useFormContext<z.infer<typeof FORM_SCHEMA>>

なぜ期待するコード生成が難しいのか?

その原因のひとつは、コードの依存関係にあるのではないかと考えました。
たとえばFormコンポーネントでは、多くのコードがFormスキーマに直接または間接的に依存します。
Formスキーマが間違っていると関連するコードに波及する確率が高くなります。
Formスキーマから得られる型の定義やAPI hookが間違っている場合にも影響は大きくなります。
逆に正しく定義された汎用コンポーネントやFormスキーマを使用するだけのコンポーネントでは、それらを正しく使用できれば問題が起こりにくいです。
コード生成の結果を見ていると、そうではない場合もありますが、参照されることが多いコードが間違っていることも多いことに気がつきました。
人による実装ではまずそこを作成しある程度確定させた後に残りのコードに着手しますが、AIは間違ったコードを参照したまま残りのコードも作り続けてしまいます。 (人間のように依存関係の核となる部分から順に構築するわけではなく、AIは文脈を考慮せずに並列的・部分的に出力を始める傾向があるように思えます)

Formコンポーネントの依存関係

支払い.com で採用されているFormコンポーネントの構造)

タスク分割してコード生成する

次にタスク分割してコード生成をします。
上記の画像のように細かくモジュールが分割され、その単位でタスク分割することができますが、まずは参照が多いFormスキーマの実装に関する詳細な指示をとコードの依存関係を指示し、残りのコードも生成させます。

※ 以下の既存モジュールはあらかじめ定義済みであることを前提とします。

すると生成されるコードの品質が向上し、ランダム性が減少します。 (実装に関する詳細な指示を与えたので当然かもしれませんが)
参照されるFormスキーマの品質が向上することにより、Formスキーマ以外のコードの質も向上することが多くなったように感じます。
Formスキーマに限らず参照されることが多いコードに対し指示を与え、それを正確に使用することで生成されるコードの全体的な質の向上を見込めるように感じます。

分割してタスクを進める

先程はFormスキーマに詳細な指示を与えるもののForm全体を一度に作成する方法を取りましたが、一部のコードを先に生成・修正・確定させ、残りのコードを後から生成も可能です。
理想的には一度の指示ですべてのコードを生成できれば良いのですが、現時点ではそのようなケースは多くありません。
Formの生成の例ではまずFormスキーマのみをAIに生成させます。
このスキーマはフォーム全体の型やバリデーション、デフォルト値などに影響するため、特に重要な部分として単体でレビューや修正するアプローチをとります。
ときには人が介入して修正することも検討します。
AIを活用しながらも特に重要なコードを確実に作成した上で残りのコードを生成させます。

Formスキーマに依存するコード

このアプローチにより、次のようなメリットがあります。

  • 間違いのないコードを基盤にすることで、それに依存するコード生成の質が向上する
  • AI出力のランダム性を減らせる
  • コンテキストウィンドウを破棄しても残りのコード生成への影響が少ない

一度にコード生成する場合は詳細な指示を与えたとはいえ、レビューを行っていないため間違ったコードを参照したまま残りのコードを生成することもありました。
分割してタスクを進め確実に進めていくことで残りのコードの品質が向上することが多くなります。
一度の指示のみでAIに実装を任せることができなくなるので、非同期でAIに任せづらくなる大きなトレードオフがありますが、結果として分割してタスクを進めた方が早く品質が高くなることもあると思います。

段階的に実装を進めることができる

AIによるコード生成を行っていると何度コード生成し直しても期待したコードが得られず、進捗がまったくなく、はじめから自分で実装しておけばよかったと疲弊することがあります。
タスク分割するアプローチでは、タスク単位で確実に実装を確定させることで少しずつ確実に実装を進めていくことができます。
AIがすべてのコードを正確に実装してくれるにこしたことはないのですが、以下のようにAIによるコード生成に頼りながらも少しずつ実装を進めることができます。

  • まずはFormスキーマを確定させてから残りのコードをAIに生成させる
  • うまくいかなければFormの型を確定させてから残りのAIにコードを生成させる
  • それでもうまくいかなければFormやAPIのhookも確定させてから残りのAIにコードを生成させる

このように失敗の度に「確定させる範囲」を一段階広げることで、AIにとっての「正しい前提」を明確にし、生成精度の向上を狙います。

このアプローチにより以下の恩恵が受けられます

  • 少しずつコードを確定させていくことで、残りのコード生成の質を高めていく
  • AIにコード生成させる範囲を確実に狭めていく

あとがき

今後の展望として、AIによるコード生成をするためにルールやプロンプトのテンプレートを整備している中で、ルールベースでコードを生成をする余地が残っていることに気づきました。
ルールベースによるコード生成には柔軟性はありませんが、ランダム性もありません。
ルールベースによるコード生成とAIを併用することで、AIによるランダム性の伴うコードを生成させる範囲を狭めることができないかと模索を始めています。
またAIにコード生成させるための整備は、人間のエンジニアに対するオンボーディングにも役に立ちそうなものが多いと改めて感じています。

We Are Hiring !!

UPSIDERでは現在積極採用をしています。 ぜひお気軽にご応募ください。

herp.careers

カジュアル面談はこちら!

herp.careers

Culture Deckはこちら

speakerdeck.com