Cntlog

AIとコーディングの向き合い方

開発全般

投稿日

近年、AI技術の進化は目覚ましく、ソフトウェア開発の世界にも大きな変化をもたらしています。

特に、AIがコードを書いてくれる「AIコーディング」は、エンジニアの生産性を飛躍的に向上させる可能性を秘めた技術として注目を集めており、私もそこに注目している一人です。

この記事では、私自身がAIエディター「Cursor」を使って日々コーディングを行う中で培っている方法をこの記事でご紹介します。

AIエディターCursorを使ったコーディング方法

私がメインで使っているAIエディターは「Cursor」です。

数あるAIエディターの中からCursorを選んだ理由は、VSCodeとほぼ同じUIで強力なAI支援機能が備わっており様々なAI用のインターフェイスが追加されていたからです。

特に、次の2つの機能が私のコーディングスタイルにマッチしていました。

  • Chat機能: まるで経験豊富なエンジニアと会話するように、コーディングの方針について相談できる機能。
  • Composer機能: Chatで決めた方針に基づいて、実際にコードを生成してファイルを書き換えてたり新たに生成してくれる機能。

Chatでコーディングの方針を決める

新しい機能を追加したり、既存のコードをリファクタリングしたりする際には、まずCursorのChat機能を使ってAIに相談します。例えば、ユーザーが入力したキーワードでデータを検索し、結果を一覧表示するUIを作成する場合、私は次のようにChatに質問を投げかけます。

あなた: ユーザーが入力したキーワードでデータを検索し、結果を一覧表示する検索UIを作成したいと考えています。
このUIを設計する上で、どのような点に注意すべきでしょうか?特に、ユーザーが快適に検索できるよう、パフォーマンスやユーザビリティの観点から考慮すべき点を教えてください。
また、検索結果が0件の場合や、エラーが発生した場合のハンドリング方法についてもアドバイスをお願いします。

このように、具体的なUIの目的(キーワード検索と結果表示)と、重視する観点(パフォーマンス、ユーザビリティ)を明示することで、Cursorはより的確なアドバイスを提供してくれます。

Cursor: 検索UIを設計する際には、ユーザー体験を最優先に考えることが重要ですね。以下に、パフォーマンスとユーザビリティの観点から考慮すべき点をいくつか挙げます。

1.  **インクリメンタルサーチの検討**: ユーザーがキーワードを入力するたびに検索を実行することで、即座に結果を確認できるようにします。ただし、サーバーへの負荷も考慮し、リクエストの頻度を適切に調整する必要があります(例:デバウンスの導入)。
2.  **検索結果のページネーション**: 一度に大量の検索結果を表示すると、パフォーマンスが低下する可能性があります。ページネーションを導入し、ユーザーが必要に応じて「次へ」をクリックして結果を閲覧できるようにすることを検討してください。
3.  **ローディング状態の明示**: 検索実行中であることをユーザーに明確に示すため、ローディングインジケーター(スピナーなど)を表示します。
4.  **検索キーワードのハイライト**: 検索結果の中で、ユーザーが入力したキーワードをハイライト表示し、視覚的に分かりやすくします。
5.  **検索結果0件時の対応**: 検索結果が0件だった場合は、「該当する結果がありません」といったメッセージとともに、別のキーワードでの検索を促すようなUIを検討します。
6.  **エラーハンドリング**: APIエラーなどが発生した場合は、ユーザーに適切なエラーメッセージを表示し、再試行を促すか、別の操作を案内します。

これらの点に加えて、モバイルフレンドリーなデザインや、アクセシビリティへの配慮も重要です。具体的な実装方法については、使用しているフレームワークやライブラリによって異なるため、それらのドキュメントを参照することをお勧めします。

この例のように、ユーザー視点に立った設計の提案を受けることで、自分が見落としていたUIの品質向上に繋げることができ次のプロンプトに活かせます。

さらに、エラーハンドリングについても具体的な指示を出すことで、ユーザーに不便を強いることがないよう、適切な対処法を検討することができます。

プロンプトを作る前に具体的な要件や不明点をAIとすり合わせ、認識の相違がないかを確認しあい、最終的にこれから書くコードの品質向上につなげています。

Composerでコードを実装する

Chatでコーディングの方針が決まったら、次はComposer機能を使って実際にコードを書いていきます。

