Create a gist now

Instantly share code, notes, and snippets.

@mizchi /hanabi.md
Last active Mar 29, 2017

Embed
What would you like to do?

花火~ 最高な俺たちと糞コードの海

written by mizchi at 小物エンジニアの会.

最高の夏の花火について 花火

自己紹介

  • ゲームエンジニア
  • 仕事 JavaScript/AS3/C#
  • 最近 League of Legends しかしてない

最初に伝えたいこと

  • この発表は省力モードです(gist)
  • 原稿消えた + 前日糞眠かった
  • スライドにするにはコード多すぎた
  • サーセン

今日伝えたいこと

最高の夏 = 花火 = イベントディスパッチャー = オブザーバーパターン

オブザーバーパターン

購読と発火

よくあるjQueryのコード

<button id="clickme">click me</button>
$("button#clickme").on("click", function(e){
  console.log("clicked", e);
});
  1. buttonをマウスでクリック
  2. DOMがclickイベントを発火
  3. 登録された関数をeventオブジェクトを引数に実行

DOMはイベントを発火する

自分でイベントを発火してもよい

今日はjQueryで解説するけど、各自適当なフレームワークに置き換えて読んで

$target = $("#target")

$target.on "myevent", (e) -> console.log "hello", e.type
$target.trigger 'myevent', {type:"myevent"}

オブザーバーパターンを使う目的

イベントを実行する人が、イベント発火元を知らなくて良いようにする

購読者は発火元を知っているが、発火元は購読者が誰か知らなくて良い

=> だいたいにおいて特定の構造を要求してダックタイピングする

(タスクシステムのタスクがexecute要求する、みたいな話)

オブザーバーパターンを使うケース

  • HTML
  • Flash
  • Node/EventEmitter
  • というかモダンなGUIライブラリだいたい全部

GUIをやるなら避けては通れない概念。 サーバーでもNodeのEventEmitter等で使うし多分いろいろある

便利な実装パターン: イベントメッセージの翻訳

何はともあれ聞いてくれ。今日ドラッグイベントからドラッグアンドドロップを実装したいとする。

設計

標準のマウスイベントをハンドリングして、ドラッグアンドドロップのイベント名で発火しなおす

目的

泥臭くて面倒臭い実装になるであろうフラグ処理や座標計算を、受け手では意識しないように必要な情報を受け取れるようにする 「うまく実装出来れば」 dispatcherとobserverが疎結合になり、コード完結になり、再利用しやすい。

(今回は面倒臭いので擬似コードだけで実装はしない)

ドラッグアンドドロップに最低限必要な情報は以下の3つ

  • x : Number
  • y : Number

まず擬似コードを書いてみる(jQuery/coffee)

$target = $("#target")
$area   = $("#dragAndDropArea")

# ドラッグ領域がターゲットにイベントをdispatchするように登録する関数
$.registerDragAndDrop({
  from: $area
  to  : $target
})

$target.css {backgroundColor:"blue"} # 最初は青

$target.on "dragstart", (e) ->
  $target.css {backgroundColor:"red"} # 赤くする

$target.on "dragmove", (e) ->
  $target.css {left:e.x, top:e.y}

$target.on "dragend", (e) ->
  $target.css {left:e.x, top:e.y}
  $target.css {backgroundColor:"blue"} # 青に戻す

こんだけできりゃ十分

(似非コードなので全然正しくないです)

実装概要

使うマウスイベントは以下の三種

  • mousedown
  • mousemove
  • mouseup

このとき内部的には以下の状態を持つ

  • isDragging: 現在ドラッグ中か否か
  • target : ドラッグする対象

要点

イベントの受ける側は、内部状態を知りたくない(興味が無い -> 関心の分離)

実際のフローは以下のようになる

  1. 「開始条件」mousedown時に、もしtargetが現在の座標の上にかぶっていれば、targetをセットし、現在の座標を保存してdragstartイベントを発火
  2. 「更新」mousemove時に、もしisDragging == true ならば、座標計算してdragmoveイベントを発火
  3. 「終了条件」mouseup時に、もしisDragging == true ならば、dragendイベントを発火

