Slide 1

Slide 1 text

Cursor でアプリケーションの追加開発や保守を どこまでできるか試したら得るものが多かった話 2024/11/27 Classmethod AI Talks(CATs)#9 クラスメソッド株式会社 リテールアプリ共創部 中野ヨシユキ

Slide 2

Slide 2 text

みなさん、開発で生成AI (LLM) 使ってますか?

Slide 3

Slide 3 text

『Cursor で開発生産性を上げたい!』という方 3

Slide 4

Slide 4 text

『ハルシネーションを起こして開発現場で全然使えない!』 4

Slide 5

Slide 5 text

という方に向けてお話します 5

Slide 6

Slide 6 text

自己紹介 部署 クラスメソッド株式会社 産業支援グループ リテールアプリ共創部 職務 ソフトウェアエンジニア 名前 中野ヨシユキ (@engin_yo) 拠点 福岡オフィス 趣味 ランニング(マラソン大会継続参加中) 自作キーボード 6

Slide 7

Slide 7 text

リテールアプリ共創部と私の所属チーム DevelopersIO ブログ 『共にアプリを創造する。リテールアプリ共創部のご紹介。 』より 7

Slide 8

Slide 8 text

本日伝えたいこと3 行まとめ Cursor (というか生成AI )の得意なこと、不得意なことを理解する その前提を理解したうえでCursor の便利機能をフル活用する 結果、エンジニアのアウトプット出力を増やす🎉 8

Slide 9

Slide 9 text

目次 Cursor の概要 Cursor 利用前の課題感と解決策 生成AI の『得意な領域』と『不得意な領域』 Cursor を使った追加開発の流れ まとめ 9

Slide 10

Slide 10 text

Cursor の概要 10

Slide 11

Slide 11 text

Cursor とは AI 搭載コードエディター VS Code からフォークされて開発されているため、VS Code の使用感そのまま プランによって使える機能が変わる(モデル、機能など) Privacy Mode はマストでON にする(OFF の場合は、プロンプトが学習で利用されてしまう) 11

Slide 12

Slide 12 text

Cursor で主に使う画面部分 AI ペインで、AI とのチャットの会話が可能 ターミナルやエディター上でもショートカットコマンドを使ったAI との対話が可能 12

Slide 13

Slide 13 text

個人的に便利で多用している機能〜Debug 機能〜 直感的なUI を持ち、コーディング中にAI と対話しながら作業を進められる 特定のコードについて質問したり、改善点を尋ねたりすることが可能 AI による自動デバッグ機能もあり、エラーの原因を特定し、修正案を提示してくれる 13

Slide 14

Slide 14 text

Debug 機能の例 React のStrictMode をインポートし忘れている例 [AI Fix In Chat] をクリックする 14

Slide 15

Slide 15 text

Debug 機能の例 15

Slide 16

Slide 16 text

個人的に便利で多用している機能〜AI チャットとシンボル参照〜 AI チャットで複数のモデルを選択可能 プロンプトだけではなくエディターで最前面に開いているファイルをモデルに自動的に送信 @ 記号を使った「シンボル参照」という機能をつかうと、特定の参照をモデルに送信可能 例 プロジェクト内の特定のFolder 単位 プロジェクト内の特定のCode 単位 Web 単位(Web から検索する) 特定のURL 単位 @Docs のカスタムドキュメント追加がオススメ TypeScript のコーディング規約や特定のライブラリのドキュメントを参照しておくことで、URL の内容を自動的にクロールしてインデックスとしてCursor 側が保持してくれる 16

Slide 17

Slide 17 text

AI チャットとシンボル参照の例 インデックスするドキュメント追加 回答結果 17

Slide 18

Slide 18 text

Cursor 料金プラン Cursor 公式サイトより(2024/11/27 現在) 18

Slide 19

Slide 19 text

個人的な使い方 Cursor 無料プラン + API Key の設定 従量課金制の利用ができる API Key を設定例 OpenAI API Key OpenAI のコンソールで発行可能 Google AI Studio Key 無料枠があるが基盤モデルの学習にデータ利用されるため、有料枠にする 19

