t-hom’s diary

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

Static変数を利用してステータスバーに文字をスクロールさせる。

今回はExcelのステータスバーに電光掲示板のように文字をスクロールさせるマクロを紹介。
もともと作りたかった案件とは違うが、そちらが失敗して副産物として単体で何か使えそうな気がしたので簡単にメモ。

矢印の方向に文字が流れる。
f:id:t-hom:20180527132653p:plain

コード

※Mainプロシージャを起動すると無限ループになるので止め方をご存知の方のみ実行してください。

#If VBA7 Then
    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
#Else
    Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
#End If

Sub Main()
    Do
        Application.StatusBar = RotateString("ただいまマクロを実行しています。しばらくお待ちください。", " ")
        DoEvents
        Sleep 100
    Loop
End Sub

Private Function RotateString(cap As String, pad As String)
    Dim paddedString As String
    paddedString = String(Len(cap), pad) & cap & String(Len(cap), pad)
    Static i As Long
    If Len(cap) * 2 > i Then
        i = i + 1
    Else
        i = 1
    End If
    RotateString = Mid(paddedString, i, Len(cap))
End Function

解説

RotateString関数のStatic変数 i がポイント。
Staticで宣言した変数はプロシージャが終了しても値が保持されるので、このように呼び出すたびに違う値を返す関数を作るのに便利だ。

Staticを使いこなすコツは、「明示的に初期化しない」こと。

たとえば以下の箇所は、宣言された後、値が代入される前にIf文の判定でiを参照している。

    Static i As Long
    If Len(cap) * 2 > i Then
        i = i + 1
    Else
        i = 1
    End If

Long型で宣言したなら初期値は0なので、初回の呼び出し時にiは0である。
2度目の呼び出し時には、iは前回の呼び出しの終了時点の値をキープしている。

もし以下のように0で初期化する処理を挟んでしまうと何度呼び出しても0が入ってしまい、Staticにした意味がなくなる。

    Static i As Long
    i = 0
    If Len(cap) * 2 > i Then
        i = i + 1
    Else
        i = 1
    End If

明示的に代入する前に参照させるのがStaticを使いこなすコツだ。
これは慣れるまでなんとなく気持ち悪いかもしれないが、そういうものだと割り切るしかない。

関数のおおまかな処理

(1) まず、引き渡された文字列に、それと同じ長さの詰め物を両サイドに敷き詰める。
詰め物 & "サンプル" & 詰め物
      ↓こうなる
"□□□□サンプル□□□□"

(2) iが文字列の長さの2倍未満なら、iを増分させる。
初回は0なので、iが1になる。

(3) iの値の位置から元の文字列と同じ長さを切り出す。
"□□□□"となる。

2回目の実行ではiが増えているので、"□□□サ"、3回目は"□□サン"という風に、切り出す位置を変えているだけ。

以上

プログラムコード中に値(あたい)は登場しない

VBAに限らず全てのプログラミング言語で言えることだが、コード中に値(あたい)が直接登場することは無い。
最近、「値とは何か」ということを掘り下げて考えることがあり、ようやくこの結論に行きついた。

たとえば、数字の「1」。これは一般的に値だと理解されるが、表面的には単なる文字にすぎない。
「ひとつ」という概念を表した文字だ。

「1」という値が現れるのは、その文字を見た人の頭の中である。
値は確かに存在するが、書かれた文字は単なる記号でしかない。人が理解することで、初めて値が現れる。

プログラミングにおいても同じことが言える。
コンピューターは物事の概念を理解しないが、しいていえばコンピューターにとっての値とは、CPUが直接処理可能な「電気信号」である。

ただしブログ上に電気信号を書き表すというわけにもいかないので、便宜上スイッチのON・OFFを1と0で表した二進数で説明する。

たとえば次のコードを考えてみる。

Debug.Print 2 + 3

