JenkinsとPMD CPDを使って、コピペコード(重複コード)を継続的に検出・監視する方法を説明します。


コードの品質についてみなさんはどんなことを気をつけていますでしょうか? カバレッジなんかがよくある指標ですね。他にも静的解析ツールの指摘数なんかもありますね。 他には「コピー&ペーストで作られたコードがどのくらいあるか」というのも指標になりうると思います。 コピペでコードを書くのは基本的にやってはいけないことです。 ですので、そのようにして作られた重複コードが多いかどうかは一つのメトリクスになりうると考えます。

「そんなの常識だぜ?」と思うかもしれませんが、この世は広いもので、この基本原則を守らない、むしろ積極的にコピペを推奨するプログマもいるのが実際です。 そのような不埒者にプロジェクトの足を引っ張られるのも癪に障るというもの。 そこでこの記事では、Jenkinsを使ってコピペで作ったコードをチェックして可視化し、さらにその量の推移を可視化する方法をご紹介します。

なぜコピペでコーディングしてはいけないのか| DRY 原則

まずはじめに初心者の方向けに、そもそもなんで「コピー&ペーストでコードを作っちゃダメ」なのかを簡単に説明します。 「そんなの当たり前だよ」という方はここは飛ばして次へ進んでくださいませ。

プログラミングの原則として「コピー&ペーストでコードを作っちゃダメ」というのがあります。これは「DRYの原則」と呼ばれています。 DRYは 「Don’t Repeat Yourself!」の略です。直訳すると「繰り返しはするな!」という感じでしょうか。

このDRYの原則は「達人プログラマー」という名著に説明があるかと思います。なぜコピペがダメなのかはこの本を読んでいただくのが一番良いです。

このDRY原則をものすごく簡単に説明しますね。例えばある処理を関数化せずにそのままべたっと10箇所にコピペしたとします。 コピペした直後は期待通り動作するでしょう。

さて月日が経ち、この処理を修正する必要が発生したとします、コピペした10箇所全部にです。 仮に1箇所修正するときに間違いをしない確率を99%とします。では10箇所すべて修正するときにバグを出す確率(1つ以上間違えてしまう確率)は何パーセントでしょうか?

(1 - 0.99^10) \* 100 ≒ 10 となります。つまり約10%の可能性でバグるわけです。

単に全く同じ機能をコーディングするだけでバグる確率が10%ってのは恐ろしく高い確率です。そして、どうせそのバグを修正するときにもまたバグりますよ。 無間地獄です。 ちなみにコピペが「職場文化」となっている場合、コピペ箇所が10個なんかではすみません。 あの処理を数十個コピペして、また別の処理を数十個コピペして……となるので、合計のコピペ数は爆発的に増えていきます。 コピペ自体は C-c C-v で一瞬で終わっちゃいますから爆速で増えていきます。

ちなみにコピペ箇所が300個あった場合(コピペが文化になっているとこのくらいはあっという間に増えます)、バグる確率は95%です。 それを修正しようとしてまたバグる確率まで考えると、目眩がしますね。いかにコピペが悪い文明かがご理解できたかと思います。

PMD CPD で重複コードを検出する

この悪しきコピー&ペーストを検出するには、PMD CPDというツールを使います。 CPDは 「Copy/PasteDetector」の略です。インストールは簡単です。今回は Windows & msys2環境で説明します。 Windows DOS や Mac、Linuxでもほぼ同じだと思います。

まずは、Jenkinsで逐次実行させる前に、手動で動かしてみます。

インストールする

PMD の公式サイトにインストールの方法が書いてあります。 それをみて作業すればOKです。 Windowsの場合は、ダウンロードした zipを任意の場所に展開します。今回はc:\Programs\の下に展開することにします。

実行してみる