Slide 20

Slide 20 text

Cursor 利用前の課題感と解決策 20

Slide 21

Slide 21 text

Cursor 利用前の課題感 プロジェクト引き継ぎのコンテキストキャッチアップ: アプリケーションの運用保守の引き継ぎ時に、詳細なドキュメントやコードを読む必要があり、 時間がかかっている 各プロダクトの初期開発メンバーのアーキテクトによって設計の癖があったり方針があるため差 異を感じ取る必要がある 設計方針に従った修正や差分開発: 既存のアーキテクチャを無視した変更により技術的負債が発生してしまう 負債の少ない開発を目指すために既存の設計方針に基づいた差分開発を意識する必要がある 初期メンバーは不在のためキャッチアップはほぼ困難 21

Slide 22

Slide 22 text

『プロジェクト引き継ぎのコンテキストキャッチアップ』の解決策 方法 GitHub Wiki や専用のGitHub リポジトリでドキュメント管理している場合、Wiki ごとローカルに クローンしてCursor のチャットでドキュメント参照できる Cursor のFolder 参照でWiki のあるディレクトリごと読み込ませることができる 結果 コンテキストキャッチアップの効率が格段に上がった ある種、そのプロダクト専用のRAG をプログラミングなしでCursor につくることができるでオス スメ ただし、ドキュメントが不足している箇所はハレーションをおこしやすいため最終的な判断は自 分でやる 22

Slide 23

Slide 23 text

Folder 参照 フォルダ構成 (Wiki をプロジェクトコードベースと並列におく) 回答結果 23

Slide 24

Slide 24 text

『設計方針に従った修正や差分開発』の解決策 プロジェクト単位とユーザー単位でCursor に対して振る舞いを調整できるルールを設定できる ユーザー単位 Rule for AI 出力フォーマット(日本語で回答して、など)を調整させている プロジェクト単位 .cursorrules プロダクトごとにコンテキスト、コーディング規約、技術スタックを明示しておく .cursorrules をプロダクトごとに育てていくことで、ルールを強制しやすくなりそう 24

Slide 25

Slide 25 text

生成AI の『得意な領域』と『不得意な領域』 25

Slide 26

Slide 26 text

回答は確率論的 同じ入力に対して同じ回答が得られるとは限らない 確率的であることが大前提である以上、回答が正しいかどうかの判断は自分に委ねられる 質問: Python でFuzzFizz を ワンライナーでかいて print('\n'.join(['FizzBuzz' if i % 15 == 0 else 'Fizz' if i % 3 == 0 else 'Buzz' if i % 5 == 0 else str(i) for i in range(1, 101)])) print('\n'.join(map(lambda i: 'FizzBuzz' if i % 15 == 0 else 'Fizz' if i % 3 == 0 else 'Buzz' if i % 5 == 0 else str(i), range(1, 101)))) print('\n'.join(['Fizz' * (i % 3 == 0) + 'Buzz' * (i % 5 == 0) or str(i) for i in range(1, 101)])) 26

Slide 27

Slide 27 text

生成AI の『得意な領域』と『不得意な領域』 得意な領域 既存で学習された情報、もしくは、指示した内容の中にある情報から、確率的に正しいと思われ る内容を判断にして回答を生成 粒度が小さく、理解が十分可能な領域の回答の生成 不得意な領域 学習されていない範囲外の情報に基づいた判断できず生成しても間違っている 曖昧なタスクの指示による生成 0 -> 1 などまだ世に出てきていないクリエイティブな分野 27

Slide 28

Slide 28 text

生成AI の『得意な領域』と『不得意な領域』 つまり人間にできることは、まだまだいっぱいある というより、逆説的に人間にしかできないことはなくならないかもしれない なので、やらせたい仕事を小さく分解して、得意な領域だけAI にやってもらうスタンス やらせたい仕事を最大限効率化するために、Cursor の便利機能を駆使する 28

Slide 29

Slide 29 text

