鳩舎

レースしない

Rails 4.1.0.rc2 で AR オブジェクトその他の #to_json に Oj を使う

config/initializers/json_with_oj.rb あたりに以下のコードを置く

class Oj::Encoder
  def initialize(options)
    @options = options
  end

  def encode(value)
    ::Oj.dump(value, @options)
  end
end

ActiveSupport::JSON::Encoding.json_encoder = Oj::Encoder

多分そのうち Oj 側に Encoder みたいな実装入るだろうから、それが入ったらそっち使うといい。

コードはパブリックドメインです。

1人でよいコードを書く

1人でよいコードを書くのは、3人でよいコードを書くことの3倍難しい。悪いコードを書くときに説得する人間が 1/3 でよいので、つまり自分に向かって『まぁいいじゃん今回は』と言えば事が済む。続けているといつの間にか引き返せないところまできていて、適当に書いた個人プロジェクトは設計的破綻を起こし、コーディング規約もなにもあったもんじゃないという破滅が待っている。

ということで、1人でよいコードを書くために。今回は Rails プロダクトを1人で書くとして。

rubocop

コーディング規約と、あまりに長すぎるメソッドやあまりに長いクラス定義などを見つけてくれる。全てに従うと厳しすぎて死ぬので、適宜各チェッカを無効にするのがよい。

僕はこんな感じの設定で使っている。

AllCops:
  Includes:
    - Rakefile
    - Gemfile
    - config.ru
  Excludes:
    - bin/*
    - lib/monkey_patches/*
    - db/schema.rb
  RunRailsCops: true

Documentation:
  Enabled: false

LineLength:
  Max: 127

ClassAndModuleChildren:
  Enabled: false

これで一旦、コーディング規約は得られた。

code_hunter

全自動モヒカンという画期的なプロダクトである所の code_hunter を使う。

これも各チェッカの設定を書くことで適宜無効に出来る。

scss-lint

scss-lint app/assets/stylesheets で実行してとにかく何度も繰り返しみる。

そんなに厳しいチェッカではないので、相当変なことをしてない限りはどやされない。

coffeelint.rb

coffeelint.rb app/assets/javascripts/**/*.coffee で実行する。

これもそんなに厳しくないので何度もやる。

きっかけをもらう

『まぁええわ』と言うのはとにかく楽で、いくらでも言えるけど、その後『やっぱやらないと』と言うのは難しい。なので機械にきっかけを貰う。機械に言われて直している途中で『あ、やっぱここちょっとダメだな』とか思い直せる。思い直したら即やるとよい。

引数

引数、変数によってオブジェクトに適切な名前を与えるのはあたりまえのことです。

class Calc
  def plus(x, y)
    x + y
  end
end

calc = Calc.new
calc.plus(1, 2)

この場合、 #plus の中で 1x2y と名前付けがされており、それぞれ加算式の左の項と右の項として名前が与えられている。

これは実は抽象化が足りていなくて、本来なら 1.plus(2) と呼び出されるべき場面で、その場合 2 に相当する適切な名前は多分 y ではなく another_number とかそういう名前になる。

class Number
  def plus(another_number)
    self + another_number
  end
end

ではこれが『値段』オブジェクトだったらどうか、という場合もある。

class CashRegiser
  attr_reader :total_price

  def plus(price)
    @total_price += price
  end
end

レジで計算するときは基本加算だけで済むので、とにかく何らかの金額を加算してくことになる。

cachier = CashRegister.new
cachier.plus(Potato.new.price)
cachier.plus(Pasta.new.price)

いますごく安直に pirce とか名前つけてるけど、これは本当に price でいいのか、とか、そういうことを延々考えてる。

こんがらがってきた。

Remove all ads

メソッド

思いつきの雑文

メソッドを『呼び出している』のは誰か、という疑問。これが最近僕の中で実にひっかかりを覚えるところなので。

とりあえずコード。

1) publisher.send
2) publishing.execute

この2つの違いについてずっと考えている。なにが違うと言われると、これは単に主語が違う。

1の例では主語は publisher で、 publisher が何かを send するっぽい感じのコードに見える。send が気に食わないなら適宜 publish に読み替えて頂いて構わない。

で、 2の例だとそうではない。主語はメソッドの前にある動名詞ではない。 publishing という行動を誰かが、コードには現れていない誰かが execute している。

この2つのどちらかが優位であるということはない。一切ない。

みんなどっちに寄せるもんなんだろう。僕は今のところ癖で1の書き方を採用してるけど。