Composerでは、Chatで議論した内容を踏まえて、具体的なコードを生成してファイルに反映する事ができます。

先ほどの検索UIの例で考えてみましょう。Chatでは、インクリメンタルサーチ、ページネーション、ローディング状態の表示などの機能を備えたUIを設計する方針が決まりました。この方針を基に、私はComposerに次のような指示を出します。

あなた: Chatでの議論を元に、Reactを使って検索UIを実装してください。
インクリメンタルサーチ、ページネーション、ローディング状態の表示機能を備え、検索結果が0件の場合やエラー発生時のハンドリングも適切に行ってください。
APIのエンドポイントは`/api/search`で、クエリパラメータ`q`に検索キーワードを指定するものとします。

ここでは、具体的なフレームワーク(React)を指定し、必要な機能(インクリメンタルサーチ、ページネーションなど)を明示しています。また、APIのエンドポイントとクエリパラメータの情報も提供しています。

すると、ComposerはChatでのやり取りとこれらの情報を理解した上で、以下のようなコードを生成してくれます。

import React, { useState, useEffect } from 'react';

function SearchUI() {
  const [keyword, setKeyword] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [page, setPage] = useState(1);
  const resultsPerPage = 10;

  useEffect(() => {
    const timerId = setTimeout(() => {
      if (keyword) {
        search(keyword, page);
      } else {
        setResults([]);
      }
    }, 500); // デバウンス: 500ms

    return () => clearTimeout(timerId);
  }, [keyword, page]);

  const search = async (query, currentPage) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(`/api/search?q=${query}&page=${currentPage}&limit=${resultsPerPage}`);
      if (!response.ok) {
        throw new Error('検索エラーが発生しました');
      }
      const data = await response.json();
      setResults(data);
    } catch (err) {
      setError(err.message);
      setResults([]);
    } finally {
      setLoading(false);
    }
  };

  const handleInputChange = (event) => {
    setKeyword(event.target.value);
    setPage(1); // キーワードが変わったらページをリセット
  };

  const handlePageChange = (newPage) => {
    setPage(newPage);
  };

  return (
    <div>
      <input type="text" value={keyword} onChange={handleInputChange} placeholder="キーワードを入力" />

      {loading && <div>検索中...</div>}

      {error && <div>エラー: {error}</div>}

      {!loading && !error && results.length === 0 && keyword && (
        <div>該当する結果がありません</div>
      )}

      <ul>
        {results.map((item) => (
          <li key={item.id}>
            {/* ここに検索結果の表示内容を記述 */}
            {item.title}
          </li>
        ))}
      </ul>

      {!loading && !error && results.length > 0 && (
        <div>
          <button onClick={() => handlePageChange(page - 1)} disabled={page === 1}>前へ</button>
          <span>{page}</span>
          <button onClick={() => handlePageChange(page + 1)}>次へ</button>
        </div>
      )}
    </div>
  );
}

export default SearchUI;

このコードは、Chatで提案された設計方針に沿って、Reactを使って検索UIを実装しています。

インクリメンタルサーチ(デバウンス付き)、ページネーション、ローディング状態の表示、エラーハンドリングなどの機能が組み込まれています。

Composerが生成したコードは、多くの場合そのまま動作しますが、細かな調整が必要な場合もあります。

例えば、検索結果の表示部分(item.titleの部分)は、実際のデータ構造に合わせて変更する必要があります。また、ページネーションの「次へ」ボタンの無効化条件なども、APIの仕様に合わせて調整が必要になってくるでしょう。

一度で理想的なコードを生成してもらうのではなく、全体像を間違えずに生成してこれから細部を微調整していくやり方を私は好んでやっています。

AIに良いコードを書いてもらうための工夫

CursorのChatとComposer機能を使って、効率的にコーディングを進める方法を説明してきましたが、この章ではAIを使ったコーディングとの向き合い方を紹介しまます。

簡潔にAIに「理想的なコード」を書いてもらうための具体的なテクニックを、5つのポイントに分けて紹介します。

1. プロンプトに画像キャプチャを活用する

AIへの指示(プロンプト)は、テキストだけでなく画像も活用することで、より正確に意図を伝えることができます。

Cursorでは、プロンプトにコピー&ペーストで画像のキャプチャを含めることができるので不具合が起きている画面や参考のUIの指示出しに活用しています。

