Subscribed unsubscribe Subscribe Subscribe

インターフェースで副作用を分離する

プログラミング

techlife.cookpad.com

この記事、わざわざgemを作ったりしていて、まあそのgem自体は便利そうだからいいと思うんですが、時刻以外の場合はどういうアプローチをしてるんでしょうか。そのたびにgemを作るんでしょうか。 そんなに悩まなくとも、オブジェクト指向の引き出しがあれば一瞬で片付く問題です。

package com.example

import java.time.LocalDateTime

// Clockインターフェース
trait Clock {
  def now(): LocalDateTime
}

// デフォルトのClock実装
trait DefaultClock extends Clock {
  def now(): LocalDateTime = LocalDateTime.now()
}

// 時刻を取得する時にはClockインターフェースを通して取得する
class SpecialContent(clock: Clock) {
  def enabled: Boolean = clock.now()
    .isAfter(LocalDateTime.of(2016, 7, 1, 0, 0, 0))
}
package com.example

import java.time.LocalDateTime

import org.scalatest.FunSuite

// 固定された時間を返すClockインターフェースの実装
class FixedClock(fixedDateTime: LocalDateTime) extends Clock {
  override def now(): LocalDateTime = fixedDateTime
}

class SpecialContentTest extends FunSuite {

  test("disabled at 2016-06-30 23:59:59") {
    val content = new SpecialContent(
      new FixedClock(LocalDateTime.of(2016, 6, 30, 23, 59, 59)))
    assert(!content.enabled)
  }

  test("enabled at 2016-07-01 00:00:01") {
    val content = new SpecialContent(
      new FixedClock(LocalDateTime.of(2016, 7, 1, 0, 0, 1))))
    assert(content.enabled)
  }

}

java.time.Clock というものが存在するので自分でClockを定義する必要はないんですが、そういうのがあるないの問題じゃないので。

Java/Scalaの世界とLLな世界を行ったり来たりして文化の違いに戸惑ったことがあるのですが、 こういう外部入力とかが絡んだ場合、Java/ScalaおじさんはOOPっぽくインターフェースを使って分離したりしようとするのに対して、 LLの世界だとクックパッドの記事中にも触れられているようにtimecopみたいにメソッドごと置き換えてしまうとか、個別のアプローチを取ることが多かったです。 オブジェクト指向は無駄だと主張する人が多いんですよねえ。

またScalaな世界でもOOPではなくモナドがどうという話になることがあります。 確かに参照透過であることは良いんですが別にファンクショナルなアプローチに頼らなくてもテストは書けるし、参照透過でもテストが辛いこともあるのでまあみんな仲良くしましょう。

自宅仕事環境(2016/05)

f:id:tototoshi:20160523214129j:plain

PC

MacBook Pro (Early 2013)

主にScalaコンパイルするのに使用する。

キーボード

昔はLite使ってたけどProにしたら戻れなくなった。矢印キーないのは意外と慣れる。

トラックボール

ケンジントン 【正規品・5年保証付き 】 ExpertMouse(OpticalBlack)(USB/PS2) 64325

ケンジントン 【正規品・5年保証付き 】 ExpertMouse(OpticalBlack)(USB/PS2) 64325

10年使ってる。ボールの触り心地が良い。

モニタ

BenQ 21.5インチワイド スタンダードモニター (Full HD/VAパネル) GW2255HM

BenQ 21.5インチワイド スタンダードモニター (Full HD/VAパネル) GW2255HM

安い

モニタアーム

グリーンハウス 液晶ディスプレイアーム 4軸 クランプ式 GH-AMC03

グリーンハウス 液晶ディスプレイアーム 4軸 クランプ式 GH-AMC03

サンワサプライ ノートパソコンアーム CR-LANPC1

サンワサプライ ノートパソコンアーム CR-LANPC1

モニターアームを使った方が机がすっきりする。

椅子

前の職場で使っていたのが良かったなあと、似たようなものを選んだ。

ScalaMatsuri2016に行ってきた

Scala

「Steps to master the Play source code」というタイトルで発表させていただきました。 Play個別のセッションがなかったことと、どうせ関数型っぽいのが多いんだろうなと思って申し込みました。Playの初級者向けのPlayのソースコードの全体像とその変遷についての話です。

