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

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

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

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

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

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

新規コードなのでテストをしやすいように設計をすれば良いだけです。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!

レガシーコードも年末に向けて大掃除! - jMockitを使おう

さて、今年もあと僅かになりましたが、この1年間で溜まりに溜まったコードの負債を大掃除してみませんか?

レガシーコードだから書くのが大変? プロダクト側を改修したいけどそもそもテストが無いから改修が怖い? ですよねー。 なのでjMockitを使って既存のコードにテストを書く方法を紹介します。

まず、想定するレガシーコードですが検証したいメソッドの内部で現在時刻を取得してるケースを考えます。 普通にクラスやメソッドから日付をインジェクションできるように作るのが正しいコードですが、結構ありがちなのがこんなコード。

    public class MyCalendar {
        public Date nextDay() {
            Calendar calendar = SystemDate.getDate();
            calendar.add(Calendar.DATE, 1);
            return calendar.getTime();
        }
    }

SystemDateは検証環境なんかで結合テストしやすいように、設定ファイルやDBの値を読み込むことが多いでしょうけど、今回はシンプルに現在時刻だけ。

    public class SystemDate {
        public static Calendar getDate() {
            return Calendar.getInstance();
        }
    }

このようなケースでMyCalendar#nextDayにテストを書こうとしても日付が変更出来ないので、月またぎや年またぎ、うるう年のテストが出来ません。検証環境にデプロイして何らかの方法でシステム日付を変更し、手でポチポチと検証しながら画面のスクリーンショットを取る簡単なお仕事が待っていそうですね。

ユニットテストをする気がちっとも無い素敵な感じですが、残念なことにしばしば見かけるタイプのコードです。

jMockitを使うことで、こんな残念な設計に対しても、比較的低コストでUTを書くことが可能です。

    public class MyCalendarTest {

        String $(Date date) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            return df.format(date);
        }

        Calendar getCalendar(int y, int m, int d) {
            Calendar cal = Calendar.getInstance();
            cal.set(y, m - 1, d);
            return cal;
        }

        @Test
        public void test01() {
            new Expectations() {
                @Mocked
                SystemDate systemDate;

                {
                    SystemDate.getDate();
                    result = getCalendar(2013, 12, 13);
                    result = getCalendar(2013, 12, 31);
                    result = getCalendar(2013, 12, 13);
                }
            };

            MyCalendar myCalendar = new MyCalendar();
            assertThat($(myCalendar.nextDay()), is("2013-12-14"));
            assertThat($(myCalendar.nextDay()), is("2014-01-01"));
            assertThat($(myCalendar.nextDay()), is("2013-12-14"));
        }
    }

Expectationsブロックの中にMockとして戻り値を差し替えたい内容を記述します。 MyCalendar#nextDay()の中で利用される"SystemDate.getDate()"の戻り値は直下に記載したresultのものに差し替えられます。 今回はstaticメソッドを置き換えましたが、もちろん同様にインスタンスメソッドも置き換え可能です。

また、重要なのはこれがクラス全てではなく部分差し替えとなっていることです。 これにより、対象クラス全体の振る舞いを書き直す手間やリスク無く、モックを作成することが可能です。

ちなみにこのコードで

    MyCalendar myCalendar = new MyCalendar();
    assertThat($(myCalendar.nextDay()), is("2013-12-14"));
    assertThat($(myCalendar.nextDay()), is("2014-01-01"));
    assertThat($(myCalendar.nextDay()), is("2013-12-14"));
    assertThat($(myCalendar.nextDay()), is("2013-12-14"));

とすると

    mockit.internal.UnexpectedInvocation: Unexpected invocation of:
    SystemDate#getDate()

という例外が発生してテストが失敗します。これは、resultの数以上呼び出されるのは期待値と異なるためです。 差し替える値自体は固定で良いが複数回呼びたいケースというのもありますよね? その場合にはExpectaionsの代わりにNonStrictExpectationsを使うことで解決します。

       @Test
        public void test01() {
            new NonStrictExpectations() {
                @Mocked
                SystemDate systemDate;

                {
                    SystemDate.getDate();
                    result = getCalendar(2013, 12, 13);
                    result = getCalendar(2013, 12, 31);
                    result = getCalendar(2013, 12, 13);
                }
            };

            MyCalendar myCalendar = new MyCalendar();
            assertThat($(myCalendar.nextDay()), is("2013-12-14"));
            assertThat($(myCalendar.nextDay()), is("2014-01-01"));
            assertThat($(myCalendar.nextDay()), is("2013-12-14"));

            // 最後のresultとinstanceが同じなので12/14の更に翌日になる
            assertThat($(myCalendar.nextDay()), is("2013-12-15"));
        }