「適切に使えば」こうなる

じゃあ失敗すると…???

何も知らずにオブザーバーパターン使ったらどうなるの

gotoになる

オブザーバーパターンのアンチパターン

他人のコールバック関数の横取り

onClick = (e) ->
  console.log("clicked", e);

$("button#clickme").on "click", onClick
$("button#clickme").on "hover", (e)->
  onClick(e)

やることが一緒だからといって他人のコールバックを使ってはいけない! コードから意図が見えないので、読む人は「死ね」と呟きながらコードを辿ることになる ハハッ俺はやらないよ、と思ってるでしょ?こういうコードすごく多いから…

命名規則違反

onClick = (e) ->
  console.log("clicked", e);

initialize = ->
  onClick({})

onXXX はコールバックであることを期待するけど、普通の関数のように使われてるケース。だいたい期待を裏切るので糞コード。 onXXXだとか, XXXhandlerってのがよくある命名規則。(スレッド使うコードはhandlerが多い気がする

Fat Controller

onClick = (e) ->
  console.log("clicked", e);

$("button#clickme").on "click", (e) ->
   # 100行に渡る実装

基本的にはオブザーバーは「段階」を表現するので、MVCでいうとコントローラの役割をすることが多い。 要はコントローラに全部書くなということ。MVCの基本。

Eventを要求する非オブザーバー関数

onClick = (e) ->
  console.log("clicked", e);

execute = (e) ->
  # event scheme に依存した処理

$("button#clickme").on "click", (e) ->
  execute(e)

上のやつを改良したかのように見えて、実は何も改善していない。 コントローラとして必要な責務はパラメータの分解であり、eventオブジェクトと密結合なexecute関数はリファクタリングの余地が残っている。 逆に言えばパラメータの分解以外やるなということでもある。

イベントの書換

onClick = (e) ->
  console.log("clicked", e);

execute = (type) ->
  #...

$("button#clickme").on "click", (e) ->
  e.type = "hoge"
  execute(e.type)

イベントは受け取り手で発行されたままの状態を維持することを期待する。OOP的に言うならgetterがあってsetterがない。

イベントディスパッチャーの実装によるが、各インスタンスへ発行するイベントが共有される場合、発火順に依存する副作用が発生する。

モンキーパッチならともかく、本番コードでこのコードを書く奴は万死に値する。

クロージャによるメモリリーク

このコードをみてほしい。こいつをどう思う?

window.onload = ->
  somethingBigAndTemporaryInstance = {...}
  console.log somethingbigAndTemporaryInstance

  $("button#clickme").on "click", (e) ->
    console.log "hoge"

すごく…おおきいメモリリークです…

この somehitngBigAndTemporaryInstance は、実はGCによってリリースされない。即時関数のクロージャに引っかかって参照カウントが残っている。

最近の当たり前のようにモダンなクロージャがあるプログラミング環境ならやりがちなミスだが、発見も難しい。

RubyやC++11のlambdaはクロージャの名前空間を覗くスコープを定義できるので、こういうミスは減る。Java6(DalvikVM)にクロージャはない。

多段イベントディスパッチャー

最初に翻訳パターンを解説したのは意味がある

$target = $("#target")

$target.on "a", -> $target.trigger 'c'

$target.on "b", ->
  # ...
  $target.trigger 'd'

$target.on "c", ->
  # ...
  $target.trigger 'b'
  $target.trigger 'd'

$target.on "d", -> console.log "fire!!!"

$("#button").trgger "a"

# fire
# fire

読めますか?僕の悲しみがわかりますか?

僕は20段の絡み合ったイベントディスパッチャーをみたことがあります。

最高の夏

イベントが発火する様子

花火

糞コードの海に沈めるぞ

ご清聴ありがとうございました

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment