結論:AIはコードを書ける。でも「動くか」は人間が回さないと分からない

「AIにコードを書かせれば、もうプログラミングは要らない」とよく言われます。半分は本当で、半分は嘘です。

この記事では、AIエージェント(Claude Code)に小さなツールを実際に作らせた全工程を、出てきたコードと実行ログそのままで公開します。先に結論を言うと、AIが最初に出したコードは一発では動きませんでした。しかも、そのバグは「AIがサボったから」ではなく、AIにコードを書かせるときに構造的に起きるものでした。

題材は「指定したフォルダの中から、中身が同じ重複ファイルを探すCLIツール」。よくある実用ニーズです。これをAIに作らせ、実際のファイルで動かし、バグを踏み、直すまでをやります。AIにコードを書かせている人、これから任せたい人が、どこを自分で確認すべきかが分かる内容にしました。

実行日は2026年5月17日。Pythonが入っていれば誰でも同じことを再現できます。

なぜ「重複ファイル検出ツール」を題材にしたか

AIの実力を見るには、簡単すぎる課題ではダメです。「Hello Worldを書いて」では差が出ません。

重複ファイル検出は、見た目は単純ですが落とし穴があります。テキストファイルだけでなく、画像や動画のようなバイナリファイルも対象になる。ファイル名が違っても中身が同じなら重複。サイズが大きいファイルもある。この「言われなくても考慮すべき条件」を、AIが自分で気づいて織り込めるかが見どころです。

そして実務でよく欲しくなるツールでもあります。写真フォルダの整理、ダウンロード地獄の掃除など。読者がそのまま使える実用性もあるので、これを選びました。

Step 1:AIへの最初の指示と、出てきたコード

最初の指示はあえてシンプルにしました。実際に多くの人がやる指示の出し方です。

「指定フォルダ内の重複ファイルを内容のハッシュで検出するPython CLIを書いて」

これに対してAIが出してきたのが、次のコードです(実物そのまま)。

#!/usr/bin/env python3
"""フォルダ内の重複ファイルを内容ハッシュで検出するCLI(v1)。"""
import sys, os, hashlib

def file_hash(path):
    h = hashlib.md5()
    # テキストとして開いて読む(素朴な実装)
    with open(path, "r") as f:
        h.update(f.read().encode())
    return h.hexdigest()

def main(root):
    seen = {}
    for dirpath, _, files in os.walk(root):
        for name in files:
            p = os.path.join(dirpath, name)
            digest = file_hash(p)
            seen.setdefault(digest, []).append(p)
    for digest, paths in seen.items():
        if len(paths) > 1:
            print("重複:", paths)

if __name__ == "__main__":
    main(sys.argv[1])

パッと見、悪くないコードです。os.walk でサブフォルダも辿るし、MD5ハッシュで中身を比較している。ロジックとしては正しい。ここでコードレビューだけして「OK」と言ってしまう人が多いと思います。私もコードだけ見たら通していたかもしれません。

問題はここからです。

Step 2:実際に動かしたら、一発で落ちた

テスト用に、わざと現実に近いフォルダを用意しました。中身が同じテキストファイル2つ、ユニークなテキスト1つ、そして中身が同じ画像(バイナリ)2つです。実務のフォルダはこういう混在が普通です。

このフォルダに対してv1を実行しました。結果がこれです(実行ログそのまま)。

Traceback (most recent call last):
  File "dedup_v1.py", line 24, in <module>
    main(sys.argv[1])
  File "dedup_v1.py", line 17, in main
    digest = file_hash(p)
  File "dedup_v1.py", line 9, in file_hash
    h.update(f.read().encode())
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc6 in position 0: invalid continuation byte

UnicodeDecodeError。バイナリファイル(画像)を open(path, "r") でテキストとして読もうとして、文字コード変換に失敗してクラッシュしました。

これはプログラミングをやる人にはおなじみの初歩的ミスです。ファイルのハッシュを取るならバイナリモード("rb")で開くのが鉄則。AIはそれを知らないわけがありません。なのに、なぜこのコードを書いたのか。ここがこの記事の核心です。

なぜAIは「正しそうで動かないコード」を書くのか

AIは指示の言葉に最適化してコードを書きます。今回の指示は「重複ファイルを検出するCLIを書いて」。この文面だけ見れば、テキストファイルを読むコードは「正しい」のです。指示の中に「画像も対象」とは書いていないからです。

人間の熟練エンジニアなら、「重複ファイル検出」と聞いた瞬間に「画像や動画も来るな、ならバイナリで開かないと」と暗黙の前提を補います。この暗黙の前提の補完が、AIは指示されないと弱い。これがAIにコードを書かせるときに構造的に起きるズレです。

つまり、AIが出すコードは「指示した条件では正しい」。でも実務のデータは指示に書いていない条件を必ず含んでいる。だから、コードレビューだけでは不十分で、実データで一度回すまで「動く」とは言えないのです。これはAIの能力不足というより、指示と現実のギャップの問題です。

