t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 名前付き引数の正体はプロシージャの仮引数の変数名

今回は名前付き引数についてその概念と仕組みを紹介する。
名前付き引数はVBAにおいて基本的な機能であり、マクロの記録で作ったコードでよく使われているが、正確に理解している方は少ないように見受けられる。

かくいう私も初めて名前付き引数が何かを理解した時は目から鱗が落ちた気分だった。しかしすでに自分の中である意味常識化していてこれまで記事にするということを思いつかなかったのだ。

名前付き引数とは

名前付き引数とは、プロシージャに引数を渡す際順番ではなく名前で指定する方法をいう。「ナマエツキヒキスウ」と読む。

メッセージボックスを表示させるコードを例に名前付き引数の機能をみてみよう。
まずは名前付きではない引数から。

Sub hoge()
    MsgBox "続行しますか", vbYesNo, "確認"
End Sub

このようにMsgBox関数に引き渡す値は、1番目がメッセージ、2番目がボタンの種類やアイコン、3番目がタイトル表記というふうに、順番が決まっている。
たとえば、ボタンの種類を指定せずにタイトルを指定したければ、以下のように2番目の値を空白にする。これが通常の引数の渡し方である。

Sub hoge()
    MsgBox "こんにちは", , "挨拶"
End Sub

次に名前付き引数の場合を見てみよう。
名前付き引数では、引数名:=値と書くことで、順番に関係なく狙った場所に値を渡すことができる。

コードを書いていると関数呼び出しの際に以下のようなクイックヒントが出てくる。
f:id:t-hom:20170608210304p:plain
Prompt、Buttons、Title…などと書かれているのが名前付き引数の名前だ。

名前付き引数を使うと先ほどのコードはこのように2番目を飛ばしても問題ないし、

Sub hoge()
    MsgBox Prompt:="こんにちは", Title:="挨拶"
End Sub

このように逆にしても問題なく動作する。

Sub hoge()
    MsgBox Title:="挨拶", Prompt:="こんにちは"
End Sub

これが名前付き引数だ。
どんな時に便利かというと、たとえばWorkbooks.Openのような大量のオプション仮引数が存在するケース。

Sub hoge()
    Workbooks.Open "C:\work\Sample.xlsx", , , , "abc!xy123"
End Sub

"abc!xy123"が何なのかパッと見ても分かりにくい。

このように名前付き引数であればファイル名とパスワードを指定していることが一目瞭然。

Sub hoge()
    Workbooks.Open Filename:="C:\work\Sample.xlsx", Password:="abc!xy123"
End Sub

以下のように途中までふつうの引数で、一部だけ名前付き引数という指定もできる。

Sub hoge()
    Workbooks.Open "C:\work\Sample.xlsx", Password:="abc!xy123"
End Sub

逆に名前付き引数の後に順番によるふつうの引数は使えないので注意。

名前付き引数の正体

名前付き引数というのは、実は単にプロシージャの仮引数の変数名でしかない。

たとえば以下のように引き算を行うFunctionプロシージャを作ってみる。

Function 引き算(元の数, 引く数) As Long
    引き算 = 元の数 - 引く数
End Function

呼び出す際、同じようにクイックヒントが出るはずだ。
f:id:t-hom:20170608211725p:plain

一般的には単に値だけを書いて呼び出すが、

Sub hoge()
    MsgBox 引き算(30, 10)
End Sub

名前付き引数を使用すると以下のように項目を逆に書くこともできる。

Sub hoge()
    MsgBox 引き算(引く数:=10, 元の数:=30)
End Sub

※あくまで機能紹介のためのサンプルなので特に逆に書くメリットはありません。

名前付き引数の利点

  • Optional指定を多用したプロシージャを呼び出す際には名前付き引数で中間の引数を飛ばせる。
  • 渡している値が何なのかパッと見てわかるので、うまく使うと。コードの可読性が増す。

仮引数名なんてどうせ値を運ぶ役割しかないし、スコープも小さいから適当でいいやと思われがちだけれど、このようにプロシージャの仮引数名は、名前付き引数としてコードの可読性を助けるものなので、適当に名前をつけるべきではない。

VBA テキストボックスで複数行表示させる場合の最適なHeightは連立方程式で求まる

前回、テキストボックスの最適なフォントサイズについて書いた。
thom.hateblo.jp

そこでテキストボックスのHeightプロパティを21に設定したが、今回はその根拠と複数行書ける場合の最適なHeight値の求め方を紹介する。

Meiryo UI 12ポイントのときのテキストボックスの最適な高さ

まず以下のようにさまざまなサイズで実験を行った。
f:id:t-hom:20170521010150p:plain

このとき、高さ18では文字入力中に謎の下線が現れるというバグが、19と22ではなぜかアルファベットが太く見えるというバグが出現した。
つまりこれらは適切なサイズとは言えない。

次に余白のバランスを見るためにPowerPointで縞模様のシェイプをあてがってみた。
f:id:t-hom:20170521111647p:plain

高さ23、24は明らかに下が空きすぎているのでアウト。
ただしこれはフォントによるので注意。

メイリオなどは更に下余白が大きい。
thom.hateblo.jp

