AIに任せること、人が考えること
この記事はVoicyアドベントカレンダー202514日目の記事です。
こんにちは!Voicyでバックエンドエンジニアをしているmasaです。
13日目はVoicyに新卒入社のめいちゃんによる「Makeで実現!「あの契約書どこ?」をゼロにする、契約書管理DXへの第一歩」でした!こちらの記事もぜひ読んでみてください!
はじめに
昨今、生成AIの進化は凄まじく、AIを使ってコードを書くのが当たり前になりつつあると感じています。
そんな中で、実際に使ってみると以下のようなもどかしさを誰もが感じたことがあるのではないでしょうか?
- 頑張ってプロンプトを作ってお願いしたのに、「うーん、そうじゃないんだよなぁ...」
- 「動くかもしれないけど、後から見返した時に意図が理解しづらそうだなぁ...」
- 「なんかめっちゃ複雑なことしてない...??」
もちろん、私も感じています。(ここの例示は体験談でもあるのでw)
この記事では、そんな経験を踏まえて「AIに任せること」と「人が考えること」 を切り分け、AI時代に生産性を上げる考え方をまとめます。
先に結論:人がドメインモデルを考え、パターン化できるものをAIに任せる
自分の中での現時点のAIを使ったコーディングの答えは、
- 人が考えること:ドメインモデル(ビジネスロジック、制約、用語(名前)など)
- AIに任せること:パターン化された実装(DBアクセス、HTTPの入口、ユースケースなど)
というものです。
AIは「パターン化してコードを生成する」ということは得意で、パターンに則って生成できるものはかなりの確率で期待した結果を出してきてくれます。
一方で、「何が正しいか」の最終判断と責任は人が持つべきで、自分たちの事業に沿って実装する必要があります。
正しさの中心(ドメインモデル)を人が握り、周辺部分をAIで増やすのが品質と速度のバランスを取りやすいと感じています。
人が考えるべきこと:ドメインモデル
ここで言うドメインモデルは、ざっくり言うと ビジネスロジックの中心です。
名前の通り、事業としてサービスを提供する中で一番重要な部分であり、ここがサービスの価値を作ります。
たとえば、似たようなサービスが世の中に複数あったとしても、各社はそれぞれの事情・戦略・リスク許容度を持っています。
結果として「同じように見える機能」でも、内部には各社固有のルールが存在するはずです。
- 料金の計算ルール
- ユーザーに見せる状態の定義
- 例外ケース(イレギュラーな運用)の扱い
こういったものはドメインルールであり、各社固有のロジックになりやすい部分です。
ドメインは変更が起きやすい
ドメインは重要な部分であると同時に、頻繁に変更が入ることも多いです。
- ビジネスの方針が変わる
- リスク対策が入る
- オペレーションが変わる
- 法律の変更などの外部要因でルールが変わる
こうした変化に追従できないと、開発コストが膨らんでしまいます。
そのため、ドメインモデルは 変更しやすい作り になっている必要があります。
ドメインは最重要なのでテスタブルであるべき
もちろんDBアクセスなどにもテストは必要です。
ただ、ビジネス上「最も守るべき」なのはドメインルールです。
- 期待した計算になっているか
- 意図しない状態遷移が発生していないか
- 例外条件で破綻しないか
このあたりは不具合が混入すると事業への影響が大きいので、特にテストで品質を担保したい領域です。
だからこそ、ドメインモデルは テストを厚くできる(= テスタブルな構造) である必要があると考えています。
ドメインルールを正しくコードで表現する
ここまでの話をまとめると、ドメインモデルは
- 事業の価値そのもの
- 変更が入りやすい場所
- テストを厚くしたい場所
だからこそ、人が責任を持って設計し、表現する必要がある領域であると考えています。
(ただし、ドメインモデルの設計の壁打ち相手としてはAIを使うことは有効だとは思っています。)
AIにドメインルールごと書かせることもできますが、ルールや用語を毎回漏れなくプロンプトに落とすのは大変です。
また、ドメインモデルを自分でコードとして書いている中で、
- 「なんか違うな…」
- 「このケースはどう扱うのが正しいルールになるんだろう?」
- 「この言葉の定義、ぶれてない?」
といった気づきを得られることが多いです。
これらは生成されたコードを眺めるだけでは得られにくく、実装/設計しながら整理されていく感覚があります。
そういった理由もあり、ドメインモデルは「人が考えるべきもの」と考えています。
AIに任せること:パターン化された実装
ドメインモデルが各社固有のルールを抱えるのに対して、DBアクセスやHTTPの入口、ユースケース周りは ある程度決まった形になりやすいです。
例えばDBアクセスの部分を例にすると、やることはざっくりこの流れになります。
- 欲しい情報をシグネチャ(引数)でもらう
- 引数を元にDBへ問い合わせて必要な情報を取得する
- DBの表現からドメインの言葉へ変換して返す
この「入力 → 取得 → 変換 → 返す」という流れはパターン化しやすいので、AIが得意な領域と考えています。
例えば、以下のようなGoのコードがあるとします(説明用に最小化したコードとなっています)
package user
import (
"context"
"database/sql"
"fmt"
)
type userRow struct {
ID uint64
Name string
}
func (r userRow) toDomain() User {
return User{
ID: ID(r.ID),
Name: r.Name,
}
}
type ID uint64
type User struct {
ID ID
Name string
}
type UserRepository struct {
db *sql.DB
}
func (u UserRepository) GetUser(ctx context.Context, id ID) (User, error) {
const q = `SELECT id, name FROM users WHERE id = ?`
var row userRow
if err := u.db.QueryRowContext(ctx, q, uint64(id)).Scan(&row.ID, &row.Name); err != nil {
return User{}, fmt.Errorf("get user: %w", err)
}
return row.toDomain(), nil
}
この「指定したidに該当するユーザーの情報を取得する」というシンプルなメソッドがあり、ここに対して「指定した複数のidに該当するユーザーの情報を全て取得する」というGetUsersメソッドを作りたい場合、
func (u UserRepository) GetUsers(ctx context.Context, ids []ID) ([]User, error) {}
と、シグネチャだけ定義してしまえば、あとは
GetUserの実装を参考にしながらGetUsersメソッドの実装を完成させて
と指示するだけで、あとはGetUsersメソッドをAIが勝手にいい感じに作ってくれます。
つまり、「GetUserという1つの型」があるだけで他のメソッドはある程度機械的に作れるということになります。
(エラーハンドリングなどの細かい調整が場合によっては必要な可能性はありますが)
まとめ:パターン化できる領域はAIに任せると強い
生成AIを使ったコーディングは、うまくハマると開発が爆速になります。
一方で、「全部AIに任せればいい」という話ではなく、人が考えるべき場所/AIに任せるべき場所をうまく切り分けることが、結果的に一番生産性を上げられると感じています。
人が考えるべきこと、AIに任せることを切り分けることで爆速開発をしていきましょう!
Discussion