Macで複数バージョのJavaを切り替えたい

JDK8でたよーって、ことで色々触っている今日このごろ。 やっぱラムダ式とか使えるようになって大分普通になった感じ。

で、ここで問題になるのがまだ全面的にJava8に移行したわけでもないのでJava7とどう共存して環境を切り替えるか。たぶん、Java使っている人の9割9分9毛くらいはIDE使ってると思うので、そこはほぼ問題無し。 ※ 以前eclipseが重たいという理由でメモ帳使ってる業のモノが居ましたが...

ただ、mvnコマンド打つときとか、アプリを起動するときとか色々環境変数レベルで変更したいケースもしばしば有ります。Linuxなんかだったらalternative-javaでサクッと変えれるし、最近のLL系だったらこの辺を支援するツールがだいたいある。rvmとかそれ系。

しかし、MacOSJavaが合わさるときその道はないのです。。。 まあ、需要か。orz

無いなら作れが世の基本なので、とりあえず作ってみました。と言っても全面的に下記の記事のやり方をaliasコマンドで実行するだけど。

OSXでJavaのバージョンを切り替える

そんなこんなで出来たものは下記。

github - Alternative-Java for Mac

使い方

使い方という程でもないのですが、適当なディレクトリに上記のスクリプトをcloneして、下記の様なaliasを設定します。.zshrc.mineとか.bashrcとかその辺に追加すると良いかと。

  alias alt-java='source {YOUR_INSTALL_PATH}/alternative-java-for-mac/alternative-java-for-mac.sh'

引数にバージョン付ける感じでサクサク変えれます。

% alt-java               
Missing arguments.
Usage: alternative-java.sh [1.6|1.7|1.8]

% alt-java 1.7
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

% alt-java 1.8
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

% alt-java 1.9
Unknown Java version.
Usage: alternative-java.sh [1.6|1.7|1.8]

これで色々と捗るぜ! じゅるり。

とはいえ、これはグローバルは変えないので、あくまで実行したターミナルというかシェルだけに影響が限定されます。

個人的には開発や検証で使う想定なのでこの機能で十分だし、rvmとか本物のaltanativeみたく本気は出さなくていいよね。

それではHappy Hacking!

オカルトから科学へ - SonarQubeで静的コード解析を始めよう

ちなみに、タイトルは半分釣りというか、個人に依存したオカルトから、誰がやっても同じになる科学的な方法論をもっと入れてこう、って意図です。

もちろん、そういった部分を全部なくすのでは無くうまく組み合わせるのが大事。魔術と科学が交わるときには何かが起こるのです!(これが言いたかっただけ)

なんで静的コード解析?

チームやプロジェクトでコードレビューをしていますか?

多くの開発プロセスではコードレビューまたはペアプロが実施されていると思います。しかし、人の目で見てるだけでは不十分であり非効率です。

人間によるレビューは経験や勘、あるいはレビューアの好みに依存してしまう部分があります。 そのため、違う人がやれば違う結果になることはしばしば有ります。

また、そういった自体を防ぐためにレビューチェックシートを使う場合もあるでしょう。その場合の多くは退屈な作業を人がやることになります。 数が増えれば増えるほどミスをする可能性も増えてきます。

そんな時に役立つのがFindBugsやPMD, StyleCopなどの静的コード解析ツールです。 これらをIDEやJenkinsなどのCI環境に入れることで、ツールによる差異はありますが概ね下記の点を見つけることが出来ます。

  • 命名規則やインデントなどのスタイルが適切か
  • 一般的にバグの原因になり易い危険なコードはないか
  • コードクローンなど品質の問題になりやすいコードはないか

という感じで色々と見つけてくれます。正直、この手のツールと同じ土俵で人間が勝負するのは無理です。精度で負けます。

なので、誰でも出来る部分は科学の産物たる機械にまかせて、人間にしか出来ない観点でレビューをするのがベストです。導入することで以下の効果が期待できます。

  • レビューアに依存しない定量的なレビュー
  • コード量やレビューアの忙しさに依存せずに素早く確実にレビュー結果を得られる

個人的にはサーバサイドにレポートが出せてグラフィカルに内容を共有出来るSonarQube(旧名Sonar)を使ってるので、その紹介と導入方法を共有したいと思います。

SonarQubeとは?

まずは、ざっくりしたSonarQubeの紹介です。

かつてはSonarと言われるものでFindbugなどJavaのコード解析ラッパーから始まったツールです。

f:id:pascal256:20140211132149j:plain

Pluginを利用することでC#PHP, JavaScriptAndroidなど多くの環境をサポートします。詳細は公式参照(Plugin Library - SonarQube - Codehaus)

Sonarの気に入ってるところとしては

  • グラフィカルで分かりやすいUI。複数のツールのメトリクスを集約して見れる
  • タイムラインでメトリクスの増減を見れる
  • 導入が簡単。また、個々の開発メンバーが導入する必要は無いので、負担が少ない
  • プラグインやRESTベースのAPIで拡張可能

