細かい粒度でコードの再利用を 可能とするメソッド内メソッド の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
© Copyright 2024 ExpyDoc