Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cursorでアプリケーションの追加開発や保守をどこまでできるか試したら得るものが多かった話

 Cursorでアプリケーションの追加開発や保守をどこまでできるか試したら得るものが多かった話

本セッションでは、AIを活用した統合開発環境 (IDE)であるCursorを用いて、既存アプリケーションの追加開発や保守作業を行った経験について共有します。従来の開発手法や運用保守でのプロセスを比較しながら、Cursorの実践的な使用方法、使う中で有効活用できたポイントや課題について具体例を交えて紹介します。
https://dev.classmethod.jp/articles/cursor-potential-for-app-development-and-maintenance/

Yoshiyuki Nakano

November 25, 2024
Tweet

More Decks by Yoshiyuki Nakano

Other Decks in Programming

Transcript

  1. 個人的に便利で多用している機能〜AI チャットとシンボル参照〜 AI チャットで複数のモデルを選択可能 プロンプトだけではなくエディターで最前面に開いているファイルをモデルに自動的に送信 @ 記号を使った「シンボル参照」という機能をつかうと、特定の参照をモデルに送信可能 例 プロジェクト内の特定のFolder 単位

    プロジェクト内の特定のCode 単位 Web 単位(Web から検索する) 特定のURL 単位 @Docs のカスタムドキュメント追加がオススメ TypeScript のコーディング規約や特定のライブラリのドキュメントを参照しておくことで、URL の内容を自動的にクロールしてインデックスとしてCursor 側が保持してくれる 16
  2. 個人的な使い方 Cursor 無料プラン + API Key の設定 従量課金制の利用ができる API Key

    を設定例 OpenAI API Key OpenAI のコンソールで発行可能 Google AI Studio Key 無料枠があるが基盤モデルの学習にデータ利用されるため、有料枠にする 19
  3. 『プロジェクト引き継ぎのコンテキストキャッチアップ』の解決策 方法 GitHub Wiki や専用のGitHub リポジトリでドキュメント管理している場合、Wiki ごとローカルに クローンしてCursor のチャットでドキュメント参照できる Cursor

    のFolder 参照でWiki のあるディレクトリごと読み込ませることができる 結果 コンテキストキャッチアップの効率が格段に上がった ある種、そのプロダクト専用のRAG をプログラミングなしでCursor につくることができるでオス スメ ただし、ドキュメントが不足している箇所はハレーションをおこしやすいため最終的な判断は自 分でやる 22
  4. 回答は確率論的 同じ入力に対して同じ回答が得られるとは限らない 確率的であることが大前提である以上、回答が正しいかどうかの判断は自分に委ねられる 質問: 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
  5. アプリケーション保守のお仕事を分解してみる 仕事一覧 初期開発からの既存プロダクトの引き継ぎ ★非定型 追加開発 ★非定型 ソースコード解析 ★非定型 定型的な運用業務 ★定型

    不具合調査 ★非定型 障害対応 ★非定型 ライブラリアップデート ★非定型 非定型でもさらに分解すればAI に任せられる作業はある 31
  6. アプリケーションの追加開発のやり方を分解してみる 追加開発のやり方をさらに分解してみる 要求定義 要件定義 既存のコード仕様把握 ★得意な領域 設計(基本設計ベース) 設計レビュー ★一部得意な領域 コーディング

    テストコードを書く ★一部得意な領域 プロダクションコードを書く ★一部得意な領域 自分でコードレビューする 他者がコードレビューする ★一部得意な領域 コードをデバッグして修正 ★一部得意な領域 32
  7. 前提の原理原則:クラス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
  8. 23 // 使用例 24 const a = new クラスA(); 25

    console.log(a.greet()); // " こんにちは、クラスB です!" 前提の原理原則:インターフェイスを使って間接的に依存している状態 A 機能 クラスA インターフェースB クラスBImpl クラスB テスト⽤ダミーImpl 依存 依存 依存 38
  9. 前提の原理原則:インターフェイスを使って間接的に依存している状態 // インターフェース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
  10. 前提の原理原則:インターフェイスを使って間接的に依存している状態 クラス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
  11. Cursor を使ったコーディングの全体の流れ 流れ 自然言語でクラスのInterface を伝えて作ってもらう ★Cursor テストコードのベースをつくる ★セルフ修正 Interface をもとにテストのベースを書く ★セルフ修正

    他のテストを別の観点で書いてもらったり、テストケースをだしてもらう ★Cursor プロダクションコードを書く ★Cursor にベースを書いてもらう、必要に応じてセルフ修正 41
  12. 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<FindPhotosByAlbumIdResult>} 12 * @memberof PhotoRepository 13 */ 14 findByAlbumId(albumId: string): Promise<FindPhotosByAlbumIdResult>; 15 } 43
  13. テストコードの事前準備 テストファイルを作る テスト対象の依存する別の関数がある場合、その関数を偽装した戻り値を返すダミーオブジェクト を作っておく 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
  14. テストコードの事前準備 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
  15. テストコード生成例 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
  16. 参考 Cursor - Build Software Faster Cursor - The AI

    Code Editor Cursor Community Forum - The official forum to discuss Cursor. 57
  17. 58