例えば、既存のUIコンポーネントのデザイン変更を依頼する際に、変更対象のコンポーネントのキャプチャを添付して、「このボタンのデザインを、添付画像のように変更してください」と指示することで、テキストだけで説明するよりも正確かつ効率的に意図を伝えられます。

また、Webサイトのデザインを参考にしたい場合も、参考サイトのスクリーンショットを添付して、「このサイトのヘッダーのようなデザインのコンポーネントを作成してください」と指示することで、AIがより具体的にイメージを掴みやすくなります。

このように、視覚的な情報をプロンプトに含めることで、AIとのコミュニケーションの齟齬を減らしより精度の高いコードを生成できます。

2. 関連ファイルを適切に指定する

大規模なプロジェクトでは、コード全体をAIが常に把握しているわけではありません。

そのため、AIにコードの生成や修正を依頼する際には、関連するファイルを適切に指定することが齟齬を減らせます。

Cursorの良いところでサイドバーから対象のファイルをchatやComposeに指定できるのでとても簡単です。

関連ファイルを適切に指定することで、AIはコードの対象範囲を把握しやすくなり、コンテキストに沿ったコードを生成できるようになります。これは、コードの品質向上だけでなく、手戻りの削減にも繋がります。

3. .cursorrulesを活用して繰り返し指示を減らす

Cursorには.cursorrulesという便利な機能があります。

これは、よく使う指示やルールを定義しておくことで、AIが自動的にそのルールに従ってコードを生成してくれる機能です。

例えば、私はJavaScriptのコーディング規約として、「変数名はキャメルケースを使う」「インデントはスペース2つ」といったルールを.cursorrulesに定義しています。 これにより、毎回同じような指示を繰り返す必要がなくなり、AIが自動的にルールに従ったコードを生成してくれます。

.cursorrulesは、使い込むほどに効果を発揮します。

AIに同じような指示を何度か繰り返していると感じたら、その指示を.cursorrulesに追加することで、徐々に自分好みのコーディングスタイルをAIに学習させることができます。

.cursorrulesを育てていくイメージで、積極的に活用することをお勧めします。

4. 詳細なレビューと質問でコードの理解を深める

AIが生成したコードは、そのまま鵜呑みにするのではなく、必ず自分でレビューするようにしています。

レビューを通じて、コードが何をしているのか理解し、必要に応じて修正や改善を加えることで、正しい指示を自分ができるようにします。

私は、AIが生成したコードをレビューする際には、以下のような点を特に確認しています。

  • コードのロジックは正しいか?
  • 想定通りに動作するか?
  • エラーハンドリングは適切か?
  • 可読性や保守性は高いか?
  • リファクタリングの余地はないか?

また、レビュー中に疑問点があれば、積極的にAIに質問します。

例えば、「この部分のコードは、なぜこのような実装になっているのですか?」「この処理をもっと効率化する方法はありますか?」といった質問を投げかけることで、AIの思考プロセスを理解し、自分自身の学びにも繋げることができます。

また、積極的にリファクタリングの提案をAIに求めることも有効です。「この関数をもっと簡潔にリファクタリングする方法はありますか?」と質問することで、より洗練されたコードを得られる場合があります。

AIはあくまでもツールであり、最終的なコードの品質に責任を持つのは自分自身です。

AIが生成したコードを鵜呑みにするのではなく、詳細なレビューと質問を通じて、コードの理解を深め、品質を高めていくことを忘れてしまうとプログラミング能力の成長は衰退してプロンプトが上手く作れるだけの成長に偏ってしまいます。

AIコーディングは時間がかかる場合もありますが、こうして自分が繰り返し改善を繰り返すことでプロンプトの質が上がり、無駄な手戻りも減らせます。 この「AIを理解する」時間も、開発プロセスの一部として私は重要視しています。

3-5. テストの重要性と細かいコミット

AIにコードの修正を依頼する際には、指示とは関係のない部分が意図せず変更されてしまうことがあります。 このような変更に気づかずに放置してしまうと、思わぬバグの原因となります。

この問題を防ぐためには、テストコードの作成が非常に効果的です。

AIにコードの生成や修正を依頼する際には、必ず関連するテストコードも一緒に作成・更新してもらうようにします。 テストコードがあれば、意図しない変更があった場合に、テストが失敗することで早期に発見できます。

