和風スパゲティのレシピ

日本語でコーディングするExcelVBA

VBAにおいて「隠蔽」ってどのくらい重要?

クラスモジュールに関する考察です。

今回は「隠蔽」について考えたいと思います。


隠蔽をサクッと説明すると、

「2つのPropertyプロシージャによって変数を包み、
 代入と参照に何らかの制約をつけること」

が、隠蔽ですね。


これがExcelVBAにおいて、どのくらい大事なことなのかを考えます。


お忙しい人のために先に結論を書きますと、

  • 隠蔽は別に必須ではないから、使うかどうかをその都度ちゃんと考えよう
  • 特にExcelVBAでは、隠蔽はそこまで便利じゃない
  • むしろ隠蔽しないPublic変数のすごさをもっと褒めるべし
  • 隠蔽とは別に、Propertyプロシージャ自体はとっても便利

です。

そんな結論を言いたいんだな~と思いつつ、読み進めてください。
 

「隠蔽」の定義

始めに用語の確認だけしておきます。

先ほど、
「2つのPropertyプロシージャによって変数を包み、
 代入と参照に何らかの制約をつけること」

を「隠蔽」としましたが、これは厳密な書き方ではありません。


これ以外の方法で隠蔽をすることもできますし、
これをやったからと言って隠蔽とは呼ばないこともあるでしょう。

そもそも隠蔽とは、手段の話ではなく、目的の話です。


「オブジェクト指向とはなんぞや」を語りたいわけではないので、
「隠蔽」の学術的な定義を知りたい方は、他のところで補完してください。


用語での混乱を避けるために、先に結論だけ書きますと、

  • 隠蔽は別にしなくてもいいし、隠蔽するにしてもPropertyプロシージャが必須というわけではない。
  • 隠蔽とはプログラマを楽にするための仕組みなので、楽が出来そうと思ったやり方でやればいいし、楽ができなそうならやらなければいい。

が、最終的に言いたいことです。


ただ、これを説明する上で、いちいち注釈をつけながら「隠蔽の中で○○なもの」と書くと、私の力量では無駄にわかりづらい文章になってしまうため、

ExcelVBAの界隈ではそこそこ一般的な表現と思われる、

「2つのPropertyプロシージャによって変数を包み、
 代入と参照に何らかの制約をつけること」

を、ひとまず狭義での「隠蔽」と呼び、話を進めていきますことをご了承ください。


では始めます。

隠蔽はプログラマのための機能

ひとまず超簡単なサンプルクラスを作ります。

隠蔽を試すクラス

まだメソッドもないですが、こんな感じで。



ところで皆さん、健康診断はちゃんと受けましょうね。


健康診断はプログラミングでいうところの「テスト」です。

「俺元気だから健康診断受けなくていいや」

は、

「脳内デバッグしたからテストしなくていいや」

と同義ですよ?


と、話がそれましたが、
さてこれを隠蔽してみましょう。
 

Property Let 身長(設定値 As Double)
    If 設定値 > 2.72 Then
        Call Err.Raise(1000, , "ギネス記録より背が高いけどいいの?")
    End If
    身長_ = 設定値
End Property

こんな感じですね。


と見せかけて、実はこの判定、隠蔽でやる必要はないです。


この判定は、「入力データが正しいか」の判定です。

標準モジュールで、普通にユーザーにMsgBoxで聞くこともできますよね?


クラス内に判定を書くにしても、代入するたびに判定を行うのではなく、
データが渡された時点ですべてのプロパティに対して実行する

Function 取り込むデータに不審な検査値があるかチェックする() As Boolean
    If 身長 > 2.72 Then 警告Msg = "ギネス記録より背が高いけどいいの?"
    If 体重 > 1000 Then 警告Msg = "車検用のマクロと間違ってない?"

というメソッドを用意し、そこで判定した方がスマートです。
(広義ではこれも隠蔽と呼びますが)


ではProperty Letに書くのに適した判定はなにかというと、
例えば今回のクラスを、

  • このクラスで検査結果を取り込むのは最初の1回だけ
  • 一人の処理が終わったら、クラス(インスタンス)は破棄してNewしなおす

