Transcript
Page 1: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

細かい粒度でコードの再利用を可能とする

メソッド内メソッドとその効率の良い実装方法の提案

平松 俊樹 千葉 滋

東京工業大学数理・計算科学専攻 1

Page 2: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッドの一部を切り出す

• メソッドの一部を 別メソッドに

例 . Eclipse での リファクタリング extract method–分割–再利用

2

class Max { int calc (int[] a) { : for (int i = 0; ..) { sum += a[i]; if (max < a[i]) max = a[i]; } : }}

Page 3: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッドの一部の再利用

• 切り出したメソッドをサブクラスで上書き

• メソッドの一部分を変更–実際は難しい

3

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 = .. }}

Page 4: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

切り出しは困難

• ローカル変数の参照–大量の引数

• 変数への代入は?

4

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]; : }}

Page 5: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

提案:上書き可能なメソッド内メソッド

• ローカル変数にアクセス可能• サブクラスで上書き可能

5

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]; }}

上書き

Page 6: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッド内メソッドの定義

• メソッドボディにメソッド定義を記述• 定義だけでは呼ばれない

public については後述

6

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); } : }}

Page 7: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッド内メソッドの上書き

• メソッド名を “ . ” で区切って指定

7

class Min extends Max { void calc (int[]).calcSum(int[] a, int i) { sum += a[i]; if (max > a[i]) max = a[i]; }}

Page 8: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

ローカル変数の参照

• 外側のメソッドの全ローカル変数が    メソッド内メソッドから参照、代入可能– メソッド内メソッドを上書きしていないクラス– グローバル変数のように見える

8

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);}}}

Page 9: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

public 変数

• 上書き後は public 変数だけが参照、代入可能–非 public 変数は参照も代入も不可–カプセル化

9

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]; } 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]; }}

Page 10: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッド内メソッドのスコープ

• メソッド内メソッドの有効範囲–ひとつ外側のメソッドのボディ–自身のメソッドボディ

10

void f() { void g() {} g();}               

void f() { void g() { g(); }}

void f() {

void g() {

void h() {

}

}

}

.. void g() { .. void h() {

} .. } ..

void h() {

}

Page 11: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

他の方法 : 参照渡し

• C++ における参照渡し–変数への代入が可能

–大量の引数– Java には無い–呼ぶ側から値渡しと 

区別がつかない• 副作用の有無

11

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) { : }}

Page 12: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

他の方法 : クロージャ

• ローカル変数にアクセスできる–上書きするとアクセスできない

12

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]; } : }}

Page 13: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

外側のメソッドのコード変換

• 外側のメソッドを2種類用意–メソッド内メソッドをインライン展開したもの–展開しないもの• メソッド内メソッドは通常のメソッドに変換• サブクラスでメソッド内メソッドを上書き• サブクラスからはこちらが呼ばれる

13

int calc(int[] a) { Var$calc $var = new Var$calc(); $var.sum = ..; : calcSum($var);}void calcSum(Var$calc $var) { :

呼び出しを展開していない

メソッド内メソッドを通常のメソッドに変換

Page 14: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

効率的な実装

• ソースコードを変換• メソッド内メソッドをインライン展開–再帰呼び出しが無く、上書きされないコードの場

合–メソッド呼び出しのオーバーヘッドが消える

14

int calc(int[] a) { void calcSum(int[] a, int i) { sum += a[i]; if (max < a[i]) max = a[i]; } for (int i = 0; ..) { calcSum(a, i); }}

int calc(int[] a) { for (int i = 0; ..) { sum += a[i]; if (max < a[i]) max = a[i]; }}

展開

Page 15: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

外側のメソッドのコード変換

• メソッド内メソッドをインライン展開しない

15

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; ..}

メソッド内メソッドを通常のメソッドに変換

ローカル変数を集めたクラスを作成

Page 16: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

メソッド内メソッドの上書きの実装

• 通常のメソッドに変換–スーパークラスにおいて通常のメソッドに変

換されたメソッド内メソッドを上書き–変数へのアクセスはオブジェクトを介す

16

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]; }}

変換

Page 17: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

実験 : マイクロベンチマーク• 実行時間・コード量の比較

– 本システムを用いたコード– 通常の Java でメソッドを切り出さない– 通常の Java でメソッドを切り出す– 上書きの有無– 100,000,000回実行

• 実験環境– OS: Windows 7– CPU: Intel Core i5         2.67GHz– メモリ: 4GB

17

class Max { 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; }}

Page 18: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

実験結果・実行時間–本システムを用いても、上書

き前はメソッドに切り出さない場合と差が無い

–上書き後であっても、別のメソッドを定義した場合よりは速い• 効率よく書くのは難しい

–切り出し• 初めから別メソッドとして定

義• extract a method 18

本システム

切り出しなし

切り出しあり

0

1000

2000

3000

4000

5000

6000

7000

8000

1454 1472

7295

5103

1386

7585

元のクラスサブクラス

ミリ秒

Page 19: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

実験結果・コード量–本システムでは、差分のみ

の記述で変更が可能であるため、上書き時のコード量が少なくなる

19

本システム

切り出しなし

切り出しあり

0

5

10

15

20

25

30

16

13

26

6

13

10

元のクラスサブクラス

行数

Page 20: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

関連研究

• Regioncut [Akaiら’ 09]– コード領域をジョインポイントとして選択– コード領域に対する変更が可能– ローカル変数への代入が不可能

• Closure Joinpoints [Bodden ’11]– コードブロックをジョインポイントとして選択– ローカル変数への代入が不可能

• Beta [Knudenら’ 94]– オブジェクト指向言語– 上書き可能なインナープロシージャ

• スーパークラスの振る舞いが取り除けない

20

Page 21: 細かい粒度 で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案

まとめと今後の課題

• まとめ– メソッド内メソッド

• 上書き• ローカル変数を参照

– 効率的な実装• インライン展開

• 今後の課題– return の扱い

• メソッド内メソッドから?• 外側のメソッドから?

21


Top Related