さて、あとは高さ20と21のどちらかが候補であるが、さらにズームし、余白部分を半透明化してみたところ、余白20ではパイプ文字が余白に差し掛かってしまうことが判明。
f:id:t-hom:20170521112106p:plain

ということで、Meiryo UI 12ポイントの最適なテキストボックスのHeightは21とした。
ただし、BorderStyleがfmBorderStyleSingleに設定されていることが前提。ほかのスタイルでは余白値が変わってしまうので、スタイル設定後に高さを合わせよう。

複数行のテキストボックスの高さを求める

テキストボックスのMultiLineプロパティをTrueに設定すると、複数行の表示が可能になる。さらにEnterKeyBehaviorをTrueに設定すると、Enterキーで改行できるようになる。
さて、このときHeightプロパティをいくつに設定すれば綺麗に見えるだろうか。
もちろん、グリッドに合わせて適当にマウスで設定したのでは綺麗にならない。
f:id:t-hom:20170521112935p:plain

そこで地道な作業であるが、Heightをプロパティで調節しながら、画像をペイントに張り付け、1行のときと2行のときで下の余白がぴったり一致するHeight値を探る。
f:id:t-hom:20170521113745p:plain

Meiryo UI 12ポイントで2行の場合、最適なHeight値は36だった。

じゃあ3行以上なら、この作業を延々と繰り返すのかというと、そんなことをする必要はない。

1行の場合のHeightが21、2行の場合のHeightが36という2つの値があれば、あとは計算で求まるのである。

つまりこういう連立方程式になる。

文字高さ × 1行 + 余白高さ = 21
文字高さ × 2行 + 余白高さ = 36

このままでは計算しにくいので文字高さをx、余白高さをyとしよう。
x + y = 21
2x + y = 36

一応、計算過程は以下のとおり。
f:id:t-hom:20170521115421p:plain

ということで、文字高さは15、余白高さは6と求まった。

では5行表示させる場合の最適な高さは?

15 × 5行 + 6 = 81

実際に81に設定してみた。

余白はバッチリ!
f:id:t-hom:20170521115736p:plain

以上。

VBA テキストボックスの最適なフォントサイズは12ポイント

VBAでユーザーフォームをデザインする際、フォントサイズに気を配っているだろうか。

まず、ふつうにテキストボックスを配置するとこのような外観になる。
f:id:t-hom:20170521010506p:plain

フォントはMS UI Gothicの9ポイント。

これ、ブログの画像だと読みにくくないけれど、実際にフォームを実行してみるとかなり小さい。

私の視力ではちょっと読みづらい。
いや、視力だけではなくて、環境のせいもあるだろう。
私は自宅ではモニターと70センチほど離れて作業しているので、標準的なユーザーよりもモニターが遠いと思う。

ユニバーサルデザインの考え方

視力とか環境なんて言い出したら、それぞれ最適なサイズは人によってバラバラになってしまう。
じゃあ誰に合わせて設計したらいいんだ?

このような疑問に答えてくれるのが、ユニバーサルデザインという考え方。

Wikipediaでは以下のように定義されている。

ユニバーサルデザイン(Universal Design、UD)とは、文化・言語・国籍の違い、老若男女といった差異、障害・能力の如何を問わずに利用することができる施設・製品・情報の設計(デザイン)をいう。

ユニバーサルデザインガイドライン

ユニバーサルデザインは特に老若男女さまざまな人を想定しないといけない行政の分野で積極的に取り入れられている。
画面デザインではなくあくまで紙面デザインの話になるが、紙面において本文の文字サイズで12ポイントを推奨しているものがいくつか見つかったので紹介する。

■わかりやすい印刷物のつくり方(横浜市)
http://www.city.yokohama.lg.jp/kenko/chifuku/fukumachi/publication/image/insatupdf.pdf

9ページ(PDF11枚目)より引用

大きさはできるだけ12 ポイント以上にしましょう。

■印刷物のユニバーサルデザイン(広島県)
https://www.pref.hiroshima.lg.jp/uploaded/attachment/6225.pdf

4ページ(PDF6枚目)より引用

● 「A4」サイズの用紙の場合,最も文字数の多い本文を12~14ポイントで作成すると読みやすい人の幅が広がります。

■だれもが見やすい印刷物の手引き(滋賀県)
http://www.pref.shiga.lg.jp/ud/02-torikumi/files/insatsunotebiki.pdf

3ページ(PDF5枚目)より引用

○ 本文の大きさは12ポイントを基本にします。

画面デザインでも12ポイントを採用

今回残念ながら画面デザインについてはガイドラインのようなものを見つけることができなかった。
おそらく紙面とちがって解像度やディスプレイサイズ・輝度などの設定値・デバイス(PCかスマホタブレットか)・使用環境などが異なるため、一概に何ポイントが最適だと言い切れないのだろう。

タイトルで12ポイントが最適と言い切ったのは、その方が釣れると判断したから(てへ)。

まぁ全くデタラメに12ポイントと言っているわけではなく、一応印刷物のガイドラインに合わせて12ポイントに設定してみて、自宅や会社など異なる環境で使用してみたところ私は非常に見やすいと感じたので12ポイントを採用することにした。

ちなみにフォント種類は「Meiryo UI」、BorderStyleはfmBorderStyleSingle、SpecialEffectはfmSpecialEffectFlat、Heightは21に設定している。
f:id:t-hom:20170521022037p:plain

