小さい頃はエラ呼吸

いつのまにやら肺で呼吸をしています。


Active Server Pages(レガシーasp)開発で学んだこと(1)

僕が会社に入って3年が経過しました。そのうち2年くらいは同じプロジェクトに携わっていて、レガシーASP(クラシックASP)でのWEBアプリケーションの開発をしています。
この2年で僕がレガシーASPでの開発で学んだことや大事だと思うことをまとめてみました。そんなに自慢できるものではないですが、アウトプットしないよりはしたほうが良いと思い、もしかしたらこの先、必要になる人がいるかもしれないという期待も込めて書きました。
※VBScriptについての文法など、基本的なところは他に詳しいサイトがたくさんあるので、省略しています。

変数宣言を強制するOption Explicitステートメントをできるだけ使用する

スクリプト言語は、変数を宣言せずにいつでもどこでも自由に使用できる。これは、スクリプト言語の良いところでもあるけれど、実行速度や可読性の面から言っても変数宣言は強制したほうが良い。
でも、既存のソースにOption Explicitがついていないからと言って、勝手につけると大変なことになるからやめよう。

宣言された変数の方が、宣言されていない変数よりも高速である点です。スクリプトの実行時に、宣言されていない変数は使用されるたびに名前で参照されます。それに対して、宣言された変数はコンパイル時または実行時に序数が割り当てられます。その後、宣言された変数は、この序数で参照されます。Option Explicit は変数の宣言を強制するので、すべての変数が宣言され、そのため高速にアクセスされます。
パフォーマンスとスタイルに関する ASP 25+ の Tips はてなブックマーク - パフォーマンスとスタイルに関する ASP 25+ の Tips

変数の宣言位置

好みの問題かもしれないけど、必要な変数は関数の先頭ですべて宣言する。変数宣言をまとめて書くことで、変数が何を意味しているのかが探しやすくなり、わかりやすくなる。
ただし、例外的にループカウンタや値の入れ替えのための一時変数は使用する直前で宣言したほうがわかりやすい。

Function Hoge()
  Dim a
  Dim b
  Dim c
  
  ' 処理
  
  Dim i, tmp
  For i = 0 To 10
    ' tmpを使う処理
  Next
End Function

Main関数を使おう

VBScriptは、C言語みたいなMain関数がなくても動作させることができるけど、Main関数のような関数でラップして処理をしたほうが良い。Main関数で処理をラップすることで、グローバル変数の使用を減らすことができる。

Main()
Sub Main()
  ' 変数の宣言はこの中で行う
  Dim a
  Dim b

  ' 処理
End Sub

SubとFunctionの使い分け(Functionの戻り値はなるべく1つに)

SubとFunctionを僕は以下のように使い分けている。

  • Sub:戻り値がないもの(画面表示、DBへのデータの挿入・更新)
  • Function:戻り値を返すもの(DBからのデータの取得、計算や演算)

ある関数で複数の戻り値を返したいというときに、以下のコードのように、参照渡しを使って複数の戻り値を返すことができる。

Function GetData(byref a, byref b)
  ' 処理
End Function

僕は、これをできるだけやらないようにしている。理由は、戻り値を1つにしたほうがシンプルでわかりやすいからだ。2つ以上返す必要がある場合は、別々の関数にするか、配列に格納して戻してやれば良い。

ByRefとByValは省略せずに書こう

他の言語だと通常値渡しされるため、間違いやすいです。 byref, byval は省略しないのがベストプラクティスだと思います。
VBScriptの勘違いしやすいところ - ニッチー・ブラックモア はてなブックマーク - VBScriptの勘違いしやすいところ - ニッチー・ブラックモア

激しく同意。最低でもByRefくらいは書いたほうが良い。

例外処理

例外処理は、Err.Raiseを使う。たとえば、C言語みたいに関数の成功・失敗を0か非0で返す方法は、エラーの種類が少ないときは良いけど、複数の種類のエラーを処理するときは、例外を発生させたほうが良い。呼び出し元はErr.Numberが0かどうかだけ判定すれば良い。

例外を呼び出し元へライズする

Function Hoge()
  On Error Resume Next
  ' 処理
  If xx Then
    ' 成功
    Hoge = 0
  Else
    ' エラーをライズする
    ErrNo = Err.Number
    ErrDesc = Err.Description
    On Error Go To 0
    Err.Raise ErrNo, ErrDesc
  End If
End Function

' 呼び出し元
a = Hoge()
If Err.Number <> 0 Then
  ' エラー処理
End If

On Error Resume NextとOn Error Go To 0

基本的にはOn Error Resume Nextを使って、内部でエラーが発生してもスクリプトがストップしないようにする。
スクリプトが実行時エラーによって止まってしまうと、データベースのロールバックなど、適切な例外処理すらできなくなってしまう可能性がある。また、On Error Resume NextとOn Error Go To 0の命令は、使用した瞬間に今まで保持していたErrオブジェクトがクリアされてしまうから注意。

オブジェクトは使ったら解放する

レコードセットオブジェクトやコネクションオブジェクトは、使用した後、明示的にオブジェクトの解放を行ったほうが良い。VBScriptは自動的に使用しなくなったメモリの解放(JavaでいうGCみたいなもの?)を行ってくれるため、明示的に開放する必要は本来はないのだが、メモリ解放のタイミングが定かではないため、メモリの解放を即座に行ったほうが良いとされる。

ADO 接続やレコードセットはこの最適化に関する第 1 の候補です。レコードセットを使用して実行しているとき、そのデータを持つテーブルを描画した後、ページの最後まで待たずに、すぐにレコードセットを解放します。VBScript 変数に Nothing を設定することを習慣付けることをお勧めします。
パフォーマンスとスタイルに関する ASP 25+ の Tips はてなブックマーク - パフォーマンスとスタイルに関する ASP 25+ の Tips

ただし、Set obj = Nothingは、無駄なタイプであるという記述もある。

なぜ、メモリを解放する必要がないのでしょうか。VBScript はその他の言語とは異なり、使用されたメモリをクリーンアップするためです。スクリプトが終了するとすぐ、使用されていたメモリはすべて自動的に解放されます。最後にオブジェクトを Nothing に設定すると、いずれにしろ VBScript がする予定だったことを実行するだけです。無駄なタイプ入力をする必要はないでしょう。
Sesame Script: クラスはセッション中 はてなブックマーク - Sesame Script: クラスはセッション中

ループ処理では、配列長やプロパティをキャッシュしておく

2009.10.06追記
ループ処理では、配列長の取得処理が何度も呼び出されないよう、ループの外側で配列長を取得することがよく行われます。
for ループでの配列長の括り出しは JavaScript でも有効か - WebOS Goodies はてなブックマーク - for ループでの配列長の括り出しは JavaScript でも有効か - WebOS Goodiesには、JavaScriptで実施した場合について言及されていますが、VBScriptでも同様のことをやっておいて損はないと思います。

For i To UBound(arr)
  ' 処理
Nextlen = UBound(arr)
For i To len
  ' 処理
Next

とするべきだし、プロパティを参照する場合も同様。

For i To obj.count
  ' 処理
Nextlen = obj.count
For i To len
  ' 処理
Next

クラスを使って役割をまとめる

2010.01.28追記
レガシーASPでは、実はJavaのようなクラスが使えます。共通的なクラスやデータベースアクセスクラスなど、役割ごとにクラス分けしてやることで、処理がわかりやすくなります。
クラスの使い方については、以下のエントリを参照してください。