都元ダイスケ IT-PRESS このページをアンテナに追加 RSSフィード

最近は会社ブログしか書いてません。

2012-06-25

[]日経ソフトウエアにて、Eclipse逆引きポケット事典を執筆しました

ごぶさたです。都元です。

日経ソフトウエア2012年8月号が昨日発売となりました。都元が特別付録の文庫サイズ別冊「Eclipse逆引きポケット事典」に寄稿しました。この原稿は、息子誕生の混乱のさなかに脱稿したものですw その節は編集者さまにはご迷惑をお掛けしましたごめんなさい。

まぁ、日々Eclipseを使っているような皆様におかれましては全部知ってる内容な気はします。が、まぁ私が日頃Eclipseを使うにあたってのノウハウを書いて行ったものなので、まだそこまでEclipseに慣れていない、という方にはオススメな内容です。

日経ソフトウエア 2012年 08月号 [雑誌]

日経ソフトウエア 2012年 08月号 [雑誌]

ちなみに、ブログで報告しませんでしたがこの2号前、6月号でも特集の「プログラミングでコレがやりたい! 40選」にて1つ、JavaでのXMLの扱い方(StAX)についてさらっと寄稿させていただいてます。

日経ソフトウエア 2012年 06月号 [雑誌]

日経ソフトウエア 2012年 06月号 [雑誌]

2012-05-22

[]父親になりました♪

なんか、気づいたら半年近くブログ放置してましたw この半年色々ありましたけど、それはまた、別のお話。

ってことで、えーー、先ほど 5/22 22:04 に息子が産まれました! ワーーイ。産まれた瞬間、ベタですが、涙出ました。嬉しい! 良い人生を送って欲しいな、と心から思いました。

っていうかね。悪いけど、マジかわいいぜ、ウチの息子!! うはwww これは、毎日風呂に入れたくなるってもんですわーーww ベビーバスまだないけどなwwww っつーか、まだ名前も付けてないけどなwwwwwww 早く考えてやんないと。名前呼びてーーーけど、ないwww だめだ、テンションがアレだww

まぁそれはともかく、とりあえずwishlist 貼っておきますね :) http://www.amazon.co.jp/registry/wishlist/3U2NT3SPZNGP

追記

https://p.twimg.com/Atyby-BCEAAPIO6.jpg

というわけで本当にたくさんの方からお祝いをいただきました。メールアドレスが分かる方にはメールでもご挨拶させていただきましたが、送り主不明のお祝いが約半分を占めており、この場であらためてお礼申し上げます。ありがとうございました!

まぁ、いまだにバタバタしていますが、先日、無事に一ヶ月検診も終え、家族3人元気に過ごしています。

そうそう、Facebook等ではご報告しましたが、名前も決まりました。えーと、都元キョウスケですw

しばらくは寝不足の日々が続きそうですが、キョウスケともども、都元家を今後ともよろしくお願いします :)

2011-12-05

[]JIRAの課題検索とフィルターを使いこなそう

JIRA Advent Calendarってのに巻き込まれました。巻き込まれサイコーw

昨日は@oota_ken氏のJIRAのスキーマーをastahのクラス図で書いてみる ユーザー編 – テスト自動化他何かのメモでした。確かに、JIRAのデータ構造って、ぶっちゃけ理解するの大変ですよね…。なんちゃらスキーム苦手ですw

で。@yusukeyサンからの「オマエのJIRAへの愛を語れ」っていう振りは完全スルーして、普通に活用Tips書きますw

自分はJiemamyプロジェクトにOSSライセンスを頂いて以来、随分長いことJIRAユーザをしています。なので、もうちっとディープな話もできなくもないかなぁとは思ったのですが、周囲を見ていると、玄人向けの難しい機能を紹介するよりも「願わくば全てのJIRAユーザが活用して欲しい、けどもなかなかこれを活用している人はいないんですよね」的な機能の紹介をしたほうがウケがいいよね、と思ってそんなネタを書きます。

JIRA Advent Calendarに参加するような人たちは既知のお話だと思いますが、活用歴が浅めのユーザさんに向けて。

まず課題検索を理解する

f:id:daisuke-m:20111205210334p:image:right

JIRA上には様々なチケットが蓄積されていきます。まず、そのチケットを「探す」ことにフォーカスを当ててみましょう。

検索と言えば、まずは普通にキーワード検索が浮かぶと思います。また、チケットID*1でチケットを探すこともあると思います。このような検索は、通常画面の右上にある「クイック検索」を利用します。

とりあえず、Jiemamy ProjectのJIRAに行ってみて、右上のクイック検索欄に「FileNotFoundException」とか入力してみましょう。この記事の執筆時点では ISM-18 とCORE-128 という2つのチケットが引っかかるはずです。続いて、同じくクイック検索に「ECL-100」と入力してみると、該当チケットに直接ジャンプします。って、普通ですね。