1日目は自分の出番もあったためにあまり落ち着いて見て回れませんでした。2日目のアンカンファレンスをその分楽しみました。Async Testing in ScalaTest 3.0,Scala.js Compile Pipeline,Freer Monad Extensive Effect in Scalaあたりが特に面白かったです。

Async Testing in ScalaTest 3.0はScalaTestの作者によるScalaTestにAsyncなAPIを追加したいという話でしたが、質疑応答が非常に濃く、Asyncつれーな!って感じでした。

Scala.js Compile PipelineはScala.jsコンパイラの内部的な話でしたが、いかにScala.jsがガチを思い知らされるものでした。使わないけどね!

Freer Monad Extensive Effect in Scalaはおなじみねこはる先生のセッションでしたが、なぜかScala.jsコンパイラの人がツボだったらしく終始爆笑していました。私はなるほどわからんと聞いてましたが、となりでりりろじさんが解説してくれたので助かりました。使わないと思うけどね!あとどうやらFreer Monad Extensive Effect in ScalaのウラではScalaプログラマの年収について語り合う闇のセッションがあったようですね。こわいこわい。

ScalaMatsuri全体を通しては、同時通訳のクオリティが高かったため、英語セッションも存分に楽しめる満足度の高いカンファレンスでした。1日目の昼、夜、2日目の朝、昼と食糧(うまい)の配給があり、お菓子も飲み物も無限に湧いてきて、年収が低い私もお腹いっぱいです。食糧だけでなく衣類、Tシャツやスピーカー特典のパーカーも配給されましたので、全裸で行っても楽しめたのではないかと思います。

というわけで非常に楽しいカンファレンスでした。スタッフ、スポンサーの皆様ありがとうございました。

PlayのWebCommandsの使い方

Scala Play

Playのドキュメントを見るときに、公式サイトに行かなくても手元で http://localhost:9000/@documentation というURLにアクセスすればドキュメントサイトが見えるということはご存知でしょうか?

これにはWebCommandsという仕組みで動いていて、ドキュメントのほかにもEvolutionsの実行ページの表示などにも使われています。 普通のアプリケーションを作る分には必要ないですが、開発用のライブラリにWeb UIをつけたいときなどは便利です。

例えば、/@hello, /@hello/:name というパスにアクセスすると反応するWebCommandは次のような具合です。

package controllers

import javax.inject.{Provider, Singleton, Inject}

import play.api._
import play.api.inject.{Binding, Module}
import play.api.mvc._
import play.core.{HandleWebCommandSupport, WebCommands}


// 処理本体
class HelloWebCommandHandler extends HandleWebCommandSupport {
  def handleWebCommand(request: play.api.mvc.RequestHeader, buildLink: play.core.BuildLink, path: java.io.File): Option[play.api.mvc.Result] = {
    val pathPattern = """/@hello/([a-zA-Z0-9_]+)""".r
    request.path match {
      case """/@hello""" => Some(Results.Ok("Hello!"))
      case pathPattern(name) => Some(Results.Ok("Hello, " + name + "!"))
      case _ => None
    }
  }
}

// 以下DIのためのお決まりのコード
@Singleton
class HelloWebCommand @Inject() (webCommand: WebCommands) {
  webCommand.addHandler(new HelloWebCommandHandler())
}

class HelloWebCommandModule extends Module {
  override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = {
    Seq(bind[HelloWebCommand].toSelf.eagerly)
  }
}

DIのbindのためのコードが多くて目がやられますが、HelloWebCommandHandlerというのが処理本体です。 簡単ですね。パスにマッチしたらなにか結果をOptionにくるんで返せばいいわけです。 あとはモジュールをapplication.confで登録すれば動きます。

play.modules.enabled += "controllers.HelloWebCommandModule"

以上、scalamatsuri前の小ネタでした。

アプリケーションに合ったExecutionContextを使う

Scala

scalaではFutureなどの裏側ではExecutionContextが動いています。ということはExecutionContextの使い方がいまいちだとFuture周りで問題が起きることになります。

よく起きる問題の1つとして、標準のExecutionContextがいまいちだった、というのがあると思います。 標準のExecutionContextはダメというか、適さないパターンがあるのですが、あまりよく考慮されずに使われてしまう印象があります。 まあ悪いのはコンパイラの親切すぎるエラーメッセージでしょう。

scala> import scala.concurrent.Future
import scala.concurrent.Future