仮にCPUが32ビットだとすると、
「2」は「00000000000000000000000000000010」に変換され、
「3」は「00000000000000000000000000000011」に変換され、
CPUで加算されて「00000000000000000000000000000101」になり、
「5」という文字に変換されて出力される。

プログラム中に現れる「2」や「3」は値ではなく式である。
式と聞くと数式を思い浮かべる方が多いと思うが、本来の意味は「何かの事物や構造を記号で書き表したもの」を指す。
たとえば「2」は2という概念を表した式だ。

このように特定の値を直接的に指す式のことを、リテラルと呼ぶ。
リテラルの和訳は「即値」といい、これまで私はリテラルを式でもあり、値でもあると考えていた。

しかし、リテラルは単に式であり、値ではない。
つまりコード中に現れるこれまで「値」と呼ばれてきたものは、すべて「式」だ。

この視点でFizzBuzzを見てみよう。

Sub FizzBuzz()
    Dim i As Integer
    For i = 1 To 100
        If i Mod 15 = 0 Then
            Debug.Print "FizzBuzz"
        ElseIf i Mod 3 = 0 Then
            Debug.Print "Fizz"
        ElseIf i Mod 5 = 0 Then
            Debug.Print "Buzz"
        Else
            Debug.Print i
        End If
    Next
End Sub

文法的には、こういうことになる。

Sub 識別子()
    Dim 変数 As 型名
    For 変数 =ToIfThen
            Debug.PrintElseIfThen
            Debug.PrintElseIfThen
            Debug.PrintElse
            Debug.PrintEnd If
    Next
End Sub

「式」という表記が、すべての例外を吸収してくれる。

ザッツ、シンプル!ビューティフォー!

裏付け

以下はVBAの言語仕様書
VBA Language Specification(英語)

この76~77ページにFor文の定義がある。

For文の定義

for-statement = simple-for-statement / explicit-for-statement
simple-for-statement = for-clause EOS statement-block "Next"
explicit-for-statement = for-clause EOS statement-block ("Next" / (nested-for-statement ",")) bound-variable-expression
nested-for-statement = explicit-for-statement / explicit-for-each-statement
for-clause = "For" bound-variable-expression "=" start-value "To" end-value [stepclause]
start-value = expression
end-value = expression
step-clause = Step" step-increment
step-increment = expression

For文の定義の訳

for-statement = simple-for-statement / explicit-for-statement
for文 とは シンプルなfor文 または 明示的なfor文 である。

simple-for-statement = for-clause EOS statement-block "Next"
シンプルなfor文 は for節 ステートメントの終わり ステートメントブロック Next で構成される。

explicit-for-statement = for-clause EOS statement-block ("Next" / (nested-for-statement ",")) bound-variable-expression
明示的なfor文 は for節 ステートメントの終わり ステートメントブロック (Next または (ネストされたfor文とカンマ)) 束縛変数式 で構成される。

nested-for-statement = explicit-for-statement / explicit-for-each-statement
ネストされたfor文 とは 明示的なfor文 または 明示的なfor-each文である。

for-clause = "For" bound-variable-expression "=" start-value "To" end-value [stepclause]
for節 は For 束縛変数式 = 開始値 To 終了値 [step節] で構成される。

start-value = expression
始値 は 式である。

end-value = expression
終了値 は 式である。

step-clause = Step" step-increment
step節は Step step増分 で構成される。

step-increment = expression
step増分 は 式である。

参考

VBAの言語仕様書の定義はABNFというメタ言語で記述されている。基本情報技術者試験で出題されるBNFを拡張したものだ。
ABNFの定義はRFC4234 http://www.rfc-editor.org/rfc/rfc4234.txt を参照(英語)

以上。

学習において、疑問を抱えたまま次のページをめくる勇気

何かを学んでいると、まだ説明されていない箇所に対して色々と疑問が湧いてくることがある。たしかに、分からないというモヤモヤした気分のまま次に進むのはなんとなく気持ち悪いものだ。

