手続きを意識させない設計をする

Twitter のツイート数を取得できる URL がある。

http://urls.api.twitter.com/1/urls/count.json?url=example.org

例えば、この URL からツイート数をコンソールに表示するプログラムを書きたいとする。coffeescript

愚直に書くとこんな感じ

JSON_URL = "http://urls.api.twitter.com/1/urls/count.json?url="
request = require "request"

request JSON_URL+"example.org", (err, res, body) ->
  data = JSON.parse body
  console.log data.count

これだと使いまわせないから、関数にする。

getTweetCount = (url, callback) ->
  request JSON_URL+url, (err, res, body) ->
    data = JSON.parse body
    callback data.count

getTweetCount "example.org", (count) ->
  console.log count

良さ気に見える。
でも、これだと、複数回よんだとき、
同じ URL でも何度も読みに行ってしまうから、良くない。

getTweetCount "example.org", (count) ->
  console.log "first", count
getTweetCount "example.org", (count) ->
  console.log "second", count

もちろん、これぐらい近い位置で呼ぶなら、
同じコールバックで括ればいいけど、そうも行かないことは、ままある。

ここで満を持して、クラスやイベントを活用する。
予め、load をしておいて、値を取得するときは、 get を使う

EventEmitter = (require "eventemitter2").EventEmitter2

class TweetCount extends EventEmitter
  constructor: (@url) ->
  load: =>
    request JSON_URL+@url, @onLoad
  onLoad: (err, res, body) =>
    data = JSON.parse body
    @count = data.count
    @emit "load"
  get: -> @count

tweetCount = new TweetCount "example.org"
tweetCount.load()
tweetCount.on "load", ->
  console.log "first", tweetCount.get()
tweetCount.on "load", ->
  console.log "second", tweetCount.get()

しかし、これだと、
load 前と load 後で、値の呼び方を変えなければいけない。
load 後は .on "load" が呼ばれないし、load 前は @count が定義されていない。 使うときには、TweetCount が既にロードされているかどうかを、常に意識しなければいけない。

そういう事のないように、
複雑な手続きを踏むインターフェイスは、
順番や状態を意識せずに使えるよう、善く設計する必要がある。

class TweetCount extends EventEmitter
  constructor: (@url) ->
    @loaded = false
    @loading = false

  get: (callback) =>
    return callback @count if @loaded
    @once "load", callback
    @load() unless @loading

  load: =>
    @loading = true
    request JSON_URL+@url, @onLoad

  onLoad: (err, res, body) =>
    data = JSON.parse body
    @count = data.count
    @loading = false
    @loaded = true
    @emit "load", @count

tweetCount = new TweetCount "example.org"

tweetCount.get (count) ->
  console.log "first", count
tweetCount.get (count) ->
  console.log "second", count

状態を吸収することで、
常に同じインターフェイスを利用できるように、設計をする。