🤖
GPT-4にコードを書かせるのはDDDなら超簡単
プログラマ界隈は阿鼻叫喚かと思いますが、色々実験して楽しかったので共有します。
エンティティ定義からのReact ComponentのPropsへのmapping
以下のような依頼を出してみました。(原文ママ)
以下のようなエンティティが存在するとします。以下は全てtypescriptで記述されています。
type User = { id: string; name: string }
type Task = { id: string; description: string; }
type Assignment = { id: string; userId: User['id'], taskId: Task['id'] }
type Organization = { id: string; name; string }
type Group = { id: string; organizationId: Organization['id']; name: string; }
type Belonging = { id: string; userId: User['id'}; groupId: Group['id'] }
これらの配列を引数として受け取り、以下の型にマッピングして返却する関数を書いてください。
type UserListPageProps = {
users: User & { tasks: Task[], groups: (Group & { organization: Organization })[]}
}
ただし、配列の変数は以下のような名前とします
users: User[]
tasks: Task[]
assignments: Assignment[]
organizations: Organization[]
groups: Group[]
belongings: Belonging[]
帰ってきたコードがこれです (一発でこれを書いてきました)
function mapToUserListPageProps(
users: User[],
tasks: Task[],
assignments: Assignment[],
organizations: Organization[],
groups: Group[],
belongings: Belonging[]
): UserListPageProps {
return {
users: users.map(user => {
const userTasks: Task[] = assignments
.filter(assignment => assignment.userId === user.id)
.map(assignment => tasks.find(task => task.id === assignment.taskId))
.filter(task => task !== undefined) as Task[];
const userGroups = belongings
.filter(belonging => belonging.userId === user.id)
.map(belonging => {
const group = groups.find(group => group.id === belonging.groupId);
if (!group) return null;
const organization = organizations.find(org => org.id === group.organizationId);
return organization ? { ...group, organization } : null;
})
.filter(group => group !== null) as (Group & { organization: Organization })[];
return { ...user, tasks: userTasks, groups: userGroups };
})
};
}
完璧ですね?
それどころか、僕の書いたPropsの定義が実は間違っていることが後でわかったのですが、
type UserListPageProps = {
users: User & { tasks: Task[], groups: (Group & { organization: Organization })[]}
} // []にする場所間違えとる
変数名などから空気を読んで正しい実装にしてくれています。
そして、エンティティを入れ子にしたProps型の定義から、各エンティティのスコープが親によって絞られているということを特に言及していないはずなのにきちんと理解してfilterをかけています。
凄すぎる。。。。。
あと、
.filter(task => task !== undefined) as Task[];
この辺の記述から分かるように、typescriptの型システムの理解も完璧です。
キャスト(as)は型安全じゃないよ
理想を言えば、できるだけ型安全に書いて欲しかったので、以下のお願いを追加でしてみました。
`as` 演算子を使用しないようにするため、`excludeNullOrUndefined`という名前の関数を実装してください。この関数は(T | null | undefined)[]型の配列を受け取り、配列からundefinedもしくはnullのものを削除し、返却する配列の型をT[]にします。
これに対しては、
function excludeNullOrUndefined<T>(array: (T | null | undefined)[]): T[] {
return array.filter((item): item is T => item !== null && item !== undefined);
}
function mapToUserListPageProps(
users: User[],
tasks: Task[],
assignments: Assignment[],
organizations: Organization[],
groups: Group[],
belongings: Belonging[]
): UserListPageProps {
return {
users: users.map(user => {
const userTasks: Task[] = excludeNullOrUndefined(
assignments
.filter(assignment => assignment.userId === user.id)
.map(assignment => tasks.find(task => task.id === assignment.taskId))
);
const userGroups: (Group & { organization: Organization })[] = excludeNullOrUndefined(
belongings
.filter(belonging => belonging.userId === user.id)
.map(belonging => {
const group = groups.find(group => group.id === belonging.groupId);
if (!group) return null;
const organization = organizations.find(org => org.id === group.organizationId);
return organization ? { ...group, organization } : null;
})
);
return { ...user, tasks: userTasks, groups: userGroups };
})
};
}
もはや言うことがありません
てか、
return array.filter((item): item is T => item !== null && item !== undefined);
この is
の使い方を知っている「typescriptエンジニア」にあんまり会ったことがないのですが?
結論: ドメインモデル大事!!!
GPTにコードを書かせるためのポイントとして「どうやって仕様を伝えるか」が当然肝になるわけですが、今回の実験により「静的に定まるエンティティ同士のリレーション」から「別の型」へのマッピングは完璧にできるということがわかりました。
GPTが理解しやすいポイントと思われるのは、
- 仕様として、エンティティの定義とPropsの定義を「型」という表現として伝えている。
- Propsの定義をエンティティ定義に依拠する形で記述している。
この辺りなのではないかと推測されます。そもそも人間がソースコードを書くにしてもこのような表現になっていると曖昧さなくユースケースを記述しやすいというのがDDDの旨味なわけなので、ドメインモデルをしっかり構築するというのがやはり重要だということが言えます。
続きます
Discussion