ここまでは敢えて説明するまでもない、普通の検索です。しかしこの「クイック検索」ボックスは、実は恐るべき機能を備えています。例えばここに「ECL unresolved」と入力してみましょう。そうすると「スマート クエリが起動しました。」というメッセージと共に「未解決で、かつECLプロジェクトに属するチケット」が表示されます。そう、unresolvedなど、いくつかの単語は普通にキーワード検索されるのではなく、特殊な意味として解釈されるのです。

f:id:daisuke-m:20111205210335p:image

その他、「CORE bug created:-10w」(COREプロジェクトで過去10週間以内に作成されたバグチケット)など、色々な検索を手軽にできるんですが、その中でも私が一番使うのは「my unresolved (プロジェクト名)」(ex: my unresolved ECL)ですかね。このスマートクエリは、是非知っておきたい機能です。

あわせて読みたい: no title

色々なフィルターを紹介

クイック検索を使って、簡単にチケットを検索できることがわかりました。しかし、前述のような何度も使う簡単な検索条件は、覚えてしまえば良いのですが、覚えるのには向かない程度に複雑であるものの、よく使う検索条件、というのも存在します。そういったものは「検索条件を保存」しておいて、必要な時に呼び出せば良いのです。このように「よく使う検索条件を保存したもの」のことを「フィルター」って呼びます。

f:id:daisuke-m:20111205210336p:image:w100:right

f:id:daisuke-m:20111205210337p:image:w150:left

少し話が飛びますが、検索には「簡易検索」と「高度な検索」というものがあります。前者はフォームに検索条件を細かく指定するタイプ(右図)の検索なのですが、私はほぼこれを使いません。まどっろっこしい! まぁ多分、エンジニアだからでしょうけどw フィルターに保存するくらいの複雑さを持つ検索条件は、「高度な検索」を使うと良いと思います。というわけで、これを高度な検索に切り替えてみましょう。

まず、クイック検索で適当に「my unresolved」等と検索してみましょう。その上で、検索結果の左側にあるエリアの上方「高度な検索に切り替える」をクリック(左図)します。そうすると、検索結果の上に「クエリ」というテキストエリアが表示され、ここに検索条件を「式として」記述できるようになります。この条件式の記述言語はSQLに似た形になっており、「JQL (JIRA Query Language)」と呼ばれています。JQLを活用すれば、簡易検索では表現できない複雑な検索条件を記述できるようになります。

f:id:daisuke-m:20111205210339p:image

で、JQLの細かい書き方はドキュメントに任せることにして、ここでは私が普段使っているフィルターを、成り立ちと共に具体的に紹介したいと思います。(エンジニアの方は普通にJQLを読めると思います)

報告者にアサインして解決確認を依頼すべき担当課題

JIRAのチケットは、特にカスタマイズしていない状態では、「オープン→対応中→解決済み→クローズ」というライフサイクルを辿ります。ただ、私の経験上、デフォルトのままのJIRAを運用すると、解決済みのステータスで止まってしまってクローズに至らないチケットがあまりにも多いのです。しかしこれはある意味当然の事。実施者が満足して解決済みとしたタスクは、多くの場合特に問題はなく、完全に終わったタスクだとみなされてしまいます。従って、それ以上、そのチケットに対してアクションをする必要はない、と考えてしまうのです。そもそも「クローズ」という状態の存在さえ忘れてしまう。だって、解決したんだから良いでしょう? という気持ちです。

ただ、より一層厳密に仕事を進めるために、対応者が行った仕事の結果を報告者*2に見てもらい、チケットを作った意図通りの結果になっているかどうか最終確認をしてもらう、というプロセスを踏んだ方がトラブルが少ないでしょう。従って、まずチーム内で「対応者が満足して解決済みにしたチケットは、報告者にアサインを返し*3、クローズを依頼する」というルールを作り、コンセンサスを得ます。(このルールを厳密化するために、私が管理するJIRAでは「報告者でなければチケットをクローズできない」ように設定しています。この方法は今回の話から脱線するので、また別の機会にでも。)

しかし、人間というのは基本的にミスをする生き物です。前述のルールでは、対応者がチケットを「解決済み」にする際に、報告者にアサインバックすべきなのですが、担当者の変更を忘れてそのままにしてしまう、というミスが頻発します。こんなエントリを書いている私でさえ、よくやります。このようにアサインバックを忘れられたチケット*4は、ダッシュボードの「自分の担当課題」には表示されなくなるため、ほぼ確実に "忘れ去られた存在" になります。

そこで効果を発揮するのがこのフィルター。

status = Resolved AND assignee = currentUser() AND reporter != currentUser()

