⚒️

ChatGPTとGitHub Copilotでどこまで出来るか。

2023/03/22に公開

目的

タイトル通りで、ChatGPTとGitHub Copilotを用いるだけでどこまでのコードが生成できるのかを確認したい。
実際に自分で調べてコーディングするのが早いのか、Copilotにお願いするのかのジャッジをしながら仕事をしていくスタイルになりそうだなという予感がするため。
※現時点でコーディングをする機会はほとんどないが、上流下流問わず一貫して作る時代が来るかもなあとぼんやり考えていたりする。

ChatGPT バックエンド処理を依頼

TypeScriptでAPIサーバーの基本的な処理を書かせる。オーソドックス技術を選定することで汎用的な回答が得られることを期待したので、以下の構成でお願いすることにした。

  • TypeScript
  • typeORM
  • tsoa
  • express

具体的な依頼内容

こちらの記事を参考にプロンプトを組んでみた。
色々いじった範囲では、このプロンプトの形式でなくても同レベルの回答は得られるとは思う。(面白そうだったので試験的にやってみただけです)
やりようによってはもっといい感じに出力できそうな気もするのでもっといじってみたいところですね。

実際のプロンプト

- [Goal]:
- Goalを達成するために必要な手順を分解します。
- 分解した手順は「P#」に願番にNumberを付けて格納していきます。
- 変数を定義します。
- [Goal]:
- controller → service → repository → entityパターンを用いる。
- Userテーブルに対してCRUDを実行する。
- 生成したファイルすべてに対し、ソースコードのサンプルを出力する
- 使用する技術はTypeScript、typeORM、tsoa、expressとする。
- Command
- [C1]:
- Goalを選成するために必要なことをStep by Stepで1つづつ実行していけるように手順:[P#]に分解して下さい。
- [Output style] :
[P1]=
....
[P#]=
....
[P(END}]=

回答来たけどソースないやんけ!

なんかめっちゃいっぱいきた

user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column()
  email: string;
}
user.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  // CRUD operations are provided by the Repository class from TypeORM.
}
user.service.ts
import { getCustomRepository } from 'typeorm';
import { UserRepository } from './user.repository';
import { User } from './user.entity';

export class UserService {
  private userRepository: UserRepository;

  constructor() {
    this.userRepository = getCustomRepository(UserRepository);
  }

  async getAllUsers(): Promise<User[]> {
    return await this.userRepository.find();
  }

  async getUserById(id: number): Promise<User> {
    return await this.userRepository.findOne(id);
  }

  async createUser(user: Partial<User>): Promise<User> {
    return await this.userRepository.save(user);
  }

  async updateUser(id: number, user: Partial<User>): Promise<User> {
    await this.userRepository.update(id, user);
    return await this.userRepository.findOne(id);
  }

  async deleteUser(id: number): Promise<void> {
    await this.userRepository.delete(id);
  }
}
user.controller.ts
import { Controller, Get, Route, Post, Body, Put, Path, Delete } from 'tsoa';
import { User } from './user.entity';
import { UserService } from './user.service';

@Route('users')
export class UserController extends Controller {
  private userService: UserService;

  constructor() {
    super();
    this.userService = new UserService();
  }

  @Get()
  public async getAllUsers(): Promise<User[]> {
    return this.userService.getAllUsers();
  }

  @Get('{id}')
  public async getUserById(@Path() id: number): Promise<User> {
    return this.userService.getUserById(id);
  }

  @Post()
  public async createUser(@Body() user: Partial<User>): Promise<User> {
    return this.userService.createUser(user);
  }

  @Put('{id}')
  public async updateUser(@Path() id: number, @Body() user: Partial<User>): Promise<User> {
    return this.userService.updateUser(id, user);
  }

  @Delete('{id}')
  public async deleteUser(@Path() id: number): Promise<void> {
    return this.userService.deleteUser(id);
  }
}
server.ts
import express from 'express';
import { RegisterRoutes } from './routes';
import { createConnection } from 'typeorm';

(async () => {
  try {
    // Connect to the database
    await createConnection();

    // Setup Express server
    const app = express();
    app.use(express.json());

    // Register tsoa-generated routes
    RegisterRoutes(app);

    const port = process.env.PORT || 3000;
    app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
  } catch (error) {
    console.error('Error while connecting to the database:', error);
  }
})();

ここまでの感覚

ぶっちゃけこの技術スタックを実務で使ったことないからよくわからん。
にもかかわらず、ノリで構築したらさっくり完成しました。
色々ケアできてないとは思うが、何度かやりとりすればいいコードになるのは確実かなと。

