Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


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

1 細かい粒度で コードの再利用を可能とする メソッド内メソッドと その効率の良い実装方法の提案
平松 俊樹 千葉 滋 東京工業大学 数理・計算科学専攻

2 メソッドの一部を切り出す メソッドの一部を 別メソッドに 例. Eclipse での リファクタリング extract method 分割
 別メソッドに 例. Eclipse での  リファクタリング extract method 分割 再利用 class Max { int calc (int[] a) { : for (int i = 0; ..) { sum += a[i]; if (max < a[i]) max = a[i]; } プログラミングをしていて、メソッドの一部を別のメソッドに切り出したい時があります。 これはたとえばeclipseにおけるrefactoring機能のextract methodのようなことをしたい場合です。 自分がプログラミングをしているときにも、 例えばループの中の処理が複雑になるような場合には、その部分を別のメソッドに切り出したいことがあります。 ですが、切り出したい部分が多くのローカル変数を参照している場合には、大量の引数が必要 別メソッドから元のメソッドのローカル変数への代入ができない 従って別のメソッドに切り出すのは困難 切り出す前のコード

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 = .. } また、メソッドの一部を切り出すことができれば、 その切り出されたメソッドをサブクラスで上書きすることで、 メソッドの一部分だけを変更して、 残りのコードを再利用することができます。 しかし、先にも述べたように、メソッドの一部を別のメソッドに切り出すのは難しいため、 このような再利用も現実には難しい

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]; ローカル変数の参照 大量の引数 変数への代入は?

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]; } 上書き そこで、 メソッドの一部分の切り出し、 一部分の上書きを実現するために本研究が提案するのは 上書き可能なメソッド内メソッドです このメソッド内メソッドは自身の外側のメソッドのローカル変数にアクセスでき、 また、サブクラスで上書きすることができます。 これによって、 一部分の上書きが可能になります

6 メソッド内メソッドの定義 メソッドボディにメソッド定義を記述 定義だけでは呼ばれない 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); : メソッド内メソッドの定義について説明します。 本システムでは、メソッドボディにメソッド定義を記述することができます。 以下、メソッド内メソッドをその内側に含むようなメソッドをメソッド内メソッドの外側のメソッドと呼びます。 この例では、メソッドcalcが外側のメソッドにあたります。 また、例の中にpublic の付いた変数がありますが、これについては後で説明します

7 メソッド内メソッドの上書き メソッド名を “ . ” で区切って指定 class Min extends Max {
void calc (int[]).calcSum(int[] a, int i) { sum += a[i]; if (max > a[i]) max = a[i]; } メソッド内メソッドの上書き方法について説明します。 メソッド内メソッドは、その外側のメソッド名 ドット メソッド内メソッドの名前のように記述することで上書きすることができます。 この例では、、、、

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); }}} メソッド内メソッドからの、外側のメソッドのローカル変数の参照について説明します。 本システムでは、メソッド内メソッドが上書きされずに呼ばれた場合には、 そのメソッド内メソッドから、外側のメソッドの全てのローカル変数の値を参照でき、 全てのローカル変数への代入が可能です。

9 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]; } 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]; } 次に、メソッド内メソッドが上書きされた場合のローカル変数の参照について説明します。 本システムでは、メソッド内メソッドを含むようなメソッドを定義する際に、ローカル変数にpublic修飾子を付けて 宣言することができます。 このような変数をpublic変数と呼びます。 メソッド内メソッドを上書きする場合、 その上書きするメソッド内メソッド、サブクラスのメソッド内メソッドから 値の参照、変数への代入ができるのは、public変数のみになります。 例えば、この例では、 右側のサブクラスでメソッド内メソッドを上書きしていますが、 このサブクラスで定義されたメソッド内メソッドからアクセスできるのは 外側のメソッドのpublic 変数のみです。 public変数の値の参照、public 変数への代入は可能ですが public の付いていない変数aveの値をサブクラスで上書きするメソッド内メソッドcalcSumから 参照したり、aveに代入することはできません このような制限を設けることによって、サブクラスから、スーパークラスのローカル変数を自由に変更できないようになり、 カプセル化を守ることになると考えます。

