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