JS や AS で長い関数をリファクタリングする1手法
200行ぐらいある関数を、分かりやすく書きなおそう、というお話。
お題はこんなJavaScript。あとで、軽く ActionScript 3.0 も出てくるよ。
function fly()
{
// 初期化処理
var init = false;
// 50行ぐらい初期化処理
// うまくいったら init が true になる
// 実行
if(init)
{
var succeeded = false;
// 100行ぐらい実行
}
// 終了処理
// 30行ぐらい終了処理
}
構造化プログラミングについて知ってる人なら、きっとこうする。
function fly()
{
if(startFly())
{
doFly();
}
endFly();
}
構造がたいそうすっきりした。
問題は、startFly()・doFly()・endFly() をどこに記述するか。
fly() の横に記述
一番ありがちな手法。fly() 関数の横に並べる。
function startFly()
{
// ...
}
function doFly()
{
// ...
}
function endFly()
{
// ...
}
function fly()
{
if(startFly())
{
doFly();
}
endFly();
}
局所的にはきれいになったけど、1個だったはずの関数が3個になってしまった。
一般に、関数の数が多いほどソースコードは理解しにくくなる。だから、関数の数が増えるのはできる限り避けたい。
そもそも、startFly() 関数や endFly() 関数は、fly() の中からしか呼ばないはずなのに、他の関数からも参照できるところに置くのは理にかなっていない。
ならば、fly() の横ではなく、fly() の中にしまってみるのはどうだろう。
fly() の中に収納
さっきのは C言語っぽかったけど、こうやったら JavaScript っぽい。
function fly()
{
var startFly = function()
{
//
}
var doFly = function()
{
//
}
var endFly = function()
{
//
}
// 実際の処理開始
if(startFly())
{
doFly();
}
endFly();
}
fly() 関数の行数は変わらないけど、処理の流れはすっきりする。ソースコードから流れを追いやすいし、startFly() 関数や endFly() 関数が fly() 関数の中からしか呼ばないことが一目瞭然。
構造が仕様を語る。分かりやすいソースコードの基本。
一般化してみた
function A()
{
// 関数内の変数
var param1 = {};
var param2 = {};
// 関数内の関数
var func1 = function()
{
}
var func2 = function()
{
}
// function A() の実体
var AImpl = function()
{
//
}
AImpl();
}
変数を冒頭に、次に関数、最後に AImpl() を書く。
関数 A() の中からは、AImpl() のみを呼ぶだけ。
AImpl() の中では、なるべく if 文と関数呼び出しだけで書くようにするときれいなソースになりそう。
面白いのが、このクラス全体が1つのクラスになっている。クロージャにより func1() と func2() から param1 や param2 を参照できる。param1 や param2 はクラスのフィールドのようだし、func1() や func2() はクラスのメソッドのように見える。AImple() 関数はコンストラクタといったところ。
もう1つ面白いところ。関数内の関数を他の場所で使いたくなったら、関数の外に出して、宣言部分をちょろっと変えるだけでよい。お手軽。
逆に、1箇所からしか参照してないグローバルな関数を発見したら、呼び出し元の関数に収納しちゃってもよいかもしれない。
ActionScript 3.0 でも
public class Bird
{
public function fly():void
{
if(startFly())
{
doFly();
}
endFly();
}
private function startFly():Boolean
{
// ...
}
private function doFly():void
{
// ...
}
private function endFly():void
{
// ...
}
}
と fly() の横にメソッドとして書くよりも
public class Bird
{
public function fly():void
{
var startFly:Function = function():Boolean
{
// ...
}
var doFly:Function = function():void
{
// ...
}
var endFly:Function = function():void
{
// ...
}
if(startFly())
{
doFly();
}
endFly();
}
}
と書いたほうが分かりやすいかもしれない。
private なメソッドだから、最初のやつで別にいいような気がしないでもない。でも、private なメソッドは、クラス内部からみるとグローバルにアクセスできる関数。前者の方法で書いていくと、いつの間にか private なメソッドが30個…、意味分からん…、となってしまうことを考えると、後者の方が読みやすいソースになるかもしれない。
別のクラスに分けちゃえよ、という気もしないでもないが、クラスが増えるのは、それはそれで管理対象が増えてしまうので避けておきたい。
ちゃんと設計したらこんなことしなくてもいいでしょ、という意見もありそう。ごもっともなんだけど、JS とか AS みたいなスクリプト言語って、あまり設計は考えずに勢いで書いていきたいものだ。そういうときに、この方法は便利じゃね?ってことで。