アプリケーション保守のお仕事を分解してみる 仕事一覧 初期開発からの既存プロダクトの引き継ぎ 追加開発 ソースコード解析 定型的な運用業務 不具合調査 障害対応 ライブラリアップデート 29

Slide 30

Slide 30 text

アプリケーション保守のお仕事を分解してみる 仕事一覧 初期開発からの既存プロダクトの引き継ぎ ★非定型 追加開発 ★非定型 ソースコード解析 ★非定型 定型的な運用業務 ★定型 不具合調査 ★非定型 障害対応 ★非定型 ライブラリアップデート ★非定型 30

Slide 31

Slide 31 text

アプリケーション保守のお仕事を分解してみる 仕事一覧 初期開発からの既存プロダクトの引き継ぎ ★非定型 追加開発 ★非定型 ソースコード解析 ★非定型 定型的な運用業務 ★定型 不具合調査 ★非定型 障害対応 ★非定型 ライブラリアップデート ★非定型 非定型でもさらに分解すればAI に任せられる作業はある 31

Slide 32

Slide 32 text

アプリケーションの追加開発のやり方を分解してみる 追加開発のやり方をさらに分解してみる 要求定義 要件定義 既存のコード仕様把握 ★得意な領域 設計(基本設計ベース) 設計レビュー ★一部得意な領域 コーディング テストコードを書く ★一部得意な領域 プロダクションコードを書く ★一部得意な領域 自分でコードレビューする 他者がコードレビューする ★一部得意な領域 コードをデバッグして修正 ★一部得意な領域 32

Slide 33

Slide 33 text

ドキュメンテーション ★得意な領域 デプロイ作業 Cursor を使った追加開発の流れ 33

Slide 34

Slide 34 text

ここで言及する追加開発の大前提 初期開発が終わって運用にのっている前提 既存の設計をベースに新しい機能を差分で開発していく(= 追加開発) 既存のプロダクトのキャッチアップや設計意図の理解は実施されている状態 保守性を見越したソフトウェアアーキテクチャによるコーディングを前提にしている ※ ただし、以下は今回の対象外 作成したコードを保守せずに単発利用したい 将来的に開発速度が低下してもよい理由があり、そういう判断でも差し支えない 34

Slide 35

Slide 35 text

プログラミングの原理原則 コードを書くということは解決したい複雑な問題を小さい問題に分割していく作業と同義 大きい問題を小さい問題に切り分けて、各問題を解くためのまとまりをクラスやメソッドでわけて それらを呼び出しあう あすクラスが別のクラスを呼び出すときに依存が発生する 依存が強すぎると、コード保守性(モジュール性やテスタビリティなど)が下がる なので、SOLID 原則(依存性逆転やインターフェイスなど)の手法を使ってレイヤー分けする このような原理原則を理解して生成AI を使いこなさないと、いずれ誰も保守できなくなる 35

Slide 36

Slide 36 text

前提の原理原則:クラスA がクラスB に直接依存している状態 A 機能 クラスA クラスB 依存 36

Slide 37

Slide 37 text

前提の原理原則:クラスA がクラスB に直接依存している状態 クラスA のテストコードを書くためにはクラスB のモック(jest.fn など)のセットアップが必要 クラスA のテストを書くためにはクラスB の実装を用意しないといけない 1 // クラスB.ts 2 export class クラスB { 3 public sayHello(): string { 4 return " こんにちは、クラスB です!"; 5 } 6 } 7 8 // クラスA.ts 9 import { クラスB } from "./ クラスB"; 10 11 export class クラスA { 12 private b: クラスB; 13 14 constructor() { 15 this.b = new クラスB(); 16 } 17 18 public greet(): string { 19 return this.b.sayHello(); 20 } 21 } 22 37

Slide 38

Slide 38 text

23 // 使用例 24 const a = new クラスA(); 25 console.log(a.greet()); // " こんにちは、クラスB です!" 前提の原理原則:インターフェイスを使って間接的に依存している状態 A 機能 クラスA インターフェースB クラスBImpl クラスB テスト⽤ダミーImpl 依存 依存 依存 38

Slide 39

Slide 39 text