ね、簡単でしょ? これで、今まで初期化が大変過ぎて二の足を踏んでいたコード群にもガンガンテストを書いていくことが出来ます。 それではみなさんも良い年末を! Happy Hacking!

参考:

Keynote風のプレゼンテーションをMarkdownで作ってみた

最近ちょっと英語の練習がてら上のような英語プレゼンを作ろうと思い立ちました。 目的なく写経的なの苦手なので、目的作る方式。

ただ、OpenOfficeなりKeynoteなりPowerPointなりで毎回作るのも面倒だし、JS系のプレゼンツールだとslideshareに上げれないし、良い方法無いかな、と調べてみることに。 Markdown + pandocでの変換が一番自分の好みにマッチしたので、そのやり方を記載します。 以下、内容的には上のプレゼン資料の和訳 + α。そこ、αが多いとか言っちゃダメですよ?

PowerPointKeynoteじゃダメなの?

どちらもめちゃくちゃいいツールですが、ちょっと問題があります。

  1. 同じレイアウトにするのがめんどい
  2. オフィスアプリケーションが必要.
  3. バイナリフォーマット。gitやSVNとの相性が残念

まず1ですがテンプレートを使っても少し面倒です。ブラウザや別なエディタからコピペしようものならそこだけフォントが変わるとかザラですね。 あと、GUI系のエディタだと自分の場合どうしても見た目指向になりがちで、論理構造が変になったりしてる時があるので、あとからスタイル切り替えで苦しみます><

続いて2なんですが、当たり前ですが編集にオフィス系アプリが必要です。立ち上げるのがめんどいのです。エディタが好きなのです。

最後に3。実はコレが一番大きくてまっとうな理由。バージョン管理しづらいんですよね。バイナリフォーマットなので。 修正の差分がわからないです。プラグインとか入れまくればなんとかなるかもですが、ちょっとねえ?

そこでMarkdown + Pandoc

Markdown と Pandocの簡単な説明はこんな感じ。

Markdown

  • シンプルテキストフォーマット。だからgitとの相性もバッチリ!
  • 人間が簡単に読める、そして書ける!
  • HTML風のシンプルな論理文章構造を提供

Pandoc

  • マルチドキュメントトランスレータ(HTML, PDF, docx, markdown, LaTex, reStructuredText...)
  • Markdownからスライドを作成可能
  • しかもs5とかHTML形式のものだけではなく、PDFにできる

大変自分のニーズにあっております。特にMarkdownはgithubとかでも人気ですし、資料が多いのが良い所。 wiki記法みたいなもんですが、謎の方言よりある程度標準化されてるのが良いですね。pandocは他にもwordとかにも変換できるので、マニュアル作りとか社内資料に便利そうです。

なにはともあれ使い方

インストール Pandoc.

こちらの公式から落とせます。linux系の場合は各種パッケージマネージャですかね。 haskellのパッケージ管理ツール入れてやるのも良いかもです。 http://code.google.com/p/pandoc/downloads/

PDF用にTexのインストール

PDFの作成はTex経由で行っているのでインストールが必要です。なんか色々モジュールが居るみたいですが、MacならMacTex入れれば全部揃うようです。http://tug.org/mactex/

Run コマンド

$ pandoc -t beamer test.md -o test.pdf

ね、簡単でしょ? 実行するとこんな感じのスライドが出来ます。

f:id:pascal256:20131028190551p:plain

カスタマイズ

 

テンプレートの適用