単体で見るとノッペリして野暮ったく感じるかもしれないけれど、Windows 8 あたりからフラットデザインが主流になりつつあるので慣れだと思う。

以下の記事でフラットデザインを取り入れたデザインを紹介しているので参考までに。
thom.hateblo.jp
※上の記事ではテキストボックスにはMeiryo UIではなく、メイリオを使用した。メイリオ12ポイントの場合はまた最適なHeightが変わってくるので注意

テキストボックス以外のフォントサイズ

画面デザインでも12ポイントを採用していると書いたけれど、私はテキストボックス以外ではこれより小さなサイズもふつうに採用している。
なぜかというと、ラベル・ボタンなどは慣れてしまえば、それほど読まなくなるから。
配置場所や文字数からざっくりとそれが何であるかが判別できれば十分なのでそれほど不自由は感じない。

重要なのは、毎回内容が変わるテキストボックスにおいて読みやすいフォントサイズを採用すること。
特に電話番号・日付・商品コードや管理番号など、読み間違えると致命的なものについて、目を凝らさないと読めないというのでは辛い。
逆に長文は、読み疲れはするものの意外に小さめの文字でも読める。多少こまかい漢字が混ざっても文脈から判断できるからだ。

結論

この記事で私が言いたいのは何が何でも12ポイントということではなく、色々考慮してサイズを決めてねという話。

もちろんすべてを読みやすいサイズにしたうえでレイアウト上も収まりが良いのであればそれが一番だけれど、デザインを優先して切り捨てる部分も出てくるだろう。
そのときに、何を妥協して良いか、逆にここだけは死守すべき点は何かということをよくよく考えないと、使いにくい画面になる。

これまで格好いいけど使い勝手の悪い画面は、嫌というほど見てきた。
商品としてはそのほうが売れるし、意思決定者も気に入るからだ。

でも、もうたくさんだ。

システムの役割は仕事を効率化することである。使い勝手を犠牲にした格好よさなんてのは意味がない。

本当に使う立場に立って、どういったデザインがベストか考えてみよう。

VBAからVBScriptへマクロを移植する方法

今回はVBScriptをやってみたいVBA使いの方へ、VBAで書いたコードをVBSに移植する方法を説明しようと思う。
特にあまり知られていないであろう最初から移植性を意識したコードを書く方法について紹介する。

執筆のきっかけになったのはこちらの記事。
chemiphys.hateblo.jp

VBScriptの作り方

テキストエディタでコードを書いて、拡張子をvbsとして保存するだけ。
実行するにはダブルクリックする。
ドラッグ&ドロップしたファイルを処理するようなスクリプトも作れる。

VBAとの違い

エントリーポイント(プログラムの開始点)がファイルの先頭

VBAの場合、Subプロシージャを複数作ってそれぞれがエントリーポイントになるけれど、VBSの場合はファイルの先頭がエントリーポイントになる。
つまりSubプロシージャなどを書かずに、いきなりMsgBox "Hello"と書けば動くのだ。

意外に知られていないのは、普通にSubプロシージャも書けるということ。
エントリーポイントからCallしてやれば動作する。

Call Main
Sub Main()
    MsgBox "Hello"
End Sub

VBAと同じようにプロシージャの集まりとして作っておいてCallするという方法をとれば、VBAプログラムとの高い互換性を保ちつつスクリプト化できるのでおすすめ。

変数宣言に型を持たせられない

VBAならDim a As Integerなどと書けるところ、VBSではDim aとしか書けない。
私はよく、Dim a 'As Integerという風にAs以降をコメント化している。

イミディエイトウインドウが使えない

つまりVBAから移植するためにはDebug.Printは別の方法で書き直さないといけない。
Debug.Printはそもそもデバッグ機能なので本番マクロで使うのもどうなのって思うけど、個人用マクロで重宝するのは確か。

無いなら作ってしまおう。

'下で定義するDebugPrintクラスをもとに変数Debugを準備
Dim Debug: Set Debug = New DebugPrint

Call Main
 
Sub Main()
    Debug.Print "こんにちは"
    Debug.Print "さようなら"
    Debug.Flush 'FlushしてはじめてPrintした内容がMsgBoxで表示される。
End Sub

'以下にDebugPrintクラスを定義
Class DebugPrint
    Private message

    Public Sub Print(msg)
        message = message & msg & vbNewLine
    End Sub

    Public Sub Flush()
        MsgBox message
        Call Clear
    End Sub

    Public Sub Clear()
        message = ""
    End Sub
End Class

こうするとVBAからの移植性はアップする。
メインコードをわざわざ書き直さなくても、Flushを呼ぶという変更だけで済む。

参照設定できないので遅延バインディングになる

参照設定が使えないため、外部オブジェクトの作成はCreateObjectで行うことになる。
VBAで参照設定をやっている場合、以下を参考にCreateObject方式に書き換える。

参照設定とCreateObjectの対応リスト - You.Activate