一般的に学習において疑問を持ち、自主的に調べるのは良いこととされるけれど、最近思うのはひょっとすると一概にそうとも言えないんじゃないかということ。

私がまずいと思うのは「分からない。嫌だ。」という疑問に対する不寛容さである。

例えば自分で調べてみたものの疑問が疑問を呼んでさらに混乱し、結果的に一歩も動けなくなるという事態に陥る危険性もある。特に独学の場合は適切なアドバイスが受けられないため、疑問に対する不寛容さは挫折につながりやすいのではないか。

以前、物事は全てがつながった時に初めて完全に理解できるという趣旨の記事を書いた。
thom.hateblo.jp

物事を理解するにはそれ相応のステップが必要であり、地道に学んでいく努力も必要である。疑問があって先に進めない、躓いてしまうというのは、ある意味ではそうしたコツコツとした地道な努力を嫌い「すぐに結果が欲しい」という甘えなのかもしれない。

とりあえず、疑問は疑問のまま受け入れて、今は分からないけれど次に進んでみようという勇気も必要なんじゃないかと思う。

ただこの記事を鵜呑みにして何も自分で調べないという姿勢もそれはそれで学習においてマイナスだと思うので、そのあたりはバランスが大事。

高品質なコードを短時間で編み出すには

プログラムのコーディングで一番時間を消費するのは「思考」と「試行錯誤」である。

コーディングスピードが落ちることを嫌って極端に短い変数名をつけたり、一つのプロシージャで一気に書ききったりするとコードが把握しにくくなり、「思考」に時間をとられ、うまくいかない時の「試行錯誤」に時間をとられ、結果的に相当に時間をロスしてしまう。

私が普段のコーディングで大層な変数名を付けたり、プロシージャを分割したり、クラスモジュールを使用したりするのは、コードを把握しやすくして頭の負担を減らし、思考と試行錯誤に費やす時間を大幅にカットするためだ。

わざわざクラスモジュールなどという大層なものを持ち出してきやがってという批判は時折目につくのだけれど、これは「美しいコード第一主義」などではなく、単にそれを使ったほうが早く作れて、メンテナブルで、ついでに美しいからだ。

高品質なコードを短時間で編み出すには、急がば回れである。

VBAの知識の依存関係の整理を試みる

※今回の記事は基本的に自分用の備忘録です。

VBAの学習における知識の依存関係の整理を試みた。
試みたというのは、お世辞にも成功したとは言えない為である。

書籍でプログラミングを学ぶ際は、ふつう1ページずつ読み進めていく。
これに対して、実際の知識体系は必ずしもシーケンシャルに整理できるわけではない。

たとえばオブジェクトを正しく理解しようと思ったら、中身がどうなっているのか知る必要がある。つまりオブジェクトの知識はクラスの知識に依存すると考える。
f:id:t-hom:20180504194038p:plain

クラスはモジュールなので、前提としてモジュールの知識が必要になる。また、プロパティ・メソッドの理解には前提として変数とプロシージャの理解が必要になる。
f:id:t-hom:20180504194349p:plain

そんな風に整理していった結果。。こうなった。
f:id:t-hom:20180504193300p:plain

知識の依存関係をある程度表すことができたかなとは思うけれど、見て分かりやすい図ではないし、実際のところはこれより更に複雑だと思う。相互依存もあるはず。

VBAに限らず、知識の依存関係というのは有向グラフになっていて、その学習順序は巡回セールスマン問題を抱えているのではないかという着想を得た。

きちんとグラフを定義して、その最適経路を求めれば、最も効率の良い説明順序が求められるだろうか。

理解度の整理(追記)

ただ実際のところクラスを知らなくてもオブジェクトは使えてしまうんだよなぁ…と考えてたところ、「そうか、これは理解度の問題だ」と気づいたので追記。
理解度を図るバロメーターについてもざっくりと整理してみた。

