【Maven】ビルド時のプロファイルによってコードを分けてみる

# 茶番：Java でデバッグ時にのみメッセージを表示したい！！

皆さんはこのようなことを思ったことはありますか？私はあります. 

[PaperMC(Minecraft)](https://papermc.io) プラグインを Java で開発しているときに, デバッグ用の機能(e.g. SQLのログ出力, デバッグ用コマンド...) をどうしても実装する必要がありました.

## JVM のシステムプロパティを使ってみる

このような場合における最善の解決法は, JVM の実行時にプロパティ(`hoge.debug=true`等)を指定し,  Java で読み取って if で判定することでしょう\[要出典].
これであれば, 以下のような簡単なコードで実装できます.

```java

class Constants {
  public static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("hoge.debug"));
}

class MyPlugin extends javaPlugin {
  public void onEnable() {
    if (Constans.DEBUG) {
      // ここでデバッグ時の動作等々を差し込む。
    }
  }
}
```

ですが, この方法だと実行時にいちいち `-Dhoge.debug=true` のようなオプションを JVM に指定する必要があります. これは怠惰な私にとってかなり面倒な作業です.

この記事では, この面倒な作業を行わずに, デバッグ時のみメッセージを表示する方法について考えてみます.

## 前提

この記事では, 以下のような前提で話を進めていきます.

| 要件    | バージョン  | 摘要                         |
|:------|:-------|:---------------------------|
| Java  | 1.8+   |                            |
| Maven | 3.6.3+ | 多分他のバージョンでも多分動くと思います\[要出典] |

## TL;DR

結論として, Maven のビルドプロファイル機能と, プラグイン [templating-maven-plugin](https://www.mojohaus.org/templating-maven-plugin/) を使用して実現します.
この方法であれば, ビルド時に `-Pdebug` オプションを指定するだけで, デバッグ用の特別な jar を作成できます.

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <!-- 省略 -->

    <profiles>
        <profile>
            <id>debug</id>
            <properties>
                <!-- デバッグ時のみ以下のプロパティを設定し, Java に埋め込むようにする.-->
                <project.debug>true</project.debug>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>templating-maven-plugin</artifactId>
                        <version>1.0.0</version>
                        <executions>
                            <execution>
                                <id>filter</id>
                                <goals>
                                    <goal>filter-sources</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
    
    <!-- 省略 -->
</project>
```

`Constants.java` というファイルを以下のように作成します.

```java:src/main/java-templates/com/example/Constants.java

class Constants {
  public static final boolean DEBUG = "${project.debug}".equals("true");
}
```

:::note tip
`"${project.debug}".equals("true")` が冗長だと思った熟練 Java プログラマさんは[こちら](#余談projectdebugequalstrue-は冗長か)をご覧ください.
:::

```java:src/main/java/com/example/MyPlugin.java

import com.example.Constants;

class MyPlugin extends javaPlugin {
  public void onEnable() {
    if (Constans.DEBUG) {
      // ここでデバッグ時の動作等々を差し込む。
    }
  }
}
```

ビルド時には以下のように `-Pdebug` オプションを指定して実行します.

```bash

$ mvn clean package -Pdebug

```

上記の操作により, デバッグ時にのみ `Constants.DEBUG` が `true` になる jar ファイルが生成されます.
あとはこの定数を `if` で判定し, 振る舞いを変更するだけです.

## 方式を考える.

この問題を整理すると, 要件「実行時引数なしでデバッグ時のみ特別な処理を実行したい」を満たすにはどうするか, ということになります.
これを解決するために, 最初に以下のような方法を考えました.

1. `debug.properties` のようなファイルをビルドの成果物に含め, 実行時に読み込む.
2. 何らかの方法で, ビルド時に Java のコードをブロック単位で書き換える.
3. 何らかの方法で, デバッグ中かどうかの定数を作成し, 実行時に判定する. <- この記事で採用した方法です.

### 1. `debug.properties` のようなファイルをビルドの成果物に含め, 実行時に読み込む.

この方法は, 以下のようなファイルを `maven-resources-plugin` を使用してフィルタリングし, ビルドの成果物に含める方法です.

```properties :src/main/resources/debug.properties
project.debug=${project.debug}
```

```xml:pom.xml
<!-- 省略 -->

<properties>
    <project.debug>false</project.debug>
</properties>
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

```

具体的な Java コードは省略しますが, 具体的には `debug.properties` を読み込み `project.debug` の値を取得し, それが `true` であればデバッグ時のみ特別な処理を実行するというものです.

この方法のメリットは以下のようなものがあります.

+ 要件を全て満たしている
+ pom.xml の `project.debug` を書き換えるだけで, デバッグ時のみの特別な処理を実装できる

一方で, この方法には以下のようなデメリットがあります.

+ 実行時にプロパティのリソースを読み込む必要がある
+ プロパティのリソースを読み込むための（本番には関係ない）コードを書く必要がある。
+ 間違えて デバッグ用のプロパティを含んだ `pom.xml` をコミットしてしまう恐れがある。

考えてみるとメリットよりもデメリットのほうが重大かつ多いため, この方法は採用しませんでした.

### 2. 何らかの方法で, ビルド時に Java のコードをブロック単位で書き換える.

この方法は, C 系の条件付きコンパイルのように, 以下のような Java コードをビルド時に書き換える方法です. <- 夢幻も夢幻で, 実現はできませんでした.

```java:src/main/java/com/example/MyPlugin.java

// #if ${project.debug}

System.out.println("デバッグ時のみ実行される処理です！");

// #endif

```

この方法のメリットは以下のようなものがあります.

+ 要件を全て満たしている
+ デバッグ時のみ特別な処理を実装するためのコードを書く必要がない
+ 方式 1 のような, デバッグ用のコードを実行するか判定するためのコードを書く必要がない

一方で, この方法には以下のようなデメリットがあります.

+ Maven で Java のコードをブロック単位で書き換える方法が**無い**

...いかがでしたか？
今回は __**~Maven~**__ で *C* 系のような条件付きコンパイルを実現する方法を調べてみましたが, どうやら __**Maven**__ ではそのようなことはできないようです！
この記事が良いと思った方は, ぜひ LGTM ボタンを押してください！

### 3. 何らかの方法で, デバッグ中かどうかの定数を作成し, 実行時に判定する.

今回の記事で紹介する方法です.

この方法は, Maven Profile 機能を使用して以下のような Java コードをビルド時に書き換える方法です.

```java:src/main/java/com/example/Constants.java

public class Constants {
  public static final boolean DEBUG = "${project.debug}".equals("true");
}

```

この方法のメリットは以下のようなものがあります.

+ 要件を全て満たしている
+ デバッグ用のコードを実行するか判定するためのコードが最小で済む
+ 方式 1 のように, `pom.xml` を書き換える必要がない

一方で, この方法には以下のようなデメリットがあります.

+ ビルド時に 特別なオプションを指定する必要がある
  => 最近の IDE は, ビルドオプションを管理する機能があるので, １クリックでビルドオプションを指定できるようになっています.
  そのため, このデメリットはあまり気にならないと思います.

以下のセクションより, この方法の具体的な手順を紹介します.

## Maven Build Profile とは

[Maven 公式(英語)](https://maven.apache.org/guides/introduction/introduction-to-profiles.html#:~:text=Profiles%20are%20specified,and%20production%20environments)の説明曰く, 

> プロファイルは、POM自体で利用可能な要素のサブセット（プラス1つの追加セクション）を使用して指定され、様々な方法のいずれかでトリガーされます。
> ビルド時にPOMを変更し、ターゲット環境のセットに対して同等だが異なるパラメータを与えるために、
> 補完的に使用することを意図しています（たとえば、開発環境、テスト環境、本番環境におけるappserverルートのパスを提供する）。

だそうです.
つまり, Maven Profile は, ビルド時に POM を変更する(今回は CLI で指定する)ことで, 単一の POM から複数の環境に対応できるようにする機能です.

この記事では, この機能を~~悪用~~利用して, デバッグ時にのみ実行される特別な処理を実装します。

## 実装

### 1. Maven プロジェクトを用意する.

詳しくは割愛しますが, 新規の Maven プロジェクトまたは 既存の Maven プロジェクトを用意してください.

### 2. ビルドプロファイルを定義する。

`pom.xml` にビルドプロファイルを定義します.
プロファイルには `project.debug` というプロパティを定義し, 値を `true` にします.

```xml:pom.xml
<project> 

  <!-- 省略 -->
  
  <profiles>
    <profile>
      <id>debug</id>
      <properties>
        <project.debug>true</project.debug>
      </properties>
    </profile>
  </profiles>
  
  <!-- 省略 -->

</project>
```


### 3. [templating-maven-plugin](https://www.mojohaus.org/templating-maven-plugin/) を構成する

この方式を実現するためには, ビルド時に Java ソースを（自動で）書き換える必要があります.
ちょこっと調べたところ, [templating-maven-plugin](https://www.mojohaus.org/templating-maven-plugin/) というプラグインを使用すれば POM で定義されたプロパティを Java ソースに埋め込めることがわかりました.
今回はこれを使用して, ビルド時に Java ソースを書き換えます.

ステップ 2 で定義したプロファイルに `templating-maven-plugin` を追加します.

```xml:pom.xml

<!-- 省略 -->

<profile>
  <id>debug</id>
  <properties>
    <project.debug>true</project.debug>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>templating-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>
            <id>filter-sources</id>
            <goals>
              <goal>filter-sources</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</profile>

<!-- 省略 -->

```

実行のゴールを `filter-sources` に設定することで, コンパイル前に Java ソースを書き換えられます.

### 4. 置換対象になる Java ソースを用意する

`templating-maven-plugin` はデフォルトでは `src/main/java-templates` 以下のファイルを置換対象としています.
そのため, `src/main/java-templates` 以下に `Constants.java` を用意します.

```java:src/main/java-templates/Constants.java

public class Constants {
  public static final boolean DEBUG = "${project.debug}".equals("true");
}

```

上記のソースをビルドしデコンパイルすると, 以下のような結果が得られ,`"${project.debug}".equals("true")` が 置換されていることがわかります.

```java:Constants.java

public class Constants {
  public static final boolean DEBUG = "true".equals("true");
}

```

:::note tip
`"${project.debug}".equals("true")` が冗長だと思った熟練 Java プログラマさんは[こちら](#余談projectdebugequalstrue-は冗長か)をご覧ください.
:::

### 5. ビルドする

#### デバッグ時

`mvn clean package -Pdebug` を実行すると, ソースの置換が行われ, 上記のように `DEBUG` が `true` になります.

#### 本番環境時

`mvn clean package` を実行すると, ソースの置換は行われず, `DEBUG` は `${project.debug}` のままになります.
そのため, 本番ではデバッグ用の処理は実行されません.

### 6. 糸冬 了

デバッグビルドと本番ビルドを比べ, 正常に動作しているか検証してください。

お疲れ様でした。

## まとめ

今回は, Maven のプロファイルを使用して, デバッグ時のみ特別な処理を実装する方法を紹介しました.
もしかしたらこれよりも良い方法があるかもしれません. なにかご存知の方はコメントぜひ教えてください.

ここまで読んでいただきありがとうございました.

## 余談：`"${project.debug}".equals("true")` は冗長か？

中には `"${project.debug}".equals("true")` は冗長だと思った熟練 Java プログラマさんもいらっしゃるかもしれません.
置換対象を `public static final boolean DEBUG = ${project.debug};` とすればよいですし,   
実際に `"${project.debug}"` は 置換されると `true` になります.
そのため, ビルドの成果物は `public static final boolean DEBUG = true;` となります.
これなら余計なオーバヘッドがないので, 一見こちらのほうが良いと思います.

ですが, この方法には欠点があります.

上記のようにした場合, エディタで編集する置換前のコードは実際には有効な Java コードではなく, 無効な式です.
多くのエディタはこのようなコードを不正とみなし, エラーを表示します. これは余計なエラーであり開発効率を下げる原因にもなります.

私は, このような少し冗長なコードは許容されるべきだと思います.
(もちろん, このようなコードを書くときはコメントを残すなどしたり, チームでルールを決めておくべきです.)

## 参考

- [Maven - Introduction to Build Profiles](https://maven.apache.org/guides/introduction/introduction-to-profiles.html)
- [MojoHaus - templating-maven-plugin](https://www.mojohaus.org/templating-maven-plugin/)