「ステータスが解決済み かつ 担当者が自分 かつ 報告者が自分ではない」チケットですね。この条件に引っかかるチケットは、前述の「アサインバック忘れ」ですので、見つけ次第、報告者にアサインを返す操作をしましょう。

解決を確認の上クローズすべき担当課題

次に、上記のシナリオでアサインバックされた側の話。このような経緯でアサインバックされたチケットは既に「解決済み」のチケットです。従って、このチケットも「自分の担当課題」には挙ってこないんですね。というわけで、確認しなければならない立場の人にとっても同様、"忘れ去られて然るべき存在" なのです。同じように、フィルターで検出してあげましょう。

status = Resolved AND assignee = currentUser() AND reporter = currentUser()

解決済みで、担当者と報告者がどちらも自分のもの、というフィルターです。この条件に引っかかるチケットは、対応内容を確認して、問題が無ければクローズ、問題があれば再オープンして対応者に差し戻す必要があります。

期限超過の担当課題

JIRAのチケットには「期限」という項目があります。が、別に期限を過ぎたからって、デフォルトのJIRAは何をしてくれる訳でもありません。でも、期限をオーバーしてしまったチケットには早めに気づきたいですよね。そこでコレ。

resolution = Unresolved AND assignee = currentUser() AND due <= now() ORDER BY due

このように、期限の値が現在より小さい(つまり過去)で、解決されていない自分担当のチケットを検出します。この条件に引っかかったら、早急に対応するか、または期限の見直しをすべきでしょう。

長期放置の担当課題

とは言え、チケットの期限設定というのは意外と難しいものです。チケットを作る人が勝手に期限を設定したとしても、実際の対応者が対応しきれなければ全く無意味です。対応者の忙しさを度外視して、チケット作成者の都合だけを押し付けた期限を設定しても印象悪いだけですしw 従って、よほど明確なデッドラインがあるチケットでなければ、基本的に期限を付けずにチケット発行することが多いようです。経験的に。

しかし、期限がないままチケットをアサインすると、そのチケットをそのまま握り続けられてしまう場合があります。期限がないので「期限超過の担当課題」にも引っかかりません。他にもっと優先度が高いチケットがある、という錦の御旗の下、黙殺されるチケットになってしまうのです。

しかし私は、そういった優先度の低いチケットであっても、定期的にレビューし、放置されている状態が適切かどうかを考える必要があると考えています。もしかしたら状況が変わっていて、もっと優先度が上がっているかもしれません。GTDでも、定期的にタスクをレビューしますよね。

というわけで、長期に渡って放置されている課題をフィルターで検出します。

updated < -14d AND status != Closed AND assignee = currentUser()

ここでは「長期」を「14日間」としていますが、まぁここは各自調整してみてください。この条件に引っかかるチケットは、引き続き放置することが適切であるかをレビューしましょう。放置してはいけないと判断した場合はチケットを編集して優先度を上げましょう。編集すればupdatedの値が更新されるのでこのフィルターには引っかからなくなります。また、引き続き放置する場合は、一言コメントしましょう。コメント操作でもupdatedの値が更新されます。

フィルターを購読する

と、まぁ私が普段使っているフィルターを色々紹介してきましたが、フィルターを作るだけではあまり効果がありません。このフィルターは「フィルターの結果」ウィジェットを使ってダッシュボードに常設しておくと良いでしょう。ウィジェットの追加方法は、説明し出すとまた長くなるので、@Sean_SFさんのブログ no title を参照してください(逃)

ここではもう一つ。定期的にフィルターの条件を満たすチケットが無いかをチェックし、条件にhitするチケットが検出されたらメールを送る、という技です。

JRIAの課題メニューの一番下に「フィルターの管理」という項目がありますので、このページに飛んでください。作ったフィルターが列挙されているはずです。そのそれぞれのフィルターの右方に「配信登録」というリンクがあるはずです。これをクリックし、適当なスケジュール(例えば毎朝9時など)で配信登録してみましょう。これで、フィルターに引っかかった課題リストを毎日メールしてくれるようになります。

朝出社したら、このメールを見ながらチケットを整理することによって、クリーンな状態で仕事を始めることができますね。

番外編:今週解決した課題

resolution = Fixed AND resolutiondate >= startOfWeek() AND resolutiondate <= endOfWeek()

私が最近作ったフィルターです。週の頭には1つも引っかかりませんが、週末になるに連れて課題が増えていきます。あー、ウチのチームは今週これだけの仕事を進めたんだなぁ、って眺めながらニヤニヤする用のフィルターです。

