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 みたいなスクリプト言語って、あまり設計は考えずに勢いで書いていきたいものだ。そういうときに、この方法は便利じゃね?ってことで。