その他の特徴

  • Rnd関数を使用する場合、その前の任意の行に「Call Randomize()」を挿入しないとランダムにならない。
  • 配列の宣言にDim a(1 to 3)といった指定ができず、3つの要素がほしい場合はDim a(2)と書いて0,1,2の3つを作るしかない。
  • Select文がVBAより低機能で基本的な書き方しかできない。
  • 日本語変数が利用できない。

追記

Twitterで、有益なリンクが流れてきたので追記(id:imihitoさんありがとう)
VBA の機能で VBScript に含まれていない機能

VBSではCollectionが使えないようなので、代替手段として以下の記事もどうぞ。
imihito.hatenablog.jp

VBAからVBScriptへマクロを移植する方法

  1. マクロをそのまま貼り付ける。
  2. 変数宣言から型の宣言を取り除く(あるいはコメントアウト)。
  3. エントリーポイントから実行したいマクロをCallする。
  4. 参照設定からCreateObject形式に切り替える。

上記で大体動くが、Debug.Printを使っていたり、Select文の高度な機能(Is比較、n To n表記など)を利用している場合は修正が必要になる。

VBScriptからVBAへマクロを移植する方法

上位互換とは言わないが、基本的に、Sub~End Subでくくってやれば大体のスクリプトはそのまま動く。
VBAも型宣言なしで動作するし、CreateObjectにも対応しているのでVBSからVBAへは極めて移植性が高い。
ただしWScript.Echoを使っている場合はMsgBoxかDebug.Printへ変更が必要である。

長いスクリプトをそのまま貼り付けると、長いプロシージャが出来上がるのであまり良くない。
VBScriptにおいても最初からプロシージャにコードを書くようにし、適切にプロシージャを分割しておくとVBAに移植してもスッキリと見通しの良いコードになる。

VBScriptにオススメのエディタ

やはりVBAで使用するVBエディタが一番楽である。

VBAで最初からVBSへの移植性を意識したプログラムを書く場合、外部オブジェクトの利用は以下のように参照設定とCreateObjectのハイブリッドにしておくと良い。

Dim fso As FileSystemObject
Set fso = CreateObject("Scripting.FileSystemObject")

そうすると、VBエディタで書くときはオブジェクトメンバーの入力候補が出るし、VBSに直すときはこのようにAs以降をコメントアウトするだけで済む

Dim fso 'As FileSystemObject
Set fso = CreateObject("Scripting.FileSystemObject")

VBScript関連書籍

まずVBAができる人にお勧めするのは以下の1冊。

WSHクイックリファレンス 第2版

WSHクイックリファレンス 第2版

これはFileSystemObjectやDictionaryなどのWSHのオブジェクトについて使い方がわからないときにリファレンスとして重宝する。
また、VBAでもこれらのオブジェクトは多用するが、VBAの書籍よりも詳しく書かれている。
VBScriptJScriptに両対応しているのでJavaScript派の方にもおススメ。

以下はあまり良く知らないけど、実質VBScriptの関連書籍って現行品はこれくらいしか無いので一応ひととおり掲載だけしておく。

Windows自動処理のための WSHプログラミングガイド 増補改訂版

Windows自動処理のための WSHプログラミングガイド 増補改訂版

[改訂版] VBScriptポケットリファレンス (POCKET REFERENCE)

[改訂版] VBScriptポケットリファレンス (POCKET REFERENCE)

最速攻略 VBScriptサンプル大全集 Windows7/Vista/XP/2000対応

最速攻略 VBScriptサンプル大全集 Windows7/Vista/XP/2000対応

VBScript逆引き大全500の極意

VBScript逆引き大全500の極意

ただ私の場合はほぼVBAって感じのVBScriptを書くのであまり書籍を参考にした覚えはないな。
ただドラッグ&ドロップ操作などのVBSに特有の機能もあるので一冊くらい専門の本を持っておいても良いかなとは思う。

VBSでクラスを使う方法

以下の記事がおススメだけれど、まずクラスって何って方にはちょっとザックリしすぎてるかなとは思う。
http://tuka.s12.xrea.com/index.xcg?p=VBS%A4%CE%A5%AF%A5%E9%A5%B9%A5%AA%A5%D6%A5%B8%A5%A7%A5%AF%A5%C8

クラスについてよく分からない方はまずVBAでクラスを使うこちらの記事と
ateitexe.com

手前味噌だけど、以下の記事がオススメ。
thom.hateblo.jp

VBA Boolean型をTrueやFalseと比較することの是非について

f:id:t-hom:20170509093420p:plain
If文の条件式でBoolean型をTrueやFalseと比較することは昔から論争の種だった。

たとえば以下のようなIf文。

If IsNumeric(x) = True Then
If IsDate(x) = False Then

わざわざTrueやFalseと比較しなくても、このように書くことができる。

If IsNumeric(x) Then
If Not IsDate(x) Then


Boolean型を返す関数をTrueやFalseと比較するのは初心者に多い書き方であるが、上級者のなかには冗長であることは承知のうえであえてTrueやFalseと比較するという方もいる。以降、この勢力を比較派と呼ぶことにする。

そしてこれに反対する勢力、つまり後者の書き方を較派と呼ぶことにする。

※ちなみにここで論じるのはあくまでBoolean型とTrue/Falseとの比較の是非であって、それ以外の数値や文字列については非較派であっても比較演算子を用いる。もちろん。

さあ、比較派と非較派、あなたはどっち?