実は私はもう一工夫して「自分が今週解決した課題」として、より一層ニヤニヤしているのですが。ただしこの場合、単に assignee = currentUser() ではダメなんですね。というのも、前述のルールでは、自分が解決したとしても報告者にアサインバックしてしまうから。そこで「 lastResolutionUser = currentuser() 」っていう条件(最後に「解決済み」にしたユーザ、つまりほとんどの場合「タスクの実施担当者・対応者」を表す)を使っています。しかしこの lastResolutionUser っていうのはJIRAの標準機能ではありません。Jira Enhancer Pluginというプラグインをインストールし、設定しています。頑張れる人は各自頑張ってみてください。


さて JIRA Advent Calendar 4日目は以上です! みなさんも、便利なフィルター条件があったら教えてください。明日はエロ元上司 @j5ik2o (id:j5ik2o)…かと思いきや、どうやら@yusukeyの無茶振りに対してバックレを決め込んでいるようです。俺はそんな上司に育てた覚えはないぞっ!!w 観念してエントリーするように。

というわけで、明日は @inda_re 氏のエントリーです。お楽しみに!

*1:正確にはキーって呼ぶみたいですね

*2:チケットを作った人=仕事を振った人

*3:担当者をチケット作成者に割り当てる

*4:つまり「ステータスが解決済み かつ 担当者が自分」のチケット

2011-08-21

[]ソフトウェア開発者、完売いたしました

転職活動をはじめて2ヶ月弱。ようやく次の落ち着き先を決めました。ちなみに「転職したのに上司が変わらなかった」っていうネタも考えていたのですが、id:j5ik2o と行き先は別々になりました。まぁ、かとうさんは「永久名誉上司」として、永遠にエロ上司扱いしてやろうと思っています。あ、某D社の皆様も、早速エロ呼ばわりしてみると良いと思いますよ。喜ぶと思いますw

…さて。

正直、先日のエントリを上げる直前は、もうこの業界に自分の居場所はないかもしれない等と考え、薬屋への撤退戦略などを考えたりしていました*1。しかし、エントリを上げた途端当人らがびっくりするほどの反響を頂き、最初の1ヶ月は一つ一つお話を聞かせて頂くべく東奔西走していました。この真夏の陽気で外回りは結構体力的にも大変*2でしたが、この年になって社会科見学をしているようで、様々な勉強をさせて頂きました。本当に皆さん、色んな考えを持ってソフトウェア開発に向き合っているんだなぁ、と自分の視野の狭さが恥ずかしくなるようなこともw お話を聞かせて頂いた皆々様には、改めてお礼申し上げます。

しっかし、今回ばかりは自分の体が一つしかないことを恨みましたねw 各社様、本当に素晴らしい会社ばかりでした。しかし最終的に、自分の出来ること、やりたい事、求められている事など…そして直感によって、最もチャレンジングな決断をしました。

来月からクラウドスタディという会社にお世話になります。一言で言えば、従来孤独な戦いであった「勉強」を技術の力でサポートするサービスを展開する会社です。現在はスタディログというサービスを運営しています。スタートアップ企業のため、本当に取り組むことは多く、そして面白いことが出来る場所だと思っています。

というわけで、9月からは新しい世界です。クラウドスタディ関係者の皆様、改めましてどうぞよろしくお願いします。そして、友人各位、また、このブログを読んでくれている読者の皆様、今後とも今までと変わらぬご指導・ご鞭撻を宜しくお願いいたします。

あわせて読みたい 転職先が決まりました - じゅんいち☆かとうの技術日誌

*1:この話をすると「そんなバカなww」って言われるんですが、当人は本気だったんですよぉ。。。

*2:実は、4kgくらい痩せましたw リバウンドしないといいなぁ。。

2011-07-05

[]【転職活動】ソフトウェア開発者のバリューパックを発売、限定1セットのみ!

さて突然なんですが、私と上司(id:j5ik2o)は7月末を以て現職を退職することと相成りました。

まぁ、社内不仲とかクーデターとかそういうんじゃなくて、研究開発プロジェクトをクローズすることになったのであります。私が進めていた研究開発業務としては、みんなで結構頑張って企画を練っていて、その成果として、パイロット版を次週あたりにも社内向けにローンチできる!? とかいうタイミングだっただけに、非常に無念…。

で、このまま会社に残留してSI事業のメンバーとして居残る道もあったのですが、各所で囁かれているように、SIだけを続けていても近々食えなくなる日が見えてきていると思う。そして、都元自身としても、あらためて自分の想いを整理すると、そもそもSIをやりたいがためにこの業界に転進した訳ではないなと思いました。一言で言えば「なんかサービス作りてえよ」*1と思っているので、その道で頑張ってみたい。そんなわけで、残留はしないことにしました。

また、起業なんていう道もあるのでしょうけど、自分、経営者じゃなくて技術者ですねん。ガチで経営に打ち込むというのは自分のやりたいことではない、願わくば技術に集中したい、と思っているので、自分が経営の責任者になるという選択肢はありませんでした。

と、いうわけで、私と上司(id:j5ik2o)がセットで求職中、大売り出し中なのでありますよ。二人ともある程度Javaが書けますw

