Archives for 12月2013

2013年のOptionUtil(Play Java的な意味の)

Play framework 2.x Java and 1.x Advent Calendar 2013の5日目担当の @kara_d です。
もはや12/5を過ぎてしまいましたが頑張ります。。

今回は、PlayのOptionの話。PlayのOptionを楽に扱えるOptionUtilというライブラリを去年から作成して使っていたのですが、1年ほどいくつかの仕事で使ってみた感想を書きます。

Optionは、Scalaとかに搭載されているコンテナ型の一種です。
値のある状態とない状態を保持することが出来、値がある状態はSome、ない状態はNoneという型で表現されます。
PlayにはF.javaというライブラリがあり、この中に含まれています。

実は去年もOptionの話を書きました。

2.0以降のPlay JavaでパワーアップしたPlayのOptionをつかう

その後、

Play 2.x用の管理画面プラグイン kurad

を作成した際にOptionUtilという小さなユーティリティクラスを作成しました。

OptionUtil

OptionUtilは、Java上でScalaのOptionを利用できるようにしようという野心的なものではなく、単にPlayのOptionを便利にしようとしたものです。

これは、kuradプラグイン内での使用に留まらず、いくつかの仕事で使うことになりました。

サーバ実装が自分のみの仕事の場合

個人プレイに近いような仕事では、自分で作ったので当たり前ですが大いに活用しました。nullが入りそうなところでは、

Option<Hoge> hoge = OptionUtil.apply(someMethod());
if(hoge.isDefined) {
    // Do
}

などのように、Scalaに比べればまどろっこしいかもしれませんが、その値がどういう状態を持ってやってくるかということが型でわかるためにNullPointerほげほげ系のミスが減りました。

チームでの利用

別の仕事としてチームでの仕事をすることにもなり、ここでも積極的に導入しました。Play 2.x Javaなプロジェクトです。

Optionを使うというスタイルは、はじめは馴染まなかったものの、徐々に導入されるようになっていき、その結果コード量の割にはnull系のバグは少なくなったと思っています。

ただし、僕の伝え方が悪くてパラダイムになじめなかったケースでは、Option型の返り値でのgetOrElseメソッドにnullを入れて利用されてしまうなどはおきました。特に導入当初はJavaな人たちからしてみれば、なんじゃこりゃ的な感じでした。

その後、安定されて使用されていっており、別プロジェクトでもOptionUtilを導入しているケースを確認しました。

現在では、一部使用ライブラリの関係上null値を渡さないといけない部分以外はほぼすべてOptionUtilを利用している状態になっており、一部過剰な感じになっております。

PlayのOptionには、値があるかどうかを判断するメソッドとして、isDefined()がありますが、これが一番よく使いました。値が無い場合の設定値を指定可能なgetOrElse()もかなり使うメソッドですが、Java版でgetOrElse()を使うと結構冗長になりやすく、むしろ見づらくなってしまうことが多々ありました。

ScalaのOptionではきっと多用されているmap()も、PlayのOptionにはあります。ただ、このmap()というメソッドは関数的なものを渡す必要があり、Javaの場合は匿名クラスとなってしまうため、ちょっとした処理も非常に大げさなものになってしまい、あまり利用しませんでした。

いろいろなメソッドがScalaのほうにはあるのですが、Java版は、値の有無を型で表現する以上の用途では使っていないのが現状です(それでもかなり便利ですが…)

コードも当初のものからブラッシュアップされ、PlayのOptionからScalaのOptionを返す、asScalaなどというメソッドも入っています。

以下が現状のOptionUtilです。

package jp.greative.utils;

import play.libs.F;
import scala.*;
import scala.None;
import java.util.List;

/**
 * OptionUtil
 *
 * @author hara kazuhiro - http://greative.jp/
 */

public class OptionUtil {

    public static <A> F.Option<A> apply(A value) {
        if(value != null) {
            return F.Option.Some(value);
        } else {
            return F.Option.None();
        }
    }

    public static <A> F.Option<List<A>> apply(List<A> value) {
        if(value != null && value.size() != 0) {
            return F.Option.Some(value);
        } else {
            return F.Option.None();
        }
    }

    public static <String> F.Option<String> applyWithString(String value) {
        if(value != null && !value.equals("")) {
            return F.Option.Some(value);
        } else {
            return F.Option.None();
        }
    }

    public static <T> F.None<T> none() {
        return new F.None<T>();
    }

    public static <T> Option<T> asScala(F.Option<T> value) {
        if(value.isDefined()) {
            return Option$.MODULE$.apply(value.get());
        } else {
            return Option$.MODULE$.empty();
        }
    }

}

このOptionUtilでは、オブジェクトがnullなとき以外にも、文字列が””のときもNoneとしたり、リスト型のときは、空のリストのときもNoneとしたりしています。

というのも、Play JavaのEbeanモデルなどの検索時の返り値がこれらの状態のケースがあり、それらをNoneとして扱えた方が都合がいいケースが現場レベルではあったことに基づきます。

