コピペコードを逐次検出する|Jenkins と PMD CPD

シェアする

どうも、ながやすです。

コードの品質についてみなさんはどんなことを気をつけていますでしょうか?カバレッジなんかがよくある指標ですね。他にも静的解析ツールの指摘数なんかもありますね。

他には「コピー&ペーストで作られたコードがどのくらいあるか」というのも指標になりうると思います。コピペでコードを書くのは基本的にやってはいけないことです。ですので、そのようにして作られた重複コードが多いかどうかは一つのメトリクスになりうると考えます。

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

スポンサーリンク

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

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

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

この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/Paste Detector」の略です。インストールは簡単です。今回は Windows & msys2 環境で説明します。 Window DOS や Mac、Linux でもほぼ同じだと思います。まずは、Jenkins で逐次実行させる前に、手動で動かしてみます。

ちなみにこの記事では、Windows & MSYS2 という環境としますが、他のOSでもさほど変わりませんのでご安心ください。

Windows で MSYS2 のシェルを使う方法か下記の記事をご参考くださいませ。

Jenkins@Windows で bash script を動かす方法
Windows で動く Jenkins にて、bash script を動かす方法を説明します。MSYS2を使います。Windows のDOSバッチファイルではかなり苦しいので、bash scriptを使えるようにしておくと効率的だと思います。

インストールする

下記の公式サイトにインストールの方法が書いてあります。
このサイトに

  • Windows & DOS
  • MAC
  • Linux
  • のインストールの方法が書いてありますのでこれをみて作業すればOKです。

    PMD is a source code analyzer. It finds unused variables, empty catch blocks, unnecessary object creation, and so forth.

    Windows の場合は、ダウンロードした zip を任意の場所に展開します。今回はc:\Programs\の下に展開することにします。

    実行してみる

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

    $ /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 {
    

    コピペのコードがあったファイルと、コピペコードを表示してくれます。ふむ。なかなか良いですね。

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

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

  • コピペコードをブラウザ上でグラフィカルにわかりやすく見ることができる
  • 日々のコピペコードの数を計測して、その増減をグラフ化してくれる
  • PMD Plugin と Static Code Analysis プラグインをインストール

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

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

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

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

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

    Jenins PMD Plugin の実行結果の例

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

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

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

    もし重複コードが表示されない場合は、Content Security Policy (CSP)が原因かもしれません。
    例えば下記のサイトに解説がありますので、参考にして見てください。対応自体は簡単ですよ。

    Jenkins+mvn site+HTML Publisher PluginでMavenのレポートをJenkinsから見ようと思ったらスタイルが崩れまくっていたので調べた。 結局公式で解決。 CSP(Content Security Policy) CSP(Content Security Policy)は、クロスサ...

    まとめ

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