Excel VBA コントロール処理を分散させる
WindowsのルールというかMS仕様では、コントロールのイベント(メッセージ)はトップウィンドウが受け取る、というのがある。たとえばワークシート上のボタンコントロールがあったとして、こいつを押したときイベントを受け取るのはボタン自身ではなくてワークシートになる。これは何を意味するかというと、シート上のコントロールのイベント処理(イベントハンドラ)は、すべてワークシートモジュールに記述される。ちょっとしたマクロであればこの仕様は便利なのだが、ある程度の規模になると、処理が一箇所に集中しすぎて大変見にくくなる。プログラムがヘタれる。
そこでイベントハンドラの記述を分散させよう。クラスモジュールを使うとこんな風にできる。たとえば、シート上にコマンドボタン HogeButton があったとしよう。クラスモジュールを新規作成して、モジュール名をHogeButtonEventHandlerという名前にする。そのモジュールに次のように記述する。
Private WithEvents handler As CommandButton Property Let Handle(Byref obj As CommandButton) Set hander = obj End Property
WithEvents を使うと、イベントを捕獲できる変数が作れるらしい。3分で分かるかも知れないExcel VBAとか読むと、そう書いてあった。handlerをPrivateにしてあるのは、まぁいわゆるカプセル化ってやつだ。ぷろぱちーLetを使えば、3分でできるかもしれないExcel VBA本とかに書いてあるように、わざわざPublicにする必要がない。
このhandler変数に、本物のHogeButtonを結びつける必要がある。一番良いタイミングは初期化のとき、すなわちWorkbook_Openだ。ThisWorkbookモジュールに次のように書く。
Private hogeHandler As HogeButtonEventHandler Pirvate Sub Workbook_Open() hogeHandler.Handle = Worksheets("Sheet1").HogeButton End Sub
これで結び付け完了。あとは、HogeButtonEventHandlerモジュールにイベントハンドラを書きまくれば良い。
ちなみに、クラスモジュール名に〜EventHandlerとか書いちまったが、このクラスモジュールはイベントハンドラではない。クラスモジュール内のhandler変数がイベントハンドラだ。まぁどうでもいいことかもしれないが、ときどき問題になったりするから、頭の片隅にでもいれておこう。
これでいわゆるMVCモデルのCを分離できた。Mを標準モジュール、VをMicrosoft Excel Objectに書けばMVCの分離完了じゃん、と思ったのだが、Microsoft Excel Objectのモジュール内の関数には参照渡しができないらしい。だからVBAは(ry
RubyのEXE化
exerbというのがある。
http://exerb.sourceforge.jp/man/README.ja.html
これで Rubyの無い環境にも配布できる!
RDocの書き方
この辺しかねー
http://pub.cozmixng.org/~the-rwiki/rw-cgi.rb?cmd=view;name=RDoc%B3%D0%A4%A8%BD%F1%A4%AD
http://rgss-lib.sourceforge.jp/doc/comment-stipulation.html
http://rdoc.sourceforge.net/doc/index.html (英語)
http://www.kmc.gr.jp/~ohai/rdoc.ja.html
javadocのときもそうだったけど、コメントに埋め込むドキュメンテーションの仕方って、入門書くらいしかまともにかかれていないんだよね。これも一度、まとめたほうがよさげだな。
文字コードとエンコード
いわゆるコンピューター業界にいる人にとって、文字コードの知識は常識なんだそうです。そんな自分は… 全然分かんね(泣)。文字コードについては一度きちんとまとめたいと思っていたのですが、調べてみると、文字コードについての説明は、サイトにしろ本にしろかなり情報が少ないことに驚きます。見つかったとしても、文字コードの一覧とか。とにかく分かる範囲でまとめて見たいと思います。いちよう目標は、某@ITの
文字コード エンコード名 Unicode UTF-7 utf-7 Unicodeは、世界中の文字を16bitもしくは32bitの固定長の文字コードで統一的に扱うために作られたコード。 16bitの方をUCS-2、32bitの方を UCS-4という。 Unicodeをファイルへの保存や、通信回線上で送受信するためのエンコーディング方法の1つがUTF-7である。 Unicode (UCS-2)を7bitのコードでのみ送信できるように、一部のUnicode文字をBase64でエンコーディングしている
という説明を見てもヘコまない程度まで行きたいと思います。
1.コンピューターは文字なんてあつかえません
コンピューターはビットしか扱えません。それはシリコン結晶の中に不純物を少し混ぜることによって… なんて話はどうでもよく、平たく言うと数値しか扱えません。じゃぁ文字はどうするかというと、ある数値を文字にマッピングするわけです。例えば41を'A'にするとか。そんな文字セットに ASCII (American Standard Code for Information Interchange) というのがあります。最もよく、というか今でもよく使われているので知らない人はいないじゃないでしょうか。ASCIIコードは最も有名なので、検索すれば何の値が何の文字であるか、すぐ分かるでしょう。
ASCIIの特徴は、
- アルファベットも大文字と小文字が扱える
- タブや改行コードなどの制御文字がある
- 128文字(7bit)で定義されている
この一番重要なのが、7bitであるというところ。コンピューターは通常8bit単位で扱うから、文字を1byteで扱うとすると、1bit余るわけです。つまり、この余った1bitをなんかのフラグで使えてラッキーというわけです。
しかしASCIIの欠点は、文字はアルファベットしか扱えないということです。確かに!や#のような記号も一部は扱えますが、基本的にはアルファベットだけです。つまり、アルファベットしか扱えないのはアメリカ人には大変うれしいことであるが、それ以外の人にとってははなはだ迷惑な訳です。ドイツ語もギリシャ語も、そして当然日本語も中国語も扱えません。アメリカ人は自国語が扱えればあとはどうでもいいのでしょうが、それ以外の国の人はなんとかしないといけません。そこで注目したのが、ASCIIコードの最上位1bitって余ってるじゃん、これ使おーぜ。てことになるわけです。そして、ASCIIコードを拡張した文字コードが、てんこもり量産されるわけです。
次回へ続く、かも
続 糞プログラムをやっつけろ!
前回の続き。このすさまじいif - then -elseチェーン。なぜこんなことになってしまったのだろうか。
一言で言えば、モジュール分割方向がまちがっているということだ。たとえば、こんなWebアプリケーションがあったとしよう。ある商品の取引で、画面からは「買い」「売り」「取消」の3つが入力できる。株の売買みたいなのを想像してくれれば良い。さて、このユーザー入力の「買い」「売り」「取消」の選択に対して、プログラムはどのように設計するだろうか。
自分だったら、おそらく最初に「買い」「売り」「取消」の3つの処理に振り分け、それぞれ「買い」なら買いの処理特化した処理にすると思う。オブジェクト指向で言えば、ObjectFactory作って、「買い」「売り」「取消」共通のインターフェイスクラスを生成して…、といった感じか。ここでいいたいのは、処理の設計はユーザーの立場---クライアント側の立場で作られているということだ。あたりまえと思うかもしれない。なぜならクライアント側の処理だから、クライアント側の立場でプログラム設計したほうが、きっとスムーズにつくれるだろう。
しかし、だ。ヘタレプログラムはこういう風にならない。先ほどの処理で、ユーザー入力にたいして注文テーブル(RDBを使っていれば)に1件追加するとした場合、注文テーブルに対する処理をサブルーチン化する。ユーザー入力だから入力チェックも必要だろう。入力チェック処理も、ひとつのサブルーチンにする。もちろんレスポンス画面用の文言を生成する部分も、1つのサブルーチンだろう。
これの何が悪いのか? 個々のサブルーチンは「買い」「売り」「取消」に限らず同じサブルーチンが走る。だからこれらのブルーチンは次のようになっているだろう。
void init(int kbn) { switch(kbn) { case 1: // 買いの場合 …買いの初期化 case 2: // 売りの場合 …売りの初期化 case 3: // 取消の場合 …取消の初期化 } } void updateData(int kbn) { switch(kbn) { case 1: // 買いの場合 …買いのテーブル更新 case 2: // 売りの場合 …売りのテーブル更新 case 3: // 取消の場合 …取消のテーブル更新 } } …以下同様
もちろん、これを呼び出すmain側も同様だ。
int main() { string msg; if( kbn == 1 ) { msg = "買い処理"; }else if(kbn == 2) { msg = "売り処理"; }else if(kbn == 3) { msg = "取消処理"; } init(kbn); updateData(kbn); … }
これでめでたく、すべてのサブルーチンに同じ条件のif-elseが入る訳だ。これがよく見かける if-then-elseチェーンの正体である。
なぜこのような分割をするのか。それは、サービスを提供する側からの視点でモジュール分割をしているからだ。注文テーブル更新があるなら、「注文テーブル更新」というサブルーチンを作る。レスポンスの文言生成なら、レスポンス文言サブルーチンを作る。その後で買い」「売り」「取消」の処理振り分けをするから、ほとんどすべてのサブルーチンで、同じif条件が入るわけだ。この考え方はまさしくホスト系の考え方であろう。
こんな風に、クライアント側の立場で処理を分割しないから、ちょっと区分やコードが増えるだけで、簡単にifネストが深くなる。今の、いわゆる売買区分のほかに、午前/午後のような時間区分、すぐ処理するか予約かの処理区分みたいに、区分が3つになろうものなら、この地点でifは3重ネストである。いや、エラー処理もあるからifの4重ネストになる。これでさらにforループも加われば、インデントは5マスになる。あまりにインデントが深くなるので、インデントが半角空白1文字とか、ふざけたプログラムができるのもこんな背景があるからだろう。
しかし、こんな考え方をするプログラマ(?)は、再教育できないようだ。機能ごとに処理を分ける(だって分けてるじゃん)とか、グローバル変数を減らせ(だって関数の引数がふえるじゃん)とか、なぜダメかを説明しても理解しない/しようとしない/できないのだ。ヤレヤレ、だからソコ Javaで数値をStringで比較するのヤメれ
糞プログラムをやっつけろ!
昔も似たようなことを書いた気がするが、あれから少しは進展があったので、再びまとめてみようと思います。いわゆるシステム開発なんて仕事は、Excelいじっているか、他人の書いたプログラムを見てるかどっちかですからね^ ^;
ヘタレプログラムとは? -ヘタレプログラムの定義-
ヘタレプログラムとはどんなプログラムか? まぁ、ひとことで言えば「きたないプログラム」なのだが、きれい/きたないでは主観が入るとはなんとか言う人もいそうなので、定義を考えてみました。いちよう断っておきますが、これは私が勝手に考えたものであり、また今後調査/分析が進むと定義は変わる可能性はあります、というより間違えなく変わります。
ヘタレプログラムとは、以下のすべてを満たすプログラムとしよう。
- ドキュメントがない。あるが最新のプログラムと同期がとれていない。同期がとれていても、擬似コードを日本語にしただけ。
- 回帰テストができない。単体テストに何やったかわからない。
- まだ実可動していない。実可動しているがバグだらけでプログラムがFIXしていない。
まぁ、ヘタレプログラムである条件は、これらの内1つ、としてもあまり変わらないだろうが(真にヘタレは、どれか1つだけ満たす、なんて器用なことはできないから)。
ソフトウェア業の定石には、『実可動しているプログラムを、スクラッチから書き直すな』というのがある。プログラムは汚くても、それはなにかのバグ対応で付け足したものだから、また始めから書き直すと同じバグを入れ込んでしまうから、という理由である。しかし、だ。ヘタレプログラムは本当に汚いのだ。どうしようもなくダメなのだ。はっきりいって、プログラムが得意な高校生に、時給2000円くらいでプログラム書かせたほうが、はるかに良いできのプログラムができると思えるほどダメダメなのだ。こんなプログラム書くやつに月数十万払ってるなんて… なんて太っ腹w。とにかく、この定石ですら破ったほうが良いのでは、と真剣にプロジェクトの方針を考えなくてはならない程の、ヘタレっぷりなのである。
リファクタリングが効かない訳
昔も書いた気がするので、繰り返しになってしまうかもしれないが、自分でも何書いたか覚えてないので気にしないことにする。リファクタリングが出来ない理由だが、まず、リファクタリングが出来る条件として
- バグがない。少なくても現段階で認識しているバグは無い。
- 単体テストケースがある
の2つは必要であろう。最悪単体テストケースが無くてもドキュメントがあれば、テストケースは作れそうである。しかし、ヘタレプログラムはその定義から、
- そもそもバグありで、リファクタリングできない。
- せめて単体テストケースでもあればよいが、回帰テストができないので無理。
- じゃぁ、ドキュメントからテストケースを作ろうと思っても、最新ドキュメントがない!
という、ヘタレ・スパイラルでどうにも身動きがとれないのだ。じゃぁ、ヘタレプログラムをどう扱うかというのは、結局プログラムを見て仕様を起こすしかない、というどうしようもない結論に到達するわけである。
ヘタレプログラムの傾向と対策
ここが一番のメインであろうか。ヘタレプログラムは、書いた人間(?)によって汚さ、ヘタレ具合が違う、個性がはっきり出てくるものなので、なかなか体系的に扱えないのだが、それでも最低限の共通点と対策方法は考えました。
1.グローバル変数てんこもり
今も昔も忌み嫌われているコレ。ちゃんとしたプログラマなら自然と避けるこのグローバル変数は、ダメなパンチャー… じゃなかった、プログラマほど大量に使う。C言語で、関数の引数がない関数が多い場合は、ほぼこのグローバル変数病に取り付かれていたプログラム --- 平たく言えばハズレを引かされたと思って間違えないだろう。ちなみに、Javaにはグローバル変数がないから関係ない話、と思ってないだろうか。どんな言語だってグローバル変数はあります。言語レベルで無いようにしても、この手のプログラマはグローバル変数を捏造します。もっと別のところに労力を使って欲しいんだが。Javaのグローバル変数を知りたいですか? きっとあなたの向かいの自称プログラマが書いているソレだと思うのですが。ほら、そのフィールド変数です。え?ちゃんとprivateにしているから、カプセル化を守っているだって? 自分の汚いプログラムをカプセル化(=見せない)してるだけだと思うのだが、そいつのプログラムを良く見てみなさい。クラスが1つしかなくて、そのクラスが2000行くらいあるでしょ。C言語だって main に2000行書くヤツはたくさんいたんだから、Javaだって main か execute か doGet か知らないけど、この関数(もうメソッドとは呼べないでしょ)に2000行書くヤツがいてもおかしくないでしょ。こんなクラスのフィールド変数は、すでにグローバル変数と同じであることの気づいてください。あー、わかってるって。あんたの書いたプログラムは、クラスが1つじゃなくて3つあるって言いたいんでしょ。でもそのうちのひとつはこんなかな
DameDame dd = new DameDame(); dd.check(); String data = dd.getData();
ここで、check()メソッドの前にgetData()メソッドを呼ぶと動作が未定義になるという、ローカルルールが存在している。もちろんこんなことはコメントには書いてないし、ましてやドキュメントなんかアリマセン。
余談が長くなったけど、グローバル変数は一番使われながら、最も対策がとりにくいモノです。こいつの対策方法は… 残念ながらありません。あきらめるしかありません。ひとつひとつの変数について、どこで値が代入されているかを追っていくしかありません。どういつグローバル変数なのに、関数によって役割が違ったり、ループカウンタをグローバル変数にするヤツもいるので気をつけましょう。普段グローバル変数なんてあまり使わないでしょうから、この変数の奇妙な働き/使われ方には、罠が大量に仕掛けられていることでしょう。
2.コメントがない。コメントが意味不明。コメントにウソがある。
昔も書いたと思いますが、コメントは信用しないように。いきなり Step3:○○処理 というコメントを見ても、Step1,2がどこにあるんだ、とかStep3についてのドキュメントはどこにあるんだ、とか疑問に思ってはいけません。無視に限ります。
3.ふか〜いifネスト
もういいですよね。これと姉妹品にifとelseがすご〜く長い、というのがあります。これの対応方法は、ifとelseの対応を慎重に調べていくことです。
ヘタレプログラムの解析方法
長文で疲れてきました。さて、ヘタレプログラムの解読方法ですが、これの解析方法に従来のUML(クラス図とかシーケンス図とか)やフローチャートは無力です。私はすべて試しました。でもうまくかけません。なぜか? それは
- ヘタレプログラムはデータ中心に考える
ここでいうデータは変数とほぼ同義ととってよいです。結局ヘタレプログラムがヘタレである要因は、どこでどの変数に何の値が代入されているのかが、芸術的に分からないことである。だから、変数中心に解析していくのが近道なります。
このとき、解析の妨げになるのが if 分岐です。変数の状態よって、どこの処理が実行されるのかが致命的に分からなくなる。しかし、この if 分岐にもパターンがあることが分かりました。それは
- if条件文のなかで、いつも見かけるアノ変数をチェックする
結局、解析している1本のプログラムはなんかの業務1処理な訳ですから、こんなに大量な分岐を発生させるには、いつもお決まりの変数で分岐させるしかありません。それが「買い/売り」みたいな売買区分だったり、「午前/午後」みたいな時間区分だったり、「登録/削除」みたいな処理区分だったりするわけです。ヘタレプログラムは、同じ条件分岐をいろいろな関数で何度も何度も行うのです。だから、いち早く if分岐のキモ変数が何かを発見するのが、解読への近道になります。ちなみにこの変数は、関数によって名前を変えたりしているのが普通ですので気をつけましょう。
さて、変数の動きが把握できたら、関数ごとのI/O表を作っておいたほうがいいでしょう。なにが入力の変数で、なにが出力なのか。もちろんグローバル変数も含めて、です。これがあると今後の作業がかなりラクになります。
さて、変数の動きの次は
- フローを解析する
です。フローをまとめるには何図がいいのでしょうか。残念ですが、これはまだ研究中です… ただ、いまのところはシーケンス図がまぁまぁ良いです。ただ、このシーケンス図には if 分岐も書いておきましょう。ヘタレプログラムは if 分岐がキモですから。
最後に余談。なぜ if pi---ネストが生まれるのか?
これ、疲れたのでまた後日書きます。
グローバル変数てんこもり
そろそろVBAネタも飽きてきたけど、これだけは書いておきたい。グローバル変数だ。VBAにおけるグローバル変数をは、Public な変数のことだが、これだけでなかったりする。現在アクティブになっているWorkbookなりWorksheetなりCellなんかも、グローバル変数で参照できる。
これは、厳密に言うとグローバル変数とは違うとは思うが、いちようActiveWorksheetなんかでどこからでも参照できる変数、ということでグローバル変数の仲間としておく。よーは、使われるとやっかいな変数ということだ。
このActiveなんとかがなぜやっかいか。それはActiveXの仲間だから、ではなく、どこのWorksheetなりCellなりを参照しているかが分からないからだ。更に言うと、現在アクティブになっているシートなりセルは、ユーザー入力によって変わってしまうかもしれないのでバグが発生しやすい、というのもある。だから、誰かが(間違えて)Excel VBAでなんかを作っているとき、「Activeなんとかは使うな」と言うようにしている。しかし、世の中にはラクするというかテ抜くやつが多いせいか、やっぱり使うんだよね… これと似たようなヤツにCopy関数ってのがあるけど、今回はこの話はしません。
5回に渡ってVBAネタを書いたけど、やっぱりVBAは開発用言語ではないってことです。VBAはちょっとしたツール用で使うべきです。だから間違ってもVBAで開発、なんて提案しないようにしましょう。工数が下がる、なんてウソですから。