キーパーソンは誰だ
引き続き目標の言語化。今後の行動の方向性を決めるものなのですごく大切だ。
開発シーンでは開発チームと問題が遠くなる場合もあるが今日は近い場合、そして一緒に解決する場合の話。
チームのオーダーをくれた人
チームが作ったものに対して承認をする人。この人と話をすると合格の方向性が見えてくる。抽象化された目標でも明後日の方向に向かって作られたものは承認されないのだ。この人とは合格の方向性を握ろう。
事業の偉い人
一緒に仕事をする人の上長。ミッションを与えている。進んでほしい理想像を知っている。方向性を決める参考にしよう。なぜそのミッションになったのかもっと聞いてみよう。さらにその先○年後のビジョンなども聞いてみよう。
一緒に仕事をする人
実際の課題を体系的に感じ取っている人。一番課題に近い人。直近の戦略を決めるときの参考になる。どんな問題やどんな人が存在しているのか聞いてみよう。対策をとっているのならどんな人にどんな対策をとっているのか聞いてみよう。技術でサポートできるのはどんなことだろうか。目標達成のためのストーリーが描けるかも すると自ずと優先順位が導き出せそうだ
ここまで話をすると作戦が決まった。ポイントは当事者だけで話をすること。小さくやっていく。
誰と会話するべきなのかはきっちり手持ちの情報を照らし合わせて決めていこう。狙いを持って話を聞きに行くことが肝心。
話のときには着目ポイントに沿ってメモしよう。すると議論に一貫性が生まれる。
大きくうなずき、話に割って入らない。深掘り、向きを変え、欲しかった答えを聞き出そう。
話は色んなエピソードを分解して再構成したり、抽象化と具体化を意識するだけで見え方が変わる。抽象度合いは話す相手によりレイヤーが違う事業責任者に具体策を聞いても組織構造の意味がない。
曲者
ちゃぶ台を返しそうな人。鶴の一声でいろいろ変えられるひと。ストーリーが決まったらこの人に合意を取ろう。
チームメイト
この時点で同意を一度取ろう。このあと具体策がでてきてもストーリーに沿っていれば疑問は生まれない。納得感を作り出そう。
目標の言語化の間にいろんなキーパーソンが存在する。ひたすら話をしてまとめて戦略を練って話を聞くことの繰り返し。こうして戦略は練られていく。
チームの一歩先を行く人
チームを率いるリーダーになりました。 日記のような形で日々の学びを書き出したくなったので頻度上げて書いていこうと思います。
なので自分に対する話し言葉で書き残します。
チームメイトの一歩先を行き、水先案内人になろう
リーダーに求められることは大きく2つあった。
- チームに期待されている成果を上げること
- チームメイトの先を行き水先案内人になること
特に下について、だいたい1Qを見渡すことから始めることに。大変。 チームメイトが頑張ってくれたのに結果を出せなければリーダーの責任と言われる。全くそのとおり。 戦略をしっかり練って水先案内をしないといけない。なのでリーダーの仕事は考えることっぽい。 このあたりで意識が引き締まった。
大きなプロセスイメージを描く
早速わからなくなったのが次にどうすればいいか。 先を行くにあたりどういう動きをすればいいんだろう。
こうらしい。水先案内人をするってことは抽象度が高い目標をチームメイトに見せる前にある程度具体化の作業が必要。 なぜなら新しい開発をするに当たり、問題提起した人がいるわけで、それに関わるキーパーソンからのヒアリングはすることになるから先んじてやっておけばチームメイトが迷いなくスタートを切れる。 抽象度が高いと不安になるし、それに集団で立ち向かって機能的に動けるほどまだチームは成熟してない。ならばリーダーが率先して動いたほうが良い。
業務のつながりを意識したコミュニケーションを取る
大きい組織になれば分業制は当たり前。そうなると仕事のパスが行われるときにたいてい取りこぼしがある。つながりを意識したコミュニケーションを取ることで個々の知識が流動的につながっていることを感じられてチームとして一体感を作れそうだと思った。
メンバーの体験設計でも生きる。チームメイトのバックグラウンドなんて多種多様。ならばちゃんとチームメイトが直前にお世話になっていた人 (コーチやメンター)に話を聞きに行くと目指していくキャリアやどういう人なのかわかる。これはメンバーが気持ちよく仕事をできるようにする材料になる。大いに役立ちそうだ。
まとめ
まだ動き出したばかり、言葉もつまり、ヘルプを出すシーンも多い。悔しいがしょうがない。とりあえず明日の動き方を決めて今日は良く寝よう。
UIから考えるモデリング in Elm
Modelは大切
皆さんは何を考えながらモデリングしてますか?まずはElm Architectureを見てみましょう。
ViewはModelに依存しUpdateもModelに依存しています。したがってModelの影響の大きさは言わずもがなです。
ちょっとしたModelの修正も影響範囲が多くて溜息をつくことは少なくありません。影響は少ないほうがいいですよね。
今回はモデリングについて考えてみましょう。
UIから考える
今回はこの動画にある方法を使ってモデリングをやってみようと思います。
リチャードさんはまずはUIから考えようと言っています。インライン編集可能なブログ記事のページを考えてみましょう。
TitleとStoryという本文で構成されたブログ記事のページです。記事の作成者はインライン編集ができます。
ペンのアイコンをクリックすると右のページようなインライン編集状態になります。Saveボタンをクリックすると左のページのTitleが編集したものに置換されます。
みなさんもこのようなページをモデリングしてみてください。それでは次に私がモデリングしてみます。
type Article = Article { title : Title, story : Story } Property type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String -- MODEL type alias Model = { article : Article }
Titleのインライン編集に着目してモデリングしてみました。皆さんのモデリングと見比べてどうでしたか?
それでは次のモデリングと見比べてみましょう。
type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String -- MODEL type alias Model = { title : Title -- READ/WRITE , story : Story -- READ/WRITE , property : Property -- READ/WRITE }
前者と後者は何が違うのでしょうか?後者はレコードの要素をいつでも操作することができます。
そのためPropertyがReadOnlyであってもtitleとstoryは変更できます。もちろんそのようにしないロジックを構築すれば防ぐことができます。
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleEditTitle -> ( { model | property = WriteTitle model.title }, Cmd.none ) SetTitle title -> case model.property of WriteTitle _ -> ( { model | property = WriteTitle title }, Cmd.none ) ReadOnly -> ( model, Cmd.none ) SaveTitle -> case model.property of WriteTitle title -> ( { model | title = title, property = ReadOnly }, Cmd.none ) ReadOnly -> ( model, Cmd.none )
それでは前者の場合はどうでしょう。
module Article exposing (Article, empty, mapTitle, toggleTitle) type Article = Article { title : Title, story : Story } Property type Property = ReadOnly | WriteTitle Title type alias Title = String type alias Story = String empty : Article empty = Article { title = "", story = "" } ReadOnly toggleTitle : Article -> Article toggleTitle (Article edits property) = case property of ReadOnly -> WriteTitle edits.title |> Article edits WriteTitle title -> ReadOnly |> Article { edits | title = title } mapTitle : (Title -> Title) -> Article -> Article mapTitle modifier ((Article edits property) as article) = case property of ReadOnly -> article WriteTitle title -> WriteTitle (modifier title) |> Article edits
module Page.Article exposing (..) import Article exposing (Article) -- MODEL type alias Model = { article : Article } -- UPDATE update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleEditTitle -> ( { model | article = Article.toggleTitle model.article }, Cmd.none ) SetTitle title -> ( { model | article = Article.mapTitle (always title) model.article }, Cmd.none ) SaveTitle -> ( { model | article = Article.toggleTitle model.article }, Cmd.none )
いかがでしょうか。モジュールを2つに分けてみました。
これならArticleはReadOnlyのときにtitleを変更できません。なぜならそのような関数がArticleモジュールから公開されてないからです。
また前述のパターンと異なり、WriteTitleコンストラクタも公開していません。このためArticleモジュール内部にArticleの実態は隠蔽されています。
この方法をOpaque Typeと呼びます。テクニックの一つですが動画では必須とまで言われています。
これで、Articleモジュールを編集してもUpdateやViewに影響が出ないように作ることができました。ネストが浅くなり多少見やすくなってないでしょうか。
また、title編集時の状態を WriteTitle Title
と定義しているのもポイントです。
type alias Model = { title : Title , story : Story , editableTitle : Maybe Title , editableStory : Maybe Story }
少し定義を変えてみました。それではこの状態はとり得るのでしょうか。
type alias Model = { title : Title , story : Story , editableTitle : Just Title , editableStory : Just Story }
インラインで編集可能なのは一箇所だけとします。するとeditableTitleとeditableStoryの両方がこのような状態を取ることはありえません。
このようなアプリケーションにありえない状態を作り出すことが可能なモデリングを避けることでバグの少ないアプリケーションを構築できます。不可能な状態はモデリングで防ぐことができます。
サマリ
- UIから考えることでモデリングができる
- Oppaque Typeで変更に強いモデルを作ることができる
- 不可能な状態 (Impossible state) を排除することができる
所感
またもや動画からモデリングの方法の一部を紹介しました。Elmでアプリケーション開発をしているとモデルを変更する機会は多々訪れます。
そのたびにModel, Update, Viewのすべてを変更するのはとても大変です。今回の方法を用いてなるだけ変更の影響がもれないように気をつけてみてください。
しかし、モデリングのポイントはこれだけではありません。UI StateとDomainをどのようにモデリングするのかという話題もあります。
フロントエンドでDDDをしようものならもっと複雑になることでしょう。一概にこれが正解というわけではないので皆さんの課題に沿った最善の方法を模索していきましょう。
そしてどんどん私に教えてください。 (違
Elmのモジュールを作るときはどういうとき
モジュールを作るとき
いきなりですがこの動画を見てください。この動画はElmの作者のEvanさんによる発表です。
まず見て驚くのはElmの場合、一つのファイルに対してプログラムを育てていくことではないでしょうか。通常システムを作るときには責務を切り分けて責務ごとにモジュールを作成することが結果的にファイル分割につながっていました。
Elmでも同様にElm ArchitectureによりModel, Update, Subscribe, Viewにシステムが分割されています。しかし単一の責務ごとにモジュールを作成することなく一つのファイルにアプリケーションを構築していきます。
この発表いわく通常それは400から1000行くらいのコードまで成長するそうです。そのあたりからモジュール作成の兆しが訪れることが述べられています。
Modelの構築の仕方に習っていくと新しいCustom Typeを作る機会が訪れます。まずはそのCustom Typeを作成して、そのCustom Typeにまつわる関数を作ります。
こうしてできたCustom Typeとそれにまつわる関数を別モジュールに切り出します。これがElmにおけるモジュール作成のタイミングと作成方法です。
このことはドキュメントにまとまっています。
Modules · An Introduction to Elm
モジュールの変更による影響範囲を閉じ込める
またいきなりですがこの動画を見てください。この動画はEvanさんの同僚NoRedInkで働くRichardさんの発表です。
動画で述べられている通りModelに依存しているシステムは多いのでModelの変更は影響が多岐にわたることが多いです。これをModel内の変更に留めるにはどうするといいでしょうか?
積極的にOpaque Typeを使うことを主張しています。Opaque Typeについては下記の記事が参考になります。
Advanced Types in Elm - Opaque Types – Charlie Koster – Medium
Elm PackagesのAPI Documentにも記述があります。
適切なインターフェース (関数) を作成し、exposingで公開するものを絞り込むことで変更をモジュールに閉じ込めることができます。
module Post exposing (Post, fromDoc) type Post = Post doc fromDoc : doc -> Post fromDoc = Post
やりすぎないこと
Evanさんはやりすぎないことを注意としています。Custom Typeによるモジュール化を進めると最初のうちから再利用を意識して同じロジックをまとめたがります。
そんなときには一呼吸おいて問題が起きてから対処するようにしましょう。例えばページによって同じCustom Typeでもデータ構造が異なることがあります。
module PageX exposing (..) type Doc = Doc Title Content type alias Title = String type alias Content = String
module PageY exposing (..) type Doc = Doc { title : String , content : String , description : String }
早い段階から共通化を見越して過剰に手を加えるのではなく実際に重複するロジックが出てくるまで待ってみましょう。
共通したデータ構造があるのならコードを改善してシェアしましょう。このようにElmでは作っていく最中に発見してリファクタリングを積極的に繰り返すことを勧めているように感じます。
それは強力な型と言語設計によりリファクタリングをしやすい環境が整っているからでしょう。
サマリ
- Elmでは必要な時が来るまでモノリシックなモジュールでアプリケーションを育てていく
- カスタムタイプが増えてヘルパー関数ができたときがモジュールを作るサイン
- モジュールはカスタムタイプに対する責任を持つ
- Opaque Typeを積極的に使い影響範囲をなるべくモジュール内に限定する
- 過度なモジュール化はすべきではないリファクタリングすることを念頭に置く
所感
今回はElmのモジュールについて記事にしました。Elmではこのような思想がありますが、一つのファイルで1000行もコードを書いていてはチーム開発をしているとコンフリクトが多発しそうですね。
また、モジュールの作り方にこのような指針がないとチームで開発ではこの関数はどのモジュールに属するのがいいのか迷ってしまうシーンがあります。
JavaScriptとは異なるモジュールの作成プロセスですが、Elmではこのようなプロセスをおすすめしていることを紹介しました。
Elmはどうやって超小さいアセットを実現したのだろう
Elmはとても小さいアセットを作ることができる
We had a community member working on a 49,315 line application try the new version and he got 114kb after compilation, minification, and gzip. That is in the ballpark of the 100kb produced by the 2000 lines in the Vue implementation!
blog/small-assets-without-the-headache
リンク先の画像が示すように、Elmはリリースされた0.19で十分に小さいJavaScriptファイルを生成することができるようになりました。RealWorldアプリケーションでの検証結果ですが、ひと目で主要なフロントエンドのライブラリ利用時よりも小さくなっている事がわかります。
0.19リリース時に公開されたこのブログ記事を見て僕は「すごいなー」と感心しました。一体どうやってこの小さなファイルを生成することができるようになったのでしょう。
デッドコードを削除する
僕はしばしば課題に直面すると同じような課題に対してアプローチしているライブラリがないか探す事があります。JavaScriptにはNPMという素晴らしいシステムがあるので、簡単にプロジェクトにライブラリを導入できます。
しかし、ライブラリのコードをすべて利用することは殆どないのです。ライブラリが公開しているAPIはどれも魅力的なものですが課題を突破するためにはいくつかのAPIで事足りる事が多いからです。
デッドコードはこうしたときに生まれます。利用してないコードがあってもライブラリをインストールした以上、ビルド時には無視できない存在となっています。もちろんライブラリだけではなくプロジェクト内のコードだって同じように考えることができるでしょう。
巨大なプロジェクトでは利用されてないコードが生まれることを見落としていることもあるかもしれません。webpackに代表されるようにツリーシェイキングによってこのようなデッドコードは削ぎ落とされることになりますが、完璧に削ぎ落とせてないそうです。
どうやらwebpackはモジュール単位でルーチンを走らせており、モジュールの粒度が大きくなればなるほど利用してないコードが紛れ込む可能性が高くなります。直接APIを呼んでない場合でも別のルーチンから呼ばれている場合もあるためライブラリが増えれば増えるほどデッドコードは増加する傾向にあります。
対策としてlodashに代表されるように関数ごとにモジュールを分ける方法があるものの、JavaScriptではどのモジュールでも実行時に関数の変更ができるので今呼ばれてないからと言って簡単に削除することはできないのが実情です。
しかし、Elmの関数は実行時に再定義と削除ができないため、メイン関数から到達可能であれば残して到達できなければ削除することができます。これがElmがアセットを小さく保つことができた1つ目の理由です。
また、Elmの場合はNPMとは異なり、package.elm-lang.orgでライブラリを公開しており、ここで公開されているライブラリはすべてElmで書かれているので安心して同じように削除できるのです。
レコードフィールドをリネームする
Elmにおけるレコードとは次のようなものです。
student = { name = "Haruka" , age = 17 }
フィールドとはname
とage
を指していいます。これを参照する場合はstudent.name
となりますが、0.19ではコンパイルの前後でs.n
とリネームされるのです。
驚くべきことにこれで5% ~ 10%縮小するそうです。やはりこれもライブラリを含めたエコシステム全体で機能できます。
サマリ
- Elmは0.19で主要なライブラリのどれよりも小さいJSファイルを生成する事ができる
- デッドコードを削除する
- レコードフィールドをリネームする
所感
非常に小さなアセットの生成が可能になった背景には言語の特性とその特性沿ったエコシステムがありました。一方でElm 0.19には多くの破壊的変更が含まれています。
compiler/0.19.md at master · elm/compiler · GitHub
コンパイラの最適化にはこのような変更を盛り込むしかなかったのでしょうか。Elmは純粋な関数型プログラミングの土壌を用意してくれていますが、JavaScriptに変換する際に問題を作り出してしまうこともあったのではないでしょうか。
変更の裏にはこうした課題を突破できないのでやむを得ず適用した変更したものもあるかもしれません。世界観を一貫して守りきる思想の強い言語という雰囲気を感じ取ることができました。それにしても破壊的変更が多くて大変です。
Elmのアプリケーションコードを読んでみよう
ElmアプリケーションはElmアーキテクチャーに沿って構築していきます。構築したアプリケーションコードはElmのコアコード上でアプリケーションとして振る舞います。
Elmのコアコードがどんなことをしているのか気になったので、今回はElmアプリケーションが起動するまでにどういうことをやっているのかを追っていきたいと思います。
起動呼び出し
Elmアプリケーションを起動するには以下の呼び出しを最初に行います。
var app = Elm.Main.fullscreen()
他にもいろいろ起動するために呼び出せる関数はありますが、とりあえずこちらから考えます。見て分かる通り Elm.Main
オブジェクトを操作しているので、まずはこれがどういうオブジェクトなのか知ることから始めていきましょう。
Elm.Main
var Elm = {}; Elm['Main'] = Elm['Main'] || {}; if (typeof _user$project$Main$main !== 'undefined') { _user$project$Main$main(Elm['Main'], 'Main', undefined); }
非常にシンプルなコードですね。_user$project$Main$main
をみると、どうやらコンパイラは モジュール名$関数名
という名前付けをしているようです。
Elm['Main']
にどういう変更が加えられるのか見ていきましょう。
makeProgram
function makeProgram(flagChecker) { return F2(function(debugWrap, impl) { return function(flagDecoder) { return function(object, moduleName, debugMetadata) // == _user$project$Main$main { var checker = flagChecker(flagDecoder, moduleName); if (typeof debugMetadata === 'undefined') { normalSetup(impl, object, moduleName, checker); } else { debugSetup(A2(debugWrap, debugMetadata, impl), object, moduleName, checker); } }; }; }); }
_user$project$Main$main
は object, moduleName, debugMetadata
を引数に取る無名関数みたいです。flagChecker
は checkNoFlags
と checkYesFlags
が用意されています。
F2
は関数を受け取ってカリー化して返します。逆に A2
はカリー化された関数に引数を渡して評価する関数です。 debugWrap
は _elm_lang$virtual_dom$VirtualDom_Debug$wrap
です。
impl
は Html.program
に渡すおなじみのレコードです。見てわかるように Elm['Main']
は debugMetadata
の有無で呼び出す関数が異なります。
今回は undefined
でした。Html.program
はこの関数の実行結果で実装が定義されます。
normalSetup
function normalSetup(impl, object, moduleName, flagChecker) { object['embed'] = function embed(node, flags) { while (node.lastChild) { node.removeChild(node.lastChild); } return _elm_lang$core$Native_Platform.initialize( flagChecker(impl.init, flags, node), impl.update, impl.subscriptions, normalRenderer(node, impl.view) ); }; object['fullscreen'] = function fullscreen(flags) { return _elm_lang$core$Native_Platform.initialize( flagChecker(impl.init, flags, document.body), impl.update, impl.subscriptions, normalRenderer(document.body, impl.view) ); }; }
ここで Elm['Main']
に変更が加えられます。これ以上、 Elm['Main']
に変更は加えてなさそうです。
initialize
function initialize(init, update, subscriptions, renderer) { // ambient state var managers = {}; var updateView; // init and update state in main process var initApp = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) { var model = init._0; updateView = renderer(enqueue, model); var cmds = init._1; var subs = subscriptions(model); dispatchEffects(managers, cmds, subs); callback(_elm_lang$core$Native_Scheduler.succeed(model)); }); function onMessage(msg, model) { return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) { var results = A2(update, msg, model); model = results._0; updateView(model); var cmds = results._1; var subs = subscriptions(model); dispatchEffects(managers, cmds, subs); callback(_elm_lang$core$Native_Scheduler.succeed(model)); }); } var mainProcess = spawnLoop(initApp, onMessage); function enqueue(msg) { _elm_lang$core$Native_Scheduler.rawSend(mainProcess, msg); } var ports = setupEffects(managers, enqueue); return ports ? { ports: ports } : {}; }
続けて fullscreen
呼び出しによる起動に至るまでの処理を眺めていきます。この関数はかなり重要そうです。
_elm_lang$core$Native_Scheduler.nativeBinding
に渡されている関数はどちらも同じような内容です。おそらく view
を更新して effectManager
に cmds
と subs
を渡して変更を通知しています。
_elm_lang$core$Native_Scheduler.nativeBinding
を始めとする _elm_lang$core$Native_Scheduler
モジュールの関数はElmアプリケーションのイベントループに大いに関係しています。
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
nativeBinding
が返すオブジェクトは後にenqueueされて処理されます。initApp
は初期化に必要な処理、onMessage
はメッセージを受け取った時に呼び出す処理だと推測できます。
setupEffects
は dispatchEffects
のための準備ではないでしょうか。もしかするとEffectManager関係の処理かもしれないです。ここで返しているオブジェクトは app
に代入されますが利用してないですね。
spawnLoop
function spawnLoop(init, onMessage) { var andThen = _elm_lang$core$Native_Scheduler.andThen; function loop(state) { var handleMsg = _elm_lang$core$Native_Scheduler.receive(function(msg) { return onMessage(msg, state); }); return A2(andThen, loop, handleMsg); } var task = A2(andThen, loop, init); return _elm_lang$core$Native_Scheduler.rawSpawn(task); }
一見するとこんがらがりそうなんですが、return
文に注目すると task
を enqueue
しているのがわかります。
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
この task
は _elm_lang$core$Native_Scheduler.nativeBinding
の仲間です。これは _elm_lang$core$Native_Scheduler.andThen
で処理がラップされています。
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
コールバック関数の loop
は handleMsg
という task
を設定して再度 andThen
でラップしています。_elm_lang$core$Native_Scheduler.receive
も _elm_lang$core$Native_Scheduler.nativeBinding
の仲間です。
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
rawSpawn
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
process
で task
をラップしています。 process
を enqueue
します。
work
キューに溜まった process
を処理します。取り出して process.root
があれば step
関数に渡します。process
がなくなると活動を停止します。
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
step
enqueue
された process
は step
関数で処理されます
core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub
enqueue
された task
をおさらいします。
process(andThen(loop, nativeBinding(task)))
順に処理していきますが、 loop
が実行される気配がありません。 loop
は process.stack.callback
に積まれたままです。
ちょっと謎ですね。 loop
はいつ呼ばれるのでしょうか。
nativeBinding(task)
の処理では登録していた関数を呼び出しています。ここで呼び出しているのは初期化処理をする関数です。
var initApp = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) { var model = init._0; updateView = renderer(enqueue, model); var cmds = init._1; var subs = subscriptions(model); dispatchEffects(managers, cmds, subs); callback(_elm_lang$core$Native_Scheduler.succeed(model)); });
renderer
は normalRenderer(document.body, impl.view)
の実行結果です。
function normalRenderer(parentNode, view) { return function(tagger, initialModel) { var eventNode = { tagger: tagger, parent: undefined }; var initialVirtualNode = view(initialModel); var domNode = render(initialVirtualNode, eventNode); parentNode.appendChild(domNode); return makeStepper(domNode, view, initialVirtualNode, eventNode); }; }
normalRenderer
では初期DOMの生成を行い、stepper
関数の作成を行います。
function makeStepper(domNode, view, initialVirtualNode, eventNode) { var state = 'NO_REQUEST'; var currNode = initialVirtualNode; var nextModel; function updateIfNeeded() { switch (state) { case 'NO_REQUEST': throw new Error( 'Unexpected draw callback.\n' + 'Please report this to <https://github.com/elm-lang/virtual-dom/issues>.' ); case 'PENDING_REQUEST': rAF(updateIfNeeded); state = 'EXTRA_REQUEST'; var nextNode = view(nextModel); var patches = diff(currNode, nextNode); domNode = applyPatches(domNode, currNode, patches, eventNode); currNode = nextNode; return; case 'EXTRA_REQUEST': state = 'NO_REQUEST'; return; } } return function stepper(model) { if (state === 'NO_REQUEST') { rAF(updateIfNeeded); } state = 'PENDING_REQUEST'; nextModel = model; }; }
makeStepper
は 1フレームごとに実行される関数の登録を行う関数 stepper
の作成を行います。virtualDOMの振る舞いを記述しているのもここになります。DOM差分を計算して適用している様子がわかります。
applyPatches
では差分適用のほかイベントに対する関数の登録も行っています。renderer
によって作成された関数 updateView
は onMessage
関数の中で利用されています。
dispatchEffects
では特に何もしないため最後にコールバック関数を呼び出します。
model
を succeed
でラップしてものをコールバック関数は受け取り新たな process.root
としています。同時に process
を enqueue
しています。
succeed
は step
関数で処理されます。process.stack.callback
に登録した loop
関数がようやく実行されます。
が、特に何事もなく process
は処理されて初期化処理は終了します。
まとめ
Elmアプリケーションの初期化処理でした。今回の重要登場人物は次のとおりです。
makeProgram
... デバッグの有無を判定normalSetup
...fullscreen
,embed
を定義initialize
... 初期化タスク定義と初期化実行spawnLoop
... 初期化タスクの合成rawSpawn
... processでラップ、キューにタスクを登録work
... キューに溜まったprocess
を処理step
... タスク処理makeStepper
... DOMの差分処理
流れもざっくり振り返りましょう。
Elmオブジェクト準備
- オブジェクトを作る
normalSetup
を呼んでfullscreen
を定義する
初期化する
initialize
を呼ぶspawnLoop
で初期化タスクを作成rawSpawn
を呼び、キューに追加work
を呼び初期化タスクを順番に実行step
を呼びタスクを処理、process.root
を設定initApp
に登録された初期化処理を実行- 初期viewの追加
「超速! Webページ速度改善ガイド 使いやすさは「速さ」から始まる」を読んだ初学者の感想
今までパフォーマンスといえばSQLチューニングやらWebサーバのチューニングやらしかやってこなかったpastelIncです。 Webパフォーマンスについて必要に駆られなかったこともあり、勉強してなかったんですがSPA開発をしていて必要にかられたので表題の本で勉強してみました。
なぜこの本だったのか
あほむさん (twitter: @ahomu) と煎茶さん (twitter: @1000ch) の存在が自分の中で大きかったからです。 以前から勉強会や記事、podcastでお見かけすることの多かったお二人です。なかでもパフォーマンスを語るシーンにおいては勉強になることも多く僕にとってのWebパフォーマンスの代名詞でした。そのお二人が書かれた本ということでこの本を手に取った次第です。
パフォーマンス初学者が読んでも良い内容か
間違いなく良いです。個人的には初学者が読んじゃいけない本なんてないと思ってます。興味があれば読む、それだけです。いきなり高度すぎたら少しずつ読み解いていけばよいのです。
超大満足
本書はそんなパフォーマンス初学者の僕にもうってつけな最高の本でした。読みながらただひたすらに「へぇ〜」って感じでした。 もう少し詳しく言うと本書は読み進めていくと「実際に自分でコード書いて確認したい〜」欲が溢れてきます。面白いです。
DevToolsとお友達になれる
Chrome DevToolsは Console
タブをメインで使ってましたが、実はいろいろできることを本書で知りました。
パフォーマンスボトルネックの手がかりをつかむための基礎知識が分かる本
本書を読むとパフォーマンス調査をするための基礎知識が手に入ります。パフォーマンス調査は仮説 -> 検証の繰り返しになると思いますが、仮説を立てるときの材料が豊富に用意されています。勘で仮説を立てるシーンが減りそうな気がします。また、仮説を怠り検証ばかり繰り返しなぜかわからないけどうまくいった経験がある方いると思いますが、なぜうまくいったのかを知らなければ同じことの繰り返しになってしまいます。本書は推測するな、計測せよを掲げていきなり検証をするのではなくまずは計測することの大切さを訴えています。本書を読むとなぜ計測が大切なのか身にしみると思います。
非機能要件を勉強する必要あるのか
あると思います。機能要件は開発チームの誰でも割りとヘルプできると思いますが非機能要件となると途端に職人を必要とする現場は多いのではないでしょうか。非機能要件は学ぶことでエンジニアとして一つ尖る事ができると思うのです。 また、パフォーマンスを知りビジネスサイドとも話ができると一口にパフォーマンスを担保してほしいと言われた時に、どこまで出来て何を妥協してほしいのか説明できるようになります。お金を積んで解決する問題であれば訴えることも出来ますし、ビジネスサイドも天秤にかけることができます。結果としてプロダクトは継続して受け入れられやすくなるのではないでしょうか。
まとめ
買いましょう。読みましょう。おすすめです。