Step 3:直し方の指示で結果が変わる

ここでAIに修正を依頼します。指示の出し方で結果が大きく変わるので、2パターン比較します。

悪い指示は「エラーが出たから直して」。これだとAIはエラー箇所だけを最小限いじり、根本原因を踏まえない場当たり修正になりがちです。

良い指示は、現象と原因仮説をセットで渡すこと。私はこう指示しました。

「画像などバイナリファイルでUnicodeDecodeErrorになった。ファイルはテキストと限らないので、ハッシュ計算をバイナリ前提に直して。あと大きいファイルでもメモリを食わないようにして」

原因(バイナリをテキストで開いている)と、ついでに改善してほしい点(大きいファイル対策)まで渡しました。返ってきたのがv2です(実物)。

#!/usr/bin/env python3
"""フォルダ内の重複ファイルを内容ハッシュで検出するCLI(v2)。"""
import sys, os, hashlib

def file_hash(path, chunk=65536):
    h = hashlib.md5()
    with open(path, "rb") as f:               # "r" から "rb" へ
        for block in iter(lambda: f.read(chunk), b""):
            h.update(block)
    return h.hexdigest()

def main(root):
    seen = {}
    for dirpath, _, files in os.walk(root):
        for name in files:
            p = os.path.join(dirpath, name)
            seen.setdefault(file_hash(p), []).append(p)
    found = False
    for digest, paths in seen.items():
        if len(paths) > 1:
            found = True
            print(f"[重複] {digest[:8]}")
            for p in paths:
                print(f"  - {p}")
    if not found:
        print("重複ファイルはありませんでした")

if __name__ == "__main__":
    main(sys.argv[1])

修正は2点。open(path, "rb") でバイナリモードに変更。さらに、ファイルを一気に read() せず6万バイトずつチャンクで読むようにして、巨大ファイルでもメモリを食わないようにしてあります。出力も「どのファイルが重複か」が読みやすい形になりました。原因と要望を具体的に渡すと、AIは指示以上の改善まで返してきます。指示の解像度が、出力の質に直結します。

Step 4:v2を実行。今度は正しく動いた

同じテストフォルダでv2を実行した結果です(実行ログそのまま)。

[重複] 6f5902ac
  - testdir/a.txt
  - testdir/b_copy.txt
[重複] 8189bf88
  - testdir/img1_dup.bin
  - testdir/img1.bin

テキストの重複ペア(a.txt と b_copy.txt)と、バイナリの重複ペア(画像2つ)の両方を正しく検出。ユニークなファイルは重複として出ていません。クラッシュもしません。これでようやく「動くツール」になりました。

ここまでの工程を整理します。

工程 内容 結果
v1:素朴な指示 「重複検出CLIを書いて」だけ コードは正しそう
v1を実データで実行 バイナリ混在フォルダ クラッシュ(UnicodeDecodeError)
v2:原因+要望つき指示 原因と改善点を明示 バイナリ対応+メモリ対策まで改善
v2を実データで実行 同じフォルダ 正しく重複検出

コードを見ていた段階では、v1もv2も「正しそう」でした。差が出たのは実データで回した瞬間だけです。

Step 5:v2が動いた後に、もう一段だけ意地悪をした

ここで止めてもよかったのですが、この記事で「人間が想定外の入力を試せ」と書いておいて自分がやらないのは筋が通りません。なので実際にもう一段試しました。フォルダの奥深く(サブフォルダのさらに下)に、既存ファイルと同じ中身のファイルを置く。さらに、中身が空のファイルを2つ置く。この状態で再実行した結果がこれです(実行ログそのまま)。

[重複] d41d8cd9
  - testdir/empty2.dat
  - testdir/empty1.dat
[重複] 6f5902ac
  - testdir/a.txt
  - testdir/b_copy.txt
  - testdir/sub/deep/a_again.txt
[重複] 8189bf88
  - testdir/img1_dup.bin
  - testdir/img1.bin

良かった点は、深い階層に置いたファイルもちゃんと同じグループに束ねられたこと(3つに増えています)。os.walk の再帰は効いている。

問題は1行目です。中身が空のファイル2つが「重複」として検出されました。ハッシュ d41d8cd9 は、空データのMD5として有名な値です。技術的には「中身が同じ(どちらも空)」なので正しい。でも、フォルダ整理の道具としては、空ファイル同士を重複扱いされても普通は嬉しくありません。0バイトのログファイルや書きかけのメモが、全部「重複グループ」にまとめられてしまうからです。

これはAIが悪いのではありません。私が「重複とは何か」をAIに厳密に定義しなかったから、AIは一番素直な定義(バイト列が一致)で実装した。それだけです。実務でこのツールを使うなら、「サイズ0は対象外にする」か「空ファイルは別表記にする」という仕様を人間が決めて、追加で指示する必要があります。仕様の曖昧さは、実データで回して初めて表面化する。これもコードレビューだけでは絶対に出てこなかった発見です。