そして、以下が、OptionUtilの簡単なテストコードです。

package jp.greative.utils;

import org.junit.Test;
import play.libs.F;
import java.util.ArrayList;
import java.util.List;
import static org.fest.assertions.Assertions.assertThat;

public class OptionUtilTest {

    @Test
    public void testApplyShouldSome() throws Exception {
        String test1 = "abc";
        F.Option<String> test1Result = OptionUtil.apply(test1);
        assertThat(test1Result.getClass()).isEqualTo(F.Some.class);
        assertThat(test1Result.getClass()).isNotEqualTo(F.None.class);
    }

    @Test
    public void testApplyShouldNone() throws Exception {
        String test2 = null;
        F.Option<String> test2Result = OptionUtil.apply(test2);
        assertThat(test2Result.getClass()).isEqualTo(F.None.class);
        assertThat(test2Result.getClass()).isNotEqualTo(F.Some.class);
    }

    @Test
    public void testApplyShouldSomeList() throws Exception {
        List<String> test3 = new ArrayList<String>() {
            {
                add("a");
                add("b");
                add("c");
            }
        };
        F.Option<List<String>> test3Result = OptionUtil.apply(test3);
        assertThat(test3Result.getClass()).isEqualTo(F.Some.class);
        assertThat(test3Result.getClass()).isNotEqualTo(F.None.class);
    }

    @Test
    public void testApplyShouldNoneList() throws Exception {
        List<String> test4 = new ArrayList<String>();
        F.Option<List<String>> test4Result = OptionUtil.apply(test4);
        assertThat(test4Result.getClass()).isEqualTo(F.None.class);
        assertThat(test4Result.getClass()).isNotEqualTo(F.Some.class);
    }

    @Test
    public void testApplyWithStringShouldSome() throws Exception {
        String test1 = "abc";
        F.Option<String> test1Result = OptionUtil.applyWithString(test1);
        assertThat(test1Result.getClass()).isEqualTo(F.Some.class);
        assertThat(test1Result.getClass()).isNotEqualTo(F.None.class);
    }

    @Test
    public void testApplyWithStringShouldNone() throws Exception {
        String test2 = "";
        F.Option<String> test2Result = OptionUtil.applyWithString(test2);
        assertThat(test2Result.getClass()).isEqualTo(F.None.class);
        assertThat(test2Result.getClass()).isNotEqualTo(F.Some.class);
    }

    @Test
    public void testAsScalaShouldSome() throws Exception {
        String s = "String";
        F.Option<String> result = OptionUtil.apply(s);発売日
        scala.Option<String> resultScala = OptionUtil.asScala(result);
        assertThat(result.getClass()).isEqualTo(F.Some.class);
        assertThat(resultScala.getClass()).isEqualTo(scala.Some.class);
    }

    @Test
    public void testAsScalaShouldNone() throws Exception {
        String n = null;
        F.Option<String> result = OptionUtil.apply(n);
        scala.Option<String> resultScala = OptionUtil.asScala(result);
        assertThat(result.getClass()).isEqualTo(F.None.class);
        assertThat(resultScala.getClass()).isEqualTo(scala.None$.class);
    }
}

さて、このOptionUtilですが、プラグイン化して使ったらいいのにという考えもあります。

ただ、PlayのOptionを内部的に使っていて、Playに依存するため、プラグインを作成する際にどのバージョンのPlayかをプラグイン側で取り込む必要があり、Playのバージョンごとにプラグインを出力する必要がありそうということで、一旦はコードのみで提供とします。

ライセンスは、とくにないです。

Play framework 2.x Javaの本、出ます!!

上記OptionUtilですが、12/16に出版予定のPlay frameworkの書籍

Play Framework 2徹底入門
http://www.amazon.co.jp/dp/4798133922/

でもばりばりと使われております。よろしければこちらも是非ご覧ください。540ページくらいあり、頑張って書いています。

2.2対応です。

つまり、新しくなった埋め込み型のCSRFトークンとか、API用のヘッダーレベルのCSRFトークンとか、build.sbtとかにも少し触れています。

  • 発売日:2013年12月16日
  • ISBN:9784798133928
  • 判型:B5変
  • ページ数:540P
  • 定価:本体3,800円+税

アドベントカレンダー、まだ募集中です!

Play frameworkのアドベントカレンダーはScalaとJava両方があります。
まだまだ執筆者募集中なので、皆さんお気軽にぜひ!

Play framework 2.x Java and 1.x Advent Calendar 2013
http://www.adventar.org/calendars/104

Play framework 2.x Scala Advent Calendar 2013
http://www.adventar.org/calendars/114

次のアドベントカレンダーの執筆者は yuba さんです。
と思ったらもう書かれていました。速い!(というか僕が遅い…)

EBeanでもユニットテストの導入はスマートです。そう、Lombokならね。 – C Sharpens you up

Read More