があります。特にタイムラインで見れるので、コードメトリクスがどう変化していったかを追うことができるので、すでに運用しているコードにも適用しやすいのが便利でした。

Sonarのインストール

インストールはかなり簡単です。

wget http://dist.sonar.codehaus.org/sonarqube-4.1.1.zip
unzip ~/Downloads/sonarqube-4.1.1.zip
cd sonarqube-4.1.1/
./bin/macosx-universal-64/sonar.sh start

起動したら

http://localhost:9000/

にアクセス。これで完了。ね、簡単でしょ?

本格的に使うにはDBをMySQLに変えるとか、Apacheと連携させるとかすると良いと思いますけど、まずは動かすだけならこれで大丈夫。

Sonarの使い方

JavaMaven環境を作ってるならゴールをsonar:sonarにするだけです。ブランチ名の指定とかしたいので、普段は以下のようなオプションにしています。

これをJenkinsに登録するだけで、定期的にSonarのレポートが作られるようになります。

# デフォルト名
mvn sonar:sonar -D.sonar.forceAnalysis=true -Dsonar.host.url=http://localhost:9000/

# ブランチ名とか指定したいとき
mvn sonar:sonar -D.sonar.forceAnalysis=true -Dsonar.host.url=http://localhost:9000/ -Dsonar.branch=branch-name

Sonarを使った開発プロセス

やりかたは色いろあると思いますが、うちでは主に以下のようなやり方を実践しています。

  1. 開発開始時にmasterより開発ブランチを作成
  2. 各開発ブランチをJenkinsに登録しCI環境に登録毎時でブランチ専用のSonar画面を作成
  3. 開発メンバーは随時、Sonarの画面を確認。問題が上がれば修正
  4. 開発完了時にレビューアはSonarの指摘が無いことを確認した上で、レビューを行い開発メンバにFBする
  5. FBの修正及び再レビューが終われば、リリースブランチにマージ

開発の規模感としては並行して5から10案件程度は動いており、同じ数だけ開発ブランチがあります。これより大きい規模の開発だと、また違った方法論が必要な気がしますが、同程度以下ならある程度同じようなプロセスでいけると思います。

この運用でポイントとなるのは、3のSonarの画面を見て問題があれば修正する、という部分です。

まず、基本的なサイクルとしては指摘を見て修正すれば良いのですが、ルールベースで指摘しているという仕組み上の問題で、設計上は問題なかったりする指摘やら誤検知がそこそこがあります。

これをレビューアなど適切に判断出来る人にエスカレーションした上で、問題なしのチェック等を入れます。そうすると指摘上から消えるので、この部分の指摘は無視して良いとか人間が毎回判断する必要はありません。

残念ながらこの設定がブランチで共有出来ないので、現在は新規でブランチを作る度に手動でコピーしています。 この辺、良い方法があったら、ぜひ教えて下さい。

また、新規コードから適用してるなら指摘を全部0にすれば良いので簡単ですが、すでに運用段階に入ってる場合など、過去の指摘を全て直すのは現実的ではありません。

この場合は、masterの指摘数と比較して、今回の開発等で増えないことを指標とすると良いです。もちろん、たまたま修正が可能だったから減るって分には良いことなので褒める文化に。

Sonarはそれに適した機能があってTimeMachineで比較することが出来る。これを使うことで運用中のコードであっても、適用していくことが比較的簡単に出来ます。

f:id:pascal256:20140211132258j:plain

いずれにしても"指摘を増やさない"という運用が大事で、例外は原則認めてはいけない。そうしないとみんな守らなくなるからね。元のルールが厳しすぎるとかなら適度にカスタマイズすればいいし、うちもそうしてる。

あと、Issuee以外のDuplicationsとかComplexityは今はルールにしてない。この辺のルールを入れると縛りが強くなりすぎる気がしたので、あくまで開発者の参考レベル。

そして、怒る人重要。指摘を増やした状態で4や5にプロセスが行ってしまった場合は、きちんと怒る。必要に応じてマネージャとかもCCにして、全体周知して怒る。嫌われ役になっちゃいがちだけど、最初はみんな慣れてないから、ついやっちゃうし、新規メンバーもしてしまいがちなので、文化としてやっちゃダメなこと、とみんなが認識しきれるまでは、きちんと怒り役の人が居た方がうまく回るかな。

プラグインAPI

拡張性の高さもSonarの良いところです。単に対応している言語やテストツールを増やすだけではなく、PDF出力をはじめレポート類も充実しています。

Plugin Library - SonarQube - Codehaus

また、RESTベースのAPIで簡単に情報を取得したりも出来るので、独自のツールやExcelへの連携も簡単です。

たとえば、プロジェクト毎のIssuesとコードカバレッジの一覧は以下のURL。

http://localhost:9000/api/resources?metrics=coverage,violations,blocker_violations,critical_violations,major_violations,minor_violations,info_violations

f:id:pascal256:20140211132325j:plain

APIのドキュメントはこちら(Web Service API - SonarQube - Codehaus)を参考。