たとえば以下のように知識Bが知識Aに依存していたとする。
f:id:t-hom:20180504201657p:plain

知識Bを単体で学習したとき、知識Bの理解度が1、知識Aを学んだ場合、知識Bの理解度は2になると考える。
逆に、知識Bを学んだことで、より知識Aの理解が進むということも考えられる。何等かの関係がある2つの知識がその理解において完全に一方通行であるとは考えにくい。※ただし矢印を両向きにすると分かりづらくなるのでグラフ上は、より強い依存関係のある方を採用する。

つまり知識Aを単体で学んだ場合に比べ、知識Bを合わせて学んだ場合、知識Aの理解度も2になると考える。

サンプルとして知識A~Dの依存グラフとその最大理解度を描いてみた。
f:id:t-hom:20180504203107p:plain

その知識自体を学習したときに1、関連する項目を学習したときに項目ごとに1レベル上がるという整理。
厳密に言えばその知識自体の学習と、依存元の学習と、依存先の学習で、それぞれその知識に入ってくる経験値って違うんだろうな。
でもシンプルに整理したほうが活用はし易いのでそれは置いておこう。

なんでこんなことを考えているのか(追記2)

なんでこんなことを考えているかというと、発端としては私がブログ記事を書く際に、「この記事を読むには知識Aと知識Bが必要です。」という風に、依存関係をライブラリの参照設定のように書けたら、わざわざ毎回同じような説明を書かなくても済むなぁと思ったから。

↓この先、妄想の世界なので注意。

要は知識のモジュール化である。
知識のモジュールはフォーマットだけ決めておいて、誰でも代替モジュールを作れるようにする。決められたヘッダの付いたパワポ1枚とかでも良いし、ブログのようなメディアの記事1本を1モジュールとしても良い。

学習する人は、ジェネリック医薬品のように、自由に代替モジュールを選んで学習することができる。変数はAさんのモジュールで学習し、関数はBさんのモジュールで学習する。オブジェクトは複雑なのでAさんのモジュールをメインにしつつもBさん、Cさんのモジュールで知識を補完するなど。

既刊のVBA書籍を買ってきてバラバラに切り刻んでモジュール化する手もあるか。。でも著作権がある以上オープンな場に出せないから自由に参照させるってわけにもいかないし、書籍の場合は前後で依存関係を考えて編纂されてるので切り出した単品をモジュールとするのは無理があるか。。

ということで今のところアイデアを温めているだけ。何も作る予定はない。

以上、そういう学習方法があるとおもしろそうだなぁという妄想でした。

Excelのセキュリティ設定と怪しいファイルの対策

Excelには、悪意のあるマクロによってコンピューターが被害を受けることがないようにいくつかの防御機構が備わっている。
しかしExcelマクロの解説書・解説サイト等では利便性の観点からか、設定を無効化しましょうという方向での解説が多く、セキュリティリスクについては「注意しましょう」という漠然とした勧告か「自己責任で」という免責のみで具体的に何をどう気を付ければ良いのかが書かれていないことが多い。

今回は、これらの防御機構の紹介と、正しい活用方法、外した場合の具体的なリスクについて説明する。
Excel 2013を基に紹介するが、これらの機能はExcel 2010にも存在する。(2007にも恐らくあるけれど、持っていないので不明)

目次

(1) 保護ビュー

説明

Excelの初期設定では、インターネット等のネットワーク経由で入手したファイルは次のように、保護ビューで開くような設定になっている。
f:id:t-hom:20180503230028p:plain

保護ビューはファイルを安全に閲覧することに特化したビューアで、一切の編集ができない。煩わしいことに、社内ネットワーク経由で入手した正当なファイルまでインターネット経由と判定されてしまうが、セキュリティに気を配るのであればたとえ社内ネットワーク経由で入手したとしても原則編集の必要が無いファイルは保護ビューのまま閲覧するのが安全である。

設定箇所

ファイル→オプション→セキュリティ センターからセキュリティ センターの設定ボタンを押し、左のメニューから保護ビューを開く。
f:id:t-hom:20180503230355p:plain

煩わしいので設定ごと無効化を推奨する記事も多いが、ファイルに悪意のあるコードが埋め込まれていた場合はコンピューターに被害を与えることも考えられるので、基本的には編集が必要かつ安全が確認されたものだけ個別に保護ビューを外すという使い方がオススメ。

保護ビューを外したファイルを再度保護ビューに戻す方法
基本的に一度安全を確認したファイルのはずなので、クリアする必要性はあまり無いが一応紹介。

セキュリティ センターの設定から信頼済みドキュメントをクリアすることで、保護ビューに戻すことができる。
f:id:t-hom:20180503233031p:plain

(2) 標準ブック形式へのマクロ保存不可

説明

Excel 2003まではマクロの有無にかかわらず、Excelブックの拡張子は「xls」だったが、Excel 2007以降はマクロが保存できない「xlsx」と、マクロが保存できる「xlsm」に分かれた。従来の「xls」や新しいバイナリ形式「xlsb」もサポートしているが、基本的にはマクロを利用しないファイルは「xlsx」とし、マクロを利用するファイルは「xlsm」とすることが望ましい。

熟練者がその気になれば悪意のあるマクロを作ることもできるため、マクロを使わないファイルではそもそもマクロを使えない「xlsx」形式にしてしまうことで、無用なリスクを減らすことができる。

ちなみに保存した後でファイル名の拡張子だけ変えてもダメで、以下のようなエラーになる。
f:id:t-hom:20180503232051p:plain

Excelはすべてお見通し。きちんと名前を付けて保存のダイアログから正しい形式を選択するべし。

(3) セキュリティの警告

説明

標準の設定では、マクロ付きのファイルを開く際に以下のような警告が表示され、マクロが一旦無効にされる。
f:id:t-hom:20180503232756p:plain

単にコンテンツを有効化してくださいと書かれている記事が多いが、なんでもかんでも有効化してはダメで、自分で作成した安全なマクロと信頼できる入手元から取得したファイルに限定してコンテンツを有効化すると良い。インターネットから入手したファイルは基本的に信頼性が低いものと考え、安易にコンテンツの有効化を行わないこと。

保護ビューとの違いはマクロを実行する以外の操作は自由に行えるという点である。
ファイルの編集もできるし保存もできる。マクロの編集もできる。ただ実行ができないというだけ。

入手元が完全に信頼できない場合、マクロが無効化の状態のままコードを読み、安全性を確認してから有効化すると良い。
コード確認の際のポイントをいくつか挙げてみた。

  • 完全には理解できなくとも、ある程度処理の流れが読めるコードであること。
  • わざと難読化されているコードや、プロジェクトが保護されていて閲覧できない場合はコンテンツを有効化しない。
  • 参照設定とCreateObjectを確認し、マクロが謳っている目的とかけ離れたオブジェクトが参照されていないことを確認する
  • ファイルシステムオブジェクトやファイル操作関連の関数が利用されている場合は特に内容を理解し、意図しないファイル操作が行われないことを確認する
  • 標準モジュールにAuto_Openプロシージャがあれば、特に注意深く処理内容を確認する。
  • ThisWorkbookモジュールにWorkbook_Openプロシージャがあれば、特に注意深く処理内容を確認する。

対象のコードを読むスキルが足りない場合、熟練者にお願いするか、マクロの利用を諦めたほうが無難である。

コンテンツを有効化したファイルを再度無効化に戻す方法
保護ビューを外した場合のクリア方法と同じく、セキュリティ センターの設定から信頼済みドキュメントをクリアすることで、コンテンツ無効化に戻すことができる。

設定箇所

