コードクローンの特徴に基づく メソッド引き上げリファクタリングパターンの提案 ○吉岡一樹(阪大), 吉田 則裕(奈良先端大),徳永 将之 松下 誠, 井上 克郎(阪大) 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
© Copyright 2024 ExpyDoc