うちではこれを使って複数のブランチのタイムラインを1画面で見れるツールを作っています。コード品質が急激に悪くなったチームとかあれば、プロジェクト進捗にも影響するだろうから警戒する必要がありますし。

こういった拡張がさくっと書けるのは良いところです。

まとめ

色々書いてみましたが、この手のツールは単純に入れるだけじゃなくて、運用プロセスを作って文化まで昇格することが大事です。

とりあえず、勝手に導入して技術的な課題をクリアしたら現場メンバに周知。ある程度みんなが慣れたらマネージャとか偉い人系もレポートラインに加えて、進めるのが個人的には好きです。許可を取る前にやってしまえモデル。

導入も簡単だし、まずは入れてみて、自分たちの開発プロセスに合うかどうかを確認してみるのもいいと思います。

実際うちでは入れる前に比べて劇的にコード品質がマシになって、ヒューマンレビューではくだらないこと指摘せずに具体的なレビューに集中できるようになったし、効果があるケースも多いんじゃないかと。

それでは、Happy Hcking!

参考:

Javaを使うメリットはありますか? はい、それはもちろんあるに決まってます!

/.Jに聞け:Javaを使うメリットは? | スラッシュドット・ジャパン デベロッパー

え、ありますよね? Webアプリに限っても。 というか、上げてるデメリットが現代的じゃないなぁ...

個人的には少数精鋭チームを作れてないならJavaはかなり有りな選択だと思っています。

Java使ってるメンバーがスキルが低いのではなく、LL系で高品質なものを作るのにスキルが必要で、例えスキルがあっても多人数開発にはあまり向かない、という意図

というわけで、自分が思うLL系言語ではなく、JavaでWebアプリを開発するメリットを書いてみます。

静的型づけ言語

HaskellとかOCamlとかScalaとか、その辺の超強力な型付け言語から見ると弱いですが、多くのLLと違い静的型付けな事が特徴です。

これは型というメタ情報を言語仕様に明確に取り込んでいるということです。以下のメリットがあります。

  • 人間が読みやすい
  • コンピュータが読みやすい

動的型付けだと何が入ってるか不明のオブジェクトがあります。

メソッドの引数に入る値がフリーダムだったり、逆に戻り値がintだったりオブジェクトだったり。

型をインターフェースに定義することで、この辺りは非常に読みやすくなります。曖昧な書き方をすれば、コンパイラが指摘しますし。

初期のJavaだと記事で上げられてるデメリットにある通り、キャストが増えて結局型安全じゃないケースも多々有りましたが、Java5以降はGenericsがあるので、ほぼキャストに出くわすことは無いでしょう。

他にもJavadocを書くときに型情報を記載してなくても自動的に入れてくれるという地味に便利な機能もあります。

パフォーマンスも付け加えたくなりますが、JITが優秀なら型の有る無しが決定差にならないらしいですし、それ以外のメリットに注目してみました。

IDEが強力

EclipseNetBeans, IntelliJと言ったIDEが結構強力。 静的型付ということもあって、リファクタリング機能も強力にサポートしてるし、コードアシストも素早くかつ正確に動作します。

リファクタリング機能が安心して使えると、トライ&エラーの開発がしやすくなるので開発スピードもグッとアップしますね。

gitやsvnといったバージョン管理、GlassFishやJettyなどのアプリケーションコンテナ、antやmavenやgradleといったビルドツールとの連携/統合もバッチリ。

IDEとアプリケーションコンテナを連携してホットデプロイすれば、コンパイルが必要という点もほぼ意識することはないかと思います。

IDEなんて嫌だ! vimemacsが使いたい! って人も居るでしょうけど、統一した環境をサクっと作れるのは開発現場には有用なことだと思います。

静的解析が強力

Javaは静的型付け言語なのでコンピュータが読みやすいという特徴と、長年開発の第一線で利用されてきたこと、そしてアカデミックな研究に使われることも多いという理由で静的解析ツールが豊富です。

コード品質を保ち、コードレビューのコストを減らすことが出来るのでとりあえず入れといたほうが良いレベルの仕組みです。なぜなら、コードの綺麗さは中長期的な生産性のために必須な要素だからです。

とはいえ、レビューをきちんとするのはコスト的に辛くてやってない、ってケースは結構あるんじゃないでしょうか?

JavaだとFindbugやPMDをはじめ様々な解析ツールが揃っていて、最低限のコード品質を保つのが非常に簡単です。コードクローンや命名規約ミス、危険な代入などレビューチェックシートで見つかる程度のことは見つけてくれます。

また、Veracodeとかの商用製品だとセキュリティ観点でも静的解析を行いSQLインジェクションXSS, CSRFの疑い等を検出してくれます。

もちろん、RubyPHPにもそういったものは有りますが、一日の長がありJavaのレベルまでは達していない認識です。

コーディング規約の方言が少ない

Javaは全面的にSun時代のコーディング規約をベースとしたものが使われていてコーディング規約に関してJavaエンジニア全員がある程度共通の認識を持っています。

