テストを書き、実行する
本稿のタイトルは「JavaでWebサービスを作り続けるための戦略と戦術」です。作るだけなら作って終わりでいいのですが、作り続けるためにはテストの自動化が必須です。安心して作り続けるために、テストで守るのです。テストコードがほとんど無いまま数百Kstepまで膨れ上がったアプリケーションに後からテストコードを加えるのは、まさに技術的負債であり、いばらの道となります。
そこで我々は以下の通りテストの自動化を進めました。まずテストクラスを何本か試作してIntelliJでもコマンドラインのmvn testでも実行できることを確認。Springの設定をXMLではなくJavaConfigに乗り換えておいたのが功を奏し、単体テスト実行時だけミドルウェア接続関連の設定を自動的に切り替えることができました(XML方式ではif文すら難しい)。単体テストに共通で書かれているべき実装をabstractクラスにしたうえで、おおよその書き方をエンジニア全員にレクチャ。そこからジリジリとテストを書き進め、時には停滞しつつも1年、2年とたつうちにテスト数もテストカバレッジも上昇してきました。
そう、テストを書くだけでなく、それを実行し、その結果を保存し、昔と今とでどの程度違うのかわかるようにすることが大切です。JenkinsのJUnit実行結果集計機能でも、もちろんよいですが、SonarQubeサーバを立ててmvn test sonar:sonarでテストと同時にその結果をSonarQubeに投入しておくと便利です。「90日前から現在までで20Kstepもコードが増えたのに、テストの数も増えたのでカバレッジ率は下がるどころかむしろ上がっているのは良いことだ」といった傾向をビジュアルに把握できるようになります。
また、メインブランチに対するプルリクエストが作成されたことを検知して自動的にCIサーバがテストを実行し、失敗すればSlackでエンジニアに通知されます。これによってメインブランチにマージされる前に明らかにおかしな(もしマージされたらテストが失敗するような)コードを検出することができるようになりました。
ここまでやってようやく、「テストを書く」といった行動と目的、そしてその威力を、味方につけることができます。
「それでもテスト書いてないとか@t_wadaの前で同じこと言えんの?」
バージョンアップする
書いたそばから陳腐化するのはソフトウェアの宿命です。自分たちが作っているアプリケーションも、その土台としている各種のOSSも、その実行環境たるJDK/JREもです。陳腐化に対応する手段としてエンジニアが夢見がちな「ビッグ・リライト作戦」は、事業としての規模が大きくなった段階ではリスク、コスト、時間の折り合いがつかないことがほとんどです。そこで、ビズリーチシステムでは「少しずつバージョンアップする」戦略が選択されました。
以前、Java 8に全面バージョンアップした際、Javaの高い後方互換性のおかげでアプリケーションコードのほとんどはそのままか、ちょっとした調整のみで動作しました。しかし開発環境、特にEclipseでは「新しいIDEと付き合う」の節で述べた通り、秘伝のタレ的なプラグインを使っていたため、それをEclipse Luna(Java 8に対応した最初のEclipse)に移植するといった、やや不毛な作業をせざるを得ませんでした。デプロイ戦略の節で述べた組み込みTomcat方式に乗り換えることができればこの作業も不要だったのですが、tomcat-embedはバージョン7以降しか存在せず、当時のビズリーチシステムは通常型のTomcat 6でした。Java 8移行と組み込みTomcat(7系または8系)移行を同時に実施するリスクを取るか、少し回り道をしてでもJava 8移行を終えてからTomcat 8移行(同時に組み込み化)の順番にやる安全策か? 前者の方法ではエンジニアのPCだけでなくCI環境やサーバーインフラにも同時に影響がおよびます。その割にはメリットが少なすぎるため、安全な後者が選択されました。
Springフレームワーク本体も、3.0を使用していた状態から始まって、3.2、4.0、4.1、4.2、そして4.3まで段階でバージョンアップが進みました。3.0から4.2のような一足飛びのバージョンアップをなぜやらなかったかというと、できなかったからです。アプリケーションが依存する、さまざまなJavaライブラリとの関係もあるため、それらも同時にバージョンアップし、検証作業で出た不具合を少しずつ直すには、やはり少しずつバージョンアップするしかありませんでした。なお、書きためておいたテストコードのおかげで、各種のライブラリのバージョンアップが引き起こす、アプリケーションの予想外の動作不良を検知できたのは一度や二度ではありません。
こうして、ビズリーチシステムの2017年現在の主要コンポーネントは、Java 8、Spring 4.3、tomcat-embed 8.5など、全て最新鋭のバージョンが実戦投入されています。一部のサブシステムではSpring Bootの導入も進んでいます。spring-boot-starterあるいはspring-framework-bomによってライブラリの依存関係を考慮する手間が少しでも省けるのはよいことです。
さて、ここまでがんばってソフトウェアをバージョンアップしたことは、ビズリーチの事業にどのような貢献をもたらしたのでしょうか? それは「作り続けることができる状態を維持する」といった貢献です。弊社ではありませんが、この当然かつ曖昧な基本戦略を軽く見てしまった現場では、アノテーションやジェネリクスが使えない世代のJavaでメンテナンスを続けざるを得なくなったと風のうわさで聞きました。その状態で「作り続ける」ことはさまざまな意味で不可能であり、真綿で首を絞めるかのように事業に悪影響を与え続けることになるでしょう。
最新鋭の言語環境はやはり便利です。テストコードのif文の分岐網羅を気にするよりも、Stream APIでfilter()を使うほうが建設的です。古参のスーパーエンジニアが書いた、無名関数だらけの難解なクラスも、IntelliJ IDEA上において一括でラムダ式に自動置換すれば新人でも容易に理解できます。主要ライブラリのバージョンがほどよく新しければ、その使い方の情報も集めやすくなります。
最後に
繰り返しになりますが、本稿のタイトルは「JavaでWebサービスを作り続けるための戦略と戦術」です。デプロイ戦略、ログ出力、テスト、バージョンアップ、どれを取っても「作るため」の戦略と戦術とはまた一味違うことを感じていただければ幸いです。
Happy Hacking !