セキュリティ センターのマクロの設定から「警告を表示してすべてのマクロを無効にする」を選択すると上記の設定になる。これが一番お勧め。業務で一切マクロを利用しない場合は一番上の「警告を表示せずにすべてのマクロを無効にする」でも良い。
f:id:t-hom:20180503235302p:plain

(4) VBAによるマクロの書き換え防止

説明

VBAではExcelブックを開いてそのブックのマクロの内容をマクロで書き換えるといった処理も可能であるが、これは標準では無効化されている。なぜ無効化されているかというと、マクロによるマクロの書き換えができるなら、悪意のあるマクロによって、正当なマクロファイルに悪意のあるコードを埋め込むことができてしまうから。
これはマクロウイルスの感染の基本的な仕組みである。

設定箇所

前項と同じ画面で「VBA プロジェクト オブジェクト モデルへのアクセスを信頼する」のチェックが外れている状態が安全。
f:id:t-hom:20180503235302p:plain

チェックを付けると、前述のような高度な処理が可能になりプログラミングが捗る場合がある。このブログでもこの設定を外さないと実行できない便利なマクロを紹介している。しかし、設定を外すとマクロウイルス感染の危険性は高まるので、これまで紹介してきた保護機能を活用してより注意深く実行するマクロを選択・確認する必要がある。また、面倒でも入手元が不明なファイルを開く前にはこの設定を無効に戻してから開く方が安全である。

まとめ

Excelにおけるセキュリティの基本は、不必要に設定を緩めないことである。必要が生じたら、その都度、必要な分だけ個別に対応すること。そしてファイルの安全性を注意深く確認すること。そうすれば無用なリスクを避けることができる。

プログラミング言語はコンピューター語ではなく人間のための言語

プログラミング言語とは何か。
初心者に向けて、よく次のような説明がなされる。

コンピューターは日本語や英語で指示しても理解できない。だからコンピューターが理解できる言葉、つまりプログラミング言語でコードを書く必要がある。

プログラミングを学び始める段階ならこの説明で何の支障もない。

と、以前までは思ってたんだけど最近は、初めからきちんと伝えておいたほうが良いんではないかという気がしてきたのでこの記事を書いた。

プログラミング言語は本当はコンピューター語ではなく、人間のための言語である。だから動けば何でも良いわけではなく、基本的には人が読むことを想定してコードを書くべきである。

プログラミングから実行までの流れ

人間がアイデアをプログラムコードに落とし込むと、コンパイラまたはインタープリタと呼ばれるソフトウェアがそれを機械語に翻訳し、最終的にコンピューターによって実行される。
f:id:t-hom:20180430104346p:plain
※ただし、間に中間言語が挟まったり、仮想マシン上の実行だったりと言語によってこの流れは必ずしも同じではない。

人間が機械語を効率よく読み書きできるなら、コンパイラインタープリタは要らない。
機械語とは、以下のようなコードになる。

0100101011110101011101010100001...
※イメージです。内容はデタラメ。

コンピューターは0と1しか処理できないのだ。

これを読んで理解しろというのは無理がある。
だから、コンピューターにもっと簡単に指示を出すために、人間が読めるプログラミング言語が設計され、翻訳者となるコンパイラインタープリタが作られた。

プログラミング言語が作られたワケ

人間が話す言葉(自然言語)は時と場合で意味が変わり、また表現も無数に存在するためにプログラミングに適さない。もしコンピュータが解釈を間違えたために飛行機が墜落したり、大事な取引の金額を間違えたりしたら洒落にならない。だから厳密に意味を定義され、それ以外に解釈できないような言語が必要だった。そして作られたのがプログラミング言語である。

このように形式的に意味が定義されている言語を形式言語という。
つまりプログラミング言語は人間のために設計された形式言語である。

主張

人間の人間による人間のためのコードを書くべし。
プログラミング言語は、そのために作られたのだから。

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

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