有隣堂書店のブックフェアにて技術書を推薦させていただきました

秋葉原のヨドバシカメラ7F有隣堂書店にて開催中のIT技術者向けブックフェアで、推薦者として参加しました!Java系技術書を中心に10数冊推薦させていただきました。

「2010春 世界に放つ!AKIBAエンジニア育成計画」

こんな感じで推薦しました。写真付き(近所にロケしに行って嫁さんに撮影してもらったもの)。いやー太ってるね。

それぞれの推薦書籍にコメント文を書いて送ったんですが、有隣堂の店員さんによる(?)可愛い感じのポップとして仕上がってました。


自分以外には、

  • 岩野和生さん(日本IBM執行役員)
  • 高橋征義さん(日本Rubyの会会長/高橋メソッド!)
  • 伊藤直也さん(はてなCTO)
  • 柴田芳樹さん(「Effective Java」等、技術書の翻訳を多数)
  • 開米瑞浩さん(「ネーミングの掟と極意」等の著者)

などなど、そうそうたるメンバーに紛れ込んでいるかたちとなっております(o_o;)

5月中旬までやっているらしいので、ぜひお立ち寄り下さい。

[有隣堂 ヨドバシAKIBA店]
http://www.yurindo.co.jp/shop/y_akiba.html

Guiceを使ってみた

今開発しているプログラムをGuiceを使って書いてみた。そもそもGuiceを使おうと決めた理由はテストを書き易くするため。実際に使ってみた感想としては「ま〜、こんなもんか」って感じ。

だいたいの使用パターンとしては以下のような感じになると思う。Guiceの高度な機能を使うと他の人がコードを読めなくなりそうなので最低限の機能だけ使うことにする。

まずはテスト時に差し替えたいクラスのIFを切り出しておく。

public interface LoginClient {
  public boolean login(String id, String password);
}

で、実コードを実装する。

public class LoginClientImpl implements LoginClient {
  public boolean login(String id, String password){
    //DB Access..
    return true;
  }
}

次に、それを使う側のクラスを定義する。こっちはIF切らなくてもよい。

import com.google.inject.Inject;

public class Service {
  
  private LoginClient loginClient;
  
  @Inject
  public Service(LoginClient loginClient){
    this.loginClient = loginClient;
  }
  
  public String execute(){
    boolean login = loginClient.login("ken", "xxxx");
    if(login){
      return "OK";
    }else{
      return "NG";
    }
  }
}

このServiceクラスはGuiceによってnewされることを想定している。
コンストラクタに「@Inject」をつけたので、Guiceインスタンスを作るときに、LoginClientをインジェクトしておいてくれる。

なお、フィールドに@Injectをつけておけば、コンストラクタを定義しなくてもGuiceがインジェクトしておいてくれる。privateなフィールドでもOK。本当は全部フィールドインジェクションでいければ美しいんだろうけど、テストが書き辛くなる(後述)という結論に達したため、コンストラクタインジェクションを使うことにする。

次にGuiceによるDIの設定をするためのモジュールクラスを定義する。どのIFにどの具象クラスがインジェクトされるか、ってことを定義するクラス。Javaで書ける設定ファイルみたいなもんですね。

import com.google.inject.AbstractModule;

public class MyModule extends AbstractModule{
  @Override
  protected void configure() {
    bind(LoginClient.class).to(LoginClientImpl.class);
  }
}

で、最後にエントリポイントのクラスを作る。

import com.google.inject.Guice;
import com.google.inject.Injector;

public class Main {
  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new MyModule());
    Service service = injector.getInstance(Service.class);
    String msg = service.execute();
    System.out.println(msg);
  }
}

最初にModuleを指定してInjectorを作っておき、それ以降はInjectorにインスタンスを作ってもらう。

まあ、ここまではいいんだ。問題ない。すっきりしててよい。
で、ここからServiceクラスのテストコードを書いて行きたいわけだ。ただし、Serviceクラスの中で使っているLoginClientImplはDBにアクセスするので、テストのときはDBにアクセスしないモックで差し替えてテストしたい。

実コードの方ではInjectorを使ってServiceを構築したわけなので、テストの場合もテスト用のModuleを使ってInjectorを作り、DBアクセス無しのLoginClientが差し込まれたServiceオブジェクトを構築したい。

するとテストコードはこんな感じになる。

import static org.junit.Assert.assertEquals;
import guicesample.LoginClient;
import guicesample.Service;

import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

