SHOYAN BLOG

I am a pragmatic programmer.

PMDでJavaのコードのバグをチェックする

Javaにおいてバグの要因となりそうなコードをチェックするツールとして、FindBugsがあります。しかし、FindBugsは2015年以降の開発が止まっているため、言語のアップデートに追従できていません。そこで、FindBugsの代替えとなるPMDを紹介します。PMDを使えばバグの原因となりそうなコードを検知することができるため、不具合を未然に防ぐことが可能です。

PMDとは

PMDは次の4つの問題があるコードを検知することができるツールです。

  • Possible bugs - バグの要因となるコード
  • Dead code - 使われていないコード
  • Suboptimal code - 効率の悪いコード
  • Overcomplicated expressions - 複雑な構文

PMD自体はJavaで実装されていますが、Java以外の言語にも対応しています。今回はJavaのコードを対象に使い方を紹介します。

PMDのインストール方法

Homebrewでインストールする

Macであれば、次のコマンドでインストールできます。

1
brew install pmd

バイナリをダウンロードする

バイナリをダウンロードしてインストールすることができます。次のページからダウンロードしてください。

PMDでコードをチェックする

サンプルコードの準備

PMDでコードをチェックしてみます。今回はサンプルコードを作って検証します。サンプルコードはGithubにアップロードしているのでクローンしてご利用ください。

1
2
git clone git@github.com:shoyan/pmd-samples.git
cd pad-samples

PMDの実行

Pmdを実行します。-dがソースコードのパス、-Rがルールが設定してあるファイルのパス、-lに言語を指定します。

1
2
3
4
5
6
pmd pmd -d src/main/java -R rules.xml -l java
3月 20, 2018 6:03:49 午後 net.sourceforge.pmd.cache.NoopAnalysisCache <init>
警告: This analysis could be faster, please consider using Incremental Analysis: https://pmd.github.io/pmd-6.1.0/pmd_userdocs_getting_started.html#incremental-analysis
/pmd-samples/src/main/java/UnusedCode.java:2:        Avoid unused private fields such as 'FOO'.
/pmd-samples/src/main/java/UnusedCode.java:4:        Avoid unused local variables such as 'i'.
/pmd-samples/src/main/java/UnusedCode.java:6:        Avoid unused private methods such as 'foo()'.

3つの問題が検知されています。UnusedCode.javaの2行目を見てみると、使われていないメンバ変数があります。他のエラーも同様に使われていないローカル変数とプライベートメソッドを検知しています。

ルールについて

ルールの一覧については次のリンクよりご覧ください。

0の状態からルールを構築するのは大変です。そのため、PMDのリポジトリには様々なルールのサンプルが定義されています。基本的なルールが網羅されているbasic.xmlを利用するとよいでしょう。他にも様々なルールがあるので参考にしてください。

カテゴリ

PMDのルールはカテゴリに分類されています。例えば、ベストプラクティスであれば、ベストプラクティスに沿ったルールが定義されています。カテゴリの詳細については次のリンクでご覧ください。

トラブルシューティング

PMD6.1.0はPMD7への過渡期バージョンのようで、Deprecatedの警告が山のように出ます。
設定ファイルでしか警告を消す術がないので設定ファイルを修正します。

次のようにdeprecatedをfalseにするか、プロパティ自体を削除してください。

1
<rule ref="category/java/errorprone.xml/AvoidBranchingStatementAsLastInLoop" deprecated=“false" />

参考リンク

Nginxで構築するキャッシュプロキシサーバ

Nginxを使えば簡単にプロキシサーバを構築することができます。手元で動かせるサンプルコードをGithubで公開しています。git cloneしてご利用ください。PCにDockerがインストールされていれば簡単に動作環境を構築することができます。

https://github.com/shoyan/nginx-proxy-cache

キャッシュを有効にする

Nginxのキャッシュ機能はデフォルトでは有効ではないので、設定する必要があります。

/etc/nginx/nginx.conf

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
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    # キャッシュの場所を指定する
    # levels: キャッシュの階層レベル
    # keys_zone: 使用する共有メモリゾーンの名前とサイズ
    proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=my-key:10m;
    proxy_temp_path /var/cache/nginx/tmp;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

次にlocationディレクティブでキャッシュを有効にします。

/etc/nginx/conf.d/default.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
    listen       80;
    server_name  localhost;

    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    location / {
        # 8081のポートで起動しているサーバにプロキシする
        proxy_pass    http://app:8081/;
        # 全てのアクセスをキャッシュする
        proxy_ignore_headers Cache-Control;
        # my-keyというキー名でキャッシュを登録
        proxy_cache my-key;
        # キャッシュは1分間有効にする
        proxy_cache_valid any 1m;
        # レスポンスヘッダにキャッシュがヒットしたかどうかを付与する
        add_header X-Nginx-Cache $upstream_cache_status;
    }
}

以上の設定を行えばキャッシュが有効になります。

キャッシュの詳細な設定についてはNginxのドキュメントを参照ください。
ngx_http_proxy_module