【セット内容】(なお、当人材はセットでのご雇用がお薦めですが、もちろん単体でのご雇用も可能ですので、ご相談くださいw)


で、両名の詳細については、個別にお気軽にお問い合わせください。

都元は…

希望としては、やはり製品やサービス(B2Cだとより嬉しい)を自社で開発している、または開発しようとしている会社に籍を置きたいと思っています。といっても既にサービスを回してる必要は無くて、これからやるんだけどエンジニアリングの手が欲しい…なんていうチャレンジングな会社さんも好きです。しかし、そういう会社さんとの出会いって「縁」なんですよねぇ…。良いご縁がありますように。

個人的な最近の技術テーマとしては、機械学習。特にレコメンデーションなんかに興味を持って取り組んでます。なお、私は見ての通りどこからどう見てもJava色に見えると思いますが、Javaでないお仕事にチャレンジする意志もあります。学ぶチャンスですよ。勉強やめたらそこで終わりなんで。

まぁこのブログを読んでくれている人は、大抵人事担当の方ではないと思いますので、技術者として「ウチの会社面白いと思うよ!」とか「ウチじゃないんだけどあそこの会社、トガってる人求めてたよ!」なんていう情報をコソっと教えてくれる囁き女将も募集中。もちろん、現在私たちと面識が無い方からのコンタクトも歓迎いたします。そこは自重などせず、是非に。 → 都元に「一緒に働こう!」と声を掛ける

しかしまぁ、こうやって売り出していれば仕事が来るなんて甘い事は思ってないので、こちらからも積極的に行きます。友人各方面には「御社ってどんな感じですかね」っていうお声を掛けさせて頂くこともあると思います。その時は、嫌がらずに是非お話を聞かせて頂ければ、と思います。宜しくお願いします。

ちなみに、前述の研究開発成果は、会社に残しておいても誰も引き継げないということで、都元個人が会社から譲渡を受ける手はずとなっています。そのうち、個人サービスとして回したり、もしかしたら転職先の会社でさらに練り込んでから事業化したり*2することがあるかもしれませんネ。


最後に、トライクレオで過ごした2年間、大変充実していました。一緒に頑張って来た仲間には、あらためて感謝いたします。どうもありがとう!

合わせて読みたい: 転職活動を始めました - じゅんいち☆かとうの技術日誌

*1:一言で言うと…軽いなぁw

*2:譲渡なので、こういった事態でも問題は起きません。ちゃんと書面を取り交わす予定。

2011-02-25

[]ScalaでBrainf*ckのインタプリタを書いてみたよ

Scalaで書いた作品その3です。まぁ、今までハロワと妙なEclipse Pluginしか書いてないので、全くScalaらしからぬコードだと思うけど。

とりあえずコードを貼ると、モヒモヒした人達がScalaっぽくしてくれるんじゃないかなぁ…。

とりあえずブログ上のテキストだとモヒりにくいと思ったから、githubに上げた…ら、案の定モヒられています。

object Brainfuck {

  var commands = Map(
    '>' -> incrementPointer,
    '<' -> decrementPointer,
    '+' -> incrementMemoryAtPointer,
    '-' -> decrementMemoryAtPointer,
    '.' -> outputMemoryAtPointer,
    ',' -> inputMemoryAtPointer,
    '[' -> openLoop,
    ']' -> closeLoop
  )

  def main(args:Array[String]) {
    val program = readSource
    val env = new Environment(program)
    while (env.isEof == false) {
      commands.get(env.programChar) match {
        case Some(command) => command.execute(env)
        case _ => ;
      }
      env.progress
    }
  }

  def readSource = {
    // output "Hello, world!"
    """
+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.
------------.<++++++++.--------.+++.------.--------.>+.
    """
  }
}
class Environment(val program: String) {
  val memory = new Array[Int](3000)
  var pointer = 0
  var counter = 0

  def isEof = {
    counter >= program.length
  }

  def programChar = {
    program(counter)
  }

  def progress:Unit = {
    counter += 1
  }

  def memoryAtPointer = {
    memory(pointer)
  }
}

