エントリポイントというかメインルーチン――C言語いうところのmain関数、Javaだと「public static void main(String args[])」なメソッドに該当するものだが、スクリプト言語*1にも必要だろうか?
言語仕様としては不要だろう。ごくごく短いスクリプトを書く分にはエントリポイントなんて邪魔だ。私自身も、短いスクリプトならべた書きしてしまう。
では「作法」としてはどうだろうか?
ごく短いスクリプトなら不要だ。しかし、少し規模が大きくなって自前で2〜3のルーチンを書く必要があるような場合、一般的にはエントリポイント的な役割のメソッド/サブルーチンなどを用意した方が良いだろう。他人の目に触れるようなものだったら、尚更そうするべきだ。
エントリポイントが無いことで生じる不都合
スクリプト言語で何らかのツールを実装していて、規模が一定以上大きくなったとする。メインルーチンの部分をべた書きしていて、それ以外に幾つかサブルーチン的な役割のメソッドないしプロシージャなどが実装されている、そんな感じだ。
使用する言語や実装方法にもよるが、この構成には「グローバルな変数・定数と、メインルーチン部分でのみ使用するローカルな変数・定数の区別がつき難い」という問題がある*2。
「定義する場所を分けることで対応できるのでは?」という声もあると思う。グローバル変数はグローバル変数同士で1ヶ所にまとめて定義しておき、別の場所にメインルーチン部分のローカルな変数をまとめて定義する、といった「運用/作法で回避する」方法だ。しかし「メインルーチン部分のローカルな変数」の場所に定義してある変数が、本当にそうであるという保証はない。ついうっかりグローバルな変数として使用できてしまう。
メインルーチン部分を1つのメソッド/プロシージャとして定義してしまえば、今日の一般的なスクリプト言語ではこの手の問題は回避できるはずだ。本物のローカル変数にしてしまえば、他のルーチン内で直にアクセスすることはできない。
この問題のいやらしいところは、自分のみがコーディング及び管理をしている場合には大した問題にならない(ことが圧倒的に多い)という点だ。余程酷いコードでもない限りは、自分のコードの中身を把握するのは難しくない――それもそのコードの裏にある意図まで含めて、だ。
しかしそこに他人が入ってくると事情は異なる。他人はそのコードの裏にある意図を解読しなくてはならない。そしてその他人は私のように頭が良くない部類の人間かもしれない。
私が遭遇した例
以前、ある人がVBScriptで書いた小ツールをリファクタリングしたことがある。
そのツールは1つのファイルに全てコーディングされていた。全体で400行弱(コメントや空行を含む)で、そのうちメインルーチン部分は180行程度、残りは6個のプロシージャで構成されていた。
さして規模は大きくない――というかむしろ小さなコードだが、リファクタリングは困難を極めた。複数の要因の相乗効果で大変な目に遭う破目になったのだが、要因の1つは先ほど書いたような「メインルーチン部分のローカルな用途の変数とグローバル変数が区別し難い」という問題だった。
何しろ、まずメインルーチン部分で定義されていた変数の数が34個。この時点でゲンナリした訳だが、それぞれの変数の役割と寿命*3を洗い出さないことには作業が進まない。
しかもコードが結構ゴチャゴチャしていて、34個の変数のうちどれがグローバル変数なのか皆目検討もつかない状態だった。
結局34個の変数それぞれについて、何に使われていて、どこで参照されているのか地道に調べた。結果、7個がグローバル変数だった。
本業の傍らで暇を見つけて作業していたが、何だかんだでリファクタリングが完了するのに1週間近く掛かった。たった400行だったのに。
現在の個人的指針
このリファクタリングの一件以降、使用する言語にもよるけど複数の関数を自前で定義するような規模以上のスクリプトでは、例え言語仕様的に不要であってもメインルーチン的なものを用意するようにしている。
JScriptでは即時関数を(WSHではWScript.Quit()の引数として)使用している。VBScriptでは「Sub Main()」とずばりそのままの名前のプロシージャを定義して後でMainを呼び出す1行を付け加えている。他の言語では言語仕様的にこの対処法が不要であるか、またはそこまで大きなスクリプトを書くことがない。
何というかバッドノウハウ的なにおいもしないでもないが……。
*1:例えばPerl/Ruby/PythonだとかJScript/VBScript辺りが直ぐに思い浮かぶ。
*2:私の知っている範囲だと、Ruby/Tcl/REXXでは問題にならない。Rubyではインスタンス変数/クラス変数/グローバル変数として外部の変数を定義していないとメソッド内で参照できず、且つそれらの変数は頭に`@'や`@@'や`$'といった記号を付ける必要がある。Tcl/Tkではプロシージャ内でグローバル変数を参照するにはコマンドglobalかスコープ解決演算子を使う必要がある。REXXではプロシージャを定義する際にexposeで変数名を指定することで呼び出し元の特定の変数にアクセスすることになる。
*3:言語の文法としての変数のスコープではなくて、その変数が実際にプログラム中のどの時点で使われているかという話。『Code Complete 第2版 上』10.4あたりを参照。