ちなみに私は非較派。

ここから先は、なんちゃって対談形式で双方の主張を戦わせる。
もちろん、参加者も評者も私なので、どっちにしても私が肩入れする非較派が勝つんだけど。フハハハ。

比較派と非較派の対談

非較派:
さっそくだけど、まず、If IsNumeric(x) = True Thenって書きかたは無駄が多いよね。
たとえばIsNumeric(x)がTrueを返したらIf True = True Thenになるでしょ、その次にTrueとTrueをイコールで比較するとTrueになるからIf True Thenになるでしょ。
このひと手間無駄じゃない?IsNumeric(x)がTrue/Falseを返すんだから、そのまま使えばいいじゃない。

比較派:
いやいや。無駄っていうけど最近のPCだと実行時間は殆ど変らないでしょ。コンピューターはますます速くなってくるのにそんなケチくさいこと言わなくても。それにIf IsDate(x) = False Thenのケースはどうなの。たとえばTrueを返してFalseと比較しても、Trueを返してNotで反転させてもどちらにしても手間は変わらなくない?

非較派:
うーん。それは確かに。でも美しくないんだよな。たとえ速度が変わらなくても中で無駄なことしてるってのは。それに文章としても不自然に見えるんだよね。もし「xが日付」が偽と等しければ、なんて言い方しないよね。If Not IsDate(x) Thenであれば、そのまま、もしxが日付でなければと読めるし。

比較派:
プログラミング言語は英語とはまた別でしょ。

非較派:
それはそうだけど、VBってどちらかといえば英語みたいに読めることを目指した言語じゃない?C言語だと条件は丸カッコで括るところをVBAだとIf 条件 Thenだし、AndとかOrも記号じゃなくて英単語そのまま使っちゃってるし。英語には成れないけど、なるべく英語に近づけたほうが自然だと思うな。

比較派:
なるほどね。じゃあこれはどう?If文には条件式を書くって教わるでしょ?例外が増えちゃうのは良くないと思うんだ。

非較派:
うん、そこは賛成。でも「If IsNumeric(x) Then」って書いてもちゃんと「条件式」になってるし、例外も増えないよ。

比較派:
えっ?どういうこと?

非較派:
条件式って数式の親戚みたいなものと勘違いしてない?
ここでいう条件式ってのは、Boolean型に評価される式全般のことだよ。

比較派:
ちょっと待って、いきなりムズっ!Boolean型は分かるけど評価って何?式全般ってさっきのIsNumeric(x)のどのへんが式なの?式ってふつうはイコールとか、プラスとかマイナスとか記号使うよね?

非較派:
うーん。見事に数学を引きずってるね。世の中には数式以外の式もあるでしょ。たとえば化学式とか。

比較派:
あーっ。あれも式か。ん?じゃあ式って言葉の定義はなんなんだ?

非較派:
ググってみたら、「ある関係・構造を表すために、記号を連ねて書いたもの。」だってさ。

比較派:
ほらやっぱ記号…。IsNumeric(x)のどの辺が記号なの?まさかカッコとか言わないよね。

非較派:
記号をググってみたら「一定の事柄を指し表すために使う、文字・しるし等の総称。」だってさ。

比較派:
え?プラスとかマイナスとか、ハテナとか、そういうのが記号でしょ?

非較派:
それは「特殊記号」。ふつうにABCも記号だし、漢字もひらがなも記号ってこと。一般的には「特殊記号」を指して記号って呼ぶほうが多いけどね。

話を戻すと、つまり式ってのは特殊記号を使った数式だけじゃなくて、何かの関係とか構造を表してたらそれで式ってこと。たとえばIsNumeric(x)だと、「関数(変数)」っていう構造だよね。プログラミングでは関数だけじゃなくて、変数とか等価式(a > bなど)、リテラル(100とか"あ"とか直接値を書いたもの)、計算式(1 + 2など)、論理式(And、Orなど)も全部式なんだ。
式の定義は「評価すると値になる」ってこと。

比較派:
さっきから言ってるその「評価」ってのは何なのさ。

非較派:
プログラミング用語としての「評価」ってのは、何かの値を確定すること。計算って言わないのは、"A" & "B"みたいな式もあるから。これは"AB"って値になるけど計算じゃないよね。

比較派:
たしかに。

非較派:
あとたとえば単体のリテラルも一応、評価はされるんだ。
"100"は評価しても"100"だと思うでしょ。もちろんそのケースが普通だけど、そうじゃないケースもある。

たとえば文字列"100"はイチマルマルっていう文字列なんだけど、Dim a As Integer: a = "100"っていう状況なら?

比較派:
ああ、Integer型の変数に入れようとしてるから評価されると数値のヒャクになるわけね。

非較派:
そゆこと。これも計算してるわけじゃないから、「評価」っていう言葉で表すんだ。

さて、ずいぶん回り道したけど、これで「If IsNumeric(x) Then」って書いてもちゃんと「条件式」になってるってことはわかって貰えた?

比較派:
まぁそう考えると定義上は条件式だね。でも…なーんかこじ付けのような気がするんだよね。
ふつうIfに書く条件式は比較演算でしょ。関数を直接書くのってやっぱ例外だと思うけど。