abstract class Command {
  def execute(env: Environment)
}
object incrementPointer extends Command {
  def execute(env: Environment) = {
    require(env != null)
    env.pointer += 1
  }
}
object decrementPointer extends Command {
  def execute(env: Environment) = {
    require(env != null)
    env.pointer -= 1
  }
}
object incrementMemoryAtPointer extends Command {
  def execute(env: Environment) = {
    require(env != null)
    env.memory(env.pointer) += 1
  }
}
object decrementMemoryAtPointer extends Command {
  def execute(env: Environment) = {
    require(env != null)
    env.memory(env.pointer) -= 1
  }
}
object inputMemoryAtPointer extends Command {
  def execute(env: Environment) {
    require(env != null)
    env.memory(env.pointer) = Console.in.read
  }
}
object outputMemoryAtPointer extends Command {
  def execute(env: Environment) {
    require(env != null)
    print(env.memoryAtPointer.toChar)
  }
}
object openLoop extends Command {
  def execute(env: Environment) = {
    require(env != null)
    if (env.memoryAtPointer == 0) {
      var depth = 0
      while (env.programChar != ']' || depth != 1) {
        if (env.programChar == '[') {
          depth += 1
        } else if (env.programChar == ']') {
          depth -= 1
        }
        env.counter += 1
      }
    }
  }
}
object closeLoop extends Command {
  def execute(env: Environment) = {
    require(env != null)
    if (env.memoryAtPointer != 0) {
      var depth = 0
      while (env.programChar != '[' || depth != 1) {
        if (env.programChar == ']') {
          depth += 1
        } else if (env.programChar == '[') {
          depth -= 1
        }
        env.counter -= 1
      }
    }
  }
}

2010-12-22

[][]Baseunits Library

さて、Java Advent Calendar -ja 2010 : ATND 10日目。昨日は、id:yuroyoro でした。二日連続で真っ黒な魔術が紹介されたので、ここは真っ白で実用的な奴をひとつ。

最近Domain Driven Design(DDD)っていう設計手法が、自分の周辺一部で話題になっている。当然、賛否両論なんだけども*1、個人的には好きな考え方でして。ま、詳細は色々な方がブログに書いているので割愛します。興味あれば本読んでみましょう。洋書*2だけどw

Domain-Driven Design: Tackling Complexity in the Heart of Software

Domain-Driven Design: Tackling Complexity in the Heart of Software


で、前置きはここまで。本題いきます。

Javaで「時間」や「日付」を扱う時、みんなどうしてます? まぁ、大抵 java.util.Date とか java.util.Calendar を使うんじゃないかな、と思います。でもこのJavaの時間ライブラリっつーのは、意外とあちこちでdisられがちです。Deprecatedなメソッドだらけだし、Mutable(可変)だったり、継承関係が無茶だったり。まぁ正直使いにくい。