10 メソッド内メソッドのスコープ メソッド内メソッドの有効範囲 ひとつ外側のメソッドのボディ 自身のメソッドボディ void f() {
void g() { void h() { } .. void f() { void g() {} g(); }               void f() { void g() { g(); } メソッド内メソッドのスコープ・有効範囲について説明します。 メソッド内メソッドは、それを呼ぶことのできる範囲に制限があります。 呼ぶことのできるのは、一つ外側のメソッドボディと、 そのメソッド内メソッドのボディ内のみです。 この例は、メソッドfの中にメソッド内メソッドg、 gの中にメソッド内メソッドhが定義されている場合ですが、 この時、メソッド内メソッドgを呼ぶことができるのは、 gの一つ外側のメソッドfのボディと g自身のメソッドボディのみとなります。 gの中に定義されたhのボディや、 fでないメソッドからは呼ぶことはできません

11 他の方法: 参照渡し 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) { 本研究では、メソッドの一部を切り出し、変更する方法として 上書き可能なメソッド内メソッドを提案しました。 切り出し、変更を行う方法として、 他に考えられる案があります。 一つはC++等における参照渡しです。 参照渡しを用いれば、異なるメソッドからその変数への代入が可能になります。 メソッドの一部を別のメソッドとして定義し、 代入が必要な変数は、参照渡しをすることで メソッドの一部を切り出すことができ、 それを上書きすることでメソッドの一部を変更することができます。 しかし、この方法を採った場合には、 必要な変数全てを引数として渡さなければならず、 場合によっては大量の引数が必要となります。 また、切り出されたメソッドを呼ぶ側からは値渡しと区別がつかず、 副作用の有無が分からないという問題があります

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]; } : 別の方法として、 クロージャを用いるということも考えられます。 クロージャは自身が定義された周りの変数にアクセスできるので、 メソッドの一部をクロージャとして定義することで、 メソッドを切り出すことができます。 しかし、これを別の場所、例えばサブクラスで上書きしようとすると、 そこには周りにローカル変数が宣言されていないため、 このクロージャから元のメソッドのローカル変数にアクセスすることができなくなります。

13 外側のメソッドのコード変換 外側のメソッドを2種類用意 メソッド内メソッドをインライン展開したもの 展開しないもの
メソッド内メソッドは通常のメソッドに変換 サブクラスでメソッド内メソッドを上書き サブクラスからはこちらが呼ばれる int calc(int[] a) { Var$calc $var = new Var$calc(); $var.sum = ..; : calcSum($var); } void calcSum(Var$calc $var) { 実装について説明します。 本システムは、コンパイラでコードを変換することによって実現します。 本システムでは、メソッド内メソッドの外側のメソッドを二種類用意します。 一つはメソッド内メソッドをインライン展開したもの。 もうひとつはメソッド内メソッドを展開しないものです。 メソッド内メソッドを上書きせずに外側のメソッドを呼ぶ場合には、 メソッド内メソッドを展開したものが呼ばれます。 サブクラスでメソッド内メソッドを上書きして外側のメソッドを呼ぶ場合には メソッド内メソッドを展開しないものが呼ばれます。 これは、メソッド内メソッドを展開したメソッドでは、メソッド内メソッドの上書きを反映させることができないからです。 メソッド内メソッドを通常のメソッドに変換 呼び出しを展開していない

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]; } メソッド内メソッドを上書きせずに外側のメソッドを呼ぶ場合には、 メソッド内メソッドをインライン展開したメソッドが呼ばれます。 これによってメソッド呼び出し等のオーバーヘッドを消すことができます。 展開

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; .. メソッド内メソッドをインライン展開しない メソッド内メソッドを通常のメソッドに変換 ローカル変数を集めたクラスを作成

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

17 実験: マイクロベンチマーク 実行時間・コード量の比較 実験環境 本システムを用いたコード 通常の 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]; int ave; for (int i = 0; ..) { sum += a[i]; if (a[i] > max) max = a[i]; } ave = sum / a.length; return max – ave;

18 実験結果・実行時間 本システムを用いても、上書き前はメソッドに切り出さない場合と差が無い
上書き後であっても、別のメソッドを定義した場合よりは速い 効率よく書くのは難しい 切り出し 初めから別メソッドとして定義 extract a method

19 実験結果・コード量 本システムでは、差分のみの記述で変更が可能であるため、上書き時のコード量が少なくなる

20 関連研究 Regioncut [Akaiら’09] Closure Joinpoints [Bodden ’11]
コード領域をジョインポイントとして選択 コード領域に対する変更が可能 ローカル変数への代入が不可能 Closure Joinpoints [Bodden ’11] コードブロックをジョインポイントとして選択 Beta [Knudenら’94] オブジェクト指向言語 上書き可能なインナープロシージャ スーパークラスの振る舞いが取り除けない

21 まとめと今後の課題 まとめ 今後の課題 メソッド内メソッド 効率的な実装 return の扱い 上書き ローカル変数を参照 インライン展開
メソッド内メソッドから? 外側のメソッドから?


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

Similar presentations


Ads by Google