210 likes | 320 Views
細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案. 平松 俊樹 千葉 滋 東京工業 大学 数理・計算科学専攻. メソッド の一部を 切り出す. メソッドの一部を 別メソッドに 例 . Eclipse での リファクタリング extract method 分割 再利用. class Max { int calc ( int [] a) { : for ( int i = 0; ..) { sum += a[i]; if (max < a[i])
E N D
細かい粒度でコードの再利用を可能とするメソッド内メソッドとその効率の良い実装方法の提案細かい粒度でコードの再利用を可能とするメソッド内メソッドとその効率の良い実装方法の提案 平松 俊樹 千葉 滋 東京工業大学 数理・計算科学専攻
メソッドの一部を切り出す • メソッドの一部を 別メソッドに 例. Eclipse での リファクタリング extract method • 分割 • 再利用 class Max { int calc (int[] a) { : for (inti = 0; ..) { sum += a[i]; if (max < a[i]) max = a[i]; } : } }
メソッドの一部の再利用 • 切り出したメソッドをサブクラスで上書き • メソッドの一部分を変更 • 実際は難しい class Max { int calc (int[] a) { int sum = 0; int max = a[0]; int average; for (inti = 0; ..) { calcSum(sum, max, i, a); } average = sum / a.length; return max – average; } } class Min extends Max { void calcSum(int sum, int max, inti, int[] a) { sum = .. } }
切り出しは困難 class Max { int calc (int[] a) { : for (inti = 0; ..) { calcSum( sum, max, i, a); } : } void calcSum( int sum, int max, inti, int[] a) { sum += a[i]; : } } • ローカル変数の参照 • 大量の引数 • 変数への代入は?
提案:上書き可能なメソッド内メソッド • ローカル変数にアクセス可能 • サブクラスで上書き可能 class Max { int calc (int[] a) { public int max, .. for(inti=0; ..) { void calcSum( int[] a, inti) { sum += a[i]; if (max < a[i]) max = a[i]; } calcSum(a, i); } : } } class Min extends Max { void calc (int[]). calcSum(int[] a, inti) { sum += a[i]; if (max > a[i]) max = a[i]; } } 上書き
メソッド内メソッドの定義 • メソッドボディにメソッド定義を記述 • 定義だけでは呼ばれない publicについては 後述 class Max { int calc (int[] a) { public int max, .. for (inti = 0; ..) { void calcSum(int[] a, inti) { sum += a[i]; if (max < a[i]) max = a[i]; } calcSum(a, i); } : } }
メソッド内メソッドの上書き • メソッド名を “ . ” で区切って指定 class Min extends Max { void calc (int[]).calcSum(int[] a, inti) { sum += a[i]; if (max > a[i]) max = a[i]; } }
ローカル変数の参照 • 外側のメソッドの全ローカル変数が メソッド内メソッドから参照、代入可能 • メソッド内メソッドを上書きしていないクラス • グローバル変数のように見える class Max { int calc (int[] a) { public int sum = 0; public int max = a[0]; int average; for (inti = 0; ..) { void calcSum(int[] a, inti) { sum += a[i]; if (max < a[i]) max = a[i]; } calcSum(a, i); }}}
public 変数 • 上書き後はpublic変数だけが参照、代入可能 • 非public変数は参照も代入も不可 • カプセル化 class Max { int calc (int[] a) { public int sum = 0; public int max = a[0]; intave; for(inti = 0;..){ void calcSum( int[] a, inti) { sum += a[i]; if (max < a[i]) max = a[i]; } calcSum(a, i); } : } } class Min extends Max { void calc (int[]). calcSum(int[] a, inti) { sum += a[i]; if (max > a[i]) max = a[i]; } }
メソッド内メソッドのスコープ • メソッド内メソッドの有効範囲 • ひとつ外側のメソッドのボディ • 自身のメソッドボディ void f() { void g() { void h() { } } } .. void g() { .. void h() { } .. } .. void h() { } void f() { void g() {} g(); } void f() { void g() { g(); } }
他の方法: 参照渡し • C++における参照渡し • 変数への代入が可能 • 大量の引数 • Javaには無い • 呼ぶ側から値渡しと区別がつかない • 副作用の有無 class Max { int calc (int[] a) { : for (int i = 0; ..) { calcSum( sum, max, i, a); } : } void calcSum( int& sum, int& max, inti, int[] a) { : } }
他の方法: クロージャ • ローカル変数にアクセスできる • 上書きするとアクセスできない class Max { Closure calcSum; int calc (int[] a) { int max, .. for (inti = 0; ..) { calcSum = { if (max < a[i]) max = a[i]; } calcSum(); } : } } class Min extends Max { int calc (int[] a) { calcSum = { if (max > a[i]) max = a[i]; } : } }
外側のメソッドのコード変換 • 外側のメソッドを2種類用意 • メソッド内メソッドをインライン展開したもの • 展開しないもの • メソッド内メソッドは通常のメソッドに変換 • サブクラスでメソッド内メソッドを上書き • サブクラスからはこちらが呼ばれる int calc(int[] a) { Var$calc $var = new Var$calc(); $var.sum = ..; : calcSum($var); } void calcSum(Var$calc $var) { : メソッド内メソッドを通常のメソッドに変換 呼び出しを展開していない
効率的な実装 • ソースコードを変換 • メソッド内メソッドをインライン展開 • 再帰呼び出しが無く、上書きされないコードの場合 • メソッド呼び出しのオーバーヘッドが消える int calc(int[] a) { void calcSum(int[] a, inti) { sum += a[i]; if (max < a[i]) max = a[i]; } for (inti = 0; ..) { calcSum(a, i); } } int calc(int[] a) { for (inti = 0; ..) { sum += a[i]; if (max < a[i]) max = a[i]; } } 展開
外側のメソッドのコード変換 int $calc(int[] a) { Var$calc $var = new Var$calc(); for (inti = 0; ..) { calcSum(a, i, $var); } } void calcSum (int[] a, int i, Var$calc $var) { $var.sum += a[i]; if ($var.max < a[i]) $var.max = a[i]; } class Var$calc { int sum; int max; .. } • メソッド内メソッドをインライン展開しない メソッド内メソッドを通常のメソッドに変換 ローカル変数を集めたクラスを作成
メソッド内メソッドの上書きの実装 • 通常のメソッドに変換 • スーパークラスにおいて通常のメソッドに変換されたメソッド内メソッドを上書き • 変数へのアクセスはオブジェクトを介す class Min extends Max { void calc (int[] a). calcSum(int[] a, inti) { sum += a[i]; if (max > a[i]) max = a[i]; } } class Min extends Max { void calcSum( int[] a, int i, $Var $var){ $var.sum += a[i]; if ($var.max > a[i]) $var.max = a[i]; } } 変換
実験: マイクロベンチマーク • 実行時間・コード量の比較 • 本システムを用いたコード • 通常の Java でメソッドを切り出さない • 通常の Java でメソッドを切り出す • 上書きの有無 • 100,000,000回実行 • 実験環境 • OS: Windows 7 • CPU: Intel Core i5 2.67GHz • メモリ: 4GB class Max { int calc (int[] a) { int sum = 0; int max = a[0]; intave; for (inti = 0; ..) { sum += a[i]; if (a[i] > max) max = a[i]; } ave = sum / a.length; return max – ave; } }
実験結果・実行時間 • 本システムを用いても、上書き前はメソッドに切り出さない場合と差が無い • 上書き後であっても、別のメソッドを定義した場合よりは速い • 効率よく書くのは難しい • 切り出し • 初めから別メソッドとして定義 • extract a method
実験結果・コード量 • 本システムでは、差分のみの記述で変更が可能であるため、上書き時のコード量が少なくなる
関連研究 • Regioncut [Akaiら’09] • コード領域をジョインポイントとして選択 • コード領域に対する変更が可能 • ローカル変数への代入が不可能 • Closure Joinpoints [Bodden’11] • コードブロックをジョインポイントとして選択 • ローカル変数への代入が不可能 • Beta [Knudenら’94] • オブジェクト指向言語 • 上書き可能なインナープロシージャ • スーパークラスの振る舞いが取り除けない
まとめと今後の課題 • まとめ • メソッド内メソッド • 上書き • ローカル変数を参照 • 効率的な実装 • インライン展開 • 今後の課題 • return の扱い • メソッド内メソッドから? • 外側のメソッドから?