例えば2010年12月22日っていう暦日(日付)を扱いたいだけなのに、「2010/12/22 00:00.00 を java.util.Date で扱って時間部分を無視」とかしてませんか? 時間要素のない、単なる「暦日(ex. 2010/12/22等)」クラス欲しくないですか? 逆に日付要素のない、単なる「時刻(ex. 16:10等)」クラス欲しくないですか? Immutable(不変)なクラスとか欲しくないですか? 「期間(ex. 2010/09/20〜2010/12/22等)」とか「時間量(ex. 3分間, 4ヶ月間等)」クラス欲しくないですか?(ry

昔から、なぜApache commons-dateみたいなライブラリが無いのか、不思議でならんかった。まぁ今でも不思議だ。commons-langにDateUtilsってクラスがあって、少々便利なユーティリティを提供してくれているけど、まぁ全く根本的な解決になっていない感じ。

しかしまぁ、このイケてなさはJavaユーザの間ではほぼ共通認識ではあるようで。JodaTimeやら、それを元にしたJSR-310やらで、日時関連のAPIを整備しようという流れはあるわけです。ただ、このJodaTimeも結構曲者。いわゆる過モデリングなんだと思いますが、あらゆる可能性とニーズに対応しようとするあまり、通常は要らない概念がもりもり入っていて、まぁそれはそれで使いにくいらしい。使ったことないけど。

で。突然だがTime and Money LibraryっていうOSSプロジェクトがあるのです。Javaにおける時間及び金額をモデリングしたクラスライブラリだ。DDDの実例コードという名目で世に出たコード(DDD著者=Time and Money作者=Eric Evans)だけど、個人的に、普通にライブラリとしても有用なコードだと感じた。

しかし残念なことに、Domain Language, Inc.のページを見るところによると「このプロジェクトはもはやアクティブではない(Although this project is no longer active)」と明記されちゃっている。実例コードであることが大事で、実際に使うことをもう既にあまり意識しておらず、メンテナンスを続ける感じじゃないのだと思う。

あぁ、もったいない。中身を見てみると、DDD的に深く考えぬかれた設計が満載ではないか。ということで、このコードをいろいろいじくってみた。

まずね、Javadoc無いとかあり得ないから。スザケンナって思いながら全部にひと通りドキュメントつけました。日本語*3ですけどね。俺が意味を取り違えていて、間違ったことを書いているかもしれないし、ぶっちゃけ未だに意図がよくわからないクラス*4もあるw しかし、それでもとても便利だと思う。

あと、元コードはJava1.4対応だったのでジェネリクス使ってなかったんですね。で、自分なんかはJava5が出た後にJavaを始めた人間なんて、ジェネリクスの無いコードなんて怖くて触れねーよ、的なビビリな訳ですよ。というわけで必要なモンに型パラメータつけました。

また、メソッド名に統一感がない部分があったり、テスト書き足してみたり、FindBugs掛けてみたら赤いところがバグってたので直してみたり。あ、あんな機能欲しいww とか、普通にクラスも追加したりしてます。オーバーホールっていうか、ずいぶん改造しやがったな、というのが近いかもしれない。

というわけで、再構成したものを派生プロダクトとして公開します。あー、OSSってすばらしい。そしてこの派生プロダクトはBaseunits Libraryと名づけますよ。みんな覚えといてね!

まぁ、論よりコードだな。

基本的な日付演算

元プロダクトが舶来なので、アメリカーンな例ですが。

// 暦日仕様。ある暦日がその仕様を満たすかどうか、とかチェックできる
// ここでは「毎年1/15」という仕様、つまりここではキング牧師の誕生日。
// (他にも「毎月第二木曜日」とかもいける。
//  さらに、その日が祝日だったら前営業日ずらしとかも可能。)
DateSpecification mlkBirthday = DateSpecification.fixed(1, 15);

// で、2005/1/15という日付のインスタンスを作る
CalendarDate jan15_2005 = CalendarDate.from(2005, 1, 15);
// この日はMLKの誕生日ですか? → true
assertThat(mlkBirthday.isSatisfiedBy(jan15_2005), is(true));

// 2005年の中(期間=CalendarInterval)で、キング牧師の誕生日を引っ張って来る。
// (仕様を満たす暦日を探す)
CalendarDate mlk2005 = mlkBirthday.firstOccurrenceIn(CalendarInterval.year(2005));
assertThat(mlk2005, is(jan15_2005));

// 誕生日〜逝去日の暦日期間をつくって、その範囲で仕様を満たす暦日を順次取得するイテレータ
CalendarInterval mlkLifetime = CalendarInterval.inclusive(1929, 1, 15, 1968, 4, 4);
Iterator<CalendarDate> mlkBirthdays = mlkBirthday.iterateOver(mlkLifetime);
assertThat(mlkBirthdays.next(), is(CalendarDate.from(1929, 1, 15)));
assertThat(mlkBirthdays.next(), is(CalendarDate.from(1930, 1, 15)));

// っていうか、そもそもキング牧師って何日間生きたの?
assertThat(mlkLifetime.length(), is(Duration.days(14325)));

営業日を考慮するカレンダー

何に使うかピンと来ない? んじゃ、日本のビジネスに密着した例いってみましょう。

BusinessCalendar calendar = new BusinessCalendar();

// 祝日の登録(この登録は、JapaneseBusinessCalendar とかサブクラスつくってコンストラクタで処理しちゃうといいかも)
calendar.addHolidaySpec(DateSpecification.fixed(1, 1)); // 元旦
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(1, DayOfWeek.MONDAY, 2)); // 成人の日
calendar.addHolidaySpec(DateSpecification.fixed(2, 11)); // 建国記念日
calendar.addHoliday(CalendarDate.from(2010, 3, 21)); // 春分の日(毎年違うので、ひとまず今年分を単独で登録)
calendar.addHolidaySpec(DateSpecification.fixed(4, 29)); // 昭和の日
calendar.addHolidaySpec(DateSpecification.fixed(5, 3)); // 憲法記念日
calendar.addHolidaySpec(DateSpecification.fixed(5, 4)); // みどりの日
calendar.addHolidaySpec(DateSpecification.fixed(5, 5)); // こどもの日
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(7, DayOfWeek.MONDAY, 3)); // 海の日
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(9, DayOfWeek.MONDAY, 3)); // 敬老の日
calendar.addHoliday(CalendarDate.from(2010, 9, 23)); // 秋分の日(毎年違うので、ひとまず今年分を単独で登録)
calendar.addHolidaySpec(DateSpecification.nthOccuranceOfWeekdayInMonth(10, DayOfWeek.MONDAY, 2)); // 体育の日
calendar.addHolidaySpec(DateSpecification.fixed(11, 3)); // 文化の日
calendar.addHolidaySpec(DateSpecification.fixed(11, 23)); // 勤労感謝の日
calendar.addHolidaySpec(DateSpecification.fixed(12, 23)); // 天皇誕生日

// それぞれの日が「営業日」にあたるかどうかチェック。
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 8)), is(true)); // 金曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 9)), is(false)); // 土曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 10)), is(false)); // 日曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 11)), is(false)); // 月曜日だけど体育の日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 10, 12)), is(true)); // 火曜日

assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 22)), is(true)); // 月曜日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 23)), is(false)); // 火曜日だけど勤労感謝の日
assertThat(calendar.isBusinessDay(CalendarDate.from(2010, 11, 24)), is(true)); // 水曜日

