GitHub Copilotでコーディングする方法
# GitHub Copilotをコーディングに活用する方法(ゴミコードを書かせないために)
先週、GitHub Copilotが書いた関数のデバッグに3時間費やしました。コードは問題なくコンパイルされ、ロジックも正しく見えました。しかし、本番環境で負荷がかかった際のエッジケースでのみ顕在化する、微妙な誤りがありました。Copilotの特徴はここにあります。もっともらしいコードを生成するのが非常に得意ですが、盲目的に信頼すると危険です。
Python、TypeScript、Goのプロジェクトで6ヶ月間毎日Copilotを使用した結果、真に有用なツールとして活用する方法(単なるリスク要因にしない方法)を学びました。
## 実際に機能するセットアップ
まず基本を押さえましょう。私はVS CodeでCopilotを使用していますが、JetBrains、Neovimなどでも同様のパターンが適用できます。
**インストールと認証:**
```
1. GitHub Copilot拡張機能をインストール
2. GitHubアカウントでサインイン(サブスクリプションが必要、月額10ドル、学生・OSSは無料)
3. キーボードショートカットを設定:Tabで受け入れ、Ctrl+Enterで提案パネル表示
```
**多くのチュートリアルが省略する重要な設定変更:** コメントと文字列内でのCopilotの自動提案を無効にしています。理由は、`// TODO: ソートアルゴリズムを実装`と入力すると、Copilotがコンテキストを待たずにバブルソートを埋め込むことがあるからです。そのノイズが時間の無駄になります。
VS Codeのsettings.jsonで:
```json
{
"github.copilot.enable": {
"*": true,
"plaintext": false,
"markdown": false
}
}
```
## 80/20の法則:Copilotが輝く領域
数百回のセッションを経て、Copilotが特に優れているのは以下の3点だとわかりました。
### 1. 明確なパターンに従った定型コード
CRUDエンドポイントを書く場合?Copilotはこれらが定型化されているため完璧にこなします。私が入力するのは:
```python
# app/routes/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
def list_users(
```
この時点で、Copilotは関数全体(データベースクエリ、ページネーション、エラーハンドリング)を提案します。パターンが予測可能なので、90%の確率で受け入れます。
**落とし穴:** Copilotは同じ定型コードでも微妙な矛盾を伴って生成することがあります。コードベース内の異なる例から学習したため、一方のエンドポイントが`offset`ページネーションを使用し、もう一方が`cursor`ページネーションを使用していたことがありました。パラメータ名が一致しているか常に確認してください。
### 2. 正規表現と文字列操作
正規表現を記憶から書くことはできません。Copilotはそれができ、一般的なパターンでは通常正しいです:
```python
def extract_email_addresses(text: str) -> list[str]:
```
Copilotが生成するもの:
```python
import re
pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
return re.findall(pattern, text)
```
この正規表現は標準的で機能します。しかし、「有効なIPv6アドレスにマッチする正規表現」をCopilotに依頼したところ、基本テストは通過するが`::1`のような圧縮表記では失敗するものを生成しました。常にエッジケースをテストしてください。
### 3. 既存コードのテスト
これがCopilotのキラーフィーチャーです。関数を書いた後、次のように入力します:
```python
def calculate_discount(price: float, tier: str) -> float:
if tier == "gold":
return price * 0.8
elif tier == "silver":
return price * 0.9
return price
# テスト:
```
するとCopilotが提案:
```python
def test_calculate_discount():
assert calculate_discount(100, "gold") == 80.0
assert calculate_discount(100, "silver") == 90.0
assert calculate_discount(100, "bronze") == 100.0
assert calculate_discount(0, "gold") == 0.0
```
これを受け入れ、Copilotが見逃したエッジケース(負の価格、未知のティア、浮動小数点の精度)を追加します。
## 誰も語らないプロンプトエンジニアリング
Copilotは魔法ではありません—コンテキストに応答する言語モデルです。以下が導き方です:
**悪いプロンプト:**
```python
# データをソートする関数
def sort_data(data):
```
Copilotはバブルソートを生成したり、カスタムキーなしで`sorted()`を使用したりするかもしれません。
**良いプロンプト:**
```python
# ユーザー辞書のリストをlast_name、次にfirst_nameで大文字小文字を区別せずソート
def sort_users(users: list[dict]) -> list[dict]:
```
するとCopilotは以下を生成:
```python
return sorted(users, key=lambda u: (u['last_name'].lower(), u['first_name'].lower()))
```
違いは具体性です。熱心だが経験の浅いジュニア開発者に説明するようにコメントを書くことを学びました。
## Copilotが嘘をつくとき(そしてその見抜き方)
Copilotが行う最も危険なことは、存在しないAPIをでっち上げることです。
先週の例です。Pythonの`pathlib`で作業していました:
```python
from pathlib import Path
p = Path("/tmp/data")
p.
```
Copilotは存在しない`p.listdir()`を提案しました。正しいメソッドは`p.iterdir()`です。しかし`listdir`は`os.listdir`に存在するためもっともらしく見えます。Copilotが両者を混同したのです。
**私の生存チェックリスト:**
1. **読まずに受け入れない** – 当然のことですが、フロー状態にあるとタブ連打で提案を通したくなります。生成されたコードのすべての行を読むよう自分に強制しています。
2. **すぐに実際のデータでテストする** – 提案を受け入れた後、先に進む前に実際の入力に対して実行します。
3. **幻覚のインポートに注意** – Copilotは存在しないモジュールをインポートしたり、非推奨の関数を使用したりすることがあります。今では最初にインポートを確認しています。
4. **「賢い」コードを疑う** – Copilotがネストされた内包表記と可変デフォルト値を持つワンライナーを提案した場合、おそらく間違っています。単純な辞書で十分な場所で`defaultdict(lambda: [])`が使用され、微妙なミューテーションバグを引き起こしているのを見つけました。
## 実際のワークフロー:CLIツールの構築
実際のセッションを追ってみましょう。CSVファイルを処理するCLIツールを構築しています。
**ステップ1:スケルトン**
入力:
```python
import click
import csv
from pathlib import Path
@click.command()
@click.argument("input_file", type=click.Path(exists=True))
@click.option("--output", "-o", default="output.csv")
def process_csv(input_file, output):
```
Copilotが関数本体を提案:
```python
"""CSVファイルを処理し結果を書き込む"""
data = []
with open(input_file, "r") as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
# データを処理
processed = []
for row in data:
# 処理ロジックを追加
processed.append(row)
with open(output, "w") as f:
writer = csv.DictWriter(f, fieldnames=processed[0].keys())
writer.writeheader()
writer.writerows(processed)
```
このスケルトンを受け入れます。正しいですが空です。次に実際の処理を記述します。
**ステップ2:具体的なロジック**
コメントを追加:
```python
for row in data:
# 'price'列を文字列からfloatに変換、欠損値を処理
```
Copilotが提案:
```python
try:
row["price"] = float(row.get("price", "0.0"))
except ValueError:
row["price"] = 0.0
```
これは90%のケースで機能します。手動で`$`プレフィックスとヨーロッパの小数点カンマの処理を追加します。
**ステップ3:エッジケース**
テストファイルを書きます:
```python
def test_process_csv():
# 空ファイルでのテスト
# カラム欠落でのテスト
# 不正データでのテスト
```
Copilotが基本テストを生成。私が見逃したものを追加:
```python
# ファイル内のBOM文字でのテスト
with open("test_bom.csv", "w", encoding="utf-8-sig") as f:
f.write("\ufeffname,price\nitem,10.0")
```
## 生産性に関する厳しい真実
6ヶ月後、実際の生産性向上は定型コードで約30-40%、新しいアルゴリズムでは0%です。Copilotは以下の用途には役に立ちません:
- システムアーキテクチャの決定
- パフォーマンス最適化
- セキュリティクリティカルなコード(注意しないとSQLインジェクションの脆弱性を生成します)
- 深いドメイン知識を必要とするもの
私が犯した最悪の過ちは、理解できないコードをCopilotに生成させたことです。説明できない方法で`itertools.groupby`を使用する関数がありました。テストは通過し、機能しました。そして1年後、`groupby`がソートされた入力を必要とするのにデータが常にソートされていなかったため、バグが表面化しました。
## 実践的な次のステップ
Copilotの全機能を一度に学ぼうとする代わりに、次のことを行ってください:来週は、テストとドキュメント文字列を書くためだけにCopilotを使用します。それが最もリスクが少なく価値が高い領域です。その出力を批判的に読むことに慣れたら、実装コードに使い始めてください。
そして、コミットする前に必ずテストを実行してください。Copilotは正しく見えるが、最初のエッジケースで失敗するコードを書きます。私はそれを苦い経験で学びました—二度と戻ってこない3時間のデバッグです。