Remove all ads

mysql.NullTime を JSON に変換出来るようにしたい

ポッポー

package main

import (
  "github.com/go-sql-driver/mysql"
)

type Pigeon struct {
  LastFlewAt mysql.NullTime `json:"last_flew_at"`
}

みたいなことしたいとき、普通に mysql drvicer 生で使ってるとこういうことは起こらないけど gorp とか使ってると辛い感じになって困るッポー。

なので JSON に変換可能な NullTime のラッパ struct を用意しておきましたから、皆さん自由に使ってください。コードのライセンスはパブリックドメインです。

import (
    "bytes"
    "github.com/go-sql-driver/mysql"
    "time"
)

type NullTime struct {
    mysql.NullTime
}

func (nt NullTime) MarshalJSON() ([]byte, error) {
    if nt.Valid {
        return nt.Time.MarshalJSON()
    } else {
        return []byte("null"), nil
    }
}

func (nt *NullTime) UnmarshalJSON(data []byte) error {
    if bytes.Compare(data, []byte("null")) == 0 {
        nt.Valid = false
        return nil
    }

    t := time.Now()
    err := t.UnmarshalJSON(data)

    if err != nil {
        return err
    }

    nt.Valid = true
    nt.Time = t

    return nil
}

出来たポッポー

『DCI なんて面倒なだけで Service 使えばいい』への返答

NOTE: 最下部に追記があります。

よく言われる話として、 DCI なんて実装が面倒な上に夢の実装の話をしており、現実解としては Service クラスを用いて実装すればシンプルな実装になるのだから、そういったものは必要ないのだ、というご意見への返答です。

こういった批判の文脈の際、 Service クラスというのがどこの Service クラスを指しているのか、が問題なのですが、 DDD における Service ではないように思えるので、おそらく PofEAA などで語られる Service Layer などを指していると思われます(違うならそう言ってください)。

PofEAA における Service Layer(以後、 Service と呼ぶものはこの PofEAA における Service です)はドメインオブジェクトからアプリケーションロジックを切り離すことを主目的としています。これはコードサンプルを見る限り DCI における Context と非常に類似しており、 Context の説明をする際に『いわゆる Service みたいなものだよ』と説明することも多くあるかと思います。これは間違いであり、 Service と Context はスタート地点からして違うものなので、混同してはいけません。

ロミオとジュリエット

さて、今回の例題は分かった気になる DCI 、ロミオとジュリエット編を元に話をしていきます。タイトルが長すぎるのでここではロミジュリというアプリケーションとして話ましょう。

ロミジュリにおけるドメインオブジェクトは言わずもがな Actor です。役者というデータモデルであり、本来であれば Actor には年齢、性別などのデータが付与されているものかと思いますが、今回は割愛します。とにかく役者のモデルがあり、役者は喋らなくてはならないので、 #say というメソッドを持っています。

ロミジュリアプリケーションでは、ロミオとジュリエットの1シーンを再現するのがアプリケーションの仕事ですので、ロミオ役の Actor とジュリエット役の Actor が必要になります。

ここで問題になるのは、『なぜ Romeo モデルと Juliette モデルになっていないのか』です。ロミジュリアプリケーションにおける主目的はなんなのか、という根底の部分が問題になってきます。

僕がロミジュリサンプルを書いた時、これらのコードはあるアプリケーションの一部として書かれたものである、という想定で書き起こされました。それは、舞台再生アプリケーションです。舞台再生アプリケーションはシナリオをコードとして持ち、特定のシーンを役者を入れ替えて再生することが出来るというアプリケーションで、いわゆるノベルゲームの画面で、キャラを入れ替えながら特定のシーンを再生できる、といったようなアプリケーションを想定していました。

なので、役である Romeo 及び Juliette はモデルではなくあくまで振る舞いです。Romeo も Juliette もデータを持たず、アプリケーション上の都合によりどのような台詞を喋るかによって区別されます。Romeo も Juliette も、データを持ったモデルというよりは特定の台詞の集合であると捉えるべきです。

Service によるロミジュリの実装

では、実際に Service でロミジュリアプリケーションを実装するとなると、どのような実装になるのでしょうか。シナリオデータは外部からの入力などではなく、アプリケーション上にベタ書きで展開されていますので、ロミオとジュリエットが喋る台詞の内容というのはアプリケーションレイヤの話になります。

ドメインオブジェクトとしては Actor は今のところ過不足なく機能を持っているので、アプリケーションレイヤの振る舞いを抽出して Service 化するとなると、以下のようなコードになるでしょうか。

