CC発生時におけるリファクタリングのデザインパターン

コードクローンの分類に基づいた
メソッド引き上げ手順の提案とその有効性評価
井上研究室
1
吉岡一樹
コードクローン

ソースコード中のコード片で,同一または類似した
コード片を持つもの

コード片の修正 → コードクローンすべてに検討を
修正
修正を検討
ソースファイルF1
クロ―ンペア
ソースファイルF2
クローンペア
修正
クローンペア
2
リファクタリングパターン
外部的振る舞いに変更を加えずに内部的振る舞いを修
正することをリファクタリングという[1]


特に繰り返し行われる修正を,修正をする状況と手順をまと
めてリファクタリングパターンという
リファクタリングパターンの開発は未熟な開発者の手助けに
Fowlerはコードクローンに対する代表的なリファクタリン
グパターンを提案している[1]



同一クラスのコードクローン → メソッドの抽出
兄弟クラスのコードクローン → メソッドの引き上げ
[1] Fowler.:Refactoring:”Improving The Design Existing Code”,Addison Wesley (1999)
3
既存のパターンの問題点
Fowlerのパターンでは,あいまいさを残すことで修正に
柔軟に対応するようにしている



対応できるだけの開発者の技術が必要になる
未熟な開発者が利用するには手順の詳細化が必要
より詳細なリファクタリングパターンを作成することで
未熟な開発者の手助けになる
5
コードクローンの分類
徳永らが提案したもの[2]でコードクローンの特徴ごとに分類
し,分類ごとにリファクタリングパターンを提案していく
分類項目は以下の7つ









クローンペアの差異
クローンペアの位置
クローン部の長さ
メソッド抽出の際に引数となるオブジェクト
メソッド抽出の際に戻り値となるオブジェクトの数
制御構造要素の有無
Instanceof演算子の有無
上記の分類によってコードクローンを分類し,手順を詳細化
できるようにする

6
[2]徳永将之ら,“コードクローンの分類に基づくリファクタリングパターン
の提案に向けて”,ウインターワークショップ2011
本研究の提案内容

分類の一つを用いてリファクタリングパターンを提案

提案したパターンを用いて実験として被験者がリファクタ
リング

Fowlerが提唱している既存パターンとの比較実験
提案したパターンの既存パターンの比較評価をアンケー
ト

7
提案したパターン

要約



状況