つまり検証は1回では足りません。「動いた」の後に、想定外の入力でもう一段叩く。ここまでやって、ようやくツールの素性が見えます。

AIにツール開発を任せるときの実用ルール

今回の1件から、AIエージェントにコードを任せるときの線引きが見えます。

任せていい 人間が必ずやる
構文・定型ロジックの記述 実データで一度実行する
ライブラリの使い方 想定外の入力(バイナリ・空・巨大)を試す
エラーの修正案 エラーの原因を言語化して渡す
リファクタ・整形 「動いた」の最終判断

ポイントは、AIに「書かせる」のは安全だが、「動くと判断する」のは人間の仕事だということです。AIはコードを書くのは速いし上手い。でも、自分の書いたコードを実環境で回して結果を見ることは(指示しない限り)しません。この検証ループだけは人間が握る。逆にここさえ握れば、開発速度は確実に上がります。

指示の出し方テンプレート(再現用)

今回うまくいった指示の出し方を、そのまま使える形にしておきます。

最初の依頼は「何を・どんな入力で・どう出力するか」を1文ずつ書く。「重複ファイルを検出。入力はフォルダパス。テキストもバイナリも対象。出力は重複グループ一覧」。この「バイナリも対象」を最初から入れていれば、v1のクラッシュは起きませんでした。

修正依頼は「現象+原因仮説+追加要望」の3点セット。「◯◯でエラー。原因は△△だと思う。ついでに□□も改善して」。これがAIから一番良い修正を引き出す形でした。

このツールの使い方(コピペで使えます)

完成したv2は、そのまま実用ツールとして使えます。Pythonが入っていれば追加インストール不要です。

# 上のv2コードを dedup.py として保存して実行するだけ
python3 dedup.py /整理したいフォルダのパス

写真フォルダやダウンロードフォルダを指定すると、中身が同じで名前だけ違うファイルを一覧で出します。削除は自動でしません(重複の一覧表示までで、消すかは人間が判断)。誤削除が怖いツールなので、検出と削除を分けてあります。

FAQ:AIにコードを書かせるときの素朴な疑問

Q. 結局、AIにコードを書かせる意味はあった?

あります。v1からv2までの全工程で、私が書いたコードは1行もありません。書いたのは指示文だけ。バイナリ対応もチャンク読みも、人間が手で書けば調べる時間がかかります。検証だけ人間がやれば、開発は確実に速くなります。

Q. 最初から完璧なコードを出させる方法は?

指示の段階で入力の種類を具体的に書くことです。今回なら「テキストもバイナリも対象」と最初に入れていれば、v1のクラッシュは起きませんでした。AIの精度は指示の解像度で決まります。

Q. AIが「動きました」と言ったら信じていい?

実行ログを見せてもらわない限り信じない方がいいです。AIはコードを書きますが、それを実環境で回したかは別問題。今回も、AIにコードを書かせた直後の段階では、まだ一度も動かしていません。実行は人間か、明示的に指示した場合のAIがやる工程です。

Q. もっと複雑なツールでも同じ進め方でいい?

同じです。規模が大きくなるほど「実データで回す」「想定外の入力を試す」の重要度が上がります。複雑なツールほど、コードレビューだけで品質を判断するのは危険になります。

まとめ:AI開発の品質は「実行ループ」を誰が握るかで決まる

AIエージェントに重複ファイル検出ツールを作らせた実録でした。

要点です。

  • AIが最初に出したコードは、コードとしては正しそうだったが、バイナリファイルで実際にクラッシュした
  • 原因はAIの能力不足ではなく、指示に書いていない暗黙の前提(バイナリ対応)をAIが補完しなかったこと
  • 修正依頼は「現象+原因仮説+追加要望」の3点セットが効く。原因まで渡すと改善まで返ってくる
  • AIにコードを書かせるのは安全。だが「動く」と判断するのは人間の仕事
  • 実データで一度回すこのループだけ人間が握れば、開発速度は確実に上がる

AIにコードを書かせる人が増えていますが、差が出るのは「書かせ方」ではなく「検証の握り方」です。コードレビューで止めず、必ず実データで一度回す。これだけで、AI開発の事故はかなり防げます。

🔗 AI実証シリーズの他の記事
Claudeが手元で本物のAIツールに実接続して動かし、ハマり所をログで暴く一次体験記事。実画面のスクショと固有の数字で記録しています。
n8n×Gemini実証

【ノーコード】n8nとGeminiで問い合わせメールを自動でスプシ転記→Slack通知する仕組みを作る

ノーコード自動化n8nとGeminiで本物の問い合わせ仕分けを実装。「Logsを開いたらAIに本文が一文字も届いていなかった」一次体験ログを公開。
Dify実証

Difyの「質問分類器」ノードを触ってわかった、n8nとの設計思想の決定的な違い

DifyでノードたったThree個で問い合わせ4分類AIを実装。「ステータス成功なのに出力が空っぽ」というn8n経験者ほど踏みやすい静かな事故。