はじめに
この連載では「会話するだけでマクロが直る」という話を何度か書いてきました。ただ、文章でいくら説明するより、修正前と修正後のブックを両方置いておくので、実物を見てくださいというのが一番早いと思い、この記事を書いています。
GitHub に2つのブックを置きました。
- 修正前県内郵便.xlsm ── 私が昔から使ってきたままの状態
- 県内郵便.xlsm ── Claude Code に自作ツール(vba_manager)経由で修正してもらった状態
どちらもマクロ入りの実物です。データは日本郵便が公開している秋田県内の郵便番号データ(約2,300件)なので、そのまま開いて動かせます。
題材:郵便番号検索ブック
やることは単純なブックです。セルB1に地名を入れると、マクロが動いて3行目に住所・郵便番号・読みを表示します。
B1に「秋田市山王」と入力
↓(B1の変更で自動実行)
B3: 秋田県秋田市山王 C3: 010-0951 D3: あきたけんあきたしさんのう
1件に絞り込めないときはオートフィルタで候補を表示して、そこから目で選ぶ。シート上のボタンで番号のコピーや貼り付けもできる。長年これで郵便番号を調べてきました。動作に不満はありませんでした。
Before:修正前の「実行」マクロ
本体の「実行」マクロは、修正前はこうでした。
Sub 実行()
Application.ScreenUpdating = False
Dim i As Integer
Dim xrange As Range
Range("B3").Value = ""
Range("C3").Value = ""
Range("D3").Value = ""
ZZZ = Range("B1").Value
ZZZ = Replace(ZZZ, "市字", "市")
If ZZZ = "" Then Exit Sub
moji = Len(ZZZ)
XXX = ActiveSheet.UsedRange.Rows.Count
BBB = "B7:D" & XXX
CCC = "B7:B" & XXX
For i = 1 To moji
AAA = "*" & Left(ZZZ, i) & "*"
If WorksheetFunction.CountIf(Range(CCC), AAA) = 1 Then GoTo KOKO
Next i
EEE = Sheets("優先リスト").UsedRange.Rows.Count
WWW = "A1:A" & EEE
For Each xrange In Sheets("優先リスト").Range(WWW)
YYY = xrange.Value
AAA = "*" & YYY: If InStr(ZZZ, YYY) > 0 Then GoTo KOKO2
Next
Call 検索ワード選択1
Exit Sub
KOKO:
Range("C3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",2,FALSE)"
Range("B3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",1,FALSE)"
Range("D3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",3,FALSE)"
Selection.AutoFilter Field:=1, Criteria1:="=*" & AAA & "*", Operator:=xlAnd
Range("B6").Select
SendKeys "{DOWN}", True
Exit Sub
KOKO2:
Range("C3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",2,FALSE)"
Range("B3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",1,FALSE)"
Range("D3").Value = "=VLOOKUP(""" & AAA & """" & "," & BBB & ",3,FALSE)"
Selection.AutoFilter Field:=1, Criteria1:="=" & AAA & "*", Operator:=xlAnd
Range("B6").Select
SendKeys "{DOWN}", True
Exit Sub
Application.ScreenUpdating = True
End Sub
ZZZ・AAA・BBB・CCC・EEE・WWW・YYY。変数の宣言はなく、名前から意味は読み取れません。書いた本人(私)は分かっているからこれで困らなかったのですが、人に見せられるかというと、見せられません。
もうひとつ、このマクロには「優先リスト」という手動の名簿がありました。「秋田市山王」のように、名前が他の町名と頭かぶりして機械では1件に絞れない地区を、私が手作業で103件シートに登録しておくという仕組みです。データが更新されるたびに名簿の面倒も見る必要がありました。
After:修正後の「実行」マクロ
Claude Code に修正してもらった後がこうです。
Sub 実行()
'検索語(B1)から住所・郵便番号・読みを1件に絞り込み、B3:D3へVLOOKUPで書き出す本体。
'1件に絞れないときはフィルタだけかけて人の目に渡す。
Application.ScreenUpdating = False
Dim i As Long
Dim 検索語 As String 'B1の入力
Dim 文字数 As Long '検索語の文字数
Dim 最終行 As Long 'データの最終行
Dim 照合パターン As String 'VLOOKUP/CountIf用の照合パターン
Dim 表示パターン As String 'オートフィルタ用の表示パターン
Dim データ範囲 As String '住所・番号・読みのデータ範囲(B7:D…)
Dim 町名範囲 As String '町名だけの範囲(B7:B…)
Dim 語幹 As String '末尾の番地類を剥がした語
Dim 可視セル As Range 'フィルタ後に見えている先頭セル
Range("B3").Value = ""
Range("C3").Value = ""
Range("D3").Value = ""
検索語 = Range("B1").Value
検索語 = Replace(検索語, "市字", "市")
If 検索語 = "" Then
'空欄=検索の取り下げ。フィルタも解除して全データ表示に戻す
If ActiveSheet.FilterMode Then ActiveSheet.ShowAllData
GoTo OWARI
End If
文字数 = Len(検索語)
'UsedRangeはフィルタ非表示行も含む=フィルタ作動中でも検索範囲が縮まない
最終行 = ActiveSheet.UsedRange.Row + ActiveSheet.UsedRange.Rows.Count - 1
If 最終行 < 7 Then GoTo OWARI
データ範囲 = "B7:D" & 最終行
町名範囲 = "B7:B" & 最終行
'段階1: 入力の頭から1文字ずつ伸ばし、含む一致がちょうど1件になったら確定
For i = 1 To 文字数
照合パターン = "*" & Left(検索語, i) & "*"
表示パターン = 照合パターン
If WorksheetFunction.CountIf(Range(町名範囲), 照合パターン) = 1 Then GoTo KOKO
Next i
'段階2: 末尾の番地・丁目などを剥がし、末尾一致がちょうど1件なら親地区と確定
'(飯島・泉など名前が他の町名の頭とかぶる地区の救済。データ更新に自動で追随。
' 無制限に削ると「秋田市」→「*秋田」で大口事業所欄に誤命中するため番地類のみ剥がす)
語幹 = 検索語
Do While Len(語幹) > 1
If InStr("01234567890123456789--丁目番地号 ", Right(語幹, 1)) > 0 Then
語幹 = Left(語幹, Len(語幹) - 1)
Else
Exit Do
End If
Loop
照合パターン = "*" & 語幹
表示パターン = "*" & 語幹 & "*"
If WorksheetFunction.CountIf(Range(町名範囲), 照合パターン) = 1 Then GoTo KOKO
'段階3: 機械では1件に絞れない。フィルタだけかけて人の目に渡す
Application.ScreenUpdating = True
Call 検索ワード選択1
Exit Sub
KOKO:
Range("C3").Value = "=VLOOKUP(""" & 照合パターン & """" & "," & データ範囲 & ",2,FALSE)"
Range("B3").Value = "=VLOOKUP(""" & 照合パターン & """" & "," & データ範囲 & ",1,FALSE)"
Range("D3").Value = "=VLOOKUP(""" & 照合パターン & """" & "," & データ範囲 & ",3,FALSE)"
Range("B6").AutoFilter Field:=1, Criteria1:="=" & 表示パターン, Operator:=xlAnd
On Error Resume Next
Set 可視セル = Range("B7:B" & 最終行).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If 可視セル Is Nothing Then Range("B6").Select Else 可視セル.Cells(1).Select
OWARI:
Application.ScreenUpdating = True
End Sub
何が変わったか
一番の違い:手動の名簿103件が、いらなくなった
修正後のコードで新しく入ったのが「段階2」です。検索語の末尾から数字・ハイフン・丁目・番地・号だけを剥がして、残った語幹の末尾一致がちょうど1件なら親地区と確定する、というロジックです。
これは Claude Code との会話の中で新しく作ってもらったものです。「優先リストを手で更新し続けるのをやめたい」という話から、そもそも優先リストに登録していた地区の正体は「名前が他の町名の頭とかぶる地区」だと整理され、それなら末尾一致で機械的に判定できる、という流れでこの形になりました。
移行する前に、実データで検証もしてもらいました。優先リストに載っていた103地区を1件ずつ、優先リスト無しの新ロジックで検索するシミュレーションです。結果は 101件が段階2で自動確定、残り2件(横手市大雄大谷地・大雄根田谷地)はフィルタ表示で拾える、でした。それを確認したうえで、優先リストのシートごと廃止しています。
私が手で面倒を見ていた名簿が、ロジックに置き換わって消えた。今回の修正で一番大きい違いはここだと思っています。
変数名:ZZZ が「検索語」になった
対応表にするとこうです。
| 修正前 | 修正後 |
|---|---|
| ZZZ | 検索語 |
| moji | 文字数 |
| XXX | 最終行 |
| AAA | 照合パターン |
| BBB | データ範囲 |
| CCC | 町名範囲 |
| YYY | 優先語(→段階2実装により廃止) |
マクロ名が日本語なのだから変数名も日本語、というのは私の流儀です。この連載で書いてきたとおり、AIと日本語で会話しながらコードを直す作業では、コードの中身も日本語のほうが会話とコードが一本につながります。全11本のマクロに、変数の意味コメントと「何をするマクロか」の1行説明も付いています。
細かな堅牢化
ほかにも、会話の中でひとつずつ確認しながら直してもらいました。
-
SendKeys "{DOWN}"を全廃 ── キー送信でカーソルを動かす方式はタイミング依存で不安定。フィルタ後の可視セルをSpecialCells(xlCellTypeVisible)で直接つかんで選択する形に -
Selection.AutoFilter→Range("B6").AutoFilter── 選択状態に依存しない書き方に - 検索欄を空にしたときはフィルタも解除して全件表示に戻す(修正前は
Exit Subするだけでフィルタが残った) - 最終行の計算を
UsedRange.Row + Rows.Count - 1に ── 開始行のズレに耐える形 - クリップボードへのコピーは3回再試行 ── 他のアプリがクリップボードを使用中だと一発勝負では黙って失敗することがある
-
Option Explicitを付けて全変数を明示宣言
どれも「動かなくなるほどではないが、直したほうがいい」類のものです。逆に言うと、こういう細かい積み残しを全部まとめて面倒みてもらえるのが、会話で直す方式の楽なところです。
おまけ:Ctrl+Shift+M でマクロ一覧が出ます
修正後のブックには、マクロを見て回るためのメニューを付けてあります。Ctrl+Shift+M でマクロの一覧フォームが立ち上がります。
- テキストボックスに入力すると一覧が絞り込まれます
- マクロを選んで Enter かダブルクリックで実行
- 右クリックすると、VBE のそのマクロのコードにジャンプします
この記事を読んで「AIが直したコードを実際に見てみたい」と思った方が、VBE の中を探し回らなくても、一覧から右クリックで直接コードに飛べるようにしたものです。仕組みは私のアドイン(連載第3回で書いた秀.xlsm)のメニュー機構と同じもので、このブック単体で動くように載せてあります。
※ マクロ一覧は VBA プロジェクトをマクロから読む作りなので、使うには Excel のトラストセンターで「VBA プロジェクト オブジェクト モデルへのアクセスを信頼する」を有効にしておく必要があります。設定しない場合も、Alt+F11 で普通に VBE を開けばコードは見られます。
おわりに
修正前のブックも、何年も実用してきたもので、ちゃんと動いていました。それでも並べてみると、これだけの差が出ます。
- 暗号のような変数名 → 読めば分かる日本語の変数名とコメント
- 手動で面倒を見る例外名簿103件 → データ更新に自動で追随するロジック
- タイミング頼みの小技(SendKeys) → 状態に依存しない書き方
修正作業はすべて会話です。vba_manager(MCP経由)がマクロの取得と書き戻しを担当し、修正のたびに差分表示とバックアップが自動で残ります。私がやったのは、方針を伝えることと、一周ごとの動作確認だけでした。
before と after、両方置いてあります。見比べてみてください。
ここで終わりたかったのですが
……と、きれいに締めましたが、正直に書くと、今回の作業はすんなり終わっていません。最後にAIが一度やらかしています。この連載はAIの失敗も同じ温度で書く方針なので、追記しておきます。
マクロの修正そのものは順調でした。事件が起きたのは、ひとつ前の節で紹介した「マクロ一覧メニュー」を私がブックに載せた後、最終点検のときです。
メニューの右クリックジャンプは、こういうコードで動いています。
Application.Goto Reference:=モジュール名 & "." & マクロ名
Application.Goto は、文字列で「モジュール名.プロシージャ名」を渡すと VBE のそのマクロの位置に飛んでくれます。昔からある書き方で、実際、最初から正常に動いていました。
ところが、点検していた AI(Claude Opus)がこの行を「バグだ」と言い出しました。Application.Goto はセルに移動する命令だから、この文字列はセル範囲として解釈できずエラーになる、と。
私は「エラーなんか出ていない」と伝えました。実際に動いているのだから当然です。しかし AI は引き下がらず、「検証します」と言って検証用のマクロをブックに注入しました。
これが二重にまずかった。注入された関数の名前が _tmp検証——アンダーバー始まりで、VBA の識別子としてそもそも許されない名前です。VBA はこれをプロシージャとして認識できず、モジュールのコンパイルが壊れました。そして壊れた状態でマクロを実行しようとして「マクロが使用できません」というエラーが返ると、AI はそれを「ほら、やはり問題がある」と自説の証拠として読んだのです。
動いているコードを思い込みでバグと断じ、検証と称して壊し、壊れたことを自分の正しさの証拠にする。見事に閉じた円環です。
何を言っても Application.Goto を「セル移動の命令だ」と言い張り続けるので、話になりません。私はあきらめて、モデルをフェーブル(Claude Fable)に切り替えて助けを求めました。
すると、あっさり解決しました。フェーブルは経緯を読むなり「シュウさんが正しい。Application.Goto は VBE のプロシージャに飛べる由緒正しい書き方で、フォームは最初から正常に動いていた。直すところはない」と一言で認め、残っていた仕事は前任が壊した分の後始末だけだと整理して、すぐ取りかかりました。
-
grepで毒の所在を特定——壊れた関数は「実行」マクロの末尾にゴミとしてぶら下がっていた - きれいな「実行」でプロシージャ置換——ゴミごと消える
- B1 に「秋田市山王」→ 010-0951 が出ることを実機で確認
数分で戻りました。戻れたのは、修正のたびに差分とバックアップが自動で残る道具立てだったからです。皮肉なことに、事故を起こしたのも、事故から数分で戻せたのも、同じ仕組みの上でした。
教訓は3つです。
- AI の確信の強さと、正しさは別物です。 断言の口調は証拠になりません
- 「動いている」という事実は、AI の理屈に勝ちます。 作者が「エラーは出ていない」と言ったら、そちらが正しい
- AI にブックを触らせるなら、差分とバックアップが残る道具立てとセットで。 事故は起きます。起きても戻せる形でやることです
AI は直しもするし、壊しもします。それでも before と after の差が本物であることは変わりません。両方をそのまま見てもらうために、ブックを置いてあります。
