1 / 33

コードクローンを対象とした リファクタリングの有効性に関する調査

コードクローンを対象とした リファクタリングの有効性に関する調査. 肥後 芳樹,楠本 真二,井上 克郎 大阪大学 大学院情報科学研究科 {y-higo, kusumoto, inoue}@ist.osaka-u.ac.jp. コードクローン. コードクローンとは ソースコード中に存在する一致または類似したコード片 コピーアンドペーストなどのさまざまな理由により生成される ソフトウェアの保守を困難にする あるコード片にバグがあると,そのコードクローン全てについて修正の検討を行う必要がある 機能を追加する場合も同様のことがいえる. コピーアンドペースト.

lorand
Download Presentation

コードクローンを対象とした リファクタリングの有効性に関する調査

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. コードクローンを対象としたリファクタリングの有効性に関する調査コードクローンを対象としたリファクタリングの有効性に関する調査 肥後 芳樹,楠本 真二,井上 克郎 大阪大学 大学院情報科学研究科 {y-higo, kusumoto, inoue}@ist.osaka-u.ac.jp

  2. コードクローン • コードクローンとは • ソースコード中に存在する一致または類似したコード片 • コピーアンドペーストなどのさまざまな理由により生成される • ソフトウェアの保守を困難にする • あるコード片にバグがあると,そのコードクローン全てについて修正の検討を行う必要がある • 機能を追加する場合も同様のことがいえる コピーアンドペースト コピーアンドペースト

  3. リファクタリング • ソフトウェアの外部的な振る舞いを保ったままで,内部構造を改善していくこと • リファクタリングを行うことでソフトウェアの保守性が増し,後の保守作業効率が向上する • これまでに多くのリファクタリングすべきコードのパターン(Bad Smell)とその対処法(Refactoring Pattern)が提案されている[1][2] • コードクローンは最も優先してリファクタリングを行うべきものの1つとされている • しかし,具体的な評価事例はほとんど報告されていない [1] M. Fowler: Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999. [2] Refactoring Home Page: http://www.refactoring.com/

  4. 研究の目的と概要 • コードクローンに対するリファクタリングのコストと効果を調査し,その有効性を評価する • 実験対象は複数バージョンのソースコードが公開されているオープンソースソフトウェア • 最初のバージョンに対してコードクローン検出 & リファクタリング • リファクタリングのコストとして以下のものを用いた • ソースコード修正と回帰テストに要した時間 • リファクタリングの効果として以下のものを用いた • CKメトリクス値の変化 • 総行数の増減 • 集約箇所の後のバージョンでの修正回数

  5. CKメトリクス • Chidamber と Kemerer が提案したオブジェクト指向ソフトウェアの複雑度を測定するメトリクス[3]であり,広く用いられている • 各メトリクスは,クラス単位で計測される • WMC(Weighted Methods per Class): そのクラスに定義されているメソッドに重み付けをして足し合わせた値 • DIT(Depth of Inheritance Tree): クラス階層における継承の深さ • NOC(Number of Children): サブクラスの数 • CBO(Coupling Between Objects): 関係しているクラスの数 • RFC(Response For a Class): そのクラスのオブジェクトにメッセージが送られた結果,実行されうるすべてのメソッドの数を表す • LCOM(Lack of Cohesion Of Methods): そのクラスのメソッドがお互いに関連している程度を表す [3] Chidamber S. R. and Kemerer C. F.: “A Metrics Suite for Object Oriented Design”, IEEE Transaction on Software Engineering, Vol. 20, No. 6, pp. 476-493 (1994).

  6. コードクローンに対するリファクタリング • コードクローンは最も優先してリファクタリングを行うべきコード(のパターン)の1つとされている • どのようにリファクタリングを行うかは,コードクローン間の関係によって異なる[1] • 同一クラス内の複数個所が重複している場合 • 兄弟クラス間で重複コードが見られる場合 • まったく関係のないクラス間で重複コードが見られる場合 [1] M. Fowler: Refactoring: Improving the Design of Existing Code, Addison-Wesley, 1999.

  7. コードクローンに対するリファクタリング:同一クラス内の複数箇所が重複している場合コードクローンに対するリファクタリング:同一クラス内の複数箇所が重複している場合 • Extract Method を適用して,各メソッドの内部からコードクローンを抽出し,新たなメソッドとして定義する. • 抽出箇所は,そのメソッドの呼び出し文に変更する.

  8. コードクローンに対するリファクタリング兄弟クラス間で重複コードを共有している場合コードクローンに対するリファクタリング兄弟クラス間で重複コードを共有している場合 • 各クラスに対して Extract Method を行い,次に Pull Up Field や Pull Up Method を適用すれば解決する • 場合によっては,Form Template Method などを適用する場合もありうる

  9. コードクローンに対するリファクタリングまったく関係のないクラス間で重複コードが見られる場合コードクローンに対するリファクタリングまったく関係のないクラス間で重複コードが見られる場合 • Extract Class を適用し,古いクラスから新しいクラスへと処理を委譲するように変更を行う

  10. 本研究で扱うコードクローン • コードクローンの一般的な定義はない • これまでにいくつかのコードクローン検出手法が提案されているが,それらはどれも異なった定義を持つ • リファクタリング可能なコードクローンを検出したい • コードクローン検出に CCFinder[3]と CCShaper[4]を用いる [3] T. Kamiya, S. Kusumoto, and K. Inoue, CCFinder: A multi-linguistic token-based code clone detection system for large scale source code, IEEE Transactions on Software Engineering, vol. 28, no. 7, pp. 654-670, Jul. 2002. [4] 肥後 芳樹,植田 泰士,神谷 年洋,楠本 真二,井上 克郎:コードクローン解析に基づくリファクタリングの試み,情報処理学会論文誌, Vol.45, No.5, pp1357-1366, 2004-5

  11. C1 C4 C2 C5 C3 クローンペアとクローンセット • クローンペア • 互いに一致または類似しているコード片の対(ペア) • クローンセット • 互いに一致または類似しているコード片の集合(セット)

  12. コードクローン検出ツール:CCFinder概要 • ソースコードをトークン単位で直接比較することにより,コードクローンを検出する • より実用的なコードクローンを見つけることができるように設計されている • ユーザ定義名の置き換え • テーブル初期化部分の取り除き • モジュールの区切りの認識 • 検出結果はテキスト形式で出力 • 数百万行単位のソフトウェアからでも数十分で検出可能

  13. コードクローン抽出ツール:CCShaper概要 • CCFinderが検出したコードクローンに含まれるプログラミング言語の構造的なまとまりの部分を抽出 • コードクローン情報をリファクタリングに用いる場合に便利 • CCFinderが検出するコードクローンはトークンの並びであり,必ずしもリファクタリングの適用が容易なまとまりにはなっていない • 現在はJava言語のみに対応 • 抽出するコードクローンの単位は以下の12個 • 宣言:クラス,インターフェース • 関数:メソッド,コンストラクタ,スタティックイニシャライザ • 文:do, for, if, switch, synchronized, try, while,

  14. コード片1 CCFinderが検出するクローン CCShaperが抽出するクローン 609: reset(); 610: grammar = g; 611: // Lookup make-switch threshold in the grammar generic options 612: if (grammar.hasOption("codeGenMakeSwitchThreshold")) { 613: try { 614: makeSwitchThreshold = grammar.getIntegerOption("codeGenMakeSwitchThreshold"); 615: //System.out.println("setting codeGenMakeSwitchThreshold to " + makeSwitchThreshold); 616: } catch (NumberFormatException e) { 617: tool.error( 618: "option 'codeGenMakeSwitchThreshold' must be an integer", 619: grammar.getClassName(), 620: grammar.getOption("codeGenMakeSwitchThreshold").getLine() 621: ); 622: } 623: } 624: 625: // Lookup bitset-test threshold in the grammar generic options 626: if (grammar.hasOption("codeGenBitsetTestThreshold")) { 627: try { 628: bitsetTestThreshold = grammar.getIntegerOption("codeGenBitsetTestThreshold"); コード片2 623: } 624: 625: // Lookup bitset-test threshold in the grammar generic options 626: if (grammar.hasOption("codeGenBitsetTestThreshold")) { 627: try { 628: bitsetTestThreshold = grammar.getIntegerOption("codeGenBitsetTestThreshold"); 629: //System.out.println("setting codeGenBitsetTestThreshold to " + bitsetTestThreshold); 630: } catch (NumberFormatException e) { 631: tool.error( 632: "option 'codeGenBitsetTestThreshold' must be an integer", 633: grammar.getClassName(), 634: grammar.getOption("codeGenBitsetTestThreshold").getLine() 635: ); 636: } 637: } 638: 639: // Lookup debug code-gen in the grammar generic options 640: if (grammar.hasOption("codeGenDebug")) { 641: Token t = grammar.getOption("codeGenDebug"); 642: if (t.getText().equals("true")) {

  15. コード片3 コード片4 1007: if ( inputState.guessing==0 ) { 1008: buf.append(a.getText()); 1009: } 1010: { 1011: _loop144: 1012: do { 1013: if ((LA(1)==WILDCARD)) { 1014: match(WILDCARD); 1015: a=id(); 1016: if ( inputState.guessing==0 ) { 1017: buf.append('.'); buf.append(a.getText()); 1018: } 1019: } 1527: if ( inputState.guessing==0 ) { 1528: t=a.getText(); 1529: } 1530: { 1531: _loop84: 1532: do { 1533: if ((LA(1)==COMMA)) { 1534: match(COMMA); 1535: id(); 1536: if ( inputState.guessing==0 ) { 1537: t+=","+b.getText(); 1538: } 1539: } CCFinderが検出するクローンペア

  16. 実験:概要 • 実験対象はオープンソースソフトウェア Ant • 記述言語: Java • 15バージョンのソースコードを用いた • バージョン番号:1.1, 1.2, 1.3, 1.4, 1.5, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.6.0, 1.6.1, 1.6.2, 1.6.3, 1.6.4, 1.6.5 • 最も古いバージョン(1.1)からコードクローン検出を行い,検出された全てのコードクローンに対してリファクタリングを試みた • リファクタリングのコストとして以下のものを用いた • ソースコード修正と回帰テストに要した時間 • リファクタリングの効果として以下のものを用いた • CKメトリクス値の変化 • 総行数の増減 • 集約箇所の後のバージョンでの修正回数

  17. 実験:バージョン1.1に対するリファクタリング実験:バージョン1.1に対するリファクタリング • コードクローンのリファクタリングを行うために • CCFinderとCCShaperを用いて50トークン以上のコードクローンを検出 • バージョン1.1の規模 • ソースファイル数:83 • 総行数:19,986 • 検出時間は約1分 • 7個のクローンセットを検出 • そのうちの6つが実際にリファクタリングすることができた • ソースコード修正後,付属のテストケースを用いて回帰テストを行った • その後の修正回数を調査するために • バージョン1.2~1.6.5の該当箇所を調査 • 前のバージョンから修正を加わっているかどうかを diff で調査 • クローンセットになっているかを CCFinder と CCShaper を用いて調査

  18. 実験:検出されたコードクローン • 以下は検出されたコードクローンの概要である • 実際はもっと回帰テストに時間がかかる • 付属のユニットテストしか行っていない,結合テストなども行うべき

  19. リファクタリング前に比べてメトリクス値が悪化リファクタリング前に比べてメトリクス値が悪化 リファクタリング前に比べてメトリクス値が改善 実験:CKメトリクス値の変化(1/3) • S0のCKメトリクス値の変化 • S1のCKメトリクス値の変化 • S3のCKメトリクス値の変化

  20. 実験:CKメトリクス値の変化(2/3) • S4のCKメトリクス値の変化 • S5のCKメトリクス値の変化 • S6のCKメトリクス値の変化

  21. 実験:CKメトリクス値の変化(3/3) • コードクローンのリファクタリングは,必ずしもCKメトリクス値を改善するとは限らない • 中には,クローンセット S0や S1のようにメトリクス値を悪化させているだけのリファクタリングも存在した • CKメトリクスを測定するために ckjm を用いた • このツールはメトリクス WMC として,単純にそのクラスで定義されたメソッドの数を用いている • WMCは Weigthed Method per Class の略 • 本来は,各メソッドに対して重み付けを行った後,値を足し合わせる • Extract Method のようにメソッド数が増えるリファクタリングの場合は,必ず値が悪化してしまう • サイクロマチック数などを用いて適切に重み付けをすれば,メソッド数を増やすリファクタリングであっても WMC の改善があるかもしれない

  22. 実験:総行数の変化 • 下図は各クローンセットのリファクタリング後の総行数の変化を表す • 5つのリファクタリングで総行数が減っている • 多くても20行程度しか減っておらず,サイズ面で効果があるとはいい難い • “Extract Class”パターンを適用したリファクタリングは,新しいクラスを作成したために,リファクタリング前に比べて行数が増えてしまっている

  23. コード片は Cnで表され,修正が加わるたびに Cn’, Cn’’ と変化していく 同一クローンセットに含まれるコード片は長方形で囲まれる 実験:修正の流れ(1/5)(クローンセット S0,S2) • クローンセット S0 • クローンセット S2

  24. 実験:修正の流れ(2/5)(クローンセット S1)

  25. 実験:修正の流れ(3/5)(クローンセット S3,S4) • クローンセット S3 • クローンセット S4

  26. 実験:修正の流れ(4/5)(クローンセット S5,S6) • クローンセット S5 • クローンセット S6

  27. 実験:修正の流れ(5/5) • リファクタリング箇所の後のバージョンでの修正回数はクローンセットによってさまざまであった • 全てのクローンセットがバージョンをまたいで存在しているわけではなく,各コード片に対して異なる修正が加わった結果,コードクローンでなくなる場合もあった. • 本実験では7つクローンセットのうちの4つ • これらのクローンセットには一度も共通の修正がなかった • 一度でも共通の修正が加わった場合は,その後も修正される傾向がある • 実際に全てのコード片に同様の修正が加わった場合にリファクタリングを行うことで,その後の修正回数を減らすことができるのでは?

  28. 考察 • リファクタリングが有効であるかどうかは,その効果がコストを上回っているかどうかで決まる • 例:クローンセット S0 • コスト:ソースコード修正と回帰テストに7分を要した • 効果(CKメトリクス):WMC,RFC,LCOM が悪化 • 効果(修正回数):バージョン 1.5 と 1.6.3 での修正箇所が2つから1つに減った • 効果とコストの大小関係は一意に決めることができない • 本実験で行ったリファクタリングは,基本的かつ汎用的であり,ソースコード修正を行う者のスキルや対象ソフトウェアのドメインを限定するものではない • リミテーション • リファクタリングの規模が小さい • メトリクス値で調査したのは複雑度の変化のみ • 他の面からの評価も必要である

  29. まとめ • コードクローンに対するリファクタリングのコストと効果を調査した • 実験対象は15バージョンのソースコードが公開されているオープンソースソフトウェア • その最初のバージョンに対してリファクタリングを行った • コストの尺度 • ソースコード修正と回帰テストに要した時間 • 効果の尺度 • CKメトリクス値の変化 • 総行数の増減 • 集約箇所の後のバージョンでの修正回数 • こらからの予定 • コスト・効果項目を増やす • 企業ソフトウェアでも実験を行う(+ 開発者へのインタビュー)

  30. Source files 字句解析 トークン列 変換処理 変換後トークン列 検出処理 クローン情報 出力整形処理 クローンペア位置情報 1. static void foo() throws RESyntaxException { 2. String a[] = new String [] { "123,400", "abc", "orange 100" }; 3. org.apache.regexp.RE pat = new org.apache.regexp.RE("[0-9,]+"); 4. int sum = 0; 5. for (int i = 0; i < a.length; ++i) 6. if (pat.match(a[i])) 7. sum += Sample.parseNumber(pat.getParen(0)); 8. System.out.println("sum = " + sum); 9. } 10. static void goo(String [] a) throws RESyntaxException { 11. RE exp = new RE("[0-9,]+"); 12. int sum = 0; 13. for (int i = 0; i < a.length; ++i) 14. if (exp.match(a[i])) 15. sum += parseNumber(exp.getParen(0)); 16. System.out.println("sum = " + sum); 17. } 字句解析 字句解析 字句解析 トークン列 トークン列 トークン列 変換処理 変換処理 変換処理 変換後トークン列 変換後トークン列 変換後トークン列 検出処理 検出処理 検出処理 クローン情報 クローン情報 クローン情報 出力整形処理 出力整形処理 出力整形処理 コードクローン検出ツール: CCFinder検出プロセス 1. static void foo() throws RESyntaxException { 2. String a[] = new String [] { "123,400", "abc", "orange 100" }; 3. org.apache.regexp.RE pat = new org.apache.regexp.RE("[0-9,]+"); 4. int sum = 0; 5. for (int i = 0; i < a.length; ++i) 6. if (pat.match(a[i])) 7. sum += Sample.parseNumber(pat.getParen(0)); 8. System.out.println("sum = " + sum); 9. } 10. static void goo(String [] a) throws RESyntaxException { 11. RE exp = new RE("[0-9,]+"); 12. int sum = 0; 13. for (int i = 0; i < a.length; ++i) 14. if (exp.match(a[i])) 15. sum += parseNumber(exp.getParen(0)); 16. System.out.println("sum = " + sum); 17. }

  31. リファクタリングできなかったクローンセット S2 • コード片 C0 • コード片 C1 protected void fireTargetStarted(Target target) { BuildEvent event = new BuildEvent(target); for (int i = 0; i < listeners.size(); i++) { BuildListener listener = (BuildListener) listeners.elementAt(i); listener.targetStarted(event); } } protected void fireTaskStarted(Task task) { BuildEvent event = new BuildEvent(task); for (int i = 0; i < listeners.size(); i++) { BuildListener listener = (BuildListener) listeners.elementAt(i); listener.taskStarted(event); } }

More Related