sel.ist.osaka

コードクローンの特徴に基づく
メソッド引き上げリファクタリングパターンの提案
○吉岡一樹(阪大), 吉田 則裕(奈良先端大),徳永 将之
松下 誠, 井上 克郎(阪大)
1
コードクローン

ソースコード中のコード片で,同一または類似した
コード片を持つもの

コード片の修正
コードクローンすべてに検討を修正

ソースファイルF1
クロ―ンペア
ソースファイルF2
クローンペア
修正
クローンペア
2
修正を検討
リファクタリングパターン
パターンは,特定の状況で発生する問題の解法である


状況,手順,実例で構成
外部的振る舞いに変更を加えずに内部的振る舞いを修
正することをリファクタリングという[1]


特に繰り返し行われる修正を,修正をする状況と手順をまと
めてリファクタリングパターンという
[1] Fowler.:Refactoring:”Improving The Design Existing Code”,Addison Wesley (1999)
3
Fowlerのパターンの例
Fowlerはコードクローンに対する代表的なリファクタリン
グパターンを提案している
メソッドの引き上げ






メソッドが同一であることの確認
シグニチャ(メソッド名,引数のリスト)の変更
親クラスにメソッドを作成.サブクラスからコピーするときに必要であ
れば修正を行い,コンパイルを行う
サブクラスからメソッドを取り除き,コンパイルを行う
Employee
4
Salesman
Engineer
getName()
getName()
Employee
getName()
Salesman
Engineer
既存のパターンの問題点

Fowlerのパターンでは,あいまいさを残すことで修正
に柔軟に対応するようにしている
 対応できるだけの開発者の技術が必要になる
 未熟な開発者が利用するには手順の詳細化が
必要
int a=0;
抽出範囲
:
修正の手順についてファウラーは述べていない
使用する変数の宣言が
while(a<100){
:
a++;
}
b = a*100;
5
抽出部の外にある
使用する変数を戻り値に
する必要がある
コードクローンの分類
徳永の論文[2]ではコードクローンの特徴ごとに分類して
おり,分類毎にリファクタリングパターンを提案していく
分類項目は以下の7つ









クローンペアの差異
クローンペアの位置
クローン部の長さ
メソッド抽出の際に引数となるオブジェクトの種類
メソッド抽出の際に戻り値となるオブジェクトの数
制御構造要素の有無
Instanceof演算子の有無
[2] Masayuki Tokunaga, et al., "Towards Collection of Refactoring Patterns
Based on Code Clone Classification", AsianPLoP 2011 ( to appear )
6
コードクローンの分類と詳細化

パターンの適用状況を詳細化することで,修正手順を明
確化する

修正する対象の明確化



コードの差異
スコープ外のオブジェクト
前述の分類によってコードクローンを分類

12
各分類に対応した手順を考案し,問題なく抽出,もしくは抽出
できないものを分類する
本研究の提案内容

コードクローンの分類の一つを用いてリファクタリングパ
ターンを提案する

従来のリファクタリングにおいては,コードクローンを取り
除く際の修正を開発者自らが考えて行っていた

リファクタリングパターンを提案することで,パターンの状
況に合うコードクローンの修正を容易に
13
提案したパターン

要約



ユーザ記述データ,定数を差異に持つ
兄弟クラス間クローンを親クラスに引き上げ
状況

差異


位置


なし
Instanceof演算子

14
0,もしくは1
制御構造要素


引数の権限:private
メソッド抽出の際に戻り値となるオブジェクトの数


ブロック
メソッド抽出の際に必要となるオブジェクトの種類


兄弟クラス間クローン
クローン部の長さ


