コードクローンの分布情報を 用いた特徴抽出手法の提案 大阪大学 大学院情報科学研究科 服部 剛之,肥後芳樹,楠本真二,井上克郎 コードクローン ソースコード中に存在する同一,もしくは類似したコード 片を同一システム中に持つコード片 (以降単にクローンと呼ぶ) コピーアンドペースト等により生成される ソフトウェア保守を困難にする コードの変更が必要な場合 バグ修正,機能追加 保守作業の手間が増大 クローンペア クローンセット CCFinder,Gemini クローンを対象とした保守支援ツール 検出ツール: CCFinder[1] 分析環境: Gemini[2] 国内外の個人・組織に配布 研究機関での利用 企業での商用ソフトウェア開発プロセスへの導入 配布先からのフィードバックを得ている [1] T. Kamiya, S.
Download
Report
Transcript コードクローンの分布情報を 用いた特徴抽出手法の提案 大阪大学 大学院情報科学研究科 服部 剛之,肥後芳樹,楠本真二,井上克郎 コードクローン ソースコード中に存在する同一,もしくは類似したコード 片を同一システム中に持つコード片 (以降単にクローンと呼ぶ) コピーアンドペースト等により生成される ソフトウェア保守を困難にする コードの変更が必要な場合 バグ修正,機能追加 保守作業の手間が増大 クローンペア クローンセット CCFinder,Gemini クローンを対象とした保守支援ツール 検出ツール: CCFinder[1] 分析環境: Gemini[2] 国内外の個人・組織に配布 研究機関での利用 企業での商用ソフトウェア開発プロセスへの導入 配布先からのフィードバックを得ている [1] T. Kamiya, S.
コードクローンの分布情報を
用いた特徴抽出手法の提案
大阪大学 大学院情報科学研究科
服部 剛之,肥後芳樹,楠本真二,井上克郎
1
コードクローン
ソースコード中に存在する同一,もしくは類似したコード
片を同一システム中に持つコード片
(以降単にクローンと呼ぶ)
コピーアンドペースト等により生成される
ソフトウェア保守を困難にする
コードの変更が必要な場合
バグ修正,機能追加
保守作業の手間が増大
クローンペア
クローンセット
2
CCFinder,Gemini
クローンを対象とした保守支援ツール
検出ツール: CCFinder[1]
分析環境: Gemini[2]
国内外の個人・組織に配布
研究機関での利用
企業での商用ソフトウェア開発プロセスへの導入
配布先からのフィードバックを得ている
[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.
3
コードクローン検出ツール CCFinder
ソースコードをトークン単位で直接比較すること
によりコードクローンを検出
名前空間の正規化
ユーザ定義名の置き換え
テーブル初期化部分の取り除き
モジュールの区切りの認識
数百万行規模でも実用的な時間で解析可能
様々なプログラミング言語に対応
4
CCFinder(処理概要)
Source files
static
throws
{{{ String
1. static
static
foo()
throws
RESyntaxException
{
{{ $$ $$
(( (( )) ))throws
$void
void
foofoo()
throws$$RESyntaxException
RESyntaxException
String aaa {
static
RESyntaxException
String
1.
void
throws
RESyntaxException
static void
$ $$foo
throws
]{{] {${$ "123,400"
,
[[ [][] String
$String
$$ =
new
$[] {
]]] === a[]
new
$[]
2. [[String
a[]
=
new
String
{ "123,400",
"123,400",
"abc", "orange
"orange 100"
100" };
};
2.
new
"abc",
[String
$String
"abc"
,
"orange
100"
}
;
org
.
apache
.
regexp
;
}
3. org.apache.regexp.RE
org.apache.regexp.RE
pat == new
new org.apache.regexp.RE("[0-9,]+");
org.apache.regexp.RE("[0-9,]+");
} ;
3.
pat
$$ === new
$$ pat
RE
pat
RE
new
4. .int
int
sum
0; org . apache . regexp
4.
sum
==new
0;
. RE
)) ;;; int
$$ ==== $0$00
$$ sum
$$
$$ ((( "[0-9,]+"
RE
int
sum
sum
5. for
for
(int"[0-9,]+"
i == 0;
0; ii) << a.length;
a.length;
++i)
5.
(int
i
++i)
;; for
$$ i$$i === 0$0$ ;;; i$$i <<<<
int
for (( int
6. a$ ifif. (pat.match(a[i]))
(pat.match(a[i]))
6.
;++
$$ ii)) ))if
$$ ;; ++
length
; ++
++
pat
pat
$ . length
if ifif(( ((($$ pat
7. .. match
sum
+=
Sample.parseNumber(pat.getParen(0));
7.
sum
+=
Sample.parseNumber(pat.getParen(0));
$$ (( (( $$ aa [[ [[ $$ ii ]] ]] )) )) )) ))) $$sum
sum
sum
. match
8.
System.out.println("sum
" ++ sum);
sum);
8. +=
System.out.println("sum
"getParen
+=
Sample
((( 000
(( $$ .. $$ (( ((pat
$$ .. $.$. parseNumber
parseNumber
pat$$ .=
. getParen
pat
.=
getParen
+=
9. }} )) )) ;; System
(( $$ ((( "sum
.. .$$. println
$$ .. .$$. out
System
out
println
"sum==="""
9.
println
"sum
static
String
$$ $$goo
}}} static
))) ;;; goo(String
$$ void
sum
static void
void
goo
String
10. static
static
void
goo(String
[]((a)
a)((( $$throws
throws
RESyntaxException {{
+++ sum
goo
String
10.
[]
RESyntaxException
static
{{ $$ $$ =
$$RE("[0-9,]+");
throws
RESyntaxException
RE exp
exp ===
[[[ exp
]]] )))=
RESyntaxException
RE
exp
11. a$a$RE
RE
exp
=throws
newRESyntaxException
throws
= {{{ RE
11.
new
RE("[0-9,]+");
new
RE
=
$$ )) ;;)) $$;; $$int
$$ (( "[0-9,]+"
int sum
sum
new
=sum$$=== 000
12. new
int
sum"[0-9,]+"
0;
12.
int
sum
== 0;
$$ i$$i === 0$0$ ;;; i$$i <<<<
for
int
for
((( int
for (int
13. ;;;for
for
0; ii << a.length;
a.length; ++i)
++i)
13.
(int
ii == 0;
$
(
$$ ii)) ))if
$$ ;; ++
if
(
exp
length
; ++
++
if
(
exp
a$a$ ... length
;++
(
exp
if ( $
14. . match
(exp.match(a[i]))
14.
ifif (exp.match(a[i]))
]
$
[
$
(
$
(
a
[
i
]
(
a
[
i
]
sum
sum
. $ ( $ [ $ ] )) )) )) ))) $$sum
15. +=
sum
+=
parseNumber(exp.getParen(0));
15.
sum
+=
parseNumber(exp.getParen(0));
(( .. $$ getParen
$$ (( ( $$exp.. ((. $$exp
+=
getParen
()) 0)) )(( )00 )) ))
parseNumber
exp
getParen
$$$ ... parseNumber
+= parseNumber
16. ;;;System.out.println("sum
System.out.println("sum
==="""""+++++sum
sum);
16.
sum);
$$ =
(( $$ ((++ "sum
.. .$$. println
$$ .. .$$. out
=
System
out
println
"sum
sum
System
"sum
sum
17. }}))) ;;; }}}
17.
字句解析
字句解析
トークン列
トークン列
変換処理
変換処理
変換後トークン列
変換後トークン列
検出処理
検出処理
クローン情報
クローン情報
出力整形処理
出力整形処理
クローンペア位置情報
5
コードクローン分析環境 Gemini
CCFinderの解析結果を読み込み,クローン情報
を視覚的に表示
クローン散布図(スキャタープロット図)
メトリクスグラフ
6
クローン散布図
ファイルA
a
b
c d
ファイルB
e
b
c
d
f
b
トークン列
a b
c
ファイルA
d e
b c
d
ファイルB
f
b
クローン
7
フィードバックから得られた情報
クローン情報の利用法
リファクタリング支援
設計情報との一貫性確認
互いにクローンになっているものを1つにまとめる
設計上説明できるクローンであるか
問題点
検出されたクローンの数が多すぎてどれをみるべきかわからない
調査の必要がないクローン(定型処理など)が含まれている
調査すべきクローンをうまく特定したい
8
目的とアプローチ
目的: クローン分析の効率化
クローンを特徴付けして分類し,分類に応じた対策方法
を提示する
アプローチ: メトリクスを用いてクローンを分類
以下のメトリクスを用いる
RAD: クローン間の遠さの尺度
NIF: クローンが存在するファイルの数
9
RAD: クローン間の遠さの尺度
RAD: 与えられたクローンセットの各要素を含む
ファイルから共通の親ディレクトリまでの距離の
最大値
•RAD = 2
•例:RAD = 1
2
1
ファイルC
ファイルA
ファイルB
は,クローンを表す
ファイルA ファイルB
10
NIF: クローンが存在するファイルの数
NIF: 与えられたクローンセットの各要素を含む
ファイルの数
•例:NIF = 2
•NIF = 3
ファイルC
ファイルA
ファイルB
2
は,クローンを表す
ファイルA ファイルB
3
11
クローンの分布に着目した分類方法
RAD,NIFは,クローンの分布の特徴を表すメトリクス
RADは垂直方向の広がり,NIFは水平方向の広がりを表わす
値の高低により,クローンを4つに分類する
RAD
高
vertical
global
local
horizontal
低
低
高 NIF
12
分類: local
クローンセットに含まれるクローンがディレクトリ
階層上近い少数のファイルに存在する
RAD
高
vertical
global
local
horizontal
低
は,クローンを表す
低
高 NIF
13
分類: horizontal
クローンセットに含まれるクローンがディレクトリ
階層上近い多数のファイルに存在する
RAD
高
vertical
global
local
horizontal
低
は,クローンを表す
低
高 NIF
14
分類: vertical
クローンセットに含まれるクローンがディレクトリ
階層上遠い少数のファイルに存在する
RAD
高
vertical
global
local
horizontal
低
低
は,クローンを表す
高 NIF
15
分類: global
クローンセットに含まれるクローンがディレクトリ
階層上遠い多数のファイルに存在する
RAD
高
vertical
global
local
horizontal
低
低
は,クローンを表す
高 NIF
16
適用実験
目的: RAD,NIFに着目して各分類に含まれるクローンの特徴を調べる
Java,C,C++で書かれたオープンソースソフトウェアについて分類を
行った
CCFinderにより検出された30トークン以上のクローンセットを対象
人の手によって確認
Java(4つ): Ant,Webmail,httpunit,Art of Illusion
C(2つ)
: Small Device C Compiler,Sketch
C++ (2つ): CppUnit,SWIG
総ファイル数: 2948個
総クローンセット数: 14874個
Ant
総ファイル数: 954個
総クローンセット数: 1643個
17
クローンの特徴を表す指標
クローンが含まれる関数の名前を用いて指標を定義する
クローンがどのような関数に存在するかで分類
same: 同じ名前の関数内に存在するクローン
similar: 名前の似た関数内に存在するクローン
名前が似ている: 関数名の一部に共通した単語を持つ
例) addConfiguredInputMapper
addConfiguredOutputMapper
addConfiguredErrorMapper
different: 名前が全く異なる関数内に存在するクローン
クローンセットが各指標に該当するか調べることで分類の
特徴を評価する
複数の指標に該当することを許す
18
評価条件
RAD,NIFの高低の閾値はそれぞれの最大値の半分とした
各分類内でメトリクスRNRについて分割
分類に属するクローンの特徴を詳しく調査
RNRについては次のように分割した
RNR低: 0 ≦ RNR ≦ 0.3
RNR中: 0.3 < RNR ≦ 0.7
RNR高: 0.7 < RNR ≦ 1
低
中
高
RNR
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1
19
RNR: クローン内の重複した処理の
少なさの度合い
∑ Tokensrepeated(c)
RNR = 1 -
c∈S
∑ Tokensall(c)
c∈S
S: クローンセット
c: クローンセットの要素
Tokensall(c) : クローンc中の総トークン数
Tokensrepeated(c) : クローンc中の繰り返し部分のトークン数
8トークン
X A B A B A B Y
4トークン
直前の繰り返し
Tokensall = 8
Tokensrepeated = 4
20
繰り返しの定義に対する説明
X A B A B A B Y
8トークン
正規表現のメタ文字“+”を用いて表す
X (A B)+ Y
4トークン
コード片中の繰り返しの割合: 4 / 8 = 0.5
21
RNRに着目した特徴
RNRの低いクローンは単純なロジックのクロー
ンであった(繰り返しの多いクローン)
public static String getMethodAccess(int access_flags) {
StringBuffer sb = new StringBuffer();
if (isPublic(access_flags)) {
sb.append("public ");
全体が1つのクローン
} 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 ");
}
22
Antでのクローンセットの分布
RAD
高
vertical
33個
global
7個
local
530個
horizontal
11個
低
低
高 NIF
要素数が3以上のクローンセットについて調べた
(検出されたクローンセットの約35%)
23
分類: local の結果
RNR高: 305個
RNR中: 131個
RNR低: 94個
different
similar
same
0
20
40
60
80
100 %
same,similarに該当するクローンが多い
あるデータ構造の要素に対して処理を行う各関数内にクローン
が見られた
同じデータ構造を用いている
24
分類: 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
などが存在した
}
25
分類: horizontal の結果
RNR高: 9個
RNR中: 1個
RNR低: 1個
different
similar
same
0
20
40
60
80
100 %
sameに該当するクローンが多い
他のプログラムを実行する関数内にクローンが見られた
関数名は同じであるがシグネチャが異なる
26
分類: 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());
}
}
27
分類: vertical の結果
RNR高: 4個
RNR中: 12個
RNR低: 17個
different
similar
same
0
20
40
60
80
100 %
differentに該当するクローンが多い
類似した構造のパッケージにクローンが見られた
28
分類: vertical に属するクローンの例
main
ant
taskdefs
while (classEnum.hasMoreElements()) {
String className = (String)
classEnum.nextElement();
log(" Class " + className + " affects:",
Project.MSG_DEBUG);
testcases Hashtable affectedClasses
= (Hashtable) affectedClassMap.get(className);
Enumeration affectedClassEnum =
affectedClasses.keys();
while (affectedClassEnum.hasMoreElements()) {
ant
String affectedClass = (String)
affectedClassEnum.nextElement();
ClassFileInfo info
taskdefs
= (ClassFileInfo)
affectedClasses.get(affectedClass);
log(" " + affectedClass + " in "
+ info.absoluteFile.getPath(),
Project.MSG_DEBUG);
}
29
分類: global の結果
RNR高: 4個
RNR中: 0個
RNR低: 3個
different
similar
same
0
20
40
60
80
100 %
differentに該当するクローンがほとんどである
定型処理に該当するクローンが見られた
30
分類: global に属するクローンの例
} catch (IOException ioex) {
throw new BuildException("Error while
+ ioex.getMessage(), ioex);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignore) {
// ignore
}
}
if (os != null) {
try {
os.close();
} catch (IOException ignore) {
// ignore
}
}
}
}
concatenating: "
クローンが存在している関数は
ストリームを閉じる処理を2回続けて
行う内容であった
31
考察
異なるソフトウェアでも,同じ分類に属するクローンに同じ特徴が見ら
れた
ソフトウェアの記述言語を問わない
メトリクス値に基づいてクローンの特徴を抽出可能
各分類の特徴を利用することでクローンをフィルタすることができる
例1) globalに属するクローンは定型処理であるので,リファクタリング
対象にならない
例2) verticalに属するクローンは設計情報との一貫性を確認すること
が有益
他のパッケージからのアドホックなコピーの恐れ
例3) RNRが低いクローンは単純なロジックの繰り返しであるので利用
しにくい
単純な代入文の連続など
リファクタリングの対象にならない
32
まとめと今後の課題
まとめ
コードクローン分析の効率化を支援するコードクロー
ンの特徴抽出手法の提案と評価
今後の課題
分類の詳細化
ツールとしての実装
実際の開発現場での適用実験と評価
33
34