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

Slides:



Advertisements
Similar presentations
Software Engineering Laboratory, Department of Computer Science, Graduate School of Information Science and Technology, Osaka University 保守支援を目的とした コードクローン情報検索ツール.
Advertisements

背景 ソフトウェアの大規模化・複雑化 生産性と品質の向上 ↓ オブジェクト指向分析設計の適用 開発ツールの投入.
AGMアルゴリズムを用いた ギャップを含むコードクローン情報の生成
剽窃 他人の作品や論文を盗んで,自分のものとして発表すること. プログラムが剽窃される事例もある. Aさんのプログラム Xさんのプログラム
リファクタリングのための 変更波及解析を利用した テスト支援ツールの提案
プログラム変更支援を目的とした コードクローン情報付加手法の提案と実装
川口真司 松下誠 井上克郎 大阪大学大学院情報科学研究科
CKメトリクスを用いてリファクタリングの 効果を予測する手法の提案
プログラム実行履歴を用いたトランザクションファンクション抽出手法
アイテムセットマイニングを利用した コードクローン分析作業の効率向上
大規模ソースコード集合を対象とした 類似関数集合群の抽出
コードクローン検出技術と その利用法 神谷年洋‡, 植田泰士†, 肥後芳樹†, 楠本真二†, 井上克郎†
ソースコードの変更履歴における メトリクス値の変化を用いた ソフトウェアの特性分析
コードクローン分析ツールGeminiを用いたコードクローン分析手順
コードクローンの分布情報を用いた特徴抽出手法の提案
オブジェクト指向プログラムのための 動的結合メトリクスの評価
ギャップを含むコードクローンの フィルタリング手法の提案
ソースコードの同時修正支援における関数クローン検出ツールの有効性調査
コードクローンに含まれるメソッド呼び出しの 変更度合の分析
コードクローンに含まれるメソッド呼び出しの 変更度合の調査
ソードコードの編集に基づいた コードクローンの分類とその分析システム
コードクローンの集約によるリファクタリング支援システムの提案と実装
ネットワーク共生環境を築く情報技術の創出 テーマ4:高信頼性・高安全性を有するネットワーク共生環境の構築技術の創出
オブジェクト指向プログラミング言語に対応した コードクローン検出技法の提案と実験
コードクローンの分類に基づいた メソッド引き上げ手順の提案とその有効性評価
オブジェクト指向メトリクスを用いた 開発支援法に関する研究
CKメトリクスに基づくリファクタリングの 効果予測手法の提案と実装
ソフトウェアを取り巻く環境の変化がメトリクスに及ぼす影響について
クローンセットに対する主要編集者の分析法の提案と調査
重複コードと非重複コードにおける 修正頻度の比較
クローン検出ツールを用いた ソースコード分析ツールの試作
コードクローン検出ツールを用いた ソースコード分析システムの試作と プログラミング演習への適用
リファクタリング支援のための コードクローンに含まれる識別子の対応関係分析
ソースコードの特徴量を用いた機械学習による メソッド抽出リファクタリング推薦手法
コードクローン統合分析ツール ICCA 肥後 芳樹† ,吉田 則裕† ,神谷 年洋‡,楠本 真二† ,井上 克郎†
コードクローンの動作を比較するためのコードクローン周辺コードの解析
コードクローンに対する一貫性のない変更に起因する欠陥の検出
Token Comparison Approach to Detect Code Clone-related Bugs
柔軟に変更可能な字句解析機構を持つ コードクローン検出ツールの開発
UMLモデルを対象とした リファクタリング候補検出の試み
プログラム実行履歴を用いたコードクローン検出手法
コードクローン検出に基づくデザイン パターン適用支援手法の提案と実現
コードスメルの深刻度がリファクタリングの実施に与える影響の実証的研究
コードクローン編集者数に着目した開発履歴の分析
コード片に共通した特性を自動抽出する ソースコード閲覧ツールの試作
Geminiを用いた効果的なコードクローン分析方法
○ 後藤 祥1,吉田 則裕2 ,井岡 正和1 ,井上 克郎1 1大阪大学 2奈良先端科学技術大学院大学
ソフトウェア保守のための コードクローン情報検索ツール
コードクローンの理解支援を目的としたコードクローン周辺コードの解析
コードクローン分類の詳細化に基づく 集約パターンの提案と評価
コーディングパターンの あいまい検索の提案と実装
コードクローン間の依存関係に基づく リファクタリング支援環境の実装
コードクローンの分布情報を用いた特徴抽出手法の提案
プログラムスライスを用いた凝集度メトリクスに基づく 類似メソッド集約候補の順位付け手法
大阪大学 大学院情報科学研究科 コンピュータサイエンス専攻 井上研究室
保守請負時を対象とした 労力見積のためのメトリクスの提案
コードクローン間の依存関係に基づく リファクタリング支援手法の提案と実現
クローン検出ツールを用いた ソフトウェアシステムの類似度調査
オープンソースソフトウェアに対する コーディングパターン分析の適用
メソッドの同時更新履歴を用いたクラスの機能別分類法
コードクローン間の依存関係に基づく リファクタリング支援手法の提案と実現
欠陥検出を目的とした類似コード検索法 吉田則裕,石尾隆,松下誠,井上克郎 大阪大学 大学院情報科学研究科
容易に使用可能な grep風コードクローン検索ツール
オブジェクト指向開発における フォールト発生早期予測手法の 一提案
コードクローン解析に基づく デザインパターン適用候補の検出手法
回帰テストにおける実行系列の差分の効率的な検出手法
メソッド抽出リファクタリングが 行われるメソッドの特徴調査
Geminiを用いた効果的なコードクローン分析方法
オブジェクト指向メトリクスを用いた 開発支援に関する研究 --- VC++とMFCを用いた開発を対象として ---
Presentation transcript:

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

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

