category name  »  page title date

コーディングの基本ルール

以下、コーディングにおいて自らに課しているルール。これはあくまでマイ・ルールですので、当然いろいろな考え方があるでしょうが、ご参考までに。
先にお断りしておくと、マイ・ルールとはいえ、わたし自身これを厳密に守っているわけではなく、ときどき違反せざるを得ないこともあります。それで、あくまでご参考までに。

プログラムに完成はありません。常に修正したり拡張したりして変化していくものです。その時に戸惑わないように書いておくというのがルールの基本です。そのために一番大切なことはコードの可読性。だらだらと「とにかく動くから、いいのでは?」というコードを書いてしまうと、ちょっと経つと自分でも判読が難しくなって、どこかをいじるとどこに影響が出るのかわからないようなことになりかねませんし、ましてや他人にとっては迷路のようなコードになってしまいます。

大きな構成を統一しておく

各モジュールの書きっぷりを統一しておきましょう。

モジュール内での記載の順序

VBA の場合は、プロシージャ外の変数や定数の宣言を、プロシージャやプロパティの後に書けないので、必然的にそれらを先行させる必要があります。後々に出てくるプロシージャが使用する定数や変数を、コードの先頭に書かざるをえないので、そもそも著しく可読性を損なうことになりますから、そこは慎重に配置します。
基本的には、

Comment                                           ←このモジュールの説明
Public 変数・定数                                 ←ほかのモジュールから参照の可能性あり
Private 変数・定数                                ←このモジュール内でだけ使用する
Public Property -- Private Property        ←プロパティ
Public Sub/Function -- Private Sub/Function     ←関数とここで使用するサブルーチン

の順番で記載し、できるだけ丁寧なコメントを付しておきます。とくに変数や定数には一見してわかる名前をつけておくことにあわせて、コメントを付しておくことが肝要です。
また、それぞれの群の間を1行空けて区切っておくとわかりやすくなることがあります。
先頭の Comment のつけかたには、それぞれ流儀がありそうですが、基本的にはそこに含まれる Public Sub/Function が何をするものか、という説明になろうかと思います。筆者は恥ずかしながらまだ自身の流儀が定まっていないので、ここで例示はしません。少なくとも、モジュール名を記載しおくことは大切ですね。

モジュールのわけかた

モジュールをどう分けるかは、全体の構成をわかりやすくする上で重要です。とくに標準モジュールは自由度が高いので、無秩序になりがちです。基本的には、まとまった機能のプロシージャ群をひとつのモジュールに入れるようにしています。理想的には、プロジェクト全体で使う Public なプロシージャがひとつないしふたつと、そのための部品となる Private なプロシージャ群がひとつのモジュールにはいっている、というイメージですね。その結果、あるモジュールは10行しかないのに、あるモジュールは500行もあって、バランスが気になる、ということがあるかもしれませんが、そんなことは一切気にしないようにしましょう。ひとつのモジュールがひとつの機能をもつ、ということが重要です。
モジュール名も、できるだけ理解しやすい名前にしておきましょう。

モジュール名のつけかた

とやっていると、モジュールの数がどんどん増えていきます。それで別段問題が生じるわけではないのですが、どこに何をいれたかが怪しくなってくるのにあわせて、目的のモジュールを探すのに手こずってしまうようになります。
そこで大切なのは、もちろんモジュール名にそのモジュールの機能を表現するできるだけ具体的な命名を行うことですが、さらに、近縁のモジュールを隣接して並べたり、表示の順序を変えたりしたくなります。
ところが、VBE のプロジェクト・エクスプローラーに表示される順序は、それぞれのモジュールタイプ(ワークシート、フォーム、標準モジュール、クラスモジュール)ごとに自動的に文字でソートされてしまい、どうやってもこの順序を思うように変更できません。そこで、窮余の一策として使っている方法が、それぞれの名前に適当な接頭語をつけて、その接頭語でソートさせる、というものです。たとえば、左図のような具合。プロシージャをモジュール名をつけて呼び出さなくてはいけないことが稀にあるので、こんな変てこな名前にしておくと気持ちが悪いのですが、これで日常のストレスからはなんとか解放されています。

途中の見出しを兼ねた区切りには、空のモジュールを入れて見えやすくしてあります。つまらないことですが、これもTipsと言えますか。

小さなパーツに分ける

各プロシージャはできるだけ短く、長くても20行以内くらいに収めておくと、パッと見て流れがつかめます。このために、まとまった処理はサブルーチン化して独立させ、適切な名前をつけておきます。たとえば次のようなコードがあります(これは、「コメント設定」で紹介したもので、セル名とコメント内容の対照表を読んで、所定のセルにコメントタグを追加するコードです)。 全体で21行、インデントが3段もあって、なんか読みにくい。