public class ServiceTestWithGuice {
  @Test
  public void testExecuteWhenLoginSuccess(){
    Injector injector = Guice.createInjector(new AbstractModule(){
      @Override
      protected void configure() {
        bind(LoginClient.class).to(LoginClientSuccess.class);
      }});
    Service service = injector.getInstance(Service.class);
    assertEquals("OK", service.execute());
  }
  
  @Test
  public void testExecuteWhenLoginFailure(){
    Injector injector = Guice.createInjector(new AbstractModule(){
      @Override
      protected void configure() {
        bind(LoginClient.class).to(LoginClientFailure.class);
      }});
    Service service = injector.getInstance(Service.class); 
    assertEquals("NG", service.execute());
  }
  
  static class LoginClientSuccess implements LoginClient{
    @Override
    public boolean login(String id, String password) {
      return true;
    }
  }

  static class LoginClientFailure implements LoginClient{
    @Override
    public boolean login(String id, String password) {
      return false;
    }
  }
}

あああ・・、ながい。複雑。
本当はLoginClientのモックは無名クラスとしてテストメソッド内で定義したいんだけど、bind().to()の引数はClassを指定する必要があり、かつGuiceの仕様により非staticな内部クラスはインジェクト対象に出来ない、という制限があるため、上記のように、ログイン成功用、失敗用のLoginClientモックをわざわざ定義した、ってわけです。めんどくせー。

あまりにもテストコードが嫌な感じなんで、テストコードはGuiceを使わず以下のように書くことにする。

import static org.junit.Assert.*;
import guicesample.LoginClient;
import guicesample.Service;

import org.junit.Test;

public class ServiceTest {
  @Test
  public void testExecuteWhenLoginSuccess(){
    Service service = new Service(new LoginClient() {
      @Override
      public boolean login(String id, String password) {
        return true;
      }
    });
    assertEquals("OK", service.execute());
  }
  
  @Test
  public void testExecuteWhenLoginFailure(){
    Service service = new Service(new LoginClient() {
      @Override
      public boolean login(String id, String password) {
        return false;
      }
    });
    assertEquals("NG", service.execute());
  }
}

まあ、だいぶすっきりしますね。

ただ、フィールドインジェクションを使っている場合は上記のような書き方はできない。とくに非privateなフィールドに対してインジェクトしている場合は、テストコードもGuiceを使うか、setterを別途用意しておくか、リフレクションを使う必要がある。privateフィールドに対するインジェクションはすっきりしていて好きなんですが。

そもそも、テスト時にオブジェクトの差し替えをしたいだけなら、DI使わずにモックライブラリ使えって感じですが。

何の話をしているのか混乱してきましたが、まーとにかく、簡潔で、理解し易く、かつテストしやすいコードになるように、うまくバランスをとらなきゃいけないですね。

追記(2010/03/21):
shinさんからのコメントより。こんな感じで書けるようです!

public class ServiceTestWithGuice {
  
  @Test
  public void testExecuteWhenLoginSuccess(){
    Injector injector = Guice.createInjector(new AbstractModule(){
      @Override
      protected void configure() {
        bind(LoginClient.class).toInstance(new LoginClient() {
          @Override
          public boolean login(String id, String password) {
            return true;
          }
        });
      }});
    Service service = injector.getInstance(Service.class);
    assertEquals("OK", service.execute());
  }
  
  @Test
  public void testExecuteWhenLoginFailure(){
    Injector injector = Guice.createInjector(new AbstractModule(){
      @Override
      protected void configure() {
        bind(LoginClient.class).toInstance(new LoginClient() {
          @Override
          public boolean login(String id, String password) {
            return false;
          }
        });
      }});
    Service service = injector.getInstance(Service.class);
    assertEquals("NG", service.execute());
  }  

ニコ動のランキングページから既に見た動画を隠すユーザスクリプト

ニコニコ動画(9)になって、ランキングページに沢山動画が表示されるようになって嬉しい。
しかし、既に見たものと、そうでないものの区別がつけにくい。
というわけで既に誰かが作っている気もするけど、なんとなく作ってみた。

以下のページからインストールできます。

すでに見たランキング動画をかくす君
http://userscripts.org/scripts/show/68547

すると、

これが・・・

こうなる!!!

全消し目指してがんばろう!

2009年ふりかえり

年末らしく今年の振り返りをします。今年はわりといろいろありましたねー。過去のブログ記事、twitterのログ、Googleカレンダーなどを見返しつつ、なにをやったかを書き出してみる。

振り返り