リファクタリング ソフトウェアの外部的な振る舞いを保ったままで,内部構造を改善していくこと リファクタリングを行うことでソフトウェアの保守性が増し,後の保守作業効率が向上する これまでに多くのリファクタリングすべきコードのパターン(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/

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

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).

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

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

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

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

本研究で扱うコードクローン コードクローンの一般的な定義はない リファクタリング可能なコードクローンを検出したい これまでにいくつかのコードクローン検出手法が提案されているが,それらはどれも異なった定義を持つ リファクタリング可能なコードクローンを検出したい コードクローン検出に 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

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

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

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

コード片1 CCFinderが検出するクローン コード片2 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"); CCShaperが抽出するクローン CCFinderが検出するクローン コード片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")) { 次に提案手法での抽出の例を示します. このようなソースコードが与えられた場合,CCFinderはこのハイライトがかかった部分をクローンとして検出します. 提案手法ではこのような場合,このクローン内に存在する最大の構造的なまとまりであるif文の部分を抽出します.

コード片3 コード片4 CCFinderが検出するクローンペア もう1つ例を示します. 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: } 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: } コード片3 コード片4 CCFinderが検出するクローンペア もう1つ例を示します. このようなソースコードが与えられた場合CCFinderはこの部分をクローンをして検出します. しかし,このクローン内には特に構造的なまとまりは存在しません. 提案手法ではこの様な場合を識別し,何も抽出しません.

実験: 概要 実験対象はオープンソースソフトウェア 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メトリクス値の変化 総行数の増減 集約箇所の後のバージョンでの修正回数

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

実験: 検出されたコードクローン 以下は検出されたコードクローンの概要である 実際はもっと回帰テストに時間がかかる ID コード片数 単位 付属のユニットテストしか行っていない,結合テストなども行うべき ID コード片数 単位 分類 時間 S0 2 メソッド 単一クラス内 7分 S1 6 19分 S2 - S3 15分 S4 If文 兄弟クラス間 14分 S5 5分 S6 関係のないクラス間 17分

実験: CKメトリクス値の変化(1/3) S0のCKメトリクス値の変化 S1のCKメトリクス値の変化 S3のCKメトリクス値の変化 クラス名 WMC DIT NOC CBO RFC LCOM DirectoryScanner 21(+1) 1 47(+1) 158(+20) クラス名 WMC DIT NOC CBO RFC LCOM DirectoryScanner 21(+1) 1 47(+1) 158(+20) リファクタリング前に比べてメトリクス値が悪化 リファクタリング前に比べてメトリクス値が改善 クラス名 WMC DIT NOC CBO RFC LCOM Task 17(+1) 1 23 4 24(+6) 98(+16) $NestedElementHandler 2 14(-3) $TaskHandler 7(-2) 21(-7)

実験: CKメトリクス値の変化(2/3) S4のCKメトリクス値の変化 S5のCKメトリクス値の変化 S6のCKメトリクス値の変化 クラス名 WMC DIT NOC CBO RFC LCOM ProjectHelper 15(+2) 1 7(+1) 66(+7) 79(+27) Expand 4 2 5 35(-3) Untar 8 36(-3) クラス名 WMC DIT NOC CBO RFC LCOM Javac 23(-1) 3 1 11 93 128(-105) MatchingTask 16(+1) 2 5(+1) 56(+4) 94(+79) Rmic 17(-1) 8 66(-2) 98(-17) クラス名 WMC DIT NOC CBO RFC LCOM NewClass 2 1 7 JavacOutputStream 4 2(+1) 10(-1) TaskOutputStream 3 8(-1)

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

実験: 総行数の変化 下図は各クローンセットのリファクタリング後の総行数の変化を表す 5つのリファクタリングで総行数が減っている 多くても20行程度しか減っておらず,サイズ面で効果があるとはいい難い “Extract Class”パターンを適用したリファクタリングは,新しいクラスを作成したために,リファクタリング前に比べて行数が増えてしまっている リファクタリング S0後 S1後 S3後 S4後 S5後 S6後 行数 19,982 (-4) 19,964 (-22) 19,981 (-5) 19981 19,962 (-24) 19,990 (+4)

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

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

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

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

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

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

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

コードクローン検出ツール: CCFinder 検出プロセス 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. } 字句解析 変換処理 トークン列 検出処理 変換後トークン列 クローン情報 出力整形処理 字句解析 変換処理 トークン列 検出処理 変換後トークン列 クローン情報 出力整形処理 字句解析 変換処理 トークン列 検出処理 変換後トークン列 クローン情報 出力整形処理 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. }

リファクタリングできなかったクローンセット 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); }