はじめに
コードレビューで「このコード、正直クソですね」と言われたとき、
私は反論する代わりに AI に投げた。
「これ、クリーンに直して」
結果として返ってきたのは
静的解析的には美しいが、意味的には壊れたコードだった。
この記事では、
- 元のコード
- AI による修正
- 何がどう壊れたのか
- なぜ AI はそう直したのか
- どう頼めばマシになるのか
を技術的に整理する。
元のコード(問題はあるが意味は通る)
def get_active_users(users):
result = []
for u in users:
if u["active"] and u["last_login"] is not None:
if (datetime.now() - u["last_login"]).days < 30:
result.append(u)
return result
問題点:
- ネストが深い
- 条件が読みづらい
- 意図がコードから見えない
だが
挙動は正しい。
AI に修正を依頼した
「可読性を上げてクリーンにしてください」
AI が返してきたコード
def get_active_users(users):
return [
u for u in users
if u.get("active")
and u.get("last_login")
and (datetime.now() - u["last_login"]).days < 30
]
一見よさそうに見える。
しかしこれには重大な問題がある。
壊れたポイント
1. None チェックの意味が変わった
元コード:
u["last_login"] is not None
AI 版:
u.get("last_login")
これにより:
| 値 | 元の挙動 | AI版 |
|---|---|---|
| None | 除外 | 除外 |
| datetime | 通過 | 通過 |
| 0 | 通過 | 除外 |
| 空文字 | 通過 | 除外 |
意味が変わっている。
2. KeyError が静かに無視される
元コード:
u["active"]
キーがなければ即エラー → データ不正が検知できる。
AI版:
u.get("active")
キーがなくても False 扱い → バグが隠蔽される。
3. 意図が消えた
元のコードは:
- active なユーザー
- last_login が存在し
- 30日以内
という 仕様が構造として読める。
AI 版は:
- 条件の列挙
になり、「なぜそうしているか」が消えた。
なぜ AI はこう直したのか
AI は次の目的関数で動いている:
- 行数を減らす
- ネストを減らす
- Pythonic にする
- flake8 に怒られない
つまり 意味保存ではなく構文最適化 をしている。
クリーンコード ≠ 良いコード
AI が作るコードは:
- クリーン
- 短い
- 一貫している
だが人間の良いコードは:
- 意図が読める
- 壊れたらすぐ気づく
- 将来の修正点が見える
正しい頼み方
❌ 悪い頼み方
「クリーンにして」
「Pythonicにして」
→ 意味を捨てられる
✅ 良い頼み方
「意味を変えずに」
「仕様をコメントに残して」
「異常値はエラーにしたい」
例:
この関数は「active なユーザーで、last_login が None でなく、30日以内の人」を返します。
意味を変えずに、意図が読みやすくなるようリファクタしてください。
人間がやるべきレビュー観点
| 観点 | 確認 |
|---|---|
| 意味保存 | 挙動が変わっていないか |
| エラー検知 | バグを隠していないか |
| 意図表現 | なぜそうしているか読めるか |
| 将来変更 | 条件追加が簡単か |
結論
AI はコードを「整形」はできる。
だが「設計」も「意図」も理解していない。
だから、
AI に任せるべきは構文、
人間が責任を持つべきは意味。
それを逆にした瞬間、
クリーンなクソコードが生まれる。