また、コードの変更履歴を細かく記録するために、こまめにコミットすることも重要です。私は、AIによるコード生成や修正を行った際には、できるだけ小さな単位でコミットするように心がけています。

コミットメッセージには、Draft:というプレフィックスを付けて、AIによる作業中であることを明示しています。

最終的にコードが完成し、テストも通ったら、git rebase -iなどのコマンドを使って、Draft:のコミットを1つにまとめ、正式なコミットとして記録します。 この方法により、最終的なコミットログをきれいに保ちながら、作業途中の変更履歴も詳細に残すことができます。

テストと細かいコミットは、AIコーディングに限らず、ソフトウェア開発全般において重要なプラクティスです。これらのプラクティスを実践することで、AIによるコーディング支援の恩恵を最大限に享受しつつ、コードの劣化を防ぎます。

より良いAIコーディングのために

世の中にはCursor以外にも、GitHub Copilotなど、様々なAIコーディングツールが存在します。

それぞれのツールには得意・不得意があるので、複数のツールを併用することで、より幅広いコーディング支援を受けられる可能性があるのでその模索も忘れてはいけません。

例えば、特定のユースケースに特化したAIツールとCursorを組み合わせることで、開発タスクをより効率的に進められます。

最近は私が気になっているのはlovableCline です。
どちらもcursorと併用しながら使えるので模索中です。

プロンプトエンジニアリングを学ぶ

AIコーディングの効率と質は、プロンプトの質に大きく左右されるのでプロンプトエンジニアリングについて学ぶことは、AIコーディングのスキル向上に直結します。

プロンプトエンジニアリングとは、AIから望ましい出力を得るための、効果的なプロンプトを作成する技術です。

具体的には、以下のようなポイントを意識してプロンプトを作成します。

  • 明確かつ簡潔な指示を心がける
  • 具体的な例やコンテキストを提供する
  • AIの役割(ロール)を定義する
  • 出力形式を指定する

さらに、プロンプトエンジニアリングについて体系的に学びたい方には、「プロンプトエンジニアリングガイド」などがおすすめです。
このサイトでは、プロンプト作成のベストプラクティスや、様々なテクニックが紹介されており、実践的な知識を身につけることができます。

AIを利用し、人間が最終的な責任を持つ

AIコーディングは強力なツールですが、まだまだ万能とは言えません。
AIは、学習データに存在しないパターンや、複雑な要件を理解するのが苦手な場合があります。

そのため、AIが生成したコードを鵜呑みにするのではなく、必ず人間がレビューし、最終的な判断を下すことを忘れないようにしたいと考えています。
特に、セキュリティや信頼性が重要なコードについては、人間による入念なチェックが欠かせません。

AIはあくまでも人間の補助ツールであり、開発の主役は人間です。
AIを利用し、適切に活用することで、AIコーディングの真価を発揮させれたらと考えています。

まとめ

この記事では、私がCursorというAIエディターを使って実践している、AIコーディングの具体的な手法と、その中で得られた知見、そしてAIに「良いコード」を書いてもらうための工夫について詳しく解説してきました。

AIコーディングは、プログラマーの生産性を飛躍的に向上させる可能性を秘めた、非常に魅力的な技術です。しかし、その真価を発揮させるためには、AIの特性を理解し、適切に使いこなすためのスキルが求められます。

私がこの記事で最も伝えたかったことは、AIコーディングは「銀の弾丸」ではないということです。

AIはあくまでもツールであり、使う人によってその効果は大きく変わります。
AIの長所と短所を理解し、試行錯誤を繰り返しながら、自分なりの活用方法を見つけていくことが重要です。

その過程では、プロンプトを工夫したり、AIの出力を丁寧にレビューしたり、時には多くの時間を費やすこともあるでしょう。
しかし、そうした試行錯誤の先にこそ、AIコーディングの真の価値があると私は信じています。

AIとの対話を通じて、自身のコーディングスキルを磨き、より創造的な開発を実現する。
それこそが、AI時代のプログラマーに求められる姿ではないでしょうか。

この記事が、皆さんにとってAIコーディングを使いこなすための一助となり、皆さんがAIと共に成長し、より創造的な開発者となるための刺激となれば幸いです。