プログラムの基本的な書き方・作法についてまとめています。
「今まで我流だったけど、キレイで効率的なプログラムを書きたい」
という人は、ぜひ参考に!
VBAのオブジェクトや変数の命名規則については、別記事:VBAの命名規則で紹介しています。
基本的なコーディングルール
インデント(字下げ)をして、構造を見やすくする
見比べるとわかりますが、「字下げをしていない場合」と「字下げをしている場合」でプログラム構造のわかりやすさがまったく違います。
字下げをしてもしなくてもプログラムの動作は変わりませんが、見やすくメンテ性を向上させるためにも字下げをしましょう。
NG : 字下げなし
for文やIf文の構造がわかりにくい
1 2 3 4 5 6 7 8 9 10 |
Dim rowIdx As Long 'チェック対象のセルが空白の時、"未入力"をセットする For rowIdx = 1 To 300 If Cells(rowIdx, 1).Value = "" Then Cells(rowIdx, 1).Value = "未入力" End If Next |
OK : 字下げあり
for文やIf文の構造がわかりやすい
1 2 3 4 5 6 7 8 9 10 |
Dim rowIdx As Long 'チェック対象のセルが空白の時、"未入力"をセットする For rowIdx = 1 To 300 If Cells(rowIdx, 1).Value = "" Then Cells(rowIdx, 1).Value = "未入力" End If Next |
対象行の先頭にカーソルを合わせ、Tabキーを押すと字下げされます。
複数行を選択(ドラッグ)しTabキーを押すことで、まとめて字下げができます。
Shiftキー + Tabキーで、逆に字上げできます。
文字の結合は&演算子を使う
文字の結合は+演算子でもできますが、暗黙で文字として扱ってくれているからです。
数値が混ざった場合に、+演算子だと足し算扱いされる可能性があるります。なので、文字の結合は&演算子を使いましょう。
既定のプロパティでも省略しない
オブジェクトでは既定のプロパティが用意されていますが、省略は禁止とします。
例えばCellsの場合は、.Valueが規定のプロパティです。なのでプログラムでValueを記述してなくても、Valueに対する処理を行ってくれます。
ただ、「何に対する処理か?」を明確にするためにも、既定のプロパティであっても記述することをおすすめします。
1 2 3 4 |
Cells(1,1)="えくとしょ" 'NG Cells(1,1).Value = "えくとしょ" 'OK |
入れ子構造が深くならないようにする
If文などで、入れ子でどんどん深くなると何をプログラムをしているのかわかりにくく、またメンテナンス性も落ちます。
多少プログラムが長くなってもいいので、処理を分割して、あまり入れ子が深くならないようにしましょう。
例) ファイル名を入力するテキストボックスに対して、3つのチェック(未入力チェック、ファイル存在チェック、拡張子チェック)をするサンプルプログラムです。見比べてみてください。
NG : 入れ子が深い
各処理がからみ合って、見にくく修正がしにくい
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
'---------------------------------------- '取込ファイルテキストボックスのチェック '---------------------------------------- '未入力チェック If txtTorikomiFile.Text = "" Then Exit Function Else 'ファイル存在チェック If Dir(txtTorikomiFile.Text) = "" Then Exit Function Else '拡張子チェック (エクセルファイルのみ) If Right(txtTorikomiFile.Text, 4) <> "xlsx" Then Exit Function End If End If End If |
OK : 入れ子浅い
各処理で分割しているので、見やすく、修正やコメントアウトを個別にしやすい
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
'---------------------------------------- '取込ファイルテキストボックスのチェック '---------------------------------------- '未入力チェック If txtTorikomiFile.Text = "" Then Exit Function End If 'ファイル存在チェック If Dir(txtTorikomiFile.Text) = "" Then Exit Function End If '拡張子チェック (エクセルファイルのみ) If Right(txtTorikomiFile.Text, 4) <> "xlsx" Then Exit Function End If |
Withステートメントを使う
Withステートメントを使うことで、オブジェクト名の記述は1回のみで、プログラムの可読性が向上します。
NG : With未使用
1 2 3 4 5 6 |
Range("A1").Font.Name = "MS Pゴシック" Range("A1").Font.Bold = True Range("A1").Font.Size = 20 Range("A1").Font.Color = vbBlue |
OK : With使用
1 2 3 4 5 6 7 8 |
With Range("A1").Font .Name = "MS Pゴシック" .Bold = True .Size = 20 .Color = vbBlue End With |
※ただし、長い範囲でのWith句や、With句の入れ子は、対象のオブジェクトが何かわかりにくくなってしまうので注意しましょう。
GoToステートメントはできるだけ使用しない
Gotoステートメントが色々な場所にあると、処理が飛ぶのでプログラムの可読性が落ちます。
できる限りGotoステートメントを使わない構造を考えましょう。
使うとしても、ループ内で終了処理に抜け出る時くらいかな
コメントは意図を簡潔に記述する
「変数SyoriMode=1の時、実行する」などプログラムを読めばわかるコメントは不要です。
プログラムを読んでもわからない意図や仕様を簡潔にコメントしましょう。
全体的なコメント量は減りますが、逆にコメントの重みも増します。
と言って、コメントを全然記述しない人がいますが、それは少し違うと思います。
他の人や初心者の人でも読みやすい、「優しさ」のあるプログラム、コメントを心がけましょう。
変数のコーディングルール
変数の宣言を強制する
各モジュールの先頭で、下のコードを記述することで、変数の宣言が強制となります。
1 2 3 4 |
'変数宣言を強制 Option Explicit |
VBAの設定で、変数の宣言を強制することもできます。
- 【ツール】→【オプション】
- 【編集】タブを選択
- 【変数の宣言を強制する】をチェック
この設定は1回するだけでOKです。
次からモジュールを新規作成した時、自動でOption Explicitが出力されます。
変数定義は型指定をする
変数を定義する場合は、dim strUserName as String のように型まで定義します。
型定義がないと、どんな値でもセットできてしまいます。
数値しかセットしない変数に間違って文字列をセットしたりなど、プログラミングのミスを防止する意味でも大事なポイントです。
できるだけせまい範囲で定義をする
基本的には、イベントやサブルーチン内で変数を定義してください。
必要なものだけをPrivateやPublicで定義します。
Public変数が便利だからと言って、なんでもPublic変数にすると、どこの処理で変数を使用しているかわからなくなります。
「引数」や「戻り値」をちゃんと使えるようになれば、それほどPublic変数は使わなくなります。
初心者の時はついついPublic変数使いたくなるんですけどね・・・
変数定義は使う直前でする
少し前までは、処理内で使う変数はプログラムの先頭エリアでまとめて記述することが正とされていました。
しかし最近では、メンテ性の面などから変数を使う直前で定義することが推奨されてます。
変数にセルの値などをセットする時は型変換する
変数にセルの値そのままセットしても、暗黙の変換でうまく動くことが多いです。
ただ、本当は文字”001″として保存したいのに、0が暗黙の変換でなくなり”1″として保存されるなど、バグの原因になります。
そのため、型変換をして変数にセットしましょう。
型変換の例
1 2 3 4 5 6 7 8 9 |
'商品コード Dim strShohinCode As String strShohinCode = CStr(Cells(1, 1).Value) '←文字 '数量 Dim lngSuryo As Long lngSuryo = CLng(Cells(1, 2).Value) '←数値 |
変数の使いまわしはしない
繰返し用の変数や、ワーク用の変数を使いまわすと、プログラムの意図がわかりにくくなり、バグの原因になります。
処理ごとに、きちんと変数を定義しましょう。
NG : 変数を使いまわし
変数idxを使いまわしている
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Dim idx As Long '行の移動 For idx = 1 To 10 Cells(idx, 1).Value = "行の値" Next '列の移動 For idx = 1 To 10 Cells(1, idx).Value = "列の値" Next |
OK : 個別に変数定義
処理それぞれで変数を定義している
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Dim rowIdx As Long '行移動用 Dim colIdx As Long '列移動用 '行の移動 For rowIdx = 1 To 10 Cells(rowIdx, 1).Value = "行の値" Next '列の移動 For colIdx = 1 To 10 Cells(1, colIdx).Value = "列の値" Next |
Integer型は使わないでLong型を使う
少し前までは、メモリ節約するためにデータの大きさに応じて、Integer型とLong型を使い分けることが正とされていました。
しかし最近では、Integer型はLong型よりもパフォーマンスが劣るため、Long型を使うことが推奨されています。
整数を扱う時はLong型と覚えておけばOK!
変数の初期化を明示的に行う
Long型など数値の変数を定義した場合、初期値として0がセットされていますが、明示的に0をセットするなどして初期化を行いましょう。
1 2 3 4 5 6 |
Dim lngDataCount As Long 'データ件数 '初期化 lngDataCount = 0 |
小数点以下の計算では、Currency型を使う
小数のある計算でSingle型やDouble型を使うと、小数以下の計算結果で誤差が生じることがあります。
Currency型を使うことで、小数以下の計算結果の精度が向上します。
変更の可能性がある値は、定数を使う
消費税率など、今後変更が起こる可能性がある値は、ロジック(計算式)に直接記述しないで、定数として定義します。
たとえば計算式に直接0.08と記述していると、消費税率が変化した(0.08→0.15)時、各計算箇所すべてを修正しないといけません。。
定数として定義していれば、定数の値を設定している場所1ヶ所のみを修正すればいいので、修正時間の短縮・修正漏れの防止になります。
マジックナンバー(ソース内に直接書き込まれた、プログラマ本人にしか意味が分からない数値)はすべて禁止、というルールもたまにあります。
でも、それにしたがって全ての数値を定数化すると、かえってソースの可読性が悪くなることもあります。
なので、「変更の可能性のある数値のみ定数化」など、ある程度の例外を認めるルールがポイントになります。
目的が同じ定数は、列挙型で定義する※数値のみ
例えば、エクセルの列位置を定数で定義すると、次のようになります。
1 2 3 4 5 6 7 8 |
'商品一覧エクセルの列位置 Public Const SHOHIN_COL_SHOHINCODE As Integer = 1 '商品コード Public Const SHOHIN_COL_SHOHINNAME As Integer = 2 '商品名 Public Const SHOHIN_COL_GENKA As Integer = 5 '原価 Public Const SHOHIN_COL_BAIKA As Integer = 6 '売価 Public Const SHOHIN_COL_ZAIKO As Integer = 10 '在庫数 |
このままでも問題はないのですが、同じ目的・グループの定数は列挙型を使う事でプログラムの可読性・メンテナンス性が向上します。
列挙型による定義
1 2 3 4 5 6 7 8 9 10 |
'商品一覧エクセルの列位置 Public Enum eShohinCol ShohinCode = 1 '商品コード ShohinName = 2 '商品名 Genka = 5 '原価 Baika = 6 '売価 Zaiko = 10 '在庫数 End Enum |
名前の付け方は、一般的にe + 単語(先頭大文字)です。
数値を指定しないと、先頭から0の連番が自動でセットされますが、きちんと数値まで指定してわかりやすくしましょう。
自動入力補完機能(列挙形名.を入力すると入力候補が一覧表示される)が使えるので、早く・確実にプログラミングできます。
関数のコーディングルール
引数のByRef、ByValは明示する
関数を定義する時に、引数も定義しますがByRef(参照渡し)、ByVal(値渡し)を明記してください。(省略すると参照渡しとなる)
基本的にはByVal(値渡し)を使用します。
ByVal(値渡し)を使う事で、関数内で引数に変更がされても、呼び出し元では影響を受けないからです。
逆に、呼び出し元で複数の値を戻り値として受け取りたい場合は、ByRef(参照渡し)を使用することで、呼び出し元の引数も影響を受けるので実現できます。
※オブジェクトなど大きなデータの場合は、ByVal(値渡し)を使うとメモリを消費する(コピーが作成される)ため、ByRef(参照渡し)を使用します。
関数を使用するときはCallを使う
関数名だけの記述でも実行できますが、Call 関数名 としてコーディングします。
Callが付いていないと、変数なのか関数なのかがパッと見ではわかりません。
Callが付いていることで「何か処理を呼んでいるな」と直感的にわかり、可読性が向上します。
ブックやシートのコーディングルール
ワークブック名を明示する
ワークブックを操作するときは、ブック名を明示して処理を記述しましょう。
1 2 3 4 |
'OK例 Workbooks("生徒一覧").Save |
次のプログラムのように、インデックス(番号)でワークブックを指定することもできます。でも、ユーザがファイルを開いた順番によって、対象となるファイルが変わってしまいます。
そのため、インデックスでのワークブックの指定は、できる限りしないようにしましょう。
1 2 3 4 |
'NG例 Workbooks(1).Save |
アクティブブックは変数にセット
ユーザ操作などによって、アクティブブックは変化する可能性があります。
そのため、アクティブブックを操作する時は、アクティブブックを変数に格納し、変数に対して処理を行いましょう。
そうすることで、仮にアクティブブックが変化しても、変数に格納されたブックは変化しないので、プログラムの処理は影響を受けません。
1 2 3 4 5 6 7 8 9 |
Dim wbTargetBook As Workbook '対象ブック Set wbTargetBook = ActiveWorkbook '←変数にActiveWorkbookを格納 With wbTargetBook '←ActiveWorkbookではなく、変数に対して処理を記述 '処理 End With |
ワークシート名を明示する
ワークシートを操作するときは、シート名を明示して処理を記述しましょう。
1 2 3 4 |
'OK例 Workbooks("生徒一覧").Worksheets("1年1組").Range("A1").Value = "あいうえお" |
次のプログラムのように、インデックス(番号)でワークシートを指定することもできますが、ユーザの操作でシートの順番は変わるので、対象シートも変わってしまいます。そのため、インデックスでのワークシートの指定は、できる限りしないようにしましょう。
1 2 3 4 |
'NG例 Workbooks("生徒一覧").Worksheets(1).Range("A1").Value = "あいうえお" |
アクティブシートは変数にセット
ユーザ操作などによって、アクティブシートは変化する可能性があります。
そのため、アクティブシートを操作する時は、アクティブシートを変数に格納し、変数に対して処理を行いましょう。
そうすることで、仮にアクティブシートが変化しても、変数に格納されたシートは変化しないので、プログラムの処理は影響を受けません。
1 2 3 4 5 6 7 8 9 |
Dim wsTargetSheet As Worksheet '対象シート Set wsTargetSheet = ActiveSheet '←変数にActiveSheetを格納 With wsTargetSheet '←ActiveSheetではなく、変数に対して処理を記述 '処理 End With |
シート操作する時はブックから指定する
次のようにブックから指定しましょう。
1 2 3 4 |
'OK例 Workbooks("生徒一覧").Worksheets("1年1組") |
次のように、シート名でいきなり指定すると、VBAがシートを探す対象ファイルはアクティブなブックとなります。ユーザ操作によってアクティブなブックは変化するので、対象シートが存在しないなどエラーが発生する可能性があります。
1 2 3 4 |
'NG例 Worksheets("1年1組") |
セル操作する時はブックとシートを指定する
セルを操作する時も、上で紹介した「シート操作」の考えと同じです。ブックとシートを指定しましょう。
1 2 3 4 |
'OK例 Workbooks("生徒一覧").Worksheets("1年1組").Range("A1").Value = "あいうえお" |
次のように、いきなりRangeやCellsを使うと、アクティブなシートのセルが対象となるので、ユーザ操作によって書き込まれるシートが変化してしまう可能性があります。
1 2 3 4 |
'NG例 Range("A1").Value = "あいうえお" |
今回のように、rangeの前に毎回ブック名、シート名を記述するとプログラムが見にくくなってしまいます。
そのため、ブック名とシート名の部分はwith句に記述して、見やすいプログラムにしましょう。
まとめ
一般的なVBAのルールを紹介しましたが、1番大事なのは開発チームでルールを統一することです。
そうすることで、他人の書いたプログラムでも読みやすくなり、メンテ性も向上します。
最初はルールを守るのが難しいかもしれませんが、少しずつ取り入れてみてください。
開発チーム内で、1人だけ違うコーディング規約でプログラミングするのは絶対ダメです!
たとえそれが正しいルールでも・・・ルールを変えたい場合は、一度チームのみんなに提案しましょう。