8
ユーザ記述データ,定数を差異に持つ
兄弟クラス間クローンを親クラスに引き上げ
差異:上記の差異
兄弟クラス間クローン
ブロック単位
引数の権限:private
戻り値となるオブジェクトの数:0,もしくは1
制御構造要素なし
Instanceof演算子なし
リファクタリング
パターンは,
•状況
•手順
•実例
からなる文章で
記述される
銀行口座の
オブジェクト
生成
以下の2つのコードは兄弟クラス
間のクローンである
顧客名の
セット
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(
number);
customer.setRate
顧客番号の
セット
(ORDINARY_RATE);
:
:
差異
9
顧客名の
セット
:
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:子クラスのメソッド宣言
10
手順1-1: メソッドの宣言,コンパイル
:
Account customer =
new Account();
customer.setName(name);
customer.setNumber(
number);
customer.setRate () =
(ORDINARY_RATE;
:
:
11
:
親クラスに
Account customer
=
処理を記述
new Account();
する
customer.setName(name);
customer.setNumber(
number);
customer.setRate () =
FIXED_RATE);
:
:
手順1-1: メソッドの宣言,コンパイル
: メソッドの名前
:
はメソッドの役
Account customer
=
Account customer =
割,処理の内容
new Account();
void initialAccount(){
new Account();
から決める
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
number);
}
number);
customer.setRate () =
customer.setRate () =
FIXED_RATE);
(ORDINARY_RATE;
:
:
:
:
12
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
13
手順1-2:クローン部の引き上げ
:
:
Account customer =
Account customer =
new Account();
void initialAccount(){
new Account();
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
number);
}
number);
customer.setRate () =
customer.setRate () =
FIXED_RATE);
(ORDINARY_RATE;
:
:
:
:
14
手順1-2:クローン部の引き上げ
void initialAccount(){
:
:
Account customer =
Account customer =
Account customer =
new Account();
new Account();
new Account();
customer.setName(name);
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate
customer.setRate () =
customer.setRate ()(ORDINARY_RATE);
=
FIXED_RATE);
(ORDINARY_RATE;}
:
:
:
:
コピー&ペースト
15
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
16
手順1-3:クローン部の差異以外の変数,オブ
ジェクトの引数を用意する
void initialAccount(){
:
:
Account customer =
Account customer =
Account customer =
new Account();
new Account();
new Account();
customer.setName(name);
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate
customer.setRate () =
customer.setRate ()(ORDINARY_RATE);
=
FIXED_RATE);
(ORDINARY_RATE;}
:
:
:
:
17
手順1-3:クローン部の差異以外の変数,オブ
ジェクトの引数を用意する
void initialAccount(
:
String name,int number){
:
Account customer =Account customer = Account customer =
new Account();
new Account();
new Account();
変数の名前
customer.setName(name);
customer.setName(name);
customer.setName(name);
をそのまま,
customer.setNumber( customer.setNumber(
customer.setNumber(
number);
number);
引数に宣言
number);
customer.setRate ()する
=
customer.setRate ()customer.setRate
=
(ORDINARY_RATE;(ORDINARY_RATE); FIXED_RATE);
:
}
:
:
:
18
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
19
手順1-4:戻り値があるならオブジェクトのreturn
を追加し,メソッドの型を戻り値の型にする
void initialAccount(
:
String name,int number){
:
Account customer =Account customer = Account customer =
new Account();
new Account();
new Account();
customer.setName(name);
customer.setName(name);
customer.setName(name);
customer.setNumber( customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate () =
customer.setRate ()customer.setRate
=
(ORDINARY_RATE;(ORDINARY_RATE); FIXED_RATE);
:
}
:
:
:
20
手順1-4:戻り値があるならオブジェクトのreturn
を追加し,メソッドの型を戻り値の型にする
Account initialAccount(
String name,int number){
:
:
Account customer =
Account customer =
Account customer =
new Account();
new Account();
new Account();
customer.setName(name);
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate
customer.setRate () =
customer.setRate ()(ORDINARY_RATE);
=
FIXED_RATE);
(ORDINARY_RATE;return customer;
:
:
}
:
:
戻り値がないときにはこの手
順での操作はない
21
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
22
手順1-5:差異のための引数を作成し,差異を引数
で置き換える
Account initialAccount(
String name,int number){
:
:
Account customer =
Account customer =
Account customer =
new Account();
new Account();
new Account();
customer.setName(name);
customer.setName(name);
customer.setName(name);
customer.setNumber(
customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate
customer.setRate () =
customer.setRate ()(ORDINARY_RATE);
=
FIXED_RATE);
(ORDINARY_RATE;return customer;
:
:
}
:
:
23
手順1-5:差異のための引数を作成し,差異を引数
で置き換える
Account initialAccount(
String name,int number
:
:
,int rate){
Account customer =Account customer = Account customer =
new Account();
new Account();
共通部分を名前
new Account();
customer.setName(name);
customer.setName(name);
にしている
customer.setName(name);
customer.setNumber(
customer.setNumber(
customer.setNumber(
number);
number);
number);
customer.setRate () =
customer.setRate ()customer.setRate
=
(rate);
FIXED_RATE);
(ORDINARY_RATE;return customer;
:
:
}
:
:
手順1-6:親クラスをコンパイルして,テス
トする
24
手順1:親クラスのメソッド宣言
手順1-1:
手順1-2:
手順1-3:
手順1-4:
手順1-5:
手順1-6:
メソッドの宣言,コンパイル
クローン部の引き上げ
変数,オブジェクトの処理
戻り値の処理
差異の処理
親クラスのコンパイル,テスト
手順2:子クラスのメソッド宣言
25
手順2:元の子クラスのクローン部を取り除き,メ
ソッド呼び出しを追加する
:
Account customer =
initialAccount(name,
number,
ORDINARY_RATE );
:
:
:
Account customer=
initialAccount(name,
number,
FIXED_RATE );
:
:
各クラスをコンパイルして,テストする
26
適用実験の準備

提案したパターンを用いて、オープンソースソフトウェア
Sootを用いて実際のコードでリファクタリングを行った


コードクローン検出ツールを用いてコードクローンを見つけた
のち,手作業で分類にかけたもの
被験者三名に、3つのクローンペアに対し、提案したパ
ターンとファウラーのパターンを比較してリファクタリング
を行ってもらった


27
被験者はコンピュータサイエンス専攻のM1,M2,研究生の
三名
66個のJUnitテストを用いてリファクタリングが正常に行われた
ことを確認する
適用実験の結果(1/2)

提案手法が優れていると評価された点

引数に対する扱い


戻り値があるときの扱い


手順1-4:戻り値の処理を評価
差異に対する扱い


手順1-3:変数,オブジェクトの処理を評価
手順1-5:差異の処理を評価
提案手法が劣っていると評価された点

手順全体の柔軟さ

28
詳細化を進めたために好みの手順でプログラミング出来ない
適用実験の結果(2/2)
66個のJUnitテストケースを用いてリファクタリングパターンの妥当性を確認
被験者A
被験者B
被験者C
引数に対する扱い
✓
✓
✓
戻り値があるときの
扱い
✓
✓
✓
差異に対する扱い
✓
手順全体の柔軟さ
✓
✓
提案したパターンの手順の優位性を示している
29
まとめと今後の課題

まとめ


コードクローンの分類に基づいたメソッド引き上げ手順を提案
した
今後の課題

コードクローンの特徴ごとの分類の自動化

他の分類に対してのリファクタリングパターンの提案
30
終わり
31