ちなみにですが、特に指示してないのに以下の内容も教えてくれました(親切すぎる)

  • 必要なパッケージのインストール方法
  • ormconfig.json
  • tsoa.json
  • package.json

GitHub Copilotのターン

GitHub Copilotに放り込んだらどこまでいい感じにしてくれるんだろう...(´・ω・`)
と思ったので実際にやってみた。

  • バリデーション関連についてさくっとやってもらうことが目的。
  • nest.jsで利用しているライブラリをsuggestしてきたので、一部手動で別関数に置き換えてます。
  • chatGPTのAPI叩いても同じことはできそうだけども、トークン関連がよくわかっていないので今回は実施せずで。
user.service.ts
import { User } from "../entities/user";
import { UserRepository } from "../repositories/user-repository";
import { provideSingleton } from "../middlewares/inversify/ioc-util";
import { getCustomRepository } from "typeorm";
import { ClientError } from "../middlewares/client-error";

@provideSingleton(UserService)
export class UserService {
  private userRepository: UserRepository;

  constructor() {
    this.userRepository = getCustomRepository(UserRepository);
  }

  async getAllUsers(): Promise<User[]> {
    const users = await this.userRepository.find();
    if (!users) {
      throw new ClientError(404, `Users not found`);
    }
    return users;
  }

  async getUserById(id: number): Promise<User> {
    return await this.userRepository.findOneOrFail(id);
  }

  async createUser(user: Partial<User>): Promise<User> {
    const newUser = await this.userRepository.save(user);

    if (!newUser) {
      throw new ClientError(
        500,
        "User creation failed. Please try again later."
      );
    }

    return newUser;
  }

  async updateUser(id: number, user: Partial<User>): Promise<User> {
    try {
      const userToUpdate = await this.userRepository.findOneOrFail(id);
      const updatedUser = Object.assign(userToUpdate, user);
      await this.userRepository.save(updatedUser);
      return updatedUser;
    } catch (error) {
      throw new ClientError(404, "User not found");
    }
  }

  async deleteUser(id: number): Promise<void> {
    try {
      const user = await this.userRepository.findOneOrFail(id);
      await this.userRepository.remove(user);
    } catch (error) {
      throw new ClientError(404, "User not found");
    }
  }
}

user.controller.ts
import {
  Controller,
  Route,
  Post,
  Body,
  Get,
  Path,
  Tags,
  SuccessResponse,
  Put,
  Delete,
  Query,
} from "tsoa";
import { User } from "../../entities/user";
import { ClientError } from "../../middlewares/client-error";
import { provideSingleton, inject } from "../../middlewares/inversify/ioc-util";
import { UserService } from "../../services/user-service";

@Route("users")
@Tags("user")
@provideSingleton(UserController)
export class UserController extends Controller {
  @inject(UserService) private userService: UserService;

  constructor() {
    super();
    this.userService = new UserService();
  }

  @Get()
  public async getAllUsers(): Promise<User[]> {
    return this.userService.getAllUsers();
  }

  @Get("{id}")
  public async getUserById(@Path() id: number): Promise<User> {
    if (isNaN(id) || id < 1) {
      throw new Error("Invalid ID");
    }
    return this.userService.getUserById(id);
  }

  @Post()
  public async createUser(@Body() user: Partial<User>): Promise<User> {
    if (!user) {
      throw new Error("Invalid user");
    }
    return this.userService.createUser(user);
  }

  @Put("{id}")
  public async updateUser(
    @Path() id: number,
    @Body() user: Partial<User>
  ): Promise<User> {
    if (isNaN(id) || id < 1) {
      throw new Error("Invalid ID");
    }
    if (!user) {
      throw new Error("Invalid user");
    }
    return this.userService.updateUser(id, user);
  }

  @Delete("{id}")
  public async deleteUser(@Path() id: number): Promise<void> {
    if (isNaN(id) || id < 1) {
      throw new ClientError(404, "Invalid ID");
    }
    return this.userService.deleteUser(id);
  }
}

感想

ほっとんどコード書かずにここまでできました(´・ω・`)

ビジネスロジックはコーディングしないといけないとダメかなと思ったけど、要件定義のタイミングで雑にChatGPTに放り込んでもそれらしいものは出てきそうな気もしている。
実際にはそこまで単純なものではないけど、作業速度はアホみたいに上がることは間違いないよね。

今後の流れがどうなるか楽しみな反面、どのように「エンジニア」として戦っていくかは常に考えないといけないですな(´・ω・`)

Discussion