という想定で設計したとします。


このとき、

Property Let 身長(設定値 As Double)
    If 身長_ = 0 Then
        身長_ = 設定値
    Else
        Call Err.Raise(1000, , "このプロパティを変更できるのは最初の1回のみです。")
    End If
End Property

こう書くことで、プログラマがこのクラスの仕様を勘違いして、
一度使ったクラスに再度値を代入するのを防いでいますよね。


これがProperty Letの得意とする隠蔽の使い方です。

「=で渡される値が正しいか」はどこでも判定できますが、
「=の書き方は正しいか」の判定は、代入を検知するLetでしかできません。


要するに何が言いたいかというとですね。


隠蔽っていうのは、どちらかと言えば、

マクロの実行が正しく進むかのチェック機能ではなく、
マクロを正しく書けているかのチェック機能

なんですよ。


値を判定するのに、常にPropertyプロシージャに書く必要はありません。

マクロ全体の問題にかかわる判定は、クラスに隠すことなく、
衆人環視の中で判定してもいいわけです。


極端な言い方をすれば、「隠蔽はプログラマのための機能」です。

隠蔽を使えばマクロを書くのは便利になりますが、
完成したマクロが便利になるかはまた別のお話なんですよ。


つまり、隠蔽を頼るかどうかは、マクロを使うユーザーではなく、
クラスを使う人が決めていいということです。


まずはそこを意識しておきましょう。

隠蔽は常に便利な機能という訳ではない

さて、さっき「正しい隠蔽」の例として挙げた、
「初回以外の書き替えシールド」ってどのくらい必要ですかね?


プログラマのミスを防御する盾として、

' これらのプロパティは初回以外で変更しないこと
Public 身長 As Double
Public 体重 As Double
Public BMI As Double
Public 腹囲 As Double

こっちの、「ダンボールで作ったシールド」と比べるとどうでしょう?


これだけじゃ答えはわかりませんよね。


複雑なマクロをチームで開発し、
クラスを使いまわしながら各モジュールを組んでいくなら、
Property Letで作ったAT○ィ―ルドがいいでしょう。


逆に使い捨てのマクロであれば、
コメントすら書かずに、裸に段ボールで臨んでもいいです。


要するに、「隠蔽」は使えば使うほど便利な技術というわけではないのです。

  • クラス仕様がどのくらい複雑か
  • どのくらいの規模のプログラムか
  • どのくらいの人数で共有しているか

あたりを判断材料に、

クラスごとではなく、プロパティごとに、
隠蔽を使うべきかを考えなくてはなりません。

チーム開発、特にクラスの共有が少ないExcelVBA

さて、ExcelVBAでは、隠蔽はどのくらい有効でしょうか?


もうちょっと細かく言うと、
ExcelVBAのメインユーザーである「非プログラマの事務職」にとって、
隠蔽は便利な機能でしょうか?


基本的に自分だけで完結しているマクロであるなら、

  • クラスを別の人が使うなんて機会はそうそうない
  • 自分で作ったクラスを思い出すのは、コメントだけでもある程度いける

訳ですから、隠蔽で「仕様上想定していないマクロの書き方を防ぐ」ことには、そこまでメリットはなさそうですね。


また、1人で作るということは、クラスを共有することによる開発効率化のメリット
「自分がキッチリ作るかわりに、キッチリ作ったクラスをもらえる」
がない分、隠蔽用のコードを書く手間も馬鹿にできません。


先ほどの「身長」プロパティを見ても、
 

' 隠蔽バージョン
Private 身長_ As Double

Property Get 身長() As Double
    身長 = 身長_
End Property

Property Let 身長(設定値 As Double)
    If 身長_ = 0 Then
        身長_ = 設定値
    Else
        Call Err.Raise(1000, , "このプロパティを変更できるのは最初の1回のみです。")
    End If
End Property
' 隠蔽しないバージョン
Public 身長 As Double

 
300文字 VS 20文字 ですからね。
何個も作れば、結構な差になります。