では早速実行してみます。サンプルとして μITORO準拠のTOPPERS/JSPのカーネルのコードに対して実行してみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
$ /c/Programs/pmd-bin-5.8.1/bin/run.sh cpd --minimum-tokens 100 --files ./kernel/ --language c --failOnViolation false
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\banner.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\check.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\cyclic.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\cyclic.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\eventflag.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\eventflag.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\exception.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\exception.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\interrupt.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\interrupt.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\jsp_kernel.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\jsp_rename.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\jsp_unrename.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\mailbox.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\mailbox.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\mempfix.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\mempfix.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\queue.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\semaphore.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\semaphore.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\startup.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\syslog.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\syslog.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\sys_manage.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\task.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\task.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\task_except.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\task_manage.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\task_sync.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\time_event.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\time_event.h
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\time_manage.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\wait.c
Added C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\wait.h
Found a 21 line (109 tokens) duplication in the following files:
Starting at line 476 of C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c
Starting at line 532 of C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c

        CHECK_DISPATCH();
        CHECK_DTQID(dtqid);
        dtqcb = get_dtqcb(dtqid);

        t_lock_cpu();
        if (dequeue_data(dtqcb, p_data)) {
                if ((tcb = receive_data_swait(dtqcb, &data)) != NULL) {
                        enqueue_data(dtqcb, data);
                        if (wait_complete(tcb)) {
                                dispatch();
                        }
                }
                ercd = E_OK;
        }
        else if ((tcb = receive_data_swait(dtqcb, p_data)) != NULL) {
                if (wait_complete(tcb)) {
                        dispatch();
                }
                ercd = E_OK;
        }
        else {
=====================================================================
Found a 20 line (102 tokens) duplication in the following files:
Starting at line 477 of C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c
Starting at line 533 of C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c
Starting at line 582 of C:\Users\username\work\jsp-1.4.4.1-full\.\kernel\dataqueue.c

        CHECK_DTQID(dtqid);
        dtqcb = get_dtqcb(dtqid);

        t_lock_cpu();
        if (dequeue_data(dtqcb, p_data)) {
                if ((tcb = receive_data_swait(dtqcb, &data)) != NULL) {
                        enqueue_data(dtqcb, data);
                        if (wait_complete(tcb)) {
                                dispatch();
                        }
                }
                ercd = E_OK;
        }
        else if ((tcb = receive_data_swait(dtqcb, p_data)) != NULL) {
                if (wait_complete(tcb)) {
                        dispatch();
                }
                ercd = E_OK;
        }
        else {

このようにコピペのコードがあったファイルと、コピペコードを表示してくれます。 この例ではdataqueue.cにコピペがありますね。なかなか良いですね。

Jenkins で コピペコードを検出する

さて、いよいよ Jenkins でこれらを逐次実行する方法です。 Jenkinsで実行することの利点は、自動実行という以外にも下記のようなメリットがあります。 見やすいのに加えて日々のコピペ量を計測してくれるので、メトリクスとしても使えそうです。

PMD Plugin と Static Code Analysis プラグインをインストール

まずは、Jenkinsに PMDのプラグインをインストールします。 「Jenkinsの管理」→「プラグインの管理」からプラグインマネージャーへ進んで、PMD Plugin をインストールしてください。

JenkinsプラグインマネージャーでPMD Pluginをインストール

もし Static Code Analysis Plug-ins がインストールされていなければこれもインストールしてください。 これは PMD以外のプラグインでもよく使われるものなのですでにインストールされているかもしれません。

Jenkins のビルドの設定で PMD CPD を有効にする

これで Jenkins によって PMD CPD を使ってコピペされた重複コードを検出できるようになりました。 手順は簡単です。「ビルド後の処理の追加」にて、下記のように「重複コード分析の集計」というものが選べるようになっています。

Jenkinsビルド後処理で「重複コード分析の集計」を選択

これを選択しすると下記のような設定画面が出ます。いろいろな設定ができますが、ひとまずはデフォルトでいいでしょう。 「何行重複していたら指摘を出すか」など細かく設定できますので、使っていくうちにプロジェクトに合わせて調整すれば良いかと思います。

重複コード分析の集計設定オプション画面

Jenkins PMD Plugin の実行結果の例

上記の設定をしてビルドすると、下記のように結果が表示されます。いろいろなビューがありますのでいろいろ見てみると良いですね。 ちなみに以下の図では重複コードのあるファイル名と重複コードの行数が表示されています。

重複コード分析結果でファイル名と行数を表示

そして上記のファイル名のところをクリックすると、下記のように重複コードを表示してくれます! 視覚的に一発でどんなコピペコードがわかるので大変便利です!!!これでどんどん重複コードを撲滅できますね!

重複コードの詳細表示画面

また、これらの結果の推移も自動で集計してくれます。例えば重複コードの個数の遷移をグラフ化したり「今回のビルドで新規に発生した重複コードはここ」とか教えてくれたりとか。 重複コードの撲滅に大変有用です!

Note

もし重複コードが表示されない場合は、Content Security Policy (CSP)が原因かもしれません。 例えば JenkinsのClover pluginを利用したカバレッジレポートページにCSSが適用されない に解説がありますので、参考にして見てください。対応自体は簡単ですよ。

まとめ

今回はコピペで作られた重複コードの害と、それを逐次検出するための Jenkins と PMD CPDについて説明しました。重複コードはコード品質を著しく下げますので、今回の記事を元にどんどん減らしていって見てください! しょうもないバグや修正が減ると思います!