アプリサーバはNode.jsで構築しています。1秒でHello Worldを返す単純なアプリサーバです。こちらもDockerで動作するのでNode.jsをPCにインストールする必要はありません。

プロキシサーバとアプリサーバの起動は次のコマンドで行います。

1
2
3
4
5
6
7
$ docker-compose up
Starting proxycachesample_app_1 ...
Starting proxycachesample_app_1 ... done
Starting proxycachesample_proxy_1 ...
Starting proxycachesample_proxy_1 ... done
Attaching to proxycachesample_app_1, proxycachesample_proxy_1
app_1    | Server running at http://localhost:8081/

1回目のアクセスはリクエストがアプリサーバまで行くのでレスポンスタイムは1秒程度かかります。

1
2
3
$ curl http://localhost:8080 -w "%{time_total}"
Hello World
1.009103

2回目からのアクセスはプロキシサーバがキャッシュしているため、レスポンスタイムが速くなります。

1
2
3
$ curl http://localhost:8080  -w "%{time_total}"
Hello World
0.003797

確認が終わったらサーバを停止しておきましょう。サーバの停止はCtrl+Cか次のコマンドで行えます。

1
$ docker-compose stop

なぜ人は前例がないことを避けるのか

今までの経験がないことをやろうとすると反発の声が上がることあります。その主な理由の1つは「前例がないから」です。

なぜ人は前例がないことを避けるのかについて考えてみます。

前例がないということは不確実だということです。過去に答えはなく、未来にしか答えはありません。
未来のことはいくら考えてもわかりません。不確実な未来を確実なものにする方法は1つだけです。その方法とは実際にやってみることです。実際にやってみて、その結果からしか学ぶ術はありません。

人は不確実な状態を避けたがります。それは動物に備わっている生き残るための本能からです。人は不確実な状態に直面すると不安を感じます。不安の状態でいるのは居心地が悪いため、なんとかしてこの状態を避けようとします。

以上のように不確実な状態に直面すると人は本能的に不安を感じます。この不安を解消するための方法は2つあります。1つ目は不確実な物事に近づかないことです。不確実な物事に近づかなければ不安を感じることはありません。

2つ目は不確実な状態を確実な状態にすることです。確実な状態になれば不安は消えます。不確実な状態を確実な状態にするには、実際にやってみるしかありません。このプロセスなしに不確実な物事を確実な物事にすることはできないのです。

エンジニアが意識しておくべきこと

私たちのような物作りに関わるエンジニアが意識しておかないといけないものは成果物(アウトプット)です。成果物とは最終的にユーザーから見えるものです。例えば、Webアプリケーションであれば最終的に画面に出力されるWebページです。

私たちエンジニアは、今やっている作業が成果物に繋がるのかを理解する必要があります。

IT系のエンジニアで多いのはコードの書き方や実装の細部にこだわってしまうことです。そこにこだわってしまうと時間を使い過ぎてしまいます。その結果、私たちが最終的に作っている成果物(ユーザーから見える画面)がおそろかになってしまいます。

コードの書き方や実装の細部を高めることは私たちが目指すゴールではありません。私たちが目指すゴールとは成果物を作り、ユーザーに届けることです。

Gradleのjavaプラグインとは

build.gradleでよく見かけるapply plugin: ‘java'という記述について説明します。この記述をするとgradleにjavaプラグインが追加されます。プラグインはgradleの機能を拡張するためのもので、javaプラグインを追加するとjavaに関連するタスクが使えるようになります。

何もプラグインを追加していないgradleタスクは以下の通りです。

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
$ gradle tasks

> Task :tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'gs-gradle'.
components - Displays the components produced by root project 'gs-gradle'. [incubating]
dependencies - Displays all dependencies declared in root project 'gs-gradle'.
dependencyInsight - Displays the insight into a specific dependency in root project 'gs-gradle'.
dependentComponents - Displays the dependent components of components in root project 'gs-gradle'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'gs-gradle'. [incubating]
projects - Displays the sub-projects of root project 'gs-gradle'.
properties - Displays the properties of root project 'gs-gradle'.
tasks - Displays the tasks runnable from root project 'gs-gradle'.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>


BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Javaプラグインを定義したあとのタスクは以下の通りです。

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
$ gradle tasks
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'gs-gradle'.
components - Displays the components produced by root project 'gs-gradle'. [incubating]
dependencies - Displays all dependencies declared in root project 'gs-gradle'.
dependencyInsight - Displays the insight into a specific dependency in root project 'gs-gradle'.
dependentComponents - Displays the dependent components of components in root project 'gs-gradle'. [incubating] help - Displays a help message.
model - Displays the configuration model of root project 'gs-gradle'. [incubating]
projects - Displays the sub-projects of root project 'gs-gradle'.
properties - Displays the properties of root project 'gs-gradle'.
tasks - Displays the tasks runnable from root project 'gs-gradle'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Rules
-----
Pattern: clean<TaskName>: Cleans the output files of a task.
Pattern: build<ConfigurationName>: Assembles the artifacts of a configuration.
Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>


BUILD SUCCESSFUL in 5s
1 actionable task: 1 executed

Build tasks、Documentation tasks、Verification tasksが追加されています。