更には「隠蔽用のコードでバグを書いてしまう」なんていう、
ミイラ取りがなんとやらも、コストに加える必要があります。


この辺を考えると、個人で作るマクロの域では、
「隠蔽」を活用する必要は、あまりないんじゃないかなと思っています。


「書き間違ってしまったときのバグ対策」というメリットは確かにありますが、
↑のコストに見合った働きをしてくれるかというと、
ちょっと厳しい気がしますよね。


もちろん絶対に不要なんてことはありませんが、
少なくとも「全部のプロパティを隠蔽する」ような場面は、
そうそうないんじゃないかな~と。

基本はPublic変数で作りながら、
必要に応じてPropertyプロシージャに置き換えていくような感じでも、
十分いい感じに「隠蔽」ができます。


それこそ「全部Public変数にしてなんかまずいか?」というと、
そんなにまずくなさそうなのがExcelVBAです。


「クラスだ!Propertyプロシージャ使わなきゃ!」


と、条件反射してしまっている方は、そんな肩ひじ張らずに、
「隠蔽するとおいしいマクロかどうか」
を、お茶でも飲みながら考えてみてください( ^-^)o旦~~

バグ発生がどのくらい痛いのかも考える

ここまで言っておいてなんですが、
さっきのサンプルマクロでは、私は「隠蔽」を使います。


なぜか?



医療における検査結果は、誤った値を出すことが許されないからです。


なので「検査の結果」に関しては、
これでもかというくらい、強固なマクロを組みます。


逆に、会議の資料とかを作るマクロでは、
全部Public変数で作りますよ。