ちゃんと、スライドが出来たのは良いですが、ちょっとシンプル過ぎですね。というか気に入らないです(ぉ というわけでKeynoteのグラデーション風の奴に変更しようと思います。 まずは、Texのカスタムスタイルをダウンロード。幸い、同じようなことをしてる人がすでにいらっしゃったので、こちらを使わせてもらいます。 beamerthemeKeynoteLikeGradient.sty.

で、ダウンロードできたら、そのファイルを下記ディレクトリにコピーします。MacTex以外を入れてる方は適宜読み替えで。

$ sudo cp ./beamerthemeKeynoteLikeGradient.sty /usr/local/texlive/2013/texmf-dist/tex/latex/beamer/themes/theme/

次に、インデックス情報の更新です。残念ながらこれだけだと、テンプレートを指定しても見つからないと怒られれます。 おそらく適切な更新方法があるのですが、良くわからなかったので、直接「Tex Live Utility」を起動して、適当なパッケージを再インストールしてインデックスを更新するという荒業にw 誰か、やり方ご存知でしたら教えて下さい。

テンプレートの指定は下記のように "-V theme:"で指定します。これで、晴れてkeynote風のデザインに。

$ pandoc -V theme:KeynoteLikeGradient -t beamer test.md -o test.pdf

コードブロックのカスタマイズ

この状態だとコードブロックが背景色が透明なため少し見づらいです。そのため、ちょっとカスタマイズしたいと思います。この変換の仕組みは裏側はTexなんですが、Texでコードを綺麗に出すためにはlistingというモジュールを使うようです。

$ pandoc --listings -t beamer test.md -o test.pdf

これで、コードブロックがlistingになります。なるんですが、このままだとカスタマイズされて無くて特に見た目変わらないんで、下記のようにヘッダーにフォーマットをしています。 ここにフォント種類やサイズ、背景色やラインもろもろを記載することが出来ます。ヘッダーの書き方はこんな感じ。

\usepackage{listings}
\lstset{%
 %language={sh},
 backgroundcolor={\color[rgb]{0.1, 0.1, 0.1}},%
 basicstyle=\footnotesize,%
 commentstyle=\textit,%
 classoffset=1,%
 keywordstyle=\bfseries,%
 frame=tRBl,framesep=5pt,%
 breaklines=true,
 showstringspaces=false,%
 %numbers=left,stepnumber=1,numberstyle=\footnotesize%
}

利用するには "-H"オプションを使います。

$ pandoc -H header.tex --listings -t beamer test.md -o test.pdf

これでいい感じに出力されました。

まとめ

MarkdownとPandocはシンプルな典型的なプレゼン資料を作るのに向いています。 派手な見た目や画像の多様をした資料を作りたい時はPowerPoitとかKeynoteが確実に良いです。 ただ、おんなじような紋切型で文字多めドキュメントの時は軽量なMarkdown + Pandocはかなり有りです。

今回作成したサンプルはこちら。下記コマンドで冒頭のプレゼン資料が出来上がります。

$ pandoc --listings -H header.tex -V theme:KeynoteLikeGradient -t beamer test.md -o test.pdf

今後の課題としては画面いっぱいのデカ文字とか結構多用するので、その辺の作り方ですね。あと、コマンド毎回打つのも面倒なので、rakeベースのFWというかテンプレートを作ってみました。 それはこちらにおいてあります。良ければ試してみてください。

それでは、Happy Hacking!

参考:

電子書籍ストアのランキングを比較したよ[Book☆Walker, Kindle, Kobo]

ふと思い立って、電子書籍ストアのランキングを比較してみたら、それなりにカラーが違って面白かったのでブログにまとめてみる。

 

まずは、Book☆Walker。最近大合併したKADOKAWAの直轄ストアだけあって、やはり萌系が多い。

新刊や、アニメ化した作品がランキング反映されてる感じなので、わかりやすいですね。

一般書籍も売れてるとのことでしたが、ランキング見る限りはやはり消費者としてはアニメイト的な専門店の位置づけっぽいです。

Book☆Walker

1 ハイスクールD×D16 課外授業のデイウォーカー ¥600
2 週刊アスキー 2013年 10/29号 ¥350
3 新約 とある魔術の禁書目録(8) ¥683
4 週刊ファミ通 2013年10月31日号 ¥350
5 艦これ白書 -艦隊これくしょん オフィシャルブック- ¥1,995
6 東京レイヴンズ10 BEGINS/TEMPLE ¥600
7 艦隊これくしょん -艦これ- 鎮守府生活のすゝめ Vol.1 ¥600
8 おまえをオタクにしてやるから、俺をリア充にしてくれ!8 ¥600
9 落第騎士の英雄譚<キャバルリィ> 【電子特装版】 ¥473
10 ストライク・ザ・ブラッド1 聖者の右腕 ¥599
平均単価   ¥685

 

続いてKobo。文芸作品というか一般作品が多いですね。漫画ランキングとかも見てみたんですが、進撃の巨人が多かったです。同じ漫画というジャンルに絞っても、Book☆Walkerとは明らかに違う傾向ですね。

楽天Kobo

1 ロスジェネの逆襲 ¥1,200
2 キングダム 31 ¥368
3 マリアビートル ¥740
4 成功者に学ぶ時間術 ¥99
5 東京喰種トーキョーグール[JACK] ¥100
6 人生がときめく片づけの魔法 ¥762
7 あなたの仕事に革命を起こす!「つたえることリスト」 ¥100
8 オレたちバブル入行組 ¥690
9 宮本武蔵 完全版 ¥100
10 人類資金IV ¥525
平均単価   ¥468

 

KindleKoboと同じような傾向に見えますが、小説よりもビジネス書が多いですね。

なんとなく意識高い系?の社会人が使ってそうなラインナップですね。

Amazon Kindle

1 下半身で覚えるビジネス英会話-下ネタは世界の共通語!- (ビジネスファミ通) ¥95
2 ソニーをダメにした「普通」という病 ¥200
3 種まきガールズコレクション ¥300
4 宮本武蔵 完全版 ¥350
5 あなたの仕事に革命を起こす!「つたえることリスト」 ¥200
6 人類資金IV (講談社文庫) ¥525
7 仕事ができる人できない人 ―「あの人はできる!」と納得させる20のルール ¥200
8 しゃべれないあなたは悪くない! 英語力が伸びるホントの方法 ¥200
9 20代は勉強に狂いなさい! ¥300
10 30分で一流の仕事をマスターする方法 ¥200
平均単価   ¥257

 

参考までに紙の本を売ってる楽天ブックス楽天市場の利用者とかぶるせいか、主婦向けのラインナップが上位に来てます。

あとは、ジャンル無差別に入ってるので、傾向というほどはなく売れてるものが売れてるって感じですかね。

 

楽天ブックス(参考)

1 【バーゲン本】新干し野菜レシピ ¥480
2 ロスジェネの逆襲 ¥1,575
3 【予約】レシピブログmagazine Vol.1 ¥515
4 【予約】ポケットモンスター X・Y公式ガイドブック 完全ストーリー攻略ガイド ¥1,260
5 【予約】フェフェ2013-2014Winter fafa‘s Universe ¥1,680
6 オレたち花のバブル組 ¥690
7 【入荷予約】乃木坂派 ¥1,995
8 オレたちバブル入行組 ¥690
9 極北ラプソディ ¥735
10 艦これ白書 ¥1,995
平均単価   ¥1,162

 

比較したのはあくまでランキング内容で、売れてる絶対数は不明なので一概には言えませんが

Book☆Walker => 萌系の専門店としてオタク層が利用

Kindle => ビジネス書籍を購入するような層が利用

Kobo => 比較的広い範囲の層が利用

 

という傾向が言えるんじゃないでしょうか。たぶん。

あと、単価が圧倒的に違うのが特色だと思います。

紙を売ってる楽天ブックスは1,162円、ラノベ・コミックが中心のBook☆Walkerが685円、Koboが468円、Kindleにいたっては257円です。

KoboKindleは200円台も目立つのでそのせいでしょう。逆に言えば、萌え系は価格をそれなりに高くしても維持できるコンテンツと消費者の関係が気づけてるという話かもしれないです。

もともと、デジタルデータには慣れてる層でしょうし。Kindleが1強なイメージでしたが、売れてる価格比率を考えると、一人勝ちというほどでもなく、他社にも十分チャンスがある状態なのかなー、とおもいました。

 

それでは、Happy e-Book Life!

playframework2のHTML出力が汚すぎるのでJsoupで整形してみた

最近、playframework2に手を出し始めました。

このフレームワーク、フォームヘルパーが結構色々HTMLを生成してくれるみたいで、まだ慣れてないせいもあって結構頻繁に生成されたHTMLの中身を見ています。

で、テンプレートエンジンの例に漏れず、出力されるHTMLが超絶汚い...

 

こんな感じのテンプレートが

gist6964461

こんな感じのHTMLに出力されます。

gist6964400

 

見辛すぎるよ!!

 

for文とかテンプレートのインクルードでぐちゃっとなるんですよね。

ということで、こちらの記事を参考にjsoupで整形してみました。

scalaの標準機能だけでできるかとも最初思ってたんですが、生成されたHTMLがXHTMLではなかったので無理でした><

jsoupで変換後はこんな感じに!

gist6964381

かなり見やすくなりましたね。下記のようなコードで対応しています。

gist6964539

gist6964553

 

本当はフィルタとかのレイヤで対応した方がいいと思うんですが、Playのフィルタがどういう動きをするのかを、まだ理解してないので、一旦こんな感じに。

当然パフォーマンスは落ちるので、場合によってはプロダクション時はオフにするとかのオプションも考慮が必要かもです。

まあ、少なくとも開発中はこれで幸せになれそうかな。

 

それでは、Happy Hcking!

 

参考:

Java Mission Control + Flight RecorderをGlassFish4で使ってみた

Java SE 7 Update 40が先日でました。なんとこの子はついに待望のFlight Recorder(ちゃんとした版)が実装されたのですよ!

本番環境の障害調査用にと個人的にはJRockitの機能がHotspotに統合されるHotRockitプロジェクトで一番注目してた機能でした。

一応、JDK7u4あたりからコマンド自体は移植されてたんですが、中身は全然移植されてなかったので、今か今かと待ってた次第です。

 

というわけで早速入れてみました。まずは通常利用しているGlassFish3.1.2に下記のオプションを追加。

<jvm-options>-XX:+UnlockCommercialFeatures</jvm-options>
<jvm-options>-XX:+FlightRecorder</jvm-options>

そしてstart-domain。

Error: To use 'FlightRecorder', first unlock using -XX:+UnlockCommercialFeatures.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
launchctl bsexec failed: Inappropriate ioctl for device

Command start-domain failed.

動かない!? なんぞバグのようです。とりあえずGlassFish4では直ってるらしいので仕切りなおし。

GlassFish4 だと無事起動しました。ちなみに管理コンソールから変えるならこの辺を変更。

f:id:pascal256:20130924162004p:plain

 

 

続いてFlight Recoderを起動させます。

まず、jcmdコマンドを実行してglassfishのプロセスIDを確認

9131 /usr/local/bin/../Cellar/play/2.1.3/bin/../libexec/framework/sbt/sbt-launch.jar run
30363 bin/../glassfish/lib/client/appserver-cli.jar start-domain --verbose true
8887 
30639 sun.tools.jcmd.JCmd
30365 com.sun.enterprise.glassfish.bootstrap.ASMain -domainname domain1 -asadmin-args --host,,,localhost,,,--port,,,4848,,,--secure=false,,,--terse=false,,,--echo=false,,,--interactive=true,,,start-domain,,,--verbose=true,,,--watchdog=false,,,--debug=false,,,--domaindir,,,/Users/koduki/Downloads/glassfish4/glassfish/domains,,,domain1 -instancename server -verbose true -debug false -asadmin-classpath /Users/koduki/Downloads/glassfish4/glassfish/lib/client/appserver-cli.jar -asadmin-classname com.sun.enterprise.admin.cli.AdminMain -upgrade false -type DAS -domaindir /Users/koduki/Downloads/glassfish4/glassfish/domains/domain1 -read-stdin true

つづいて、プロセスIDを引数にして実行できるコマンド一覧を表示

jcmd 30365 help                                           
30365:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
Thread.print
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help

For more information about a specific command use 'help '.

JFRなんたらがFlightRecerder関連です。まずは起動させます。

JFR.start

 続いて、適当に動かしたあとにdumpを取ります。

jcmd 30365 JFR.dump recording=1 filename=/tmp/dump.jfr

これで稼働データの取得はOK. 続いて、JMCでの分析です。

まずは、JMCを起動。

/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/Contents/Home/bin/jmc

EclipseベースのオサレなUIが立ち上がります。VisualVM と同等のJMXのリアルタイム分析も出来ますが、今回はパス。

File -> OpenFile で先ほどのdumpしたファイルを開きます。すると、いい感じに解析されたデータが!

 

 普通にシステムサマリはもちろん。

f:id:pascal256:20130924163020p:plain

 

 

オブジェクトの利用頻度をしかも時間を絞りながら確認可能。

f:id:pascal256:20130924163048p:plain

 

 

スレッドダンプとかロックしたスレッドの分析とかも可能。

f:id:pascal256:20130924163133p:plain

 

ファイルやsocketのI/Oも分析可能。

f:id:pascal256:20130924163330p:plain

 

どんなイベントに時間がかかってるかの分析だって出来ます。

f:id:pascal256:20130924163433p:plain

 

とりあえず、なにこれ、すごすぎない!? ってのが感想です。

プロファイラで取得するレベルの情報が、本番稼動システムに対して、たかだか4%から10%程度の性能ダウンで手に入るなら安いもんですね。障害分析の超強い味方になってくれそうです。

 

さすがに、本番運用は商用ライセンス買ってね☆ ってことみたいですが、これはお金払ってでも使いたいなぁ。まじめにいくら位なんだろ。その辺は後日要確認して導入を検討しよう、と。

 

 参考:

 

それではHappy Hacking!