コードクローンの分布情報を 用いた特徴抽出手法の提案 大阪大学 大学院情報科学研究科 服部 剛之,肥後芳樹,楠本真二,井上克郎 コードクローン    ソースコード中に存在する同一,もしくは類似したコード 片を同一システム中に持つコード片 (以降単にクローンと呼ぶ) コピーアンドペースト等により生成される ソフトウェア保守を困難にする  コードの変更が必要な場合   バグ修正,機能追加 保守作業の手間が増大 クローンペア クローンセット 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