class Scene03Service
  def initialize(romeo, juliette)
    @romeo = romeo
    @juliette = juliette
  end

  def execute
    # ジュリエットによる問いかけ
    @juliette.say <<-EOS
O Romeo, Romeo! wherefore art thou Romeo?
Deny thy father and refuse thy name;
Or, if thou wilt not, be but sworn my love,
And I'll no longer be a Capulet.
    EOS
    # ロミオの当惑
    @romeo.say "Shall I hear more, or shall I speak at this?"
    # ジュリエットの懇願
    @juliette.say <<-EOS
Tis but thy name that is my enemy;
Thou art thyself, though not a Montague.
What's Montague? it is nor hand, nor foot,
Nor arm, nor face, nor any other part
Belonging to a man. O, be some other name!
What's in a name? that which we call a rose
By any other name would smell as sweet.
    EOS
  end
end

喋る内容についてはアプリケーション側の都合ですので、ドメインオブジェクトにはなりえません。ですので、愚直に実装するとこのようなコードになるかと思われます(もし、この認識が間違っている場合はご指摘いただけるとありがたいです)。

今はあえて読みにくくなるような例題を出していますので、かなり恣意的な結果になっているとは思いますが、 Service による実装はアプリケーションコードを手続き型的なコードに変容させていくのではないか、という危惧だけ伝われば幸いです。手続き型的であれ、アプリケーションが動けばよいのである、というお話でしたら、設計手法など議論する余地がないので、ここでおしまいです。

この例では、かけあいとはいえほぼお互いに言いっぱなしの状況ですから、オブジェクト間で何らかのやりとりがある、といった状況にはなっていませんが、 Actor に例えば #left_hand などの Attribute があり、手に持ったオブジェクトをやりとりする、などのシーンが実装されたとしたら、その時はより手続き型らしいコードになるかと思われます。

# ジュリエットが手に持った毒をロミオに渡す
@romeo.left_hand = @juliette.left_hand
@juliette.left_hand = nil

コード上では正しく目的どおりにジュリエットの手元の毒がロミオの手元にわたっているのですが、これは劇中進行を一切反映していません。『ジュリエットが毒をロミオに渡す』と言っているのですから、主語はジュリエットであるべきなのに1行目の主語はロミオですし、コメントがなければアプリケーションとしてこの参照の移動がどういった意味あいを持っているのか理解するのは困難でしょう。

もちろん、Service が悪い訳ではなく、今回の例は恣意的に Service で表現しにくいコードを選んで、かつ手抜きに表現しているのでわかりにくくなっています。PofEAA に造詣が深いわけでもないので、僕が勘違いしているかもしれませんがこういう時は ItemPassingService でも作って、渡す側と受け取る側に分かれてオブジェクトをやりとりするような処理が入ることになるんでしょうか。

DCI による毒の受け渡し

一方 DCI において毒を受け渡すようなシーンが必要な場合は、おそらく以下のような書き方になります。

module Juliette
  def pass(target)
    target.left_hand = self.left_hand
    self.left_hand = nil
  end
end

class PassingPoisonContext
  def execute
    @juliette.pass(@romeo)
  end
end

先ほどの例にあった処理が Juliette Role のメソッドになり、Context ではそのメソッドを呼び出すだけになりました。コード量としては増えたのですが、アプリケーションに期待するメンタルモデルがコード中に投影される量が増えたことはご理解いただけるでしょうか。

DCI は別に Role を extend して unextend する、というような部分が本質ではありません。開発として楽になるから DCI を採用する、というのも間違っているのではないかと思っています。

DCI によってコーディングが簡略化されるかというと、それはおそらくあまりないでしょうし、コードの見通し、という点にしても実際に僕自身 DCI で業務で携わる規模のアプリケーションを実装したことがありませんからわかりませんが、おそらく増えるロールとコンテキストによって、1つのコードファイルを見て把握出来ることは減るのではないかと思っています。また、現実的な利用を考えた時、一時的な振る舞いをオブジェクトに注入するのはどうするのか、という部分についてまず検討する必要があり、現在のプログラミング環境において、採用しえないパラダイムだろうなという気はしています。

それでも僕は、コード中に投影されるメンタルモデルが多くなればなるほどコードの保守性は上がっていくはずであると信じていますし、最悪、コードだけでメンタルモデルが理解出来るような日が来るのであれば、それはシナリオを書けばアプリケーションが出来上がっていくのと同義であるのではないかとすら思っています。