非較派:
いや、例外ってわけでもないよ。さっきも言ったように条件式ってのは、Boolean型に評価される式全般のことだから関数でも変数でもなんでも書ける。

比較派:
ますます例外が増えない?いま出てるだけでもこれだけあるでしょ。

  • Ifには基本的に比較演算子を使った条件式を書きます。
  • AndやOrで複数の条件を並べて書くことができます。
  • Boolean型を返す関数はそのまま書くことができます。
  • Boolean型の変数もそのまま書くことができます。

非較派:
いやいや。そもそも、基本的に比較演算を書くって前提が間違ってるよ。If文が見てるのは評価された後のTrueかFalseだけ。つまり比較演算は、TrueかFalseを返すための一つの方法に過ぎないってこと。
AndやOrで複数の条件をってのも、まるでIfが複数条件を処理してるみたいだけど、違うね。

比較派:
えっ?だって複数条件書けるじゃない。
たとえばIf a = 10 And b > 9 Thenとか。

非較派:
結果的にはね。でもそれ、Ifの機能じゃないから。
AndとかOrは論理演算っていって、両辺がTrueかFalseかしか見てないんだ。
たとえば今言ったIf a = 10 And b > 9 Thenの計算手順を分解してコードに書くとこんな感じ。

Dim a As Integer
a = 10
Dim resultA As Boolean
resultA = a = 10
'右のイコールは比較演算子、左のイコールは代入演算子であることに注意
'aは10なので、resultAにはTrueが入る。

Dim b As Integer
b = 10
Dim resultB As Boolean
resultB = b > 9
'bは9より大きいので、resultBにもTrueが入る。

Dim result As Boolean
result = resultA And resultB
'resultAとresultBはどちらもTrueなので、resultにはTrueが入る。

If result Then
    '何かする
End If

比較派:
ということは。。

非較派:
そう。例外が増えるんじゃなくて、最初からIfに例外なんて無いんだ。だってTrueかFalseしか見てないからね。
If True Thenとか、If False Thenって書く代わりに、最終的にTrueとかFalseに評価される式も書けるってだけのこと。

比較派:
なるほどね。でもやっぱ初心者には今みたいな話って難しくない?

非較派:
そうだね。話が難しいってよりも、この手の話に興味を持ってもらうのが難しいね。やっぱやりたいことがあってプログラミングするわけだから、ピンポイントでHow Toを知りたいよね。

でもそうすると「この場合はこう」っていう個別事例の積み重ねで例外だらけの知識になっちゃうからかえって覚えることも多いと思うんだ。覚えたこと以外に応用も利かなくなっちゃうし。

比較派:
なるほど。ある程度のレベルになったらやっぱりこういう基礎をしっかり学び直して、自由にプログラムが組めるといいよね。

ってことで時間も迫ってきましたので本日の対談はここまで。
ありがとうございました。

あとがき

ただいまブログ開設から2年と4か月ちょい。文体もいい感じで崩れてきたかなということで、新たな試みである対談形式にチャレンジ。

これはやっぱ書いててこっぱずかしいな。ノリで乗り切るしかない感じがする。

こういうのってやっぱキャラ立てが重要かなと。最初片方をごてごての関西弁にしようかと思ったけど、なんかその安易さが見透かされそうで恐れをなして逃げた。

結局あまり冒険しない感じの文体で書き上げてしまったのでノッペリしてつまんなかったかもしれないけど、チキンハートなのでそこはご容赦を。

さて、内容の結論。
私の立場は非較派なので、If文の条件式でBoolean型をTrueやFalseと比較しないという項目をコーディングガイドラインに追加しようかなとは思ってるんだけれど、究極的には好みの問題でもあるので、どう書くのが絶対解というのはないかなと。

Boolean型がTrueやFalseと比較されてるのを見るとなんか気持ち悪いけれど、他人が個人的に書いてるコードにいちいち目くじらを立てるつもりはない。

ただやっぱ少なくともIfの仕組みを知ったうえでの選択であってほしいなとは思う。

ブクマで興味深い意見をいただいたので追記

それがし比較派でござる。関数名が長くなってしまうと、If の次の「NOT」を見落としやすいので「=False」の書き方に統一しようぜっていう、前の会社にいた時の規約がクセになっております。

なるほど、日本人らしい感想ですね。
アメリカンならNotを見逃すなんてことはなく、むしろ日本語の最後のちゃぶ台返し「もにょもにょもにょ~では無い」が信じれらないって感じでしょうけども。私も日本人なのでNotを見逃したらヤバイ感はわからないでもなく。
まあでも英語も勉強した身としては慣れるとNotが非常にわかりやすいので、どっち派になるのかはその人のバックグラウンドにもよるのかも。

私は冒頭にNot(違うんたぜ)を持ってくる英語の明快さを支持します。逆に日本語の、さんざん話しといて最後に否定できちゃう文法ってこれズルくない?感が理解できるようになってきたといいますか。。

英語圏の方からすると、いまさら「ない」かよ、先いえよ「Not」って。という感覚ですかね。

VBAでアホな実験 ~ Not演算子は何個連続で使えるか

二重否定文…それは時に控えめな肯定を、時に強い肯定を生み出す日本語のテクニックである。