scala> Future { 1 + 1 }
<console>:9: error: Cannot find an implicit ExecutionContext. You might pass
an (implicit ec: ExecutionContext) parameter to your method
or import scala.concurrent.ExecutionContext.Implicits.global.
              Future { 1 + 1 }
                     ^

scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global

scala> Future { 1 + 1 }
res2: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@2bbaf4f0

scala> res2.foreach(println)
2

このようにScalaでFutureを使おうとすると、ExecutionContextがないからimport scala.concurrent.ExecutionContext.Implicits.globalをどうとか言われるので、とりあえず使っとくかーみたいな気分になっちゃいます。

標準のExecutionContextの特徴

まず、標準のExecutionContextはスレッド数がそんなに多くありません。2.11では最大でもコア数分のスレッドしか立ち上がりません。 それではデッドロックが起きやすいので2.12ではコア数+256までとなっているようですが、用途によってはもっと増やしたいですね。

それから、標準のExecutionContextはForkJoinPoolを使って実装されています。ForkJoinPoolは

It is designed for work that can be broken into smaller pieces recursively

https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

とのことで、parallel collectionとかには向いてそうだけどWebアプリケーションとかには向かなそうですね(ダメってこともないだろうけど)。 みなさんの用途には合ってるでしょうか?

アプリケーションに合ったExecutionContextを使う

標準のExecutionContextをやみくもに使うのはやめて、アプリケーションに合ったExecutionContextを使いましょう。 作り方はExecutionContext.fromExecutor, ExecutionContext.fromExecutorServiceを使えばjava.util.concurrentで作ったExecutor/ExecutorServiceを元にExecutionContextを作る、で大抵事足りる気がします。

Futureを使う時にJavaのExecutorServiceを利用する - CLOVER

フレームワークを使っている場合はフレームワークが独自のスレッドプールを用意していることがあります。 Playの場合はakkaのスレッドプールを利用していて、import play.api.libs.concurrent.Execution.Implicits._ することで有効になります。 それから、ドキュメントのスレッドプールについての箇所は必読です。

ライブラリのコードに標準のExecutionContextを含めない

Futureなどを用いたライブラリを作るときは、標準のExecutionContextをハードコードしないように注意してください。 ExecutionContextはメソッドの引数経由で渡すなど、なんらかの形で利用者が調整できるようにしましょう。

まとめ

ScalaではFutureなどのおかげで気軽に非同期処理などが利用されますが、その裏にはスレッドプールであるExecutionContextがいます。 しかし、ExecutionContextはimplicitパラメータとして暗黙のうちに引き回されるため、よくも悪くもあまり意識されません。困ったもんですね。

ExecutionContextついてはその実装を見てみることも参考になりますが、やはりJavaのスレッド周りの知識が不可欠です。 Scalaを始めたけれどJavaはよく分からないという人はjava.util.concurrent(便利)の使い方を学んでおくと雰囲気がつかめるかなと思いました。

DIについてあれこれ

Scala

Dependency Injectionとはコンポーネント間の依存関係をプログラムのソースコードから排除し、外部の設定ファイルなどで注入できるようにするソフトウェアパターンである

ってwikipedia先生が言ってました。

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - Qiita を読んでいろいろ考えたので、なんで今さらって感じのことを書きます。

ScalaでDIというとDIコンテナとかCake PatternとかReader Monadとかって話になっちゃうんですが、これらはいかにかっこよくDIするかの話であって、別にこういった道具やパターンを使わなくてもDIは可能という話です。

Constructor Injection

簡単な例で考えます。今ここにUserRepositoryにべったり依存しているUserServiceがあります。

class UserRepository {
  def findAll = ???
}

class UserService {
  val userRepository = new UserRepository
  def getAllUser = userRepository.findAll
}

UserServiceの中でUserRepositoryをnewしているので良くない。UserServiceのテストするのにUserRepositoryのこと考えてテストしなきゃいけなくてめんどくさい。なので

class UserRepository {
  def findAll = ???
}

class UserService(userRepository: UserRepository) {
  def getAllUser = userRepository.findAll
}

というように、UserRepositoryを外から与えるようにしよう。こうすればUserRepositoryの代わりにMockUserRepositoryを使ったりして楽にテストができますね。ちなみにこれはconstructorから注入してるからconstructor injection。

ところでUserRepositoryをnewしてUserServiceに与えるのって誰がやるんだって話になりますが、手動でやるんですね。だからこれは手動DIと呼ぶことにしましょう。