'----- コメント設定
Public Sub InstallComments()
Dim commentTopCell, index, cellName, commentText
      Set commentTopCell = Range("CommentListTop")
      index = 0
      Do While commentTopCell.Offset(index, 0) <> ""
            With commentTopCell
                  cellName = .Offset(index, 0).value
                  commentText = .Offset(index, 1).value
            End With
            Range(cellName).ClearComments
            If Not IsEmpty(commentText) Then
                  With Range(cellName).AddComment
                        .Visible = False
                        .Text Text:=commentText
                        .Shape.TextFrame.AutoSize = True
                  End With
            End If
            index = index + 1
      Loop
      Application.DisplayCommentIndicator = xlCommentIndicatorOnly
End Sub

この程度ならこのままでもいいと思いますが、これをよりわかりやすいようにパーツに分けてみました。すると、全体の行数は増えても、本体の InstallComments() はたった12行、インデントは1段のみとなり、見通しが格段に改善されます。getCommentValue() と addCommentValue() は他で使いまわしされるわけではなく、とにかく一部のまとまった機能を分離独立させただけのサブルーチンです。

Private commentTopCell As Range   'コメントリストの先頭セル
'----- コメント設定  InstallComments()
Public Sub InstallComments()
Dim index, cellName, commentText
      Set commentTopCell = Range("CommentListTop")
      index = 0
      Do While commentTopCell.Offset(index, 0) <> ""
            getCommentValue index, cellName, commentText
            addCommentValue cellName, commentText
            index = index + 1
      Loop
      Application.DisplayCommentIndicator = xlCommentIndicatorOnly
End Sub
'--- 次のセル名とコメント内容を取得
Private Sub getCommentValue(index, ByRef cellName, ByRef commentText)
      With commentTopCell
            cellName = .Offset(index, 0).value
            commentText = .Offset(index, 1).value
      End With
End Sub
'--- 所定のセルにコメントを追加
Private Sub addCommentValue(cellName, commentText)
      Range(cellName).ClearComments
      If IsEmpty(commentText) Then Exit Sub
      With Range(cellName).AddComment
            .Visible = False
            .Text Text:=commentText
            .Shape.TextFrame.AutoSize = True
      End With
End Sub

名前のつけかた

関数や変数、定数の命名のしかたは、可読性にとってとても大切です。最初、数行程度のコードを書いていて、短いからといって安直な名前をつけているうちに、だんだん拡張して数十行、数百行になってしまって、気がつくと大混乱してしまった、という経験をもつ人も多いでしょう。
最初からできるだけわかりやすい名前をつける癖をつけましょう。できればコメントが必要ないくらいにしたいものです。
変数名の付け方には、伝統的にいくつかの流派があって、命名規則を解説したサイトもたくさんあるので、気に入った規則を自分でも守るようにしましょう。
筆者の場合は、原則こうしています。

●変数・定数は名詞とする。必要なら前に形容詞をつける。
●関数やプロパティの名前は動詞とする。必要なら後に名詞や副詞をつける。
   プロパティの場合は、変数と同じように名詞としたほうがよい場合もある。
●クラスのプロパティは、簡単な名詞がわかりやすい。

このあたりのことは、実際に自身があとで参照したり代入したりする際に、あまり迷わないで、しかも冗長にならないで、自然にタイピングできるようにと気をつけていれば、自ずと身につく作法です。身についたら、厳格にそれを守るようにしましょう。
クラスのプロパティについて。たとえば、PersonClass というのがあって、その中の年齢プロパティに PersonAge というような名前をつけたいところですが、そうすると呼び出す際に person.PersonAge という具合になって冗長になりがちです。person.Age と呼べるようにすれば、より自然になりますね。


表記法にもいろいろありますが、筆者の場合はこうするように心がけています。

●全体を小文字で、後続の単語の頭を大文字にする。
    例)  addCommentValue
●Public なものの名前の1文字目は大文字にする。
    例)  InstallComments
●できるだけ英語を使う。日本語で記述するコメントとの違いを際立たせるため。

英語の勉強になります。もちろん、独語でも仏語でもマレー語でも可。ただし、日本語をローマ字表記するのはいただけません。たいていの場合、極めて読みづらくなります。

 
ほかから参照されることがなく、すぐ近くで使い捨てにする変数などは、逆に簡単な a とか b とか n とかにしておくとよいでしょう。それはそれで、使い捨てだということがよくわかります。
そのほか、たとえば配列名を personNames() として、そのうちのひとつを抜き出した値は personName とするなど、関連する同士の関係がわかる名前にする、といった工夫も効果的ですね。

例)
Public Sub ReadOutNames(personNames)
Dim personName
      For Each personName In personNames
            MsgBox personName
      Next
End Sub

マジック・ナンバー / マジック・ワード

マジック・ナンバーとか、マジック・ワードというのは、ソースコードの中に直接記述された生の値です。直接書いていても、当面は問題なく動きます。しかし、しばらくするとその意味がわからなくなる場合が多く、しかも、値を修正しようとした時に直し忘れがあったり、そもそもコードのどの部分を変更すればよいのか迷ってしまうことにもなるでしょう。
マジック・ナンバーやマジック・ワードがあると、可読性が低下するだけでなく、メンテナンスの妨げにもなります。
そこで、生の値をコードから追放し、できるだけモジュールの最初に定数として定義しておくことが推奨されます。