そのため、モジュールごとにCamelCaseだったりSNAKEだったりしないです。もちろん完全に方言というか、それぞれの規約が無いわけではありませんが、 かなり小さいので、厳密なルール無くてもある程度統一でき、宗教戦争も起きにくいです。

それなりに高い生産性

めんどくさい言語、冗長な言語として評判なJavaですが、そこまで生産性の悪い言語ではありません。

オブジェクト指向言語としての基本的な機能は揃えており、標準クラスライブラリもどんどん強化されています。 例えば、元記事で言及されいているデメリットですが

  • キャスト(1.5以降はGenericsがあるので通常不要)
  • 正規表現(1.4からできる)
  • 連想配列処理
  • 文字列フォーマット(1.5からできる)

連想配列リテラルじゃないので少し冗長なことくらいしか今は残っていません。

加えて1.7ではFilesでファイル処理が簡単になり、1.8からは待望の関数オブジェクトとStreamAPI、型推論、そして日付周りのライブラリ改修が入ります。

この辺が導入されれば、言語機能としても大きくLL系と見劣りすることはないでしょう。 また、リフレクションやAPTで基本的メタプログラミングを行うことも可能です。

加えて、JavaEEやSpringを使えば、Webアプリケーションを組むのはさほど難しく無いかと思います。

高い上位互換

Javaは言語としての安定性が非常に高いです。

バージョンアップにより非互換の言語仕様の変更が入ることはほぼ無く、リビルドすらしなくても結構そのまま動くことは多いです。 一方、LLでWebでよく使われるPHPRubyは結構ひどいです(直近のバージョンアップは比較的マシですが)。当たり前のようにAPI等が統廃合されます。

自分たちの書いたコードだけではなく、FWやライブラリが影響を受ける場合もあり、おいそれとは上げれなくなります。 なら上げなければいい、とか言われそうですが、世の中にはセキュリティアップデートというものが有ります。適度に最新化しないと致命的なセキュリティを放置することになってしまいます。

こういった点は、4,5年以上の運用には非常に重要です。

モニタリングのしやすさ

.NET系もそうらしいですが、JMXによるオンライン監視やスレッドダンプやメモリダンプ、GCログといったリソースモニタリングや障害調査のための仕組みが非常に強力です。

プロファイラもVisualVMやJava Mission Controlなど強力なものがデフォルトでついています。

最近だとFlightRecorderやENdoSnipeなど運用時に低負荷でプロファイラ級の情報を取得できるものもあります。GCのタイミングからホットメソッドまで分かるでの、問題発生時の切り分けがずいぶん楽になります。

LLでも使えるNew Relicとかも登場しており、Javaだけの特徴ではないですが、それらを含めた運用ノウハウの蓄積はまだ勝ってると思います。

まとめ

色々書きましたが、下記の2点にメリットを集約できると思います

  • 言語機能や開発環境によりコード品質や生産性の底上げがしやすいため人の確保が比較的容易
  • 言語の安定性やモニタリングなど運用時に都合の良い部分が多い

別段Javaが最高の言語だとはちっとも思いません。ScalaとかRubyとかの方が普通に好きです。

Javaのデメリットも多くあり、ScalaRubyほどの生産性はないですし、Haskellほどの安全性もありません。C++のようなパフォーマンスもないです。 メモリをよく食いますし、インメモリセッションレプリケーションを使うなら尚更GCには気をつけねばなりません。バランサ設定をスティッキーにする必要があるのも面倒です。

とはいえ、仕事で中長期的に使うなら、先に上げた2つのメリットは捨てがたいものなので、個人的にはまだまだJavaという選択肢「も」ありだな~、と思っています。

それではHappy Hacking!

参考:

ちょっとだけ追記

想像以上にブクマもらってびっくりな今日このごろです。

で、コメントとか見てて、ちょっと補足した方がいいな、と感じたこともあったので少し追記します。

それはJavaじゃなくてJVMの特徴

障害情報、モニタリング系とかはそうですね。だから私も個人的にはScala押しです。ただ、JVMの機能を使えること「も」Javaの特徴なので、メリットして評価項目には入るかと思います。

逆に、RubyScalaもGroovyも言語の安定性/静的解析ツールの品質という点ではまだJavaに及んでないと感じてるので、単純置き換えできるものじゃないかな、と。

そのメリットは◯◯言語使ったほうが得られる。

そうかもしれないです。少なくともJavaが常に最適解になるわけでは決して無いです。 当たり前ですが適材適所あります。手持ちの札(人、モノ、金、時間)によっても同じシステム要件でも変わるでしょう。

C#なんかは言語的には同じ方向でかつJavaより機能的なので、代替候補としては最有力ですね。 ただ、個人的にはWindowsサーバの適切な運用ノウハウを持って無いので、Web系とかには採用したくないです。とはいえ、海外では一般的ですし、Azureも出たので国内でも良い選択になるかもですね。

その上で、この記事自体が「Java使うメリットはあるの?」という問い対する回答なので、頭ごなしな否定を(しかも古い情報で)してほしくないなぁ、という意図なので記載したメリットを他の言語で受けれない、という意味では全く無いです。