控えめな肯定の例「できなくもない」
強い肯定の例「やらねばならない」

なんてことを考えてたら、そういやNotって二個以上つなげても大丈夫なんかしらと、アホなことを思いついたので、これは検証せねばならない。

実際にやってみた。

Sub hoge()
    a = 100
    If Not Not IsNumeric(a) Then
        MsgBox "数値に違いない!!"
    Else
        MsgBox "数値ではない"
    End If
End Sub

f:id:t-hom:20170430053159p:plain

おお、どうやら問題ないようだ。

では4個ならどうか。

Sub fuga()
    a = 100
    If Not Not Not Not IsNumeric(a) Then
        MsgBox "数値と言えなくもない"
    Else
        MsgBox "数値ではない"
    End If
End Sub

f:id:t-hom:20170430053339p:plain

いけてるね。
ちょっとこの先は数値ではなくTrueに対して反転していこう。

Sub piyo()
    If Not Not Not Not Not Not Not Not Not Not _
    Not Not Not Not Not Not Not Not Not Not _
    Not Not Not Not Not Not Not Not Not Not _
    Not Not Not Not Not Not Not Not Not Not _
    Not Not Not Not Not Not Not Not Not Not _
    Not Not Not Not Not Not Not Not Not Not _
    True Then
        MsgBox "Hello"
    Else
        MsgBox "GoodBye"
    End If
End Sub

こんなのも問題なく実行できた。


これ無限につなげていけるんじゃないの?
と行コピーしまくってたら…
f:id:t-hom:20170430054504p:plain

えっ、そんなところに限界が!?

数えてみると、行継続文字は24個、つまり25行目までで限界らしい。

またひとつ賢くなった。
いや、ひとつアホになったかな。。

いいだろう。
それなら改行なしでやってやる。

てことでNotをコピペで注入して右へ右へと伸ばしていく。
f:id:t-hom:20170430055532p:plain

あれっ?
f:id:t-hom:20170430055752p:plain
いつのまにやら二行目に。。。

一行目の最終を確認すると、1023桁。
f:id:t-hom:20170430060006p:plain

このあとスペース1個は入ったけれど、それ以上1文字も入らない。
つまりVBEの1行の最大桁数は1024桁だということ!

まぁどうでもいい知識。

ということは、Notの限界個数は計算で求まる。
まず一行目は「If 」がスペース込みで3文字、最後の行結合文字「_」で1文字、あとはスペース込みの「Not 」4文字が何個入るかということ。

(1024-3-1) / 4 = 255個

255個入る!と思ったら最初のゼロ文字目を1桁と数えるらしく、結局254個だった。文字数でいうと1行に1023文字しか入らないわけね。
気を取り直して二行目。

(1023 - 1) / 4 = 255.5

ということでNotが255個、試してみたらちゃんと入った。

最終行は「True Then」を引いて、

(1023 - 9) / 4 = 253.5

Notが253個

トータルすると、
254個 + (255個 * 23行) + 253個 = 6372個!!

理論上、Not演算子は6372個連続で使えるということに。

ではジャンジャンコピーしていこう。
f:id:t-hom:20170430061806p:plain

あれ・・・?
f:id:t-hom:20170430050848p:plain

からの。。
f:id:t-hom:20170430050543p:plain
そんなまさか!

Excel様の堪忍袋の緒が切れた。。
エラーも出してくれない。

さすがにそんなアホはもう知らんと。

どうやら一定数を超えるとバグるらしい。MSさん、しっかりデバッグしてくださいよ~。
…まずはお前の頭をデバッグしてこいと言われそうだな。

さて、仕方ないのでここから数個づつ配置しながらバグが発生するポイントを探ったところ…

ついに。。完成!
f:id:t-hom:20170430052035p:plain

今回の検証では3621個というのが限界値のようだ。
ちなみに環境はWin7 32bit Office2010 Pro 32bit。

はい、おしまい。
あー疲れた。

VBA クラスモジュールでRangeの仕組みを説明する

昨日公開した以下の記事について、比喩ではちょっと難しいという声をいただいたので、今回はクラスモジュールを使って私が想像するRangeの仕組みを具体的なコードで解説しようと思う。
thom.hateblo.jp

まずはSheetの模擬クラスとRangeの模擬クラスを用意する。
本物と同じ名前にしてしまうと、メインコードで使う際にちゃんと自作の方を使ってるの?という疑惑につながるので、オブジェクトの名前はオリジナルにしておこう。

Sheetの代わりにSheeeet、Rangeの代わりにoRange(オレンジ)。
f:id:t-hom:20170417214655p:plain

まずSheeeetのコードはこちら

Private table(1 To 9, 1 To 9) As String

Property Get oRange(adddresss As String) As oRange
    Dim ret As oRange: Set ret = New oRange
    Dim x As Long: x = Ato1(Left(adddresss, 1))
    Dim y As Long: y = CLng(Right(adddresss, 1))
    ret.Init Me, x, y
    Set oRange = ret
End Property

'列のアルファベット(A~I)を列番号1~9に変換する関数
Function Ato1(char As String) As Integer
    Ato1 = InStr(1, "ABCDEFGHI", char)
End Function

Friend Property Let Value(r As Long, c As Long, v As String)
    table(r, c) = v