上記の差異
なし
リファクタリング
パターンは,
•状況
•手順
•実例
からなる文章で
記述される
以下の2つのコードは兄弟クラス
間のクローンである
銀行口座の
オブジェクト
生成
顧客名の
セット
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
:
顧客番号の
セット
差異
15
顧客名の
セット
顧客番号の
セット
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
16
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
17
手順1-1: メソッドの宣言,コンパイル
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
:
18
:
親クラスに
処理を記述
する
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
手順1-1: メソッドの宣言,コンパイル
void intialAccount(){
メソッドで返す型
はvoidにする
:
}
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
:
19
:
メソッドの名前
はメソッドの役
割,処理の内容
から決める
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
20
手順1-2: クローン部の引き上げ
void intialAccount(){
}
:
Account customer =
new Account();
customer.setName(name);
サブクラスからの
customer.setNumber(number);
コピーペースト
customer.setRate(ORDINARY_RATE);
:
21
void intialAccount(){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
}
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
22
手順1-3:クローン部の差異以外の変数,オ
ブジェクトの引数を用意する
void intialAccount(){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
}
:
:
Account customer =
変数の名前
new Account();
customer.setName(name);
をそのまま,
customer.setNumber(number);
引数に宣言
customer.setRate(ORDINARY_RATE);
:
23
void intialAccount( int number,
String name){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
}
する
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
24
手順1-4:戻り値があるならreturn文を追加
し,メソッドの型を戻り値の型にする
void intialAccount(int number,
String name){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
}
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
:
25
Account intialAccount(int number,
String name){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
return customer;
}
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(FIXED_RATE);
:
戻り値がないときには
この手順での操作は
ない
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
26
手順1-5:差異のための引数を作成し,
差異を引数で置き換える
Account intialAccount(int number,
String name){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
return customer;
}
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(ORDINARY_RATE);
:
27
Account intialAccount(int number,
String name,int rate){
Account customer =
new Account();
customer.setName(name);
customer.setNumber(number);
customer.setRate(rate);
return customer;
}
:
Account customer =
new Account();
customer.setName(name);
共通部分を
customer.setNumber(number);
customer.setRate(FIXED_RATE);
名前にしてい
:
る
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
28
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
29
手順2:元の子クラスのクローン部を取り除
き,メソッド呼び出しを追加する
:
:
Account customer =
initialAccount(number,name,
ORDINARY_RATE);
:
Account customer =
initialAccount(number,name,
FIXED_RATE);
:
各クラスをコンパイルして,テストする
30
実験概要

提案したパターンの実験として被験者にパターンを用い
てリファクタリングしてもらう

既存パターンとの比較実験


対象ソフトウェアは Soot-2.2.4


かかった時間とアンケートで評価
実験対象のコードは3種類
被験者は計3名


31
コンピュータサイエンス専攻の研究生1名
博士前期課程2名
実験の結果(1/3)


被験者A
被験者B
被験者C
対象1
提案パターン
既存パターン
既存パターン
対象2
既存パターン
提案パターン
提案パターン
対象3
提案パターン
既存パターン
提案パターン
被験者A
被験者B
被験者C
対象1
10:57
9:32
10:00
対象2
9:16
6:38
5:39
対象3
6:27
3:02
6:08
Junitのテストケース66個を用いて,振る舞いの一貫性を
確かめた
時間的優位性は見られなかった

32
他の被験者やソースコードで実験を行うべき
実験の結果(2/3)

評価された点
被験者A
被験者B
被験者C
引数に対する扱い
✓
✓
✓
戻り値があるときの
扱い
✓
✓
✓
差異に対する扱い
✓

✓
アンケートを取った結果手順そのものの評価を受けたの
で,手順の内容を評価された点とした
33
実験の結果(3/3)

悪い評価をされた点
被験者A
被験者B
手順全体の柔軟さ

✓
詳細化を進めたために,手順を拘束されるという評価


被験者C
手順の道筋を複数考える必要
両方の意見を総合して提案手法の方が良い評価を得た
と判断できる
34
まとめと今後の課題


まとめ

コードクローンの分類に基づいたメソッド引き上げ手順を提案
した

既存のパターンとの比較実験を行った
今後の課題

様々な技術習熟度の被験者を増やしての実験

コードクローンの特徴ごとの分類の自動化

他の分類に対してのリファクタリングパターンの提案
35

ご静聴ありがとうございました
36