言語毎、ユースケース毎に採用を判断するための様々な特徴があるので、他言語で似たような観点の記事を書いてみたいですし、別の人が書いたいろんな言語や環境の記事を見てみたいですねー。

あなたの職業は何? RPG風のエンジニアの分類を作ってみた

ひとくちにエンジニアと言っても、いろんなタイプの人が居て、性格に応じたロールがある。 たとえば「◯◯さんはやたら設計に拘って動きが遅いけどバグは出さない」とか「◯◯さんは手が早いけど、よくバグも作りこむ」とか「◯◯さんはいつもCIと叫んでる」とか。

こういったそれぞれの性格のメンバーが自分にあったロールをこなすことによって、良い開発プロセス/運用プロセスってのは出来ていくと思う。

そういったロールを自分なりに分類してDQ風の職業にマッピングしてみた。 というのも以前飲み会でDQ風にエンジニアのスキルマップを作ってる人がいて、なんか面白そうだったから真似してみただけなんだけどw

偉大なる先達の「プログラマの区分」よりも、抽象度を上げてるので、プログラマだけじゃなくて、インフラ屋さんとかもそれなりに当てはまるはず。

僧侶と魔法使いの区分が分かりづらいけど、 イメージとしては品質を上げるための活動と生産性を上げるための活動の違いかな。もっとも、それらは密接に相関があって切り分けしづらいのだけど。

あと、複数の職業を兼任してるとかは普通にあるとおもう。

職業

戦士
  • 戦闘(開発/運用)の要
  • 重厚な設計、検証された方法を好む
  • 守りは固いが、素早さは低め
武道家
  • 戦闘(開発/運用)の要
  • 軽量な設計、新しい方法を好む
  • 素早さは高いが、守りは低め
僧侶
  • 品質を維持するための魔法を使う
  • レビューやテスト、静的コード解析やCI/CDを好む
  • 盗賊と連携することでより強力な魔法を使うことができる
魔法使い
  • 強力な魔法を操り、戦士や武道家を支援する
  • フレームワークや共通ライブラリを弄ることが多い
  • 盗賊と連携することでより強力な魔法を使うことができる
盗賊
  • 他所のベスト・プラクティスを盗んでくる
  • 基本的に他の職業と兼任してることが多い
  • 外部のイベントによく参加している
商人
  • お金の計算が得意。外部からの調達も行う
  • ROIにもとづき自分たちが"しないこと"を計算できる
  • 限られたリソースを最適なところに投入するために重要な職業

 振り返り

さて、せっかく作ってみたので自己分析をしてみる。

武道家(lv 6), 盗賊(lv 5), 魔法使い(lv 4), 僧侶(lv 3)

Lv 10を一つの基準にするとだいたいこんな感じかな?  いろんな職業についてるので職業技能はそれなりに持ってるんだけど、それぞれのレベルは低いイメージ。

今後の成長プランとして、武闘家とかのレベルをもっと上げるべきか、いっそのこと商人を採って技能を増やすべきかは悩ましいところ。

ちなみにこの職業はバランスよくパーティが構築されている必要があると思う。 一つのパーティに攻撃職、魔法系職、商人が居て、盗賊は全員がLv1以上とってるとかがバランスいい気がする。 戦士や武道家だけでパーティー組むとか縛りプレイの粋だよね?

ただ、現実はクソゲーと言う言葉があり、縛りプレイ大好きな日本人は「戦士、武道家、武道家、武道家」というパーティもよくあるとか。 今いるチームもこの構造な感じで、ギルド所属? の商人、魔法使い、僧侶が複数のパーティーをまとめて面倒見てる感じ。そして、輪をかけて盗賊が少ない。。。

デザインパターンじゃ無いけど、こういったチームメンバの傾向やロールに名前を付けることで、「盗賊が足らない」とか「戦士のスキルを磨きたい」とかチーム戦略や自分の成長プランを考えなおすのに参考になるんじゃないかと思う。

人事系の部署とかはこんな思いつきで作ったようなのじゃなくてちゃんとしたの持ってるだろうけど、自己分析ならこのくらい馴染みのある奴のが自分は好きかな。

でも、この手のロールをファンタジー系職業で表すのは定番といえば定番なので、エンジニアのスキルセットを「ツンデレ」とか「幼なじみ」とかヒロイン属性風に表すとかのが頭がおかしくて良かったかもしれない。誰か作って!

それでは、Happy Hacking!

ドキュメントからコードへ

最近「ドキュメントからコードへ」というのをキーワードに考えています。 その辺に関してつらつらと書いてみました。例のごとく英語で書いた資料より日本語の方が詳しいよ><

「ドキュメントからコードへ」って?

例えばいくつかの種類のドキュメントは下記のようにコードに置き換えることが出来ます。

詳細設計書、テスト仕様書 => 自動テスト

サーバの構成定義書 => サーバテストツール

  • serverspec