End Property

Friend Property Get Value(r As Long, c As Long) As String
    Value = table(r, c)
End Property

次にoRangeのコード

Private parent_ As Sheeeet
Private row_ As Long
Private column_ As Long

Sub Init(p As Sheeeet, r As Long, c As Long)
    If parent_ Is Nothing Then
        Set parent_ = p
        row_ = r
        column_ = c
    Else
        Err.Raise vbObject, "oRange", "このoRangeはすでに初期化されています。"
    End If
End Sub

Property Let Value(v As String)
    parent_.Value(row_, column_) = v
End Property

Property Get Value() As String
    Value = parent_.Value(row_, column_)
End Property

そして標準モジュールに書くメインコードがこちら

Sub Rangeモドキ実験()
    Dim sh As Sheeeet: Set sh = New Sheeeet
    
    'まずは普通に値を設定して表示
    sh.oRange("A1").Value = "Hello!"
    sh.oRange("B3").Value = "GoodBye!"
    MsgBox sh.oRange("A1").Value
    MsgBox sh.oRange("B3").Value
    
    'オブジェクト比較でFalseになる実験
    MsgBox sh.oRange("A2") Is sh.oRange("A2")
    
    'oRange型変数に入れて使用
    Dim r1 As oRange
    Set r1 = sh.oRange("C4")
    Dim r2 As oRange
    Set r2 = sh.oRange("C5")
    r1.Value = "Konnichiwa"
    r2.Value = "Sayonara"
    
    MsgBox r1.Value
    MsgBox r2.Value
End Sub

これでメインコードを実行すると、本物のRangeオブジェクトと同じようにValueの設定、取得ができる。
まあ偽物なので定義した以上の機能はないけど。本物のシートに書き込まれるわけではなく、シートを模したSheeeetオブジェクト内の配列に値が設定されるだけ。

さて、解説だけど全部は面倒なのでポイントだけ。
まずSheeeetクラスの以下の1行。

Private table(1 To 9, 1 To 9) As String

これが9×9のセルのを模したもの。実際のシート上の値はここに保存される。
しょぼいけどまぁ説明用サンプルなので。

次にSheeeetクラスのoRangeプロパティ。

Property Get oRange(adddresss As String) As oRange
    Dim ret As oRange: Set ret = New oRange
    Dim x As Long: x = Ato1(Left(adddresss, 1))
    Dim y As Long: y = CLng(Right(adddresss, 1))
    ret.Init Me, x, y
    Set oRange = ret
End Property

プロパティ名のoRangeと型名のoRangeはたまたま同じ名前になってるだけなので注意。これがRangeの理解がややこしくなる原因の一つ。名前が一緒なのでプロパティとオブジェクトを混同しがち。

このプロパティプロシージャでは内部でoRangeオブジェクトを生成して、それを返している。
ret変数に新規のoRangeオブジェクトを格納し、そのoRangeオブジェクトのInitプロシージャにMe(つまりSheeeetオブジェクト自身)を渡している。

そしてoRangeオブジェクトのInitプロシージャではこれを仮引数pで受け取り、自身のparent_変数にセットしている。このときに行と列の情報も受け取って自分のrow_とcolumn_に代入する。

Sub Init(p As Sheeeet, r As Long, c As Long)
    If parent_ Is Nothing Then
        Set parent_ = p
        row_ = r
        column_ = c
    Else
        Err.Raise vbObject, "oRange", "このoRangeはすでに初期化されています。"
    End If
End Sub

これでoRangeが準備できたので、Valueプロパティで値をいじるとparent_変数にセットしたSheeeetオブジェクトのValueプロパティが操作される。

Property Let Value(v As String)
    parent_.Value(row_, column_) = v
End Property

Property Get Value() As String
    Value = parent_.Value(row_, column_)
End Property

つまりデータはあくまでSheeeetオブジェクト内にあり、oRangeは自身が参照を保持しているSheeeetオブジェクトのプロパティを通じて操作しているということ。

ちなみにSheeeet側のValueプロパティはFriendスコープにしているけれど、今回SheeeetのValueはoRangeによってのみ操作されるのであって、メインコードから直接操作はしませんよという意味づけのためにFriendスコープにした。
Friendは同じプロジェクト内でのみ参照できるというスコープである。今回のメインコードでは同じプロジェクト内に書いたので特に操作を制限するといった効果はない。

Excelブック1つごとに1つのプロジェクトしか作れないので、Friendスコープを活かそうと思ったらアドイン化して参照設定するしかないけれど。。
【参考】
thom.hateblo.jp

4/18追記

複数セルは?と突っ込まれそうなので予め言い訳しておくと、複数セルに対応させるとコードの複雑度が増すので今回はあえて単一セルのみサポートするRange型もどきとした。
Range型はエージェントのように機能するという点を解説するにはこれで十分なので。

ちなみに複数セルに対応するには、oRange型の内部で座標をコレクションか配列で持たせ、Valueにアクセスされたら座標をForかFor Eachでまわしながら連続でSheeeetにアクセスすれば良い。広い範囲を扱う場合は開始セルと終了セルを持たせておけばOK。ただし飛び地を扱うにはもう一工夫必要。などなど考えていくと結構面倒なコードになる。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。