前提の原理原則:インターフェイスを使って間接的に依存している状態 // インターフェースB.ts export interface インターフェースB { メソッド(): string; } // クラスBImpl.ts import { インターフェースB } from "./ インターフェースB"; export class クラスBImpl implements インターフェースB { メソッド(): string { return " クラスBImpl のメソッドが呼ばれました"; } } // クラスB テスト用ダミーImpl.ts import { インターフェースB } from "./ インターフェースB"; export class クラスB テスト用ダミーImpl implements インターフェースB { メソッド(): string { return " クラスB テスト用ダミーImpl のメソッドが呼ばれました"; } } 39

Slide 40

Slide 40 text

前提の原理原則:インターフェイスを使って間接的に依存している状態 クラスA はクラスB の実装に依存せず、クラスA のテストコードをかけるようになる テストでは、クラスB のダミーオブジェクトの実装クラスを使って、テスト対象であるクラスA に依 存を注入してあげればテストできる 1 // クラスA.ts 2 import { インターフェースB } from "./ インターフェースB"; 3 4 export class クラスA { 5 private b: インターフェースB; 6 7 constructor(b: インターフェースB) { 8 this.b = b; 9 } 10 11 public 実行(): string { 12 return this.b. メソッド(); 13 } 14 } 15 16 // 使用例 17 const bImpl = new クラスBImpl(); 18 const a = new クラスA(bImpl); 19 console.log(a. 実行()); // " クラスBImpl のメソッドが呼ばれました" 40

Slide 41

Slide 41 text

Cursor を使ったコーディングの全体の流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 41

Slide 42

Slide 42 text

コーディングの流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 42

Slide 43

Slide 43 text

Interface 例 参考:https://github.com/drumnistnakano/react-clean-architecture-sample 1 // photo-album/src/core/domain/repository/photo-repository.ts 2 /** 3 * Photo リポジトリインターフェース 4 * 5 * @interface PhotoRepository 6 */ 7 export interface PhotoRepository { 8 /** 9 * 指定されたアルバムID の写真を取得します 10 * @param {string} albumId 11 * @return {*} {Promise} 12 * @memberof PhotoRepository 13 */ 14 findByAlbumId(albumId: string): Promise; 15 } 43

Slide 44

Slide 44 text

コーディングの流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 44

Slide 45

Slide 45 text

テストコードの事前準備 テストファイルを作る テスト対象の依存する別の関数がある場合、その関数を偽装した戻り値を返すダミーオブジェクト を作っておく 1 // photo-album/src/core/domain/repository/photo-repository.dummy.ts 2 import type { 3 FindPhotosByAlbumIdResult, 4 PhotoRepository, 5 } from "./photo-repository"; 6 7 export type PhotoRepositoryDummyProps = { 8 findByAlbumIdReturnValue: FindPhotosByAlbumIdResult; 9 }; 10 11 export class PhotoRepositoryDummy implements PhotoRepository { 12 readonly #findByAlbumIdReturnValue: FindPhotosByAlbumIdResult; 13 14 constructor(props?: PhotoRepositoryDummyProps) { 15 this.#findByAlbumIdReturnValue = props?.findByAlbumIdReturnValue ?? { 16 success: true, 17 data: [], 18 }; 19 } 20 45

Slide 46

Slide 46 text

テストコードの事前準備 Cursor が、テスト対象に必要な別の関数をモック(jest.mock など)で実装してしまわないように、 ダミーオブジェクトの使い方の例をプロンプトで例示する 参考:https://github.com/drumnistnakano/react-clean-architecture-sample 1 // photo-album/src/core/infrastructure/repository/photo-repository-impl.small.test.ts 2 const setUpDependencies = ({ 3 getReturnValue, 4 }: { 5 getReturnValue: JsonPlaceholderApiClientDummyProps["getReturnValue"]; 6 }): { 7 photoRepository: PhotoRepositoryImpl; 8 } => { 9 const logger = new LoggerDummy(); 10 const apiClient = new JsonPlaceholderApiClientDummy({ getReturnValue }); 11 const photoRepository = new PhotoRepositoryImpl(apiClient, logger); 12 13 return { photoRepository }; 14 }; 46