複数のモジュールで共有するような値はなおさらで、そういう値はグローバルな定数のみを記載する専用の標準モジュールを用意して、その中に Public Const として定義しておくのがよいでしょう。

マジック・ナンバーのあるサブルーチン

たとえば、次の CalcPrice() は価格と消費税、消費税込み価格を計算して表示するという簡単なマクロですが、この中に太字のマジック・ナンバーが含まれています(MsgBox の中の "価格="……などもマジック・ワードといえますが、ここでは目をつぶります)。価格を変更するには、1000を変更する、消費税率を変更するには、0.1と1.1を変更する必要があります。これらは、ソースコードを判読しないとわかりません。

マジック・ナンバーのあるサブルーチン

Public Sub CalcPrice()
Dim price, tax, priceIncludeTax
      price = 1000
      tax = price * 0.1
      priceIncludeTax = price * (1.1)
      MsgBox "価格=" & price & vbCrLf & _
            "消費税=" & tax & vbCrLf & _
            "消費税込み価格=" & priceIncludeTax, _
            vbOKOnly, "価格計算"
End Sub

(1) マジック・ナンバーを外に出す

そこで、次の CalcPrice() では、消費税率と価格を外の定数で定義しておくことにしました。showPrice() がちゃんと動くことが確認されていれば、消費税率を変更するのに、もう中を覗く必要はありません。
こんな簡単なプログラムだと、なんでそんな手間を、と思うかもしれませんが、これがもう少し大きなプログラムで、しかも消費税率がほかの関数でも使用する値だったりすると、その差は歴然としてきます。

マジック・ナンバーを外に出したサブルーチン

Private Const taxRate = 0.1
Private Const price = 1000

Private Sub showPrice()
Dim tax, priceIncludeTax
      tax = price * taxRate
      priceIncludeTax = price * (1 + taxRate)
      MsgBox "価格=" & price & vbCrLf & _
            "消費税=" & tax & vbCrLf & _
            "消費税込み価格=" & priceIncludeTax, _
            vbOKOnly, "価格計算"
End Sub

(2) マジック・ナンバーを専用のモジュールに出す

いろいろな関数が参照する共通の定数などは、専用のモジュール(たとえば "ConstModule" などと名付けて)を用意して、そこにまとめて Public Const として記載しておくと安心です。たしか Public にしたな、と思うのに、どのモジュールに書いたのか、探しあぐねるような事態を避けることができます。
このモジュールには、定数だけでなく、グローバルな変数(たいていは、プロジェクト全体の挙動を左右するような基礎的な変数)もいっしょに列挙しておくとよいでしょう。
たとえば、次のような具合。

標準モジュール         ConstModule

'----- Cell Names
Public Const MessageCellName = "Message"
Public Const AiFileCellName = "AiFileName"
Public Const LabelTopLeftCellName = "LabelTopLeft"
Public Const StartIndexCellName = "StartIndex"
Public Const EndIndexCellName = "EndIndex"
Public Const HeaddingRangeCellName = "HeaddingRange"
Public Const ProgressBarCellName = "ProgressBar"
'----- AIファイルのオブジェクト
Public AiDoc As Object
'----- レイヤ名のリスト
Public Labels()
'----- ProgressBar
Public ProgressBar As ProgressBarClass
'----- Flags
Public RequestShowLabels As Boolean
Public RequestShowSelectors As Boolean
Public RequestPrint As Boolean
Public RequestTryPrint As Boolean
Public CancelFlag As Boolean
Public PrintingNow As Boolean
Public BusyNow As Boolean

(3) 専用のワークシートに出す

もっと外に、専用のワークシート(たとえば "Const" などと名付けて)を用意して、そこに出してしまう、という手もあります。
ブックのオープン時にそのワークシートのセルから読み込んで Public 変数に入れておくなどすると、もっとメンテナンスが楽になるでしょう。
たとえば、以下の GetGeneralConst() はブックのオープン時に、Public な変数 TaxRate と Price をワークシート "Const" から読み込んでいます。もう、生の値はソースコード内にはありません。それはワークシート上にいつも見える形で、しかもいつでも変更できる形で存在します。

ThisWorkbook

Private Sub Workbook_Open()
      GetGeneralConst
End Sub

標準モジュール      ConstModule

Public TaxRate As Single
Public Price as long

標準モジュール      GetGeneralConst()

Private Const generalConstSheetName = "Const"
Private Const taxRateCellName = "TaxRate"
Private Const priceCellName="Price"
          :

Public Sub GetGeneralConst()
      With ThisWorkbook.Worksheets(generalConstSheetName)
            TaxRate = .Range(taxRateCellName).value
            Price = .Range(priceCellName).value
          :
      End With
End Sub

(1) (2) (3) のどれにするかは、その定数がどのくらいの範囲に影響するか、ソースを知らない人を含めてどのくらい更新のしやすさが求められるか、によって使い分けることになります。