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

細かい粒度でコードの再利用を
可能とするメソッド内メソッド
のJava言語への導入
理学部 情報科学科
07-22331 平松 俊樹
指導教員 千葉 滋 教授
1
巨大メソッドの一部の再利用
一部だけ上書きしたい
 メソッドに切り出す

◦
class Parser {
private Object parse(TokenStr in) {
while (..) {
Symbol token = .. ;
while (..) {
ローカル変数を参照し
short act = .. ;
ていたら?
if (..) {
:
}
else {
report.syntaxError(token);
変更
recoverFromError(token, in);
}
:
:
}
2
メソッドに切り出すことは困難
大量の引数
 変数への代入は?

切り出されたメソッド
class Parser {
private Object parse(TokenStr in) {
while (..) {
Symbol token = .. ;
while (..) {
short act = .. ;
if (..) {..}
else {elseM(token, in);}
}
}
}
void elseM(Symbol token,TokenStr in){
report.syntaxError(token);
recoverFromError(token, in);
}
}
3
クロージャを用いた場合

ローカル変数にアクセスできる
◦ 上書きするとアクセスできない
class Parser{
Closure elseM;
private Object parse(TokenStr in) {
while (..) {
Symbol token = .. ;
while (..) {
short act = .. ;
if (..) {..}
else {
elseM = {report.syntaxError(token);
recoverFromError(token, in);
}
elseM();
}}}}}
class SubParser extends Parser{
private Object parse(TokenStr in) {
elseM = {
report.syntaxError(token);
act = 0;
}
super.parse(in);
}
}
アクセス
できない
4
提案:上書き可能なメソッド内メソッド

サブクラスでオーバーライド可能
class Session {
class Parser
public {void buy(Item item) {
privateint
Object
countparse(TokenStr
= 0;
in) {
while (..)
public
{ int numItem = 0;
Symbol
public
token
int=totalAmount
.. ;
= 0;
while boolean
(..) {
inService = false;
short :act = .. ;
if (..) void
{..} service() {
else {if (inService) {
void elseM()
numItem++;
{
report.syntaxError(token);
count++;
recoverFromError(token,
}
in);
} }
elseM();
service();
}}}}
:
}
}
class Discount extends Session{
class
int limit;
SubParser extends Parser{
parse(TokenStr).elseM()
public void buy(Item).void
{ service(){
report.syntaxError(token);
if (numItem > limit) {
acttotalAmount
= 0;
*= 0.8;
} }
}}
}
5
ローカル変数へのアクセス

public宣言されたローカル変数、引数
◦ サブクラスのメソッド内メソッドから参照可能
◦ カプセル化を破壊しない
class Parser {
Object parse(public TokenStr in) {
public Symbol token;
short act;
void elseM() {
class SubParser extends Parser {
:
Object parse(TokenStr).elseM() {
}
:
}
}
}
}
parse
in
token
act
アクセス可
elseM
アクセス不可
6
実装方法
JastAddを用いて実装
 メソッド内メソッドで参照される変数を
集めたオブジェクトを作る

◦ それを引数で渡す
◦ クロージャの実装方法と類似

今回採用した方法
◦
◦
◦
◦
メソッドに対応するクラスを作成
コード変換が簡単
thisの扱いが複雑
コード量
 約8000行を読み、1100行を記述
7
コード変更の例
class C {
int outerM(int arg) {
public int localVar = 0;
void innerM() {
localVar = arg;
}
InnerM();
:
}
}
class Child extends C {
int outerM(int).void innerM(){
localVar = 0;
}
}
class C {
int outerM(int arg) {
return new C$outerM(this).run$$(arg);
}
void innerM(C$outerM $outer) {
$outer.new C$innerM($outer).run$$();
}
class C$outerM {
public C $this;オ
private int arg;ー
protected int localVar;
バ
ー
:
ラ arg$arg) {
public int run$$(int
イ
arg = arg$arg;ド
localVar = 0;
class
Child extends C {
$this.innerM(this);
void
: innerM(C$outerM $outer) {
}$outer.new C$innerM($outer) {
public
void run$$()
{
class
C$innerM
{
$outer.localVar
0;
public
C$outerM=$outer;
:}
}.run$$();
public void run$$() {
} localVar = arg;
}
}}}}
8
実験:マイクロベンチマーク

実行時間の比較
◦ メソッド内メソッド
◦ 手動で切り分けたメソッド

代入するローカル変数の個数を変えて実験
◦ 実験環境




OS: Windows 7
CPU: Intel Core i5 2.67GHz
メモリ: 4.00GB
JVM 1.6.0_20
9
実験1:
代入するローカル変数の個数=1

public void method1() {
int result = 0;
public int method2() {
int r1 = i + j; int r2 = i - j;
int r3 = i * j; int r4 = i / j;
return r1 + r2 + r3 + r4;
}
for (int i = 1; i <= 10000; i++) {
for (int j = 1; j <= 10000; j++) {
result = method2();
}
}
}
結果
プログラム
プログラム
実行時間比
実行時間
(ms)
(ms)1694
1694
メソッド内メソッド
1694 352
4.8
メソッド内
352
手動で分けたメソッド
メソッド
1
手動で分けた 352
メソッド
◦ メソッド内メソッドを呼ぶ
たびにオブジェクト作成
◦ 手動の場合はオブジェクト
を作らない
10
実験2:
代入するローカル変数の個数=4
public void method1() {
int localVar1 = 0; int localVar2 = 0;
int localVar3 = 0; int localVar4 = 0;
public void method2() {
localVar1 = i + j; localVar2 = i - j;
localVar3 = i * j; localVar4 = i / j;
}
for (int i = 1; i <= 10000; i++) {
for (int j = 1; j <= 10000; j++) {
method2();
}}}
public ReturnValue method2(int i, int j) {
int r1 = i + j; int r2 = i – j;
int r3 = i * j; int r4 = i / j;
return new ReturnValue(r1, r2, r3, r4);
}
手動で書いたmehtod2

結果
プログラム
プログラム
実行時間 比
実行時間
(ms)
(ms)
1261
メソッド内メソッド
12611515
1
メソッド内
手動で分けたメソッド
メソッド
1.2
手動で分けた 1515
メソッド
◦ 手動で書いたコードの変
数の渡し方が悪い
 効率よく書くのが難しい
 オーバーヘッドは許容範囲
11
関連研究

Regioncut [Akaiら ‘09]
◦ コード領域をジョインポイントとして選択可能
◦ コード領域に対する変更が可能
◦ ローカル変数への代入が不可能

Closure Joinpoints [Bodden ‘11]
◦ コードブロックをジョインポイントとして選択可能
◦ ローカル変数への代入が不可能

Beta [Knudsenら ‘94]
◦ オブジェクト指向言語
◦ 上書き可能なインナープロシージャ
 メソッド内メソッドと類似
 スーパークラスの振る舞いが取り除けない
12
まとめ

メソッド内メソッド
◦ JastAddJを拡張してコンパイラを実装
◦ オーバーライド可能
◦ ローカル変数を参照可能

今後の課題
◦ 実装の改善
◦ オーバーライドの記述の簡素化
13