Slide 47

Slide 47 text

コーディングの流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 47

Slide 48

Slide 48 text

テストコード生成例 TDD をベースにテストを書く テストケースが少ないと精度がさがるので不足している場合は自分で書く ある程度テストケースがあれば、既存のコードをも元にコードを生成してくれるが間違っていれば 手直しする 1 it("should return all photos for the given album ID on successful fetch", async () => { 2 const { photoRepository } = setUpDependencies({ 3 getReturnValue: { 4 success: true, 5 data: [ 6 { 7 albumId: 1, 8 id: 1, 9 title: "Photo 1", 10 url: "https://via.placeholder.com/600/92c952", 11 thumbnailUrl: "https://via.placeholder.com/150/92c952", 12 }, 13 // --- 省略 --- 14 ] as unknown as JsonPlaceholderApiResponse, 15 }, 16 }); 17 18 const result = await photoRepository.findByAlbumId("1"); 48

Slide 49

Slide 49 text

コーディングの流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 49

Slide 50

Slide 50 text

別観点でのテストケース出し 以下のようなパターンで網羅テストを提案してもらう 状態遷移図をかいてもらってその状態を漏れがないテストパターンを考えてもらう 異常系のテストが漏れている場合があるため、漏れているテストをプロダクションコード側から 見つけてもらう Cursor のFolder 参照やCode 参照で対象ファイル郡を参照して問い合わせることで、既存のコー ドを参考にしながら洗い出してくれる 50

Slide 51

Slide 51 text

コーディングの流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正 他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 51

Slide 52

Slide 52 text

プロダクションコードの生成 Cursor のFolder 参照やCode 参照で、既存のファイルを参照して参考にしながらコード生成は可能 間違っている場合もあるので、そのときは手直し 一気に書いてもらうより少しずつ書いてもらいながら進めるとよい 52

Slide 53

Slide 53 text

うまくいった使用ケース 一部のコード生成: 既存のコードを理解しながら、新しい機能の実装にはうまくいく(追加開発に向いている) ただし、クラス全体を一気につくりこむことは困難なため、一部の関数だけステップバイステッ プで作成する 部分的なテストケース作成: 既存でテストが十分に賄えているファイルの追加は比較的簡単 モックをつかわずにダミーオブジェクトを依存注入する方法でテストコードをかきたくても、モ ックでのテストコードを執拗に提案してくるため、プロンプトで例示したらうまくいった 53

Slide 54

Slide 54 text

うまくいかない使用ケース テストコードもプロダクションコードも一括生成 Cursor がRAG のようにつかえるので、万能につかえそうにおもうが、これは今のところ難しい 結果的にすべての設計見直しになってしまいかねないので、少しずつ利用する 一部分ずつコードを生成していったり、コードを書くためのある程度の環境セットアップはこち らで用意しておく必要がある 54

Slide 55

Slide 55 text

これからCursor を使ってコーディングを始める方向け AI を活用してトライアンドエラーを高速に試せる 本質を理解する必要もあるが、過去とくらべてその本質を理解する時間は早くなったと思う 学習速度が早くなるツールのため、初学者だけではなく経験者にも有用 現時点で使わないより使ったほうが得られるものも多く、Cursor などのAI エディターを使うのが最 善 55

Slide 56

Slide 56 text

まとめ 現時点のAI を使った開発ツールの得意なこと、不得意なことを理解: 作業領域を分解して得意な領域は生成AI にどんどん任せる 不得意なな領域は、人間が意思決定(判断/ 決断)をおこなう コーディング作業効率化や学習速度への貢献: 特性を理解してCursor の機能を最大限活かせば、アプリケーションの開発生産性寄与の効率化に 貢献しそう 解決したい対象領域の本質的な学習、経験値獲得は並行して行う 56

Slide 57

Slide 57 text

参考 Cursor - Build Software Faster Cursor - The AI Code Editor Cursor Community Forum - The official forum to discuss Cursor. 57

Slide 58

Slide 58 text

58