クラスモジュールに関する考察です。
今回は「隠蔽」について考えたいと思います。
隠蔽をサクッと説明すると、
「2つのPropertyプロシージャによって変数を包み、
代入と参照に何らかの制約をつけること」
が、隠蔽ですね。
これがExcelVBAにおいて、どのくらい大事なことなのかを考えます。
お忙しい人のために先に結論を書きますと、
- 隠蔽は別に必須ではないから、使うかどうかをその都度ちゃんと考えよう
- 特にExcelVBAでは、隠蔽はそこまで便利じゃない
- むしろ隠蔽しないPublic変数のすごさをもっと褒めるべし
- 隠蔽とは別に、Propertyプロシージャ自体はとっても便利
です。
そんな結論を言いたいんだな~と思いつつ、読み進めてください。
- 「隠蔽」の定義
- 隠蔽はプログラマのための機能
- 隠蔽は常に便利な機能という訳ではない
- Pubic変数だけで作るクラスもすごく便利
- Propertyプロシージャは「隠蔽」のためのプロシージャではない
- まとめ
「隠蔽」の定義
始めに用語の確認だけしておきます。
先ほど、
「2つの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プロシージャ以外の方法で隠蔽をすることもできますし、
逆にそれをやったからと言って隠蔽とは呼ばないこともあるでしょう。
そもそも隠蔽とは、手段の話ではなく、目的の話です。
とはこういうことです。
これも踏まえて結論を書くなら、
- 隠蔽は別にしなくてもいいし、隠蔽するにしてもPropertyプロシージャが必須というわけではない。
- 隠蔽とはプログラマを楽にするための仕組みなので、楽が出来そうと思ったやり方でやればいいし、楽ができなそうならやらなければいい。
ということですね。
まとめ
今回はVBAにおける「隠蔽」について考察しました。
- 隠蔽は別に必須ではないから、使うかどうかをその都度ちゃんと考えよう
- 特にExcelVBAでは、隠蔽はそこまで便利じゃない
- むしろ隠蔽しないPublic変数のすごさをもっと褒めるべし
- 隠蔽とは別に、Propertyプロシージャ自体はとっても便利
という結論でしたね。
「クラスにPropertyプロシージャは必須」と思っていた方は、
Public変数しかないクラスを作って、クラスを身近に感じてみてください。
Propertyプロシージャを「隠蔽」にしか使っていなかった方は、
上の例を参考に、別の使い方ができないか、考えてみてください。
クラスモジュールの使い方の幅が、広がると思いますよ(´∀`)