コードクローンの分布情報を用いた特徴抽出手法の提案 井上研究室 服部 剛之 コードクローンの分布情報を用いた特徴抽出手法の提案というタイトルで, 井上研究室の服部が発表を行わせていただきます.
コードクローン ソースコード中に存在する同一,もしくは類似したコード片を同一システム中に持つコード片 (以降単にクローンと呼ぶ) コピーアンドペースト等により生成される ソフトウェア保守を困難にする コードの変更が必要な場合 バグ修正,機能追加 保守作業の手間が増大 まず,始めにコードクローンとは,ソースコード中に存在する同一, もしくは類似したコード片を同一システム中に持つコード片のことを 指します.以降,単にクローンと呼びます. クローンはコピーアンドペーストなどによって生成されます. この図は,2つのファイル中にクローンが存在している例で, 色のついた部分はそれぞれお互いにクローンになっている ことを表しています. このように互いにクローンになっているのをまとめて1つの クローンセットと呼びます. 例えば,クローンが含まれている箇所に バグ修正や機能追加のようなコード変更を行う場合, すべてのクローンにおいてコード変更の検討を行うことが 必要になってきます. このようにクローンが存在すると保守作業の手間が増大します. ゆえに,クローンはソフトウェア保守を困難にする原因となっています. クローンセット
CCFinder,Gemini クローンを対象とした保守支援ツール 国内外の個人・組織に配布 検出ツール: CCFinder[1] 研究機関での利用 企業での商用ソフトウェア開発プロセスへの導入 配布先からのフィードバックを得ている 我々の研究室では,クローンを対象とした保守支援ツールとして, 検出ツールのCCFinder,分析ツールの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.
フィードバックから得られた情報 クローン情報の利用法 問題点 クローンの特徴を知る必要性 リファクタリング支援 設計情報との一貫性確認 互いにクローンになっているものを1つにまとめる 設計情報との一貫性確認 設計上説明できるクローンであるか 問題点 検出されたクローンの数が多すぎてどれをみるべきかわからない 調査の関心がないクローン(定型処理など)が含まれていて,どのクローンから見ていけばいいかわからない クローンの特徴を知る必要性 フィードバックによって,クローン情報が リファクタリング支援や,設計情報との 一貫性の確認に用いられていることが わかりました. また,クローン情報を利用する上での問題点として 次の2点がわかりました. 1点目は,検出されたクローンの数が多すぎて どれをみるべきかわからない,というものです. 2点目は,調査の関心がないクローン,例えば 定型処理のようなクローンが含まれているために, どのクローンから見ていけばいいのかわからない, というものです. これらの問題は,クローンの特徴を知ることができれば解決 できると考えられます.
目的とアプローチ 目的: クローン分析の効率化 アプローチ: メトリクスを用いてクローンを分類 クローンを特徴に基づいて分類する 分類を使って,調査の関心がないクローン(定型処理など)をフィルタする アプローチ: メトリクスを用いてクローンを分類 以下のメトリクスを用いる RAD: クローン間の遠さの尺度 NIF: クローンが存在するファイルの数 RNR: クローン内の重複した処理の少なさの度合い 本研究では,クローン分析を効率化するために, クローンを特徴に基づいて分類します. その分類を使って,調査の関心がないクローンを フィルタします. そこで,これらの3つのメトリクスを用いて クローンを分類する手法を提案します. RADというのはクローン間の遠さの尺度, NIFというのはクローンが存在するファイルの数, RNRというのはクローン内の重複した処理の少なさの度合い を表しています. 次にこれらのメトリクスの詳細について説明します.
RAD: クローン間の遠さの尺度 RAD: 与えられたクローンセットの各要素を含むファイルから共通の親ディレクトリまでの距離の最大値 2 1 最大値を表しています. 例えば,左の図では, ファイルAとファイルBのどちらのファイルからも 共通の親ディレクトリまでの距離は1となるので, RADの値は1となります. 次に右の図の場合では, 全体の共通の親ディレクトリは,ここになります. 全体の共通の親ディレクトリまでの距離は2であり, ファイルCからの全体の共通の親ディレクトリまでの 距離は1となっています. よって,RADの値は,全体の共通の親ディレクトリまでの 距離の最大値である2となります. ファイルC ファイルA ファイルB は,クローンを表す ファイルA ファイルB
NIF: クローンが存在するファイルの数 NIF: 与えられたクローンセットの各要素を含むファイルの数 2 3 例:NIF = 2 ファイルC ファイルA 次にNIFというのは与えられたクローンセットの 各要素を含むファイルの数を表します. 例えば,左の図では,2つのファイルにクローンが 含まれているのでNIF = 2となります. 右の図では,3つのファイルにクローンが 含まれているのでNIF = 3となります. ファイルB 2 ファイルA ファイルB 3 は,クローンを表す
RNR: クローン内の重複した処理の 少なさの度合い ∑ Tokensrepeated(C) RNR = 1 - C ∈ S ∑ S: クローンセット C: クローンセットの要素 Tokensall(C) C ∈ S Tokensall(C) : クローンC中の総トークン数 Tokensrepeated(C) : クローンC中の繰り返し部分のトークン数 8トークン 最後に,RNRというのはこのような式で表されます. これは,クローンセットの各要素であるクローン中に 繰り返しでない部分が存在する割合の平均値をとっています. RNRが小さいほど,クローン中に繰り返しが多く存在する ことになります. 次にTokens allとTokens repeatedについて説明します. 例えば,このようなトークン列の場合, 全体で8トークンなので,Tokens all = 8となります. このトークン列の繰り返し部分に着目すると 4トークン分繰り返しが存在するので, Tokens repeated = 4となります Tokensall = 8 X A B A B A B Y Tokensrepeated = 4 4トークン 直前の繰り返し
クローンの分布に着目した分類方法 RAD,NIFは,クローンの分布の特徴を表すメトリクス 値の高低により,クローンを4つに分類する vertical global RAD,NIFは,クローンの分布の特徴を表すメトリクスです. 本研究ではこれらのメトリクスの高低によりクローンを分類します. ここでは,図のように4つのカテゴリに 分類することにしました. 次にそれぞれのカテゴリについて説明します. local horizontal 低 低 高 NIF
カテゴリ: local クローンがディレクトリ階層上近い少数のファイルに存在する vertical global local RAD 高 vertical global カテゴリlocalでは,この図のように クローンがディレクトリ階層上近い 少数のファイルに存在しています. local horizontal 低 は,クローンを表す 低 高 NIF
カテゴリ: horizontal クローンがディレクトリ階層上近い多数のファイルに存在する vertical global local RAD 高 vertical global 次にカテゴリhorizontalでは,この図のように クローンがディレクトリ階層上近い 多数のファイルに存在しています. local horizontal 低 は,クローンを表す 低 高 NIF
カテゴリ: vertical クローンがディレクトリ階層上遠くの少数のファイルに存在する vertical global local RAD 高 vertical global 次にカテゴリverticalでは,この図のように クローンがディレクトリ階層上遠くの 少数のファイルに存在しています. local horizontal 低 低 高 NIF は,クローンを表す
カテゴリ: global クローンがプログラム広範囲の多数のファイルに存在する vertical global local RAD 高 vertical global 最後にglobalカテゴリでは,この図のようにクローンが プログラム広範囲の多数のファイルに存在しています. local horizontal 低 低 高 NIF は,クローンを表す
実験 Ant 総ファイル数: 954個 総クローンセット数(30トークン以上): 1643個 目的: RAD,NIF,RNRに着目して分類した各カテゴリに含まれるクローンの特徴を調べる Java,C,C++で書かれたオープンソースソフトウェアについて分類を行った CCFinderにより検出された30トークン以上のクローンセットを対象 人の手によって確認 Java: Ant,Webmail,httpunit,Art of Illusion C : Small Device C Compiler,Sketch C++ : CppUnit,SWIG 総ファイル数: 2948個 総クローンセット数(30トークン以上): 14874個 Ant 総ファイル数: 954個 総クローンセット数(30トークン以上): 1643個 そして,RAD,NIFの分布とRNRに着目して分類した 各カテゴリに含まれるクローンの特徴を調べる ために以下のような実験を行いました. Java,C,C++(プラスプラス)で書かれたオープンソースソフトウェアについて カテゴリ分けを行いました. 今回はCCFinderにより検出された30トークン以上のクローンセットを 調査対象としました. そして,各カテゴリに含まれるクローンの特徴を人の手によって確認しました. 実験に用いたオープンソースソフトウェアは, Javaのプログラムはこれら4つ, Cのプログラムはこれら2つ, C++のプログラムはこれら2つです. これら8つのプログラムの総ファイル数は2948個, 30トークン以上の総クローンセット数は14874個でした. 今回時間の都合上,ここではAntに行った実験について 説明します.Antの規模は,ソースファイル数954個, 30トークン以上クローンセット数は1643個でした.
クローンの特徴を表す指標 クローンが含まれる関数の名前を用いて指標を定義する クローンがどのような関数に存在するかで分類 same: 同じ名前の関数内に存在するクローン similar: 名前の似た関数内に存在するクローン 名前が似ている: 関数名の一部に共通した単語を持つ 例) addConfiguredInputMapper addConfiguredOutputMapper addConfiguredErrorMapper different: 異なる関数内に存在するクローン クローンセットが各指標に該当するか調べることで分類したカテゴリを評価する 複数の指標に該当することを許す クローンが含まれる関数の名前を用いて same,similar,differentの3つの指標を定義しました. これらの指標はクローンがどのような関数に存在するかで 分類しています. sameというのは,同じ名前の関数内に存在するクローンを表します. 次にsimilarというのは,名前の似た関数内に存在するクローンを表しています. ここでいう名前の似た関数というのは,この例のように関数名の一部に 共通した単語を持つ関数とします. 最後にdifferentというのは,異なる関数内に存在するクローンを表しています. クローンセットが各指標に該当するか調べることで 分類したカテゴリを評価します. 1つのクローンセットが複数の指標に該当することを許します. 次にこの指標を用いた評価条件について説明します.
評価条件 カテゴリ分けでのRAD,NIFの高低の閾値はそれぞれの最大値の半分とした RNRについては次のように分割した 低 中 高 RNR低: 0 ≦ RNR ≦ 0.3 RNR中: 0.3 < RNR ≦ 0.7 RNR高: 0.7 < RNR ≦ 1 低 中 高 カテゴリ分けでのRAD,NIFの高低の閾値はそれぞれの 最大値の半分としました. また,RNRについては閾値を0.3,0.7として 低中高の3つに分割しました.
RNRに着目した特徴 RNRの低いクローンは単純なロジックのクローンであった(繰り返しの多いクローン) 全体が1つのクローン 直前の繰り返し public static String getMethodAccess(int access_flags) { StringBuffer sb = new StringBuffer(); if (isPublic(access_flags)) { sb.append("public "); } else if (isPrivate(access_flags)) { sb.append("private "); } else if (isProtected(access_flags)) { sb.append("protected "); } if (isFinal(access_flags)) { sb.append("final "); if (isStatic(access_flags)) { sb.append("static "); if (isSynchronized(access_flags)) { sb.append("synchronized "); 全体が1つのクローン まず,どのカテゴリにも共通した特徴として, RNRの低いクローンは単純なロジックのクローンが 見られました. これがRNRの低いクローンの例です. 色のついた部分がCCFinderで検出された1つクローンを 表しています. このクローンを見ると同じ内容の命令が繰り返されて いることがわかります. 直前の繰り返し
Antでのクローンセットの分布 要素数が3以上のクローンセットについて調べた(検出されたクローンセットの約35%) vertical 33個 RAD 高 vertical 33個 global 7個 local 530個 horizontal 11個 低 低 高 NIF 要素数が3以上のクローンセットについて調べた(検出されたクローンセットの約35%) Antでのクローンセットの分布は図のようになります. このデータは,要素数が3以上のクローンセットについて 調べた分布です.これは全体のクローンセットに約35%にあたります.
カテゴリ: local の結果 same,similarに該当するクローンが多い あるデータ構造の要素に対して処理を行う各メソッド内にクローンが見られた 同じデータ構造を用いている カテゴリlocalでの各指標に該当するクローンの 割合はこのようになります. このグラフを見るとsame,similarに該当するクローンが多い ことがわかります. このカテゴリに属するクローンは,あるデータ構造の要素に対して処理を 行う各メソッド内に見られました. 同じデータ構造を用いているのでコードが似ていると言えます.
カテゴリ: localに属するクローンの例 public int getClassEntry(String className) { int index = -1; for (int i = 0; i < entries.size() && index == -1; ++i) { Object element = entries.elementAt(i); if (element instanceof ClassCPInfo) { ClassCPInfo classinfo = (ClassCPInfo) element; if (classinfo.getClassName().equals(className)) { index = i; } return index; 同じクローンセットに含まれるメソッドの例として, getConstantEntry getMethodRefEntry などが存在した これがカテゴリlocalに属するクローンの例です. 色のついた部分がクローンを表しています. このクローンが含まれるクローンセットでは, getConstantEntryやgetMethodEntryのような エントリを取得するメソッド内にクローン が見られました. このことから同じデータ構造を用いて いるのでコードが似ていることが言えます.
カテゴリ: horizontal の結果 sameに該当するクローンが多い 例えば他のプログラムを実行するメソッドにクローンが見られた 引数ごとに異なるメソッドとして実装されている 次にカテゴリhorizontalでの各指標に該当するクローンの 割合はこのようになります. グラフを見るとsameに該当するクローンが多いことがわかります. このカテゴリに属するクローンは,例えば他のプログラムを実行する メソッドに見られました. 引数ごとに異なるメソッドとして実装されているといえます.
カテゴリ: horizontalに属するクローンの例 public void execute() throws BuildException { Commandline commandLine = new Commandline(); Project aProj = getProject(); int result = 0; if (getViewPath() == null) { setViewPath(aProj.getBaseDir().getPath()); } commandLine.setExecutable(getClearToolCommand()); commandLine.createArgument().setValue(COMMAND_CHECKIN); checkOptions(commandLine); if (!getFailOnErr()) { getProject().log("Ignoring any errors that occur for: " + getViewPathBasename(),Project.MSG_VERBOSE); result = run(commandLine); if (Execute.isFailure(result) && getFailOnErr()) { String msg = "Failed executing: " + commandLine.toString(); throw new BuildException(msg, getLocation()); 外部プログラムの各機能を呼び出すメソッド中に クローンが存在した これがカテゴリhorizontalに属するクローンの例です. 色のついた部分がクローンを表しています. このクローンが含まれるクローンセットでは, ある外部プログラムの各機能を呼び出すメソッド中にクローンが 見られました. そのクローンは,ある親クラスの11個の子クラスがそれぞれ同名のメソッドを定義していました.
カテゴリ: vertical の結果 differentに該当するクローンが多い 類似した構造のパッケージにクローンが見られた 割合はこのようになります. グラフを見るとdifferentに該当するクローンが多いことがわかります. このカテゴリに属するクローンは類似した構造のパッケージに見られました.
カテゴリ: vertical に属するクローンの例 while (classEnum.hasMoreElements()) { String className = (String) classEnum.nextElement(); log(" Class " + className + " affects:", Project.MSG_DEBUG); Hashtable affectedClasses = (Hashtable) affectedClassMap.get(className); Enumeration affectedClassEnum = affectedClasses.keys(); while (affectedClassEnum.hasMoreElements()) { String affectedClass = (String) affectedClassEnum.nextElement(); ClassFileInfo info = (ClassFileInfo) affectedClasses.get(affectedClass); log(" " + affectedClass + " in " + info.absoluteFile.getPath(), } main testcases ant ant taskdefs taskdefs これがカテゴリverticalに見られたクローンの例です. この例では, 上位の階層で異なるパッケージに分けられているものの, それぞれのパッケージの階層がよく似た構造を 持ったファイル内にクローンが見られました.
カテゴリ: global の例 differentに該当するクローンがほとんどである 定型処理に該当するクローンが見られた 割合はこのようになります. グラフを見るとdifferentに該当するクローンがほとんどであることがわかります. このカテゴリに属するクローンは定型処理ばかりでした.
カテゴリ: global に属するクローンの例 } catch (IOException ioex) { throw new BuildException("Error while concatenating: " + ioex.getMessage(), ioex); } finally { if (reader != null) { try { reader.close(); } catch (IOException ignore) { // ignore } if (os != null) { os.close(); クローンが存在しているメソッドは ストリームを閉じる処理を2回続けて 行う内容であった これがカテゴリglobalに属するクローンの例です. 色のついた部分がクローンを表しています. このクローンが含まれるクローンセットは, 2つのストリームを続けて閉じる処理で構成されていました. Antでは2つのストリームを同時に使うことが多く, Antにおける定型処理であると言えます.
考察 異なるソフトウェアでも,同じカテゴリに分類されたクローンに同じ特徴が見られた ソフトウェアの記述言語を問わない メトリクス値に基づいてクローンの特徴を抽出可能 各カテゴリの特徴を利用することでクローンをフィルタすることができる 例1) globalに属するクローンは定型処理であるので,リファクタリング対象にならない 例2) verticalに属するクローンは設計情報との一貫性を確認することが有益 他のパッケージからのアドホックなコピーの恐れ 例3) RNRが低いクローンは単純なロジックの繰り返しであるので利用しにくい 単純な代入文の連続など リファクタリングの対象にならない 先ほど,Antについて各カテゴリの特徴について説明しましたが, 異なるソフトウェアでも同じ特徴が見られました. このことからソフトウェアの記述言語を問わず, メトリクス値に基づいてクローンの特徴を抽出することが可能であるといえます. そして,各カテゴリの特徴を利用することでクローンをフィルタする ことが可能であると言えます. 1つ目の例として,カテゴリglobalに属するクローンは定型処理であるので, リファクタリングの対象からはずす事ができます. 2つ目の例として,カテゴリverticalに属するクローンは,他のパッケージからの アドホックなコピーの恐れが考えられ,設計情報との一貫性を 確認することが有益ではないかと開発者が判断できます. また,RNRの低いクローンは,繰り返しで構成された単純なロジックのクローン であるので,リファクタリングの対象からはずすことができます.
まとめと今後の課題 まとめ 今後の課題 コードクローン分析の効率化を支援するコードクローンの特徴抽出手法の提案と評価 分類の詳細化 ツールとしての実装 実際の開発現場での適用実験と評価 最後にまとめと今後の課題です. コードクローン分析を支援するコードクローンの 特徴抽出手法の提案と評価を行いました. 今後の課題として,分類の詳細化や ツールとしての実装,実際の開発現場で適用することが 挙げられます. 以上で発表を終わります. ありがとうございました.