面倒だし、どうせちゃんと見てる人おらんやろOo。―y(´▽`*)



最悪、事が起きた時に「ごめんなさい」で済むかどうか


も「隠蔽」を採用してまで強固なマクロを組むかどうかの、
大事な判断ポイントですね。



なお、もし本当に事が起きた際は、
ちゃんとごめんなさいしてくださいね?


現実世界でいうところの「隠蔽」だけは絶対にしてはいけません。

素直に謝るより良い結果になんて、絶対なりませんから。

Pubic変数だけで作るクラスもすごく便利

さて先ほどちらっと書いた、

' これらのプロパティは初回以外で変更しないこと
Public 身長 As Double
Public 体重 As Double
Public BMI As Double
Public 腹囲 As Double

これだけで、

隠蔽を試すクラス

これができることって、実はかなり便利ではないですか?


まあ変数だけなら「構造体」でもできちゃうことなので、
1個はメソッドに変えちゃいますか。

' これらのプロパティは初回以外で変更しないこと
Public 身長 As Double
Public 体重 As Double
Public 腹囲 As Double

Function BMI() As Double
    BMI = 体重 / 身長 / 身長
End Function

これでいい感じですね!


クラスの、ひいてはオブジェクト指向の目的は、
「.」でプロパティとメソッドをまとめることです。


それをこれだけ短い記述で実現できるってことは、
もっと褒められてもいいんじゃないかなって思っています。


クラスを学ぶときに、Propertyプロシージャも一緒に学ぶため、
クラス = Propertyプロシージャって思ってしまっている方が多い気がします。


そしてその結果、クラスの学習が終わった後も、
「クラスって便利そうだけどなんか面倒」
と思ってしまう可能性があります。


ですが↑の記述だけでいいってなると、
実はクラスってそんな面倒じゃないんですよ。


極端な話、

「Propertyプロシージャを絶対使う強固なクラスを作るが、
 クラスが面倒に感じて、3回に1回しかクラスを使わない人」

「めんどくさがって全部Public変数のクラスしか作らないが、
 クラスを身近に感じていて、常にクラスを使う人」

がいた時、
バグの少ないコードを書くのは後者です。


「隠蔽」は「クラス」の機能の内のほんの一部であり、
もちろんメリットも後者の方が大きいですからね。


Public変数と、SubとFunctionしか使わないクラスは、
すごく簡単で身近なものです。


クラスをもっとフラットに考えましょう。

そうすれば扱いにどんどん慣れていきますし、
その方が、結果的に早くPropertyプロシージャもマスターできますよ。

Propertyプロシージャは「隠蔽」のためのプロシージャではない

さてここまでは「隠蔽」に否定よりの意見を書いてきました。


しかしこれは、Propertyプロシージャを否定しているわけではありません。


これも誤解している方が多い気がしますが、
Propertyプロシージャは「隠蔽ができる」というだけで、
「隠蔽のための」プロシージャではありません。


Propertyプロシージャの隠蔽以外の使い方は、
例えばこんなものがあります。

変数でないものをプロパティに

Property Let プロシージャはただ値を受け渡すだけなので、
受渡先は変数である必要がありません。

Property Get A1セルの値() As Long
    A1セルの値 = Range("A1").Value
End Property

Property Let A1セルの値(設定する値 As Long)
    Range("A1").Value = 設定する値
End Property

と、Propertyプロシージャの対「A1セルの値」を用意することで、

A1セルの値 = 1
Debug.Print A1セルの値 ' = 1

と、A1セルの値をあたかも変数を扱っているかのように記述できます。


更には値の域を超えて、例えばシートの保護なんかを、

Property Let 書き込みOKスイッチ(設定する真偽値 As Boolean)

    If 設定する真偽値 = True Then
        WS集計シート.Unprotect
    Else
        WS集計シート.Protect
    End If

End Property

と定義しておくと、

書き込みOKスイッチ = True
シートを編集する処理
書き込みOKスイッチ = False

という代入で、シートの保護をつけ外しできるようにもなります。

「処理をプロパティに」することすらできますね。


まったく「隠蔽」はしていませんが、
これも便利なPropertyプロシージャの使い方です。

代入時にお供の変数たちも一緒に取得

例えば、

Private 処理シート_ As Worksheet
Public 最終行 As Long

~~~処理シートへのメソッドなどが続く

というクラスがあったとします。


このとき、

Property Set 処理シート(Setするシート As Worksheet)

    Set 処理シート_ = Setするシート
    最終行 = Setするシート.Range("A1", Setするシート.UsedRange).Rows.Count

End Property

と、処理シートのProperty Setプロシージャ内に最終行の取得コードも書いてしまうことで、
 

Set clsサンプルクラス.処理シート = Worksheets("集計表")

For R = 2 To clsサンプルクラス.最終行 ' ← これがSetと同時に自動取得されるようになる

このように、代入の裏で最終行をセットできるようになります。

便利ですね!


こんな風に、
代入する変数から取得できる、他の変数たちも一緒にセットする
ことにも、Property Let/Setプロシージャは使えます。

この使い方は、「作れば安心だけど作るまでが面倒」な隠蔽と比べると、
楽をするための使い方なので、気兼ねなく使えますよ。


というか、最終行の取得をシートのSet内に隠し、取得忘れや、別の方法での取得を防いでいるという点では、これも立派な隠蔽なんですよ。

プログラマがバグを気にしなくなるという意味で、
隠蔽もひとつの「楽をするための仕組み」です。


冒頭で書いた、

  • 隠蔽は別にしなくてもいいし、隠蔽するにしてもPropertyプロシージャが必須というわけではない。
  • 隠蔽とはプログラマを楽にするための仕組みなので、楽が出来そうと思ったやり方でやればいいし、楽ができなそうならやらなければいい。

というのが、なんとなくでも伝わるとうれしいです。

まとめ

今回はVBAにおける「隠蔽」について考察しました。

  • 隠蔽は別に必須ではないから、使うかどうかをその都度ちゃんと考えよう
  • 特にExcelVBAでは、隠蔽はそこまで便利じゃない
  • むしろ隠蔽しないPublic変数のすごさをもっと褒めるべし
  • 隠蔽とは別に、Propertyプロシージャ自体はとっても便利

という結論でしたね。


「クラスにPropertyプロシージャは必須」と思っていた方は、
Public変数しかないクラスを作って、クラスを身近に感じてみてください。

Propertyプロシージャを「隠蔽」にしか使っていなかった方は、
上の例を参考に、別の使い方ができないか、考えてみてください。

クラスモジュールの使い方の幅が、広がると思いますよ(´∀`)