コードクローン分析ツールGeminiを用いたコードクローン分析手順 大阪大学 大学院情報科学研究科 肥後 芳樹(y-higo@ist.osaka-u.ac.jp) 楠本 真二(kusumoto@ist.osaka-u.ac.jp) 井上 克郎(inoue@ist.osaka-u.ac.jp)
背景 コードクローンとは ソフトウェアの保守を困難にする ソースコード中に存在する一致または類似したコード片 コピーアンドペーストなどのさまざまな理由により生成される ソフトウェアの保守を困難にする あるコード片にバグがあると,そのコードクローン全てについて修正の検討を行う必要がある 機能を追加する場合も同様のことがいえる copy-and-paste copy-and-paste
コードクローンに対するこれまでの取り組み コードクローンに対する支援ツールの開発 検出ツール: CCFinder[1] 分析ツール: Gemini[2] CCFinder/Geminiパッケージとして国内外の個人・組織に配布 研究機関での利用 企業での試用・商用ソフトウェアの開発プロセスへの導入 配布先からのフィードバック バグ報告,新機能の提案 分析手順に関する問い合わせ Geminiを用いたコードクローン分析方法を提案する [1] 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, 28(7):654-670, 2002. [2] Y. Ueda, T. Kamiya, S. Kusumoto and K. Inoue, “Gemini: Maintenance Support Environment Based on Code Clone Analysis”, Proc. Of the 8th IEEE International Symposium on Software Metrics, 67-76, 2002.
コードクローンを用いた分析の目的 設計とコードの一貫性確認 信頼性の改善 保守性の改善 設計情報で重複している部分はソースコードでも重複しており,それ以外の部分には重複をしている部分がないかを確認する 信頼性の改善 長期間保守をされているシステムのソースコードには多くのコードクローンが存在する可能性が高い 新たな保守作業の際に,保守対象のコード片に対するコードクローンを調査することで,変更漏れを防ぐことが可能となる 保守性の改善 複数のバージョンのソースコードが与えられたときに,バージョン間でのコードクローンの遷移を調査し,保守作業の効率化を計る 繰り返し修正が加えられているコードクローンは集約する 安定しているコードクローンにはリファクタリングは不要
クローンペアとクローンセット クローンペア: 互いに一致・類似しているコード片の対 クローンセット: 互いに一致・類似しているコード片の集合 (C1, C4) {C1, C4, C5} (C1, C5) {C2, C3} (C2, C3) (C4, C5) C1 C2 C3 C4 C5
コードクローン検出ツール CCFinder 概要: ソースコードをトークン単位で直接比較することによりコードクローンを検出 名前空間の正規化 ユーザ定義名の置き換え テーブル初期化部分の取り除き モジュールの区切りの認識 解析結果はテキスト形式で出力 数百万行規模でも実用的な時間で解析可能
コードクローン検出ツール 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. }
コードクローン分析ツール Gemini 概要: CCFinderの解析結果(テキストファイル)を読み込み,コードクローン情報を視覚的に表示 クローン散布図(スキャタープロット図) メトリクスグラフ 重要でないコードクローンのフィルタリング CCFinderの解析結果はテキストファイルなので,このまま分析に用いるのは効率的ではない. Geminiを用いて分析をする. GeminiはCCFinderの解析結果ファイルを読み込みこれらの図を用いてコードクローンを視覚的に表示
コードクローン分析ツール Gemini クローン散布図: 水平・垂直方向にソースコード中のトークンを出現順に配置 原点は左上隅 はその水平方向のトークンと垂直方向のトークンが等しいことを意味する 常に対角線が引かれる クローンペアは線分として出現する 対角線に対して線対称となる a b c a b c a d e c a b c a b c a d e c a, b, c, ... : tokens : matched position
コードクローン分析ツール Gemini メトリクスグラフ(概要) メトリクスを用いてクローンセットを定量的に特徴づける 多次元並行座標表現を用いている 各メトリクスにつき1つの座標軸が存在する 各クローンセットにつき1つの折れ線がメトリクス値に基づいて描画される ユーザは各メトリクスの上限・下限を変更することでクローンセットの選択・選択解除を行う 全てのメトリクスが上限と下限の間にあるクローンセットが選択状態になる 選択状態にあるクローンセットのソースコードは簡単に閲覧可能 S1 S1 S2 S2 RAD LEN RNR POP NIF RAD LEN RNR POP NIF
コードクローン分析ツール Gemini メトリクスグラフ(用いているメトリクス): LEN(S): クローンセット S 内に含まれるコード片のトークン数の平均値を表す POP(S): S に含まれるコード片の数を表す RAD(S): S 内のコード片がファイルシステム上でどの程度分散しているかを表す NIF(S): S に含まれるコード片を所有しているファイルの数を表す RNR(S): 次項で説明
コードクローン分析ツール Gemini 重要でないコードクローンのフィルタリング: CCFinderの検出するコードクローンはトークンの列であり,重要でないコードクローンを多数検出してしまう switch文の各caseエントリ 連続したimport文, printf文, scanf文 など フィルタリングメトリクス RNR(S) クローンセット S に含まれるコード片の非繰り返し度を表す 例 トークン列 <x a b c a b c* a* b* c* y> CCFinder は以下の2つのコード片をコードクローンとして検出 x a b c a b c*<F1> a* b* c* y x a b c a b c* a* b* c*<F2> y F1はコード片の長さが6トークン,そのうち5トークンが非繰り返し F2はコード片の長さが6トークン,そのうち2トークンが非繰り返し RNR(S1) = (5 + 2)/(6 + 6) = 7/12 = 0.583
各ビューの特徴 クローン散布図: コードクローンの分布状態を俯瞰的に知ることができる 以下の部分が目立ちやすい ある程度の領域内にコードクローンが密集している部分 繰り返し同じパターンが出現している部分 ファイルよりも大きな単位での類似部分を知ることができる ディレクトリ・モジュール単位での類似部分など クローン散布図において目立つ部分は,必ずしもその部分に存在するコードクローンが特徴的であることを示しているわけではない 目立つ部分に存在するコードクローンは一種類ではなく,複数の種類のコードクローンが混在している場合がある 目立ちやすさとコードクローンの位置が関係している 要素数の多いクローンセットであっても,そのコード片が複数のファイル内に散在している場合は,クローン散布図上ではそれらは離れた位置に描画される
各ビューの特徴 メトリクスグラフ: 定量的に特徴的なコードクローンを見つけることができる コードクローンの位置に影響されることはない メトリクスRAD,NIFを用いて,クローンセットに含まれるコード片がファイルシステム上でどの程度散らばっているかを知ることができる RAD(S) はクローンセット S の垂直方向への広がりの程度を表す NIF(S) は S の水平方向への広がりの程度を表す
提案する分析法 概要: Gemini を用いた効果的と思われる分析法を提案する STEP1: 大まかな把握 STEP2A: 要素数の多いクローンセットの特定 STEP2B: トークン数の多いクローンセットの特定 STEP2C: 多くのファイルを巻き込んでいるクローンセットの特定 (STEP2A)~(STEP2C)は順序不同
効果的な分析法 STEP1(大まかな把握): 新規でコードクローン分析を行う場合は,まずクローン散布図を用いてコードクローンの分布状態を大まかに把握するとよい クローン散布図では,マウスカーソル位置の水平・垂直方向のファイルのパスがリアルタイムで表示される 目立つ部分にマウスカーソルを移動させるだけで,その部分がどのファイルなのかを知ることができる 以下の2つの部分が目立ちやすい部分である 一定の領域内にコードクローンが密集している部分 同じようなパターンが繰り返し出現している部分 メトリクス RNR の値が閾値未満のコードクローンは青色,以上のコードクローンは黒色で描画 閾値はユーザが自由に設定可能
提案する分析法 STEP2A(要素数の多いクローンセット): 要素が多いということは,その機能がソフトウェアの多くの箇所で実装されていることを表している ソフトウェアの象徴的な処理部分であるとも考えられる その部分にバグが検出された場合,多くの箇所に同様の修正を加えなければならない リファクタリングの対象とすべき? 要素数の多いクローンセットの特定にはメトリクスグラフを用いる 要素数を表すメトリクスは POP メトリクス RNR も同時に用いた方が望ましい 繰り返し部分は要素数の多いクローンセットになりがち
提案する分析法 STEP2B(トークン数の多いクローンセット): トークン数の多いコードクローンはコピーアンドペーストにより生成されたものではないかと思われる ペースト後の変数名やメソッド名の修正漏れがバグに繋がる 修正漏れのチェックを行うのは効果的な予防保守 トークン数の多いクローンセットの特定にはメトリクスグラフを用いる トークン数を表すメトリクスはLEN メトリクス RNR も同時に用いた方が望ましい if文やcase文が数十回繰り返し存在する場合がある
提案する分析法 STEP2C(多くのファイルを巻き込んでいるクローンセット): 根本的な問題を表している可能性がある 設計が悪いことを暗示 プログラミング言語に適切な抽象化機構が存在しない(横断的関心事) 多くのファイルを巻き込んでいるクローンセットの特定にはメトリクスグラフを用いる 巻き込んでいるファイル数を表すメトリクスは NIF メトリクス RNR も同時に用いたほうが望ましい Switch文の連続したCaseエントリが繰り返しの多いクローンとなってしまう
適用実験 目的 オープンソースソフトウェア Ant (version 1.6.0) 検出対象: 30トークン以上 提案した分析手法によってどのようなコードクローンが見つかるかを調査する オープンソースソフトウェア Ant (version 1.6.0) ビルドツールの一種,Java言語で記述されている ソースファイル数: 627 総行数: 約18万行 検出対象: 30トークン以上 2,406個のクローンセット 190,004個のクローンペア
適用実験 STEP1: 大まかな把握(全体) 右図は対象ソースコード全体を表したクローン散布図 A A, B, Cの部分がどのようなコードクローンであるかを調査した A B C
適用実験 STEP1:大まかな把握(Aの部分) クローンの場所: ファイルを読み込む機能を実装している部分 先頭の数行のみを読み込み ユーザが指定した文字列を含む行のみを読み込み 各行にプレフィックスを付けて読み込み クローンが実装している機能: ストリームから1文字読み込む.終端まできたら,それに応じた処理をする 新しく java.io.Reader オブジェクトを生成し,それを返す
適用実験 STEP1:大まかな把握(Bの部分) クローンの場所: 簡単なGUIを実装しているファイル ビルド情報をAntに渡す Antの処理状況の閲覧 クローンが実装している機能: イベントがどこで起こったかを判定している if文 イベントのソースに応じて処理を変更 GUIの部品を作成しているメソッド 一つの部品につき,一つのメソッドが存在
適用実験 STEP1:大まかな把握(Cの部分) クローンの場所: ClearCaseの各機能を実装しているファイル Checkin, Checkout, Update, ファイルの特定の部分ではなく,ほぼ全体がクローンになっていた
適用実験 STEP2A:要素数の多いクローンセット 予めRNRを用いて,その値が0.5未満のクローンセットは除外 最も要素数の多いクローンセット 要素数:31個 クローンの場所: 簡単なGUIを実装しているファイル クローンが実装している機能:GUIの部品を生成しているメソッド 大まかな把握(Bの部分)のクローンの一部 } catch (Throwable iExc) { handleException(iExc); } return iAboutCommandPanel; private Label getAboutContactLabel() { if (iAboutContactLabel == null) { try { iAboutContactLabel = new Label(); iAboutContactLabel.setName("AboutContactLabel"); 二位:27,四位:19,五位:19,同様のクローン 三位:22は連続したアクセサのクローン
適用実験 STEP2B:トークン数の多いクローンセット 予めRNRを用いて,その値が0.5未満のクローンセットは除外 最もトークン数の多いクローンセット クローンの大きさ:282トークン(77行) クローンの場所:WebLogicとWebShereのタスクを定義しているファイル クローンが実装している機能:メソッド isRebuildRequired(引数で与えられたJarファイルがリビルドする必要があるかどうかを判断) 一部の使用変数,メソッド名が異なる インデント,空行,コメントなど他のコードスタイルが全く同じ コピーアンドペーストによる作成を示唆 二位:RExecTaskとTelnetタスクに定義されている連続したメソッドの定義,これらは同じクラスを継承(226). 三位:CCCheckout, CCMklabel間のクローン(217).
適用実験 STEP2C:多くのファイルを巻き込んでいるクローンセット 予めRNRを用いて,その値が0.5未満のクローンセットは除外 最も多くのファイルを巻き込んでいるクローンセット 巻き込んでいるファイル数:19ファイル クローンの場所:さまざまなファイル クローンが実装している機能:連続したアクセサ Antだからではなく,Java言語で記述されているから存在しているクローン このクローンセットに限らず,多くのファイルを巻き込んでいるクローンセットの多くが,Java言語で記述されていることがその存在理由と思われた
まとめ Geminiのビューの特徴をまとめた 効果的と思われる分析方法の提案を行った オープンソースのソフトウェアの対して実験を行った クローン散布図 メトリクスグラフ 効果的と思われる分析方法の提案を行った オープンソースのソフトウェアの対して実験を行った コピーアンドペーストによる生成と思われるもの,プログラミング言語に依存したものなど,さまざまなコードクローンが検出された
メトリクスRNR(S)について トークン列 <x a b c a b c a b c y> を考える 前者の方がどちらかというと適切であると思われるから 理論的な裏づけがあるわけではない 繰り返し部分をコードクローンとして検出する場合 1つ目のクローンコードはできるだけ非繰り返しとしたい 2つ目以降のクローンコードはできるだけ繰り返しとしたい
メトリクスRNR(S)について [a, b, c]がクローンコードである場合 <x a b c a b c* a* b* c* y>は <x a b c a b c* a* b* c* y> (非繰り返し度 = 3/3) <x a b c a b c* a* b* c* y> (非繰り返し度 = 1/3) <x a b c a b c* a* b* c* y> (非繰り返し度 = 0/3) <x a b c a* b* c* a* b* c* y>は <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 3/3) <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 0/3)
メトリクスRNR(S)について [b, c, a]がクローンコードである場合 <x a b c a b c* a* b* c* y>は <x a b c a b c* a* b* c* y> (非繰り返し度 = 3/3) <x a b c a b c* a* b* c* y> (非繰り返し度 = 1/3) <x a b c a* b* c* a* b* c* y>は <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 2/3) <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 0/3)
メトリクスRNR(S)について [a, b, c, a, b, c]がクローンコードである場合 <x a b c a b c* a* b* c* y>は <x a b c a b c* a* b* c* y> (非繰り返し度 = 5/6) <x a b c a b c* a* b* c* y> (非繰り返し度 = 2/6) <x a b c a* b* c* a* b* c* y>は <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 3/6) <x a b c a* b* c* a* b* c* y> (非繰り返し度 = 0/6)