VBAのセル参照は「名前定義」で書くべき理由 ~A1直書き・Cells(1,1)が保守で壊れる“本当の原因”~
- yuji fukami
- 2月20日
- 読了時間: 7分
はじめに:VBAは「動く」より「運用で壊れない」が価値になる
Excel VBAの解説でよく出てくるセル参照は、だいたいこの2つです。
Range("A1")
Cells(1,1)
どちらも分かりやすいし、学習段階では便利です。でも、実務でツールとして納品し、運用が始まると、ここが“保守の爆弾”になります。
なぜなら、Excelは運用中に 行・列の挿入/削除や、セルの移動でレイアウトが変わる からです。そしてレイアウトが変わると、参照先がズレて、コードの書き換えが発生する(もしくは、気づかない誤動作が起きる)からです。
この記事では、セル参照を「位置」ではなく「意味」で扱える 名前定義(Named Range) を使うべき理由と、弱点のカバー方法までまとめます。

よくあるセル参照:アドレス直指定のメリットと致命点
Excel VBAでは、セルの値を取り出して変数に入れるとき、まず教科書的に紹介されるのが セル番地を直接指定して読む方法です。
たとえば、下図のように「従業員名」が C2セル に入力されているとします。この値(例:今治 太郎)をVBAで取り出して、変数に格納する最もシンプルな書き方は次のとおりです。

また、セル番地ではなく「行番号・列番号」で指定する方法(Cells)もよく使われます。
C列の2行目は「列=3、行=2」なので、次のようにも書けます。
この2つ(Range("C2") / Cells(2, 3))は、VBA学習で最初に習うセル参照として非常に分かりやすく、 「どのセルを読んでいるか」がコード上ですぐ見えるのがメリットです。
しかし、ここに大きな落とし穴があります。
たとえば下図のように、2行目に1行挿入したとします。すると、もともと C2 に入っていた「従業員名」 は C3 に移動します。

ところがコード側が
Range("C2")
Cells(2, 3)
のように 番地(行・列)を固定して参照していると、挿入後も変わらず「C2」を参照してしまいます。つまり、参照先がズレてしまい、意図しないセルの値を読んだり、書き込んだりする状態になります。
このような「ズレ」は、行の挿入だけでなく、
行・列の削除
別の場所への挿入
セルの切り取り/コピー → 貼り付けによる移動
表のレイアウト調整(ドラッグ移動など)
といった、いわゆる ワークシートのレイアウト変更 でも簡単に発生します。
そして問題は、こうした変更が起きるたびに、アドレス直指定のコードは動かなくなったり、コードの書き換えが必要になったりすることです。(しかも怖いのは、エラーで止まらず “それっぽく動いてしまう” ケースもある点です)
「シートのレイアウトが変わったら、その都度コードを書き換えるしかない」――教科書どおりに学ぶと、そう思ってしまう方も多いかもしれません。
しかし、この問題を解決する方法があります。それが、セルに名前を付け、その名前で参照する「名前定義」 という方法です。
名前定義の設定方法
名前定義の設定方法は基本操作なので、ここでは手順だけ軽く押さえます。流れは下図のとおりです。
名前を付けたいセル(または範囲)を選択する
リボンの [数式]タブ →[名前の定義] をクリック
「名前」「参照範囲(スコープ)」「参照範囲」を設定してOK
名前は、あとから見たときに意味が分かるように、例えば 「従業員名」 のような分かりやすい名称にしておくのがおすすめです。