インストールマニュアル => プロビジョニングツール系

  • chef/puppet

運用手順書 => デプロイツール, JOB管理ツール, CI ツール

  • Capystrano
  • Fabric
  • Jenkins

他にも要件定義や基本設計をAlloy等で作るのもこの範疇に入るかもしれないです。 ドキュメントを自動ツール系のスクリプト定義に置き換えることが「ドキュメントからコード」となります

そもそもなんで必要?

さて、そもそもなにがうれしいんでしょうか? まず思いつくのはコスト削減。 自動化をすることで手作業でやっていたことがボタンひとつでできるようになります。 これはとてもわかり易いですね。では、本当にそれだけが価値でしょうか?

「ドキュメントからコードへ」に移行することで真に得られるもの、それは作業コストの削減ではなく信頼性です。

ここで言う信頼性は大きく分けて下記の3つです。

  • プロダクトの信頼性
  • オペレーションの信頼性
  • ドキュメントの信頼性

プロダクトの信頼性とはシステムのコードの信頼性です。 自動テストツールを入れることで、今まで目視で確認していた様々な項目を簡単に再実行することが出来ます。 これはリグレッションテストをするにあたって強力な武器です。リグレッションテストはデグレや修正の影響を把握するための基本的な方法です。 これによって、コードの品質が保てるというのは分かりやすいかと思います。

プロダクトだけではなく、オペレーションの信頼性もコード化で担保出来ます。 日常の些細な業務から、本番へのデプロイを始め、システム運用にはオペレーションがかかせません。 しかし、それを実施するのが人間であるかぎりミスを犯します。それは確率的な話なので、頻繁にやる作業ほどミスをする可能性が高いといえるでしょう。 一方、プログラムは間違えません。プログラムは書いたとおりには動くので、間違ってなければ正しく動きますし、修正すれば同じ失敗もしません。 これはプロダクトの品質にかなり大きな影響を与えます。

個人的にはもう一点重要な要素としてドキュメントの信頼性向上があります。無論ここで言ってるドキュメントとはコードです。

ドキュメントは多くの場合間違っています。理由は様々です。ドキュメントを作った時点と変わった修正が反映していない。元のドキュメントにミスがある。そもそもドキュメントを見て作っていない。 ドキュメントは容易に壊れます。ドキュメント管理が脆弱な組織では、ドキュメントを見ても結局信用ができないのでコードも見るということはよく有ります。

最後に信用できるのはコードだけ、です。その信頼性の差が何でしょうか? それは実際に動いて使われていることです。 もし、間違えていれば動かない。あるいは要件レベルでバグっていたとしても今動いているものは書いてあるとおりだ、ということが論理的に保証されます。 これは運用をするにあたって大きな安心感を与えてくれます。この1点のためだけでも、仮にドキュメントより作成コストがかかってもコードに落とすべきだと考えています。

まとめ

TDDやBDD, DevOpsで言われるインフラのコード化, Jenkinsなどによるオペレーションの自動化、様々な手法がありますが、作業手順書を無くし自動化することは単なる作業のコスト削減ではありません。 プロダクトの信頼性、オペレーションの信頼性、そしてそれらを支援するドキュメントの信頼性。これらを現実的なコストで実現できるようになります。 つまり、ここで削減してるコストは既存の作業コストではなく、今まで高くて支払えていなかった98%の信頼性を99%にするためのコストです。

作業コストの削減だと割に合わないと思ってた人がもしいましたら、信頼性向上のためという考え方でぜひ実施してみましょう!

それでは、Happy Hacking!

テスタブルコードの書き方 - 基本戦略編

今のチームにテストコードの導入を本格的にしようと思ってるので、思考の整理がてらメモ。内容は初学者向け。

テストの必要性をとくのは比較的簡単である程度できた。既存のレガシーコードはとりあえず忘れることに(特定メンバーでプロジェクト的に実施)。

というわけで、新規コードはみんなテスト書いてね! 

と、これだけでテストを書いてくれるでしょうか? 答えは否でした。

原因として自分の書きたいコードをどうテストすれば良いかわからないというものです。

新規コードなのでテストをしやすいように設計をすれば良いだけです。TDDはそれを支援してくれる有効な手法です。 しかし、テストが無い環境に慣れた人間はそもそもテスタブルコードを見慣れてません。なので、自分の書きたい実装をどう書けばテストが書きやすくなるかが分からないのでテストコードが非常に複雑になったり、立ち止まったりしてしまいます。

なので、テスタブルコードの基本戦略と事例集をまとめてみました。

基本戦略

  • 戻り値が無いコードを書かない
  • 同じ引数では同じ結果を返す
  • I/Oはそれに専念する
  • 依存性は注入できるようにする

戻り値が無いコードを書かない

Javaで言えばvoid型のメソッドです。なんというかテストを書くことを意識してないコードの典型例です。

戻り値が無いコードは後述するI/Oを含んだコードである可能性が高く、I/Oのテストはコストが高いものです。 なので原則的には単純に値を返すシンプルな関数を実装するのがコツです。

