コードクローンの分類に基づいた メソッド引き上げ手順の提案とその有効性評価 井上研究室 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
© Copyright 2024 ExpyDoc