// 振替休日(「国民の祝日」が日曜日にあたる場合、その直後の「国民の祝日」でない日を休日とする)とか、
// 国民の休日(「国民の祝日」と次の「国民の祝日」の間隔が中1日しかなくその中日(なかび)が
//「国民の祝日」でない場合、その日を休日とする)
// なんかには、まだ対応していないけど、DateSpecificationを上手く実装すればどうにかならんかな、と思っている。

// 2010/10/1〜2010/11/30までの間で、営業日だけを返すイテレータ
Iterator<CalendarDate> itr =
        calendar.businessDaysOnly(CalendarInterval.inclusive(2010, 10, 1, 2010, 11, 30).daysIterator());

// 1つ1つ検証するの大変なので、とりあえず文字列にしちゃうよ
StringBuilder sb = new StringBuilder();
while (itr.hasNext()) {
    CalendarDate calendarDate = itr.next();
    sb.append(calendarDate).append(" ");
}

// 土日祝日を除いた日が列挙されている
assertThat(sb.toString(), is("2010-10-01 " +
        "2010-10-04 2010-10-05 2010-10-06 2010-10-07 2010-10-08 " +
        "2010-10-12 2010-10-13 2010-10-14 2010-10-15 " +
        "2010-10-18 2010-10-19 2010-10-20 2010-10-21 2010-10-22 " +
        "2010-10-25 2010-10-26 2010-10-27 2010-10-28 2010-10-29 " +
        "2010-11-01 2010-11-02 2010-11-04 2010-11-05 " +
        "2010-11-08 2010-11-09 2010-11-10 2010-11-11 2010-11-12 " +
        "2010-11-15 2010-11-16 2010-11-17 2010-11-18 2010-11-19 " +
        "2010-11-22 2010-11-24 2010-11-25 2010-11-26 " +
        "2010-11-29 2010-11-30 "));

業態によるカスタマイズも結構できる感じ。

// 不動産屋さんって、日曜祝日も営業してるけど、水曜日はお休みだよね
// この場合isHolidayがtrueでも、isBusinessDayがfalseだとは限らない
BusinessCalendar estateCalendar = new BusinessCalendar() {

    @Override
    public boolean isBusinessDay(CalendarDate day) {
        Validate.notNull(day);
        DayOfWeek dow = day.dayOfWeek();
        return dow != DayOfWeek.WEDNESDAY;
        // デフォルト実装は return isWeekend(day) == false && isHoliday(day) == false;
    }
};

その他

自分が注目してるのは時間ライブラリの方なので、金額は省略。まぁ、同じノリですよ。

まぁ、こんな事やってたから「区間」があーだこーだとか言い出した訳なんですね、俺。

基本情報

使ってみようか、と思ったらこちら。

v1.x系 v2.x系
プロジェクトサイト http://maven.tricreo.jp/site/baseunits http://maven.xet.jp/site/baseunits
Mavenリポジトリ http://maven.tricreo.jp/release http://maven.xet.jp/release
Maven groupId jp.tricreo jp.xet
Maven artifactId baseunits baseunits
ソース置き場 https://github.com/tricreo/baseunits/ https://github.com/dai0304/baseunits/
ライセンス Apache License v2.0 Apache License v2.0

今後

今はひとまず v1.0 をリリースしてあります。単体テストも結構書きましたし、今後もきっちりメンテナンスを続けていく予定*5です。というわけで比較的安心して長期的に使ってもらえるライブラリだと思います。

ただ、今の時点でかなり安定したプロダクトだと思うので、みんながイメージするOSSほど頻繁なアップデートは無いかもしれません。決してメンテナンスしないという意味ではなく、すでに結構成熟しているので触る必要性が小さい、という意味で。

しかし、バグや要望などありましたら、どしどしお寄せください*6。バグ対応はもちろんのこと、要望に関しても方針と矛盾せず、比較的対応が楽なものであれば早めの対応をしたいと思っています。

まぁ、githubなんで、適当にいじってプル・リクエストくれても良いですし。臨機応変に対応します。このエントリのコメントでもOK。ML等は盛り上がったら考えます。クローズなやりとりをお望みでしたら個人的にメールでも。

まとめ

Immutable重要。ということで、明日は最速Eclipseの魔術師、id:Yamashiro0217 です。

*1:正直、ヘビーウェイトな考え方だとは思う。

*2:訳本情報もあります。 http://twitter.com/#!/kohsei/status/17106115712000000

*3:たまにオリジナルの英語docが残ってるけど。

*4:moneyパッケージ内のクラス。

*5:万一会社がこのプロダクトを放り出しやがったら、Jiemamy Projectが受け皿になりますw

*6:基本方針もあるので、全てに応えられる訳ではないですが。