同じ引数では同じ結果を返す

例えば日付、例えば乱数。こういった実行される度に結果が変わる値って存在しますね?

こういった値をロジックの内部で使っていると同じ引数でも毎回違う結果になってしまいテストを記述出来ません。 なのでこの手の値は関数の引数にとったり、後述の依存性の注入を使うことでロジックから切り離してしまいましょう。 こうすることで、ロジック自体は簡潔にテストを書くことが可能になります。

I/Oはそれに専念する

ファイルの入出力、DBアクセス、ネットワークアクセス、標準入出力、ログ、画面、こういったI/Oはテストの敵です!

これらをテストすることは不可能ではありませんが、記述量的にも実行速度的にもコストが高いテストになりがちです。また、DBやネットワークの場合はテスト向けの環境を用意するのも頭を悩ませる問題です。 こういった部分はなるべく減らして、場合によってはテストコードではなく別の方法で実施しましょう。そのためにはI/Oとロジックを分離してロジックの部分だけをUTできるように分離するのが重要です。

依存性は注入できるようにしよう

依存性ってなに? とか言われそうですが、ようは前述してるI/Oや日付、乱数なんかを取り扱うオブジェクトをクラス内部でnew(初期化)せずにコンストラクタやsetterで外から設定できるようにしよう、という考え方です。

DIコンテナとか使うと、こういった設計でプロダクトコードを書きやすくなりますが、必須ではないです。 逆にDIコンテナ使ってても、外から設定できるようにすることを意識してないと、とても面倒なコードになりがちです。

フィールド値はすべてコンストラクタやsetterで設定できるようにするか、package属性(default)で記載するとテストコードを非常にシンプルに書けます。

まとめ

とりあえずこの辺が基本的に意識することになると思います。 とはいえ、これだけで具体的にどう書くか分かる人はすでに書き方が分かってる人だと思うので、明日は具体的な事例集を書きます。

それではHappy Hacking!

参考

JUnitで現在時刻が関わるテストを解いてみた

これであなたもテスト駆動開発マスター!?和田卓人さんがテスト駆動開発問題を解答コード使いながら解説します~現在時刻が関わるテストから、テスト容易性設計を学ぶ #tdd に書いてある問題がUnitTestを書いていく上での教材にとても良さそうだったので、自分のベンチマークがてら書いてみました。

【仕様1】 「現在時刻」に応じて、挨拶の内容を下記のようにそれぞれ返す機能を作成したい。

まずは問題1をシンプルに問いてみました。

記事でも言及されてますけど、時間を扱った問題は普通にテストを書くことが難しいです。 環境に依存したデータのでテスト毎に結果が変わってしまうからです。他にも外部のWebサイトとか、別のシステムとかそういったパラメータが入ると同じ理由で普通にテストできません。

なので、まず考えたことはDate型を引数に渡して、テストコード側で現在値を設定できるようにすることです。 こういうのは依存性を注入できるように実装するのが王道ですしね。

ただ、Javaで任意のDate型を初期化するのは面倒なので、いったんintでhourとminuteを受け取るテストとメソッドを定義。

    @Test
    public void greeterWithMorning() {
        Greeter greeter = new Greeter();
        assertThat(greeter.greet(5, 0), is("おはようございます"));
        assertThat(greeter.greet(11, 59), is("おはようございます"));
    }

後はベタにRed => Greenのサイクルを回して仕様を満たしていきました。 その後、下記のようにint型ではなく日付型を受け取るメソッドを定義し直します。

    @Test
    public void greeterWithDate() {
        Greeter greeter = new Greeter();
        assertThat(greeter.greet(time(6, 0)), is("おはようございます"));
        assertThat(greeter.greet(time(12, 0)), is("こんにちは"));
        assertThat(greeter.greet(time(23, 0)), is("こんばんは"));
    }

境界値のテストとかはint型のケースで試してるので、書いてません。timeメソッドはさすがにCalendarの初期化を毎回書くのが面倒なので独自定義。 実際の開発ならDateUtils的な何かを入れてるはずなので、それを使うべきだと思います。

問題1はこんな感じ。intの実装は最終的には消してDateの実装だけにしたほうが、実装の詳細が残らないからいいかなぁ、とも思ったんですが、問題2でリファクタリング必要そうなのは分かってたので、いったんこのままで。全体のコードはこちらのgist.

【仕様2】 「現在時刻」と「ロケール」に応じて、挨拶の内容を下記のようにそれぞれ返す機能を作成したい。

同じ時間でもロケールが日本語なのか英語なのかで挨拶を変える課題です。単純にインタフェースを定義するとこんな感じでしょうか。

    assertThat(greeter.greet(time(6, 0), Locale.JAPAN), is("おはようございます"));
    assertThat(greeter.greet(time(6, 0), Locale.ENGLISH), is("Good Morning"));