  • 1月
    • 正月は東京ですごした
    • はじめての一般参賀に行った。天皇陛下万歳
  • 2月
    • 仕事でRubyをいろいろやってた気がする。松江にも行った。寂れた街であった・・・。
  • 3月
    • 3本目のiPhoneアプリ「AnimeMaker」をリリース。我がkenmaz.net社の売れ行き商品。ありがたい事に今でもコンスタントに売れ続けている。
    • TFM「松本人志の放送室」が終了。いい番組だった。いまでもよく聴き返してる。
  • 4月
    • このあたりから転職について考えはじめる・・。
    • QConに参加
    • OpenSocialをちょっと触り始める。
    • 布袋ライブで最前列ど真ん中の席を体験する。
  • 5月
    • 日立グループRuby研究発表会@島根でRubyについて発表。
    • Google主催のOpenSocialハッカソンに参加。大したものは完成しなかったけど、みんなでワイワイ楽しく開発できた。
    • 転職先が決まる。転職する意向を上長に打ち明けるときのドキドキ感とかw
    • ニート状態突入!!!
  • 6月
    • 結婚式@明治神宮、新婚旅行@バリ。バリでは、読書=>プール=>寝る=>メシ、をループしてた気がする。超贅沢旅行であった。村上春樹の1984を主に読んでた。そういえば何故か結婚式直前に高熱が出て、1日で治った。あれはなんだったんだ・・・。知恵熱?
    • 嫁姉の飼猫「サンゴちゃん」を2週間ほど預かる。最初はかなり警戒してたけど、2日目からは甘えられすぎて困った。至福の時であった。ニャー!!!※ちなみに名前の由来は3匹目の猫だから(3号=>さんごう=>サンゴ)らしい・・。
    • Google Developer Day 2009に参加し、GDDPhoneをもらう。アプリ作ろうと思ったけどほとんど触っていない。そろそろ開発機として活躍の出番がきそうな予感。
    • ニート状態終了!!!
    • 新職場に出社開始。私服勤務に感動する。ニコニコ動画モバイルのサーバーサイドの開発を担当。
  • 7月
    • 会社に慣れてくる
    • このあたりからニコ厨気味になってくる。し、仕事なんだからね!
    • このあたりからTwitter熱が加速し、ブログを書く頻度がどんどん低下。。
    • ドラクエ8にハマる
    • スポーツジム通い再開
    • 長年ダラダラやっていたバンドのメンバー二人が故郷に帰ることになり、バンドも自然に解散(休止?)。休日は引篭もり気味の俺にとって、バンド活動は外出の理由を提供してくれる貴重な存在でもあった。そんなわけで、休日の引篭もり度合いもこのあたりから急上昇!
  • 8月
    • 夏 休 み は な か っ た !(試用期間のため)
    • 2008年の6月から続いた日経ソフトウエア連載が一旦終了。ここまでの連載記事の内容が「ゼロから学ぶ!最新Javaプログラミング」というムックとなって日経BP社より出版される。
    • mixiアプリを作ってみるものの、いまいち面白さを感じられず飽きる。
    • なぜか神宮球場でヤクルト戦を2回も観戦。新鮮だった。
    • スポーツジム通い飽きてきたorz
  • 9月
    • GREEの方々との飲み会に参加。いろいろ刺激を受ける。
    • IKEAで本棚を買う。かなり気に入っている。
    • ActionScriptプログラミングをちょっとやる。
  • 10月
    • 「会長が思いついたアイデアをもとにプロトタイプを作る」という謎の任務を仰せつかる。
    • ニコニコ実況モバイル」プロジェクトに参加。得意の(?)ひとりプロジェクト
    • 日経ソフトウェア連載執筆再開!!
    • 結婚一周年!
    • 副鼻腔炎気味になる
  • 11月
    • ニコニコ実況モバイル」がリリース。無事サービスインできてよかった!たまにテレビ見ながら使ってるけど、割と面白いよ。
    • kenmaz.net社4本目のiPhoneアプリ「彼氏の財布が」リリース。Yahooトップに取り上げられる。そして炎上へ。
    • Googleの新言語「Go」ではなくて「Noop」に興味を惹かれる。
    • 布袋BCオンリーライブに参加
  • 12月
    • とある新サービスの立ち上げに向けた技術調査を開始。かなり楽しい。開発機器が揃うまで自宅作業の日々。自宅作業は気楽で集中できるけどちょっと寂しい。やっぱり基本は会社で仕事した方がいいね。自分の能力と比べて難易度は若干高いけど、とりあえず成功させたい。
    • 兄貴夫婦に女の子が誕生!初孫に母親大喜び風味。
    • Robotsが解散。そもそも俺が東京にいる理由は、(1)俺が働きたいIT企業は東京に集中している、(2)なにかと刺激的なイベントが豊富、の2点が大きい。「気軽にRobotsライブに参加できる」ってのが(2)で大きな位置を占めていただけに残念だけど、まあ今後もTAKUYAをフォローしていこう。
    • 大晦日に帰省予定

まとめ

こうして振り返るといろいろあったな〜。特に以下が大きかったかも。