DIを手動じゃなくて自動でやりたくなったらDIコンテナを使い始めます。DIコンテナの何が便利かっていうと手動でやっていたUserRepositoryをnewしてUserServiceにつっこむというのを勝手にやってくれるということです。かっこよく言うとオブジェクトのライフサイクル管理を任せられる、といったところでしょうか。これはこれでとても便利です。

ところでScalaではCake PatternとかでDIできるからDIコンテナなんて不要、と言う煽りをよく見るんですが、おかしいですよね。JavaだろうとPHPだろうとDIコンテナがなくてもDIできるし、DIコンテナがやってくれる仕事はCake Patternではやってくれません。

PlayでのDI

PlayではDIコンテナとしてGuiceを使っていますが、DIコンテナありきで話が進んだわけではありません。constructor injectionベースで疎結合にやっていこうぜという話が出発点でした。PlayはGuiceを使ってる、っていうのは正しいけれど、気持ちはconstructor injectionなのです。なんかそういう話を前に書いてました。

tototoshi.hatenablog.com

Minimal Cake

で、Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - QiitaのMinimal Cakeの話です。

Minimal CakeはCake Patternに制約を設けたデザインパターンとして紹介されていますが、なんかピンと来ませんでした。

でも作者の方のコメントはピンと来ました。

Scala - Minimal Cake Pattern のお作法 - Qiita

しばらくの間は Minimal Cake Pattern のメリットが明らかでなかったので、コンストラクタによる注入パターンも書かれ続けていました。しかし依存先モジュールが多いクラスが生まれてくると、だんだん Minimal Cake Pattern の便利さが分かってきました。コンストラクタでは引数の順番を気にしなければいけないのに、Minimal Cake Pattern だと MixIn の継承順序はどうでもいいのです。依存先クラスが10個以上あるクラスに対して正しい順番でコンストラクタ引数を与えるのは苦行でしかありませんでした 。

Scala界隈の一連のワイワイガヤガヤは忘れましょう。ただInjectionの仕方を工夫したと考えたほうがスムーズに頭に入る気がします。

以上とりとめのない話でした

scala用のfixtureライブラリ作った

Scala

昔作ったscalikejdbcのfixtureプラグイン、これ別にPlayのプラグインにすることはなかったなーとちょっと思ってたんですが、最近実際困りはじめたので、Playに依存しないfixtureライブラリが欲しくなったので作りました。

https://github.com/tototoshi/scala-fixture

依存しているライブラリはないので、SlickのプロジェクトでもScalikejdbcのプロジェクトでも使えます。

インストールは

libraryDependencies += "com.github.tototoshi" %% "scala-fixture" % "0.1.0"

使い方はscalikejdbc-fixtureとだいたい同じ。Ups, Downs と書いてたのが、SetUp,TearDownで動くようにしてます。(念のため?Ups, Downsでも動くようにしてます)

#!SetUp
INSERT INTO users(id, name) VALUES (1, 'user1');
#!TearDown
DELETE FROM users WHERE id = 1;

あと、1000件くらいINSERTしてやるぜ!ランダムなデータ突っ込むぜ!みたいなときのためにScalaのコードでもかけるようにしました。

import java.sql.Connection
import com.github.tototoshi.fixture.FixtureScript

class MyFixtureScript extends FixtureScript {

  override def setUp(connection: Connection): Unit = {
    connection.prepareStatement("insert into users(id, name) values (3, 'user3')").execute()
  }

  override def tearDown(connection: Connection): Unit = {
    connection.prepareStatement("delete from users where id = 3").execute()
  }

}

テストコードはこんなかんじになります。 beforeでsetUp, afterでtearDownすると良いでしょう。 詳しくはREADMEを読んでください。

import org.scalatest.{ BeforeAndAfter, FunSuite }
import com.github.tototoshi.fixture._

class FixtureTest extends FunSuite with BeforeAndAfter {

  val driver = "org.h2.Driver"
  val url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
  val username = "sa"
  val password = ""

  val fixture = Fixture(driver, url, username, password)
    .scriptLocation("db/fixtures/default")
    .scriptPackage("com.example.fixtures")
    .scripts(Seq("script1.sql", "script2.sql", "MyFixtureScript"))

  before {
    fixture.setUp()
  }

  after {
    fixture.tearDown()
  }

  test("load fixtures") {
    // write tests
  }

}