メンタルモデルを増やす

人はアプリケーションに何かを期待して操作を入力します。ATM であれば『自分の口座からお金が手元に出てくる』というメンタルモデルですが、実際のアプリケーション内での実装では、『操作者の口座の現金残高が入力値分減少する』『ATM 金庫内の現金を入力値分送出する』という実装がなされており、コード上の表現とユーザーのメンタルモデルは一致しません。

メンタルモデルにコードを近づけていくという作業は、自身のアプリケーションへの理解を向上させるとともに、ドメインエキスパートから得た知識をよりコード上に投影しやすくなることだと思っており、これこそが DCI の利点であり採用すべき理由だと思っています。

ということで

返答は以上です。

DCI 派からも、 Service 派からもツッコミがあると思うので、突っ込まれ次第修正していく所存です。

よりよい Service の例

monzou さんにより現実的な Service のコードの例が来た。帰省中にありがとうございます。

「『DCI なんて面倒なだけで Service 使えばいい』への返答」を読んだ感想とポエム

まずはじめに言っておくと、僕は Service より DCI の方が優れてるとは思っておらず、単に僕は DCI が好きです、ぐらいでしかないです。ただ、あまりにも DCI に対して Service でいいし DCI は要らない、と言われることが(個人的経験上)多かったのでいやもう Service の話はいいんですよ。という感じで書き始めたのでした。そういうエントリで Service をより良く知ることになるというのは面白い感じです。

さて、本題の monzou さんにもらった例を見ると、Scene がドメインオブジェクトになっており、僕のコード例は Service のコード例として実に不適切だったことがわかります。

ドメインオブジェクトとして Scene03 を実装して、 Service からはその Scene オブジェクトを実行するだけ、という形になっており、かつ、メンタルモデルの投影についても僕の書いた DCI の例と一切遜色ありません。

ていうか大体やってること一緒なんじゃないの?みたいな気持ちになりますが、そりゃそうだ、という感じです。この時の Scene の実装の違いは、 Delegation によって実装されているか、それとも extend による拡張かの違いぐらいです。

Jim Copelien 氏によれば、DCI において Role を Delegation によって実現すべきでないのだそうです。理由としては、 Context の中に入ったとしてもとある Data は変わったわけではないので、同一のオブジェクトであるべきであり、何かで Wrap された別のオブジェクトであるべきではない、という思想からくる理由だそうです。

逆を言えばその程度しか違いがないので、『DCI でも Service でも、どっちでもいいんじゃない?』となると返答は『はい』になります。とはいえ、上記のブログ記事だけだと僕も Service より DCI の方がいい!と言っているようにしか読めないのですが……単に好きなものへのバイアスがかかっているだけです。 Service 使えばいいといった人たちも Service が好きなだけで DCI はまったく意味がないと思っている人ばっかりじゃないのかもしれません。

また、手続き的になるのはトランザクションスクリプトだよ、というのもまったくその通りでした。完全に勘違いですね。

そういう意味で言うと、変な話ですが DCI における Context はトランザクションスクリプト的ではあります。ユーザーのユースケースのストーリーを順に書いていくことになるので、大きな Context になればそれだけ長いストーリーが並ぶことになります。ロミジュリの例で言えば、1シーンから1幕になったらどれだけの量の台詞の羅列が並ぶんだ……という感じですね。そういう時は Context そのものを分割して子 Context を作っていくことになると思いますが、ぱっと見のコードで手続き的に映るのは DCI なんじゃないかな、という気でいます。ユーザーの想像するメンタルモデルを羅列していくことになるので、まぁそうなるよな、という感じですが。

とまぁ話もそれてきたので戻しておくと、よりよい Service の実装を読む限り、DCI で実現しているそれは Service の層ではなくドメインオブジェクトの層なのでは?(いや、実際そうかというと多分ちょっと違うのですが)みたいな気になってきたので、Service 全然悪くない、むしろ僕はなんで DCI と比較して Service を勧められていたのだろうか……というのが今のところの心中です。

まだ俺に喋らせろ!とか『いやこういうのがあってだな』などの場合はご連絡ください。monzou さん、本当にありがとうございます。

Go で CPU を使い切る

単に CPU 時間を使い切るだけならすぐ出来る

package main

import (
    "runtime"
    "time"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())

    for i := runtime.NumCPU(); i > 0; i-- {
        go loop()
    }

    for {
        time.Sleep(1 * time.Second)
    }
}

func loop() {
    for {
    }
}

最後の for は main groutine が死なないように待っているだけ。