また「参照範囲(スコープ)」には、ブック全体で有効にするか、特定のワークシート内だけで有効にするかの選択があります。もし「そのワークシート内だけで使う」前提なら、参照範囲は ブックではなく、対象のワークシート名(例:Sheet1) を選ぶ方が管理しやすくなります。
実務では、名前定義を設定したワークシートを 複製して使い回す ケースがよくあります。このとき、もし名前定義の参照範囲(スコープ)を 「ブック全体」 にしていると、複製したシートの分だけ 同名の名前定義がブック内に大量に増える 状況になりがちです。
そうなると、どの名前定義がどのシートを指しているのかが分かりづらくなり、VBA側でも参照先の特定が面倒になります。結果として、コードの可読性や保守性が下がり、扱いづらい設計になってしまいます。
そのため、同じ構成のシートを複製して運用する可能性があるなら、名前定義は基本的に「ワークシート範囲(シート内のみ有効)」で設定しておく方が、VBAをコーディングする上でもメリットが大きいです。
名前定義を利用したセルの参照コード
上のように名前定義を設定しておけば、VBA側は「元のセル番地(C2など)」を直接書かずに、名前でセルを参照できるようになります。具体的には、Range オブジェクトの引数に 名前定義の名前(例:従業員名) を指定するだけです。
たとえば従業員名を取得する場合は、次のように書けます。
この方法のメリットは、レイアウト変更に強いことです。仮に行の挿入などで「従業員名」のセルが C2 から別の位置へ移動しても、名前定義が参照先に追従していれば、コードを書き換えなくても正しい値を取得できます。
一方でデメリットとして、コードを見ただけでは「その名前が具体的にどのセルを指しているか」が分かりづらくなります。その対策として、上の例のように コメントで元の番地(例:' C2)を書いておくと、後から見返すときの手がかりになり、保守が楽になります。
では、実際にこのコードを実行して結果を確認してみます。
下図のとおり、途中で 2行目を挿入したことで、「従業員名」の入力セルは C2 → C3 に移動しています。

それでも、VBA側は Sheet1.Range("従業員名").Value と 名前で参照しているため、セルの位置が変わっても参照先が追従し、正しい値(今治 太郎)を取得できます。
確認のため、取得した値を Debug.Print でイミディエイトウィンドウに出力すると、下図のように 「従業員名:今治 太郎」 と表示されます。これにより、セルが移動した後でも名前定義が正しく機能し、コードを書き換えずに値を参照できていることが分かります。

ヘッダーのセルを基準とする場合
ここまでは「単一セル」を名前定義で参照する例でしたが、もう少し実務寄りの応用として、ヘッダー行(見出しセル)を基準にして一覧を参照するケースを紹介します。
たとえば下図のように、B2セルに「従業員名一覧」という見出しがあり、その下(B3以降)に従業員名が縦に並んで入力されているとします。

この一覧をVBAで順番に取得して表示したい場合、教科書的には Cells(行, 列) を使って次のように書くことが多いです。
Cells(i + 2, 2) のように、行番号・列番号を増やしながら参照する方法
ただしこの書き方は、行の挿入・削除や列の追加などでレイアウトが変わると、参照位置がズレて コードの書き換えが必要になりやすいのが弱点です。一覧のように「繰り返し参照する範囲」ほど、このズレの影響を受けやすく、保守面で扱いづらくなります。
そこで有効なのが、ヘッダーセルに名前定義を設定し、そのセルを基準(起点)としてOffsetで参照する方法です。
たとえば、見出しセルのB2に対して「従業員名一覧」という名前定義を設定しておきます。

すると、VBA側ではまずその名前定義セルを取得し、そこから
1行下(B3)
2行下(B4)
3行下(B5)
というように、Offset(行, 列) を使って値を順番に参照できます。
この書き方のメリットは、レイアウト変更に強いことです。
仮に行の挿入などで一覧の開始位置が下にずれても、「従業員名一覧」という基準セルに名前定義が追従していれば、参照も一緒に追従するため、コードの修正を最小限に抑えられます。
最後に、実行してイミディエイトウィンドウに出力すると、下図のように「従業員名1〜3」が正しく取得できていることが確認できます。
一覧データを扱う場面では、**「基準セルを名前定義」+「Offsetで展開」**の組み合わせが、保守性の高い書き方として特におすすめです。

まとめ
今回紹介したとおり、Range("A1") や Cells(行,列) のような番地ベースの参照は、学習段階では分かりやすい反面、行・列の挿入/削除やセル移動で簡単に参照ズレが起きます。その結果、コードの書き換えが発生したり、最悪の場合は“気づきにくい誤動作”につながります。
一方、名前定義で「意味」を基準に参照しておけば、レイアウト変更が入っても追従しやすく、保守性と信頼性が大きく上がります。一覧のような繰り返し処理も、基準セルを名前定義→Offsetで展開するだけで、壊れにくい構造にできます。「場所」ではなく「意味」で参照する――これが、運用前提のVBAでは重要です。
私が開発するマクロツールも、基本方針として名前定義を前提に設計し、レイアウト変更に強い作りにしています。保守や改修のコストを減らし、長く安心して使えるVBAにしたいなら、名前定義の活用は“ほぼ必須”だと考えてください。



