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

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