パッと見の感想として、引数が2つになっています。となるとテストパターンがn * m というやんちゃそうな数になり、めんどそうです。 なので、要素を単純化するため問題1の機能を拡張せずに、まず以下のようなリファクタリングをしました。

  • 朝、昼、夜を表すenumのTime{Morning, Afternoon, Evening}を定義
  • greetから時間帯判定の条件分岐を抽出してgetTimeメソッドに。挨拶文を直接返すのではなく、Time enumを返す
  • Time enumを引数にとってメッセージを返すgetMessageを定義

上記のリファクタリングをした上でgetTime, getMessageにもテストを書きます。 これによって時間帯を判定するメソッドと、時間帯に応じたメッセージを返すメソッドを分離できました。 公開属性をpublicにしようかとも思ったのですが、テスト以外で使われることも無さそうなのでパッケージデフォルトにしてあります

では、次に新規のテストを追加します。getMessageを時間帯だけではなく、ロケールも受け取るように拡張しましょう。

    assertThat(greeter.getMessage(Time.MORNING, Locale.JAPAN), is("おはようございます"));
    assertThat(greeter.getMessage(Time.EVENING, Locale.ENGLISH), is("Good Evening"));

あとは例のごとくテストと実装のサイクルを回します。

    assertThat(greeter.greet(time(6, 0), Locale.JAPAN), is("おはようございます"));
    assertThat(greeter.greet(time(12, 0), Locale.JAPAN), is("こんにちは"));
    assertThat(greeter.greet(time(23, 0), Locale.JAPAN), is("こんばんは"));
    assertThat(greeter.greet(time(6, 0), Locale.ENGLISH), is("Good Morning"));
    assertThat(greeter.greet(time(12, 0), Locale.ENGLISH), is("Good Afternoon"));
    assertThat(greeter.greet(time(23, 0), Locale.ENGLISH), is("Good Evening"));

続いてGreeter#greetも同様の拡張をしてテスト。この時、時間帯判定ロジックは弄っていないので、テストを変える必要は無いです。 getMessageとgetTimeの2つのメソッドに分割することでそれぞれのメソッドの責務が分解されて、テストパターンも圧縮されたのでたぶんいい感じのはず。

個人的には、ひとつのメソッド毎に分岐がひとつの方がテストは書きやすいので、なるべくそうしたい。TDDするとめんどくさいから、そういった構造になりやすいのが良いです。テスト対象コードと、この時点のテストコードはこちら.

テストのリファクタリング

続いてテストのリファクタリング。記事を読んでたらParameterized Test という考え方があるらしいので、試してみることに。 私がよくやってしまう...Test01, ...Test02という残念な名前を付けなくても良いようにテストロジックとデータを分離させる記法です。

    @ToString
    @AllArgsConstructor
    public static class GetTimeParameter {

        int hour;
        int minute;
        Time expected;
    }

    @DataPoints
    public static GetTimeParameter[] moning() {
        GetTimeParameter[] dataset = {
            new GetTimeParameter(5, 0, Time.MORNING),
            new GetTimeParameter(11, 59, Time.MORNING)
        };
        return dataset;
    }

    @DataPoints
    public static GetTimeParameter[] afternoon() {
        GetTimeParameter[] dataset = {
            new GetTimeParameter(12, 0, Time.AFTERNOON),
            new GetTimeParameter(17, 59, Time.AFTERNOON)
        };
        return dataset;
    }

    @DataPoints
    public static GetTimeParameter[] evening() {
        GetTimeParameter[] dataset = {
            new GetTimeParameter(18, 0, Time.EVENING),
            new GetTimeParameter(23, 59, Time.EVENING),
            new GetTimeParameter(4, 59, Time.EVENING)
        };
        return dataset;
    }

    @Theory
    public void getTimeWith(GetTimeParameter parameter) {
        Greeter greeter = new Greeter();
        assertThat(parameter.toString(), greeter.getTime(parameter.hour, parameter.minute), is(parameter.expected));
    }

JUnit4 ではParameterized Test を支援する機能として Parameterized アノテーションやTheories アノテーションがあります。

エラー検出時の可読性はParameterizedが良いですが、それ以外のすべての面でTheoriesが使いやすかったので、こっちを使用。リファクタリング後のテストはこちら.

本気で可読性とエラー表示の見やすさを狙うなら大人しくSpock使えという感じのようなので、今度そっちも検証してみます。

まとめ

今回、直接カレンダー型呼びましたけど、プロダクトで使うなら結合テストのしやすさも考えて、SystemDateとか言う名前の現在時刻を返すクラスを作って、必要に応じて設定ファイルとかで値を制御できるようにします。

で、それを今の実装の引数なしメソッドの実装にする感じです。そうしておくことで、ブラウザ等からのテストでも時間固定ができるので。

UTもその機能で賄うことも不可能ではありませんが、テストコードがめんどくさくなるので、今回のように設計で解決したほうが基本無難な認識です。

それにしても、元記事はいろんな観点で解説があって非常に参考になりました。問題も良かったし。 まだまだTDD(というかここまで来ると詳細設計)に関して未熟なので自分も精進しないと、年の締めくくりに思った次第。

それでは、Happy Hacking!