  1. 転職し、新職場でもなんとか楽しくやってる
  2. 連載執筆が1年半続いている
  3. iPhoneアプリ開発を通じて色々なものを得た
  4. バンド活動を停止した
  5. 上記理由により、休日祝日は自宅でプログラミング/執筆/技術調査/読書、外出なし、というパターン、要するに引篭もり度合いがより強まった

まあ・・・なんだかんだで前向きな一年だったように思う。
来年はどんな年にしようか・・と考えたところで、あんまり意味はなさそうなので、気楽に、楽しくやろう。
あ、そうそう、どこで見たのかは忘れたけど

何か選択しなければいけない状況になったときは「儲かりそう・得しそう」よりも「楽しそう」を重視したほうが最終的には良い結果を招く

っていう考え方がかなり気に入っていて、今後もこんな感じで行きたいと思います。

最後に、いろいろわがままに付き合ってもらったり、おいしいご飯を食べさせてくれた妻に大感謝してます。妻、ありがとう〜!!!新しいiPhoneが出たら一緒に買おうねー!

それではみなさん良いお年を〜〜〜!!!

久しぶりのブログ

ちょっとしたことは全部twitterに書いてしまうので、ブログを書く気がなかなかおきない。メンドクサイので最近あった出来事や思っている事を箇条書きしよう。

  • 双子座流星群で3つほど流星をみた
  • 仕事でiPhoneをやることになったが、機材の準備の関係で今週いっぱいは在宅勤務になりそうだ。
  • 「彼氏の財布が」というiPhoneアプリを有料化したら炎上した(^o^)
  • 体重が増えた気がするぞ
  • 勤務時間が11:45-21:00って感じになってきた。10:30-20:00を目指してはいるが朝ダラダラしてしまう。
  • 中途半端な時期に転職したけどボーナスはちゃんともらえた
  • なんだかんだでコーディングたくさんできて嬉しい
  • 日経ソフトウェアの原稿書きが結構大変
  • C言語ともっと仲良くなりたい
  • Google日本語入力べんりすぐる
  • SoftBankのiPhonePR事務局が、去年作った「The除夜の鐘」というiPhoneアプリを宣伝してくれるらしい。「この冬のお役立ちアプリ」なんだとか。いや・・

箇条書きにすればtwitterやってるのと変わらないな。

Noopビルド成功

昨日NoopのHEADをビルドするとインタプリタが動作しなかったので、NoopのMLに「インタプリタ動かないよー」というメールを投げたら「ごめんリビジョン132でビルドぶっこわしちゃった、まさにno-opだね:-)」的な返信があって誰がうまry). どうやらインタプリタ自体をguiceベースに修正しようとしてたみたいけど「生焼け」状態でまだ動かない感じらしい。

そんなわけでとりあえずリビジョンをちょっと前に戻したら動いた(謎のデバッグメッセージがでてるけど)

%
hg up -r 131

%
export CP=core/target/noop-core-0.1.0-SNAPSHOT.jar
export CP=$CP:interpreter/target/classes
export CP=$CP:~/.m2/repository/org/antlr/antlr-runtime/3.1.1/antlr-runtime-3.1.1.jar
export CP=$CP:~/.m2/repository/org/slf4j/slf4j-simple/1.5.6/slf4j-simple-1.5.6.jar
export CP=$CP:~/.m2/repository/org/slf4j/slf4j-api/1.5.6/slf4j-api-1.5.6.jar

%
scala -cp $CP noop.interpreter.InterpreterMain HelloWorld interpreter/target/resources/stdlib/ interpreter/target/resources/helloworld

8 [main] INFO noop.model.LoggingAstVisitor - pre-evaluated: HelloWorld
9 [main] INFO noop.model.LoggingAstVisitor - Entering method call on main
11 [main] INFO noop.model.LoggingAstVisitor - String literal: something
17 [main] INFO noop.model.LoggingAstVisitor - evaluated an argument
17 [main] INFO noop.model.LoggingAstVisitor - calling method main
27 [main] INFO noop.interpreter.InterpreterVisitor - Visiting ID expr: console
Hello World!

これでようやく遊べる。