@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Ruby on Railsの開発環境でマルチスレッドでクラス定義を探索すると刺さるが再現できなかった話

現象

Ruby on Railsの、ActiveJob内で起動したスレッドで非同期にDBに書き込もうとすると、ActiveRercordのクラス探索で無限に待って固まります。

わかっている条件

  • ActiveJobで起きる
  • ActiveJobのQueueAdapterにはAsync adapterを使っている
  • DBへの接続以前のActiveRercordのクラス探索で固まる

例えば

Thread.start do
  executor = Lodqa::OneByOneExecutor.new dataset, query, debug: false

  # Bind events to colletc answers
  collected_answers = []
  executor.on(:answer) do |_, val|
    Answer
  end
end

このような処理*1をApplicationJobクラスで実行すると、Answerの探索で固まり、無限に待ちます。

Thread.startでスレッドを開始するほか、executor内でEventMachine#deferを使って更に複数スレッドで処理を行っています。

再現の努力

そこで、再現する最小のソースコードを書いてみます。

class CircularDependencyDetectedJob < ApplicationJob
  queue_as :default

  rescue_from(StandardError) do |exception|
    logger.fatal exception
  end

  def perform(*_args)
    Thread.new { EventMachine.run }

    1.times do
      Thread.new do
        2.times do
          EM.defer do
            Answer
            p 'Goal!!!'
          end
        end
      end
    end
  end
end

エラーが発生

これを実行すると、固まりはしませんが、次のようなエラーが発生します。

/usr/src/myapp # rails c
Loading development environment (Rails 5.2.0)
irb(main):001:0> CircularDependencyDetectedJob.perform_later
Enqueued CircularDependencyDetectedJob (Job ID: 9676f7ff-5c23-411c-84e3-33aca66c5eae) to Async(default)
=> #<CircularDependencyDetectedJob:0x0000562444702420 @arguments=[], @job_id="9676f7ff-5c23-411c-84e3-33aca66c5eae", @queue_name="default", @priority=nil, @executions=0, @provider_job_id="39bf3d7d-051f-4915-a752-17686211c958">
irb(main):002:0> Performing CircularDependencyDetectedJob (Job ID: 9676f7ff-5c23-411c-84e3-33aca66c5eae) from Async(default)
Performed CircularDependencyDetectedJob (Job ID: 9676f7ff-5c23-411c-84e3-33aca66c5eae) from Async(default) in 62.71ms
#<Thread:0x000056244503a970@/usr/local/bundle/gems/eventmachine-1.2.7/lib/eventmachine.rb:1067 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
    7: from /usr/local/bundle/gems/eventmachine-1.2.7/lib/eventmachine.rb:1077:in `block in spawn_threadpool'
    6: from /usr/src/myapp/app/jobs/circular_dependency_detected_job.rb:16:in `block (4 levels) in perform'
    5: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    4: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
    3: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:534:in `load_missing_constant'
    2: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    1: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
/usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:500:in `load_missing_constant': Circular dependency detected while autoloading constant Answer (RuntimeError)
#<Thread:0x00005624446907f8@/usr/src/myapp/app/jobs/circular_dependency_detected_job.rb:9 aborting> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
    7: from /usr/local/bundle/gems/eventmachine-1.2.7/lib/eventmachine.rb:1077:in `block in spawn_threadpool'
    6: from /usr/src/myapp/app/jobs/circular_dependency_detected_job.rb:16:in `block (4 levels) in perform'
    5: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    4: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
    3: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:534:in `load_missing_constant'
    2: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    1: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
/usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:500:in `load_missing_constant': Circular dependency detected while autoloading constant Answer (RuntimeError)
Traceback (most recent call last):
    7: from /usr/local/bundle/gems/eventmachine-1.2.7/lib/eventmachine.rb:1077:in `block in spawn_threadpool'
    6: from /usr/src/myapp/app/jobs/circular_dependency_detected_job.rb:16:in `block (4 levels) in perform'
    5: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    4: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
    3: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:534:in `load_missing_constant'
    2: from /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:in `const_missing'
    1: from /usr/local/bundle/gems/bootsnap-1.3.0/lib/bootsnap/load_path_cache/core_ext/active_support.rb:43:in `load_missing_constant'
/usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:500:in `load_missing_constant': Circular dependency detected while autoloading constant Answer (RuntimeError)

エラーの原因

該当のソースコードを読むと

if loading.include?(expanded)
  raise "Circular dependency detected while autoloading constant #{qualified_name}"
else

loadingに自身が含まれていたら、循環依存で読み込みをやめる処理です。 マルチスレッドで読み込むと、循環依存していなくても、このチェックをするタイミングで他スレッドで自クラスを読み込んでいることがあるので、発生しているようです*2

対応

この処理は定数の自動読み込みと再読み込み | Rails ガイドのための処理です。 自動読み込みを停止してみましょう。

Rails アプリケーションを設定する - Railsガイド

config.eager_loadをtrueにすると、config.eager_load_namespacesに登録された事前一括読み込み(eager loading)用の名前空間をすべて読み込みます。ここにはアプリケーション、エンジン、Railsフレームワークを含むあらゆる登録済み名前空間が含まれます。

config/environments/development.rb

config.eager_load = true

を指定すると、このエラーは起きなくなります。

やはり、「定数の自動読み込み」と関係がありそうです。

残る謎

  • マルチスレッドであれば起きるはずなのに、Rails consoleで次のソースコードを実行しても発生しない
1000.times { Thread.new { Answer } }
  • 元の現象では固まるのに、再現コードではエラーが発生する

暫定の結論

元のソースコードの、クラス探索で固まる現象もeager_loadを有効にすると発生しなくなります。 これ以上調査時間が取れないので、この記事で供養して完了とします。

*1:発生したソースコードと完全には一致していません。

*2:GILがあるから並列に動かないし衝突しないはず・・・と思ったら、ファイルからクラスを読み込む処理なので並列に動くようです。

<Repository (class)> yielded |nil| to block with

RSpecMockで見慣れないエラーが起こせました。 再現コードをメモります。

class Repository; end

RSpec.describe do
  it do
    expect(Repository).to receive(:doc).and_yield(nil)
    Repository.doc {}
  end
end

実行すると

~ rspec spec.rb
F

Failures:

  1) should receive doc(*(any args)) 1 time
     Failure/Error: Repository.doc {}
       #<Repository (class)> yielded |nil| to block with
     # ./spec.rb:6:in `block (2 levels) in <top (required)>'

Finished in 0.01831 seconds (files took 0.20957 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec.rb:4 # should receive doc(*(any args)) 1 time

ちょっとおもしろいのは

Repository.doc {|a|}

のように、ブロック引数を指定してRepository.docを呼び出すと、このエラーは出なくなります。 RSpecMockは、ブロック引数の個数を見て動きを変えているようです。

Node学園 31時限目でLTしました #tng31

Node.jsの並列プログラミングについて話してきました。 大量のhtmlファイルをパースする処理を、 child_process.forkを使って複数プロセスへ分散した話をしました。 また、worker_threadsを試した話をしました。 めっちゃフィードバックがもらえて楽しかったです。

speakerdeck.com

フィードバック

「並列数を増やした時のボトルネックがわかりません」と発表したところ、 楽しんでいただけだようで、たくさんのアイデアをいただきました。

週末の間、ボトルネックとして成り立つ確率と、検証方法を考えてみました。 一通り検証する予定です*1

ボトルネックファイル仮説

いずれか(複数の可能性もある)のhtmlファイルが巨大であるなどの理由で時間が掛かっていて、ボトルネックになっているのでは?

1ファイル辺りの処理時間は数十ms程度なので、多少ばらついても秒単位のボトルネックにならないようにも思えます。 しかし、現在はラウンドロビンでタスクをサブプロセスに割り振っています。 何回起動しても、同じプロセスに同じタスクが割り振られます。 1ファイルで秒単位のばらつきが出ないとしても、タスク群としては出る可能性があります。

プロセス単位で、終了までの時間を出力すれば検証できそうです。

メモリ仮説

プロセス数が増えて消費メモリが大きくなるがボトルネックになっているのでは?

現代では36プロセスが多すぎるとは思えません。 また、EC2はデフォルトでは、swapファイルの割当がなく、メモリが足りなくなると即エラーになりOOM killerが飛んできます。 今の所、OOM killerを観測していません。ただ、すべてのサブプロセスの状態を監視しているわけではないので、知らずに死んでいる可能性はあります。 この場合も、出力データが壊れても、処理時間には影響がなさそうです。

実行時の消費メモリを観測すれば検証できそうです。

物理コア仮説

AWS EC2で試したので、Virtual CPUが36コアだとしても物理CPUが10コアしか無いため、実際は36並列まで伸びないのでは?

クラウド環境にありそうな話です。 ただし、グラフがなめらかな点が気になります。 コア数が足りなくなればそこから水平になると予想できますが、公開したグラフで25コアまでは微減です。 また、機械学習勢の人たちは、僕より早くマルチコア環境を試していそうなので、そういう事実があれば炎上していそうなものです。

物理 CPU、CPU コア、および論理 CPU の数を確認する - Red Hat Customer Portal のコマンドで物理CPU数・コア数と論理コア数を確認すれば何かわかるかもしれません。

共有ログ仮説

書いているログ・ファイルが一つであれば、書き込みがボトルネックになっているのでは?

実際ログは一つのファイルなのでありそうです。 発表中に説明していませんが、重複ログを書きこまない機能があります。 ログを書くかどうかはログの状態によってばらつきます。 この機能が影響しているにしては、グラフがきれいすぎる気もします。

ログ書き込みを止めて計測すれば検証できそうです。

その他

GNU Parallel使うといいのでは?

元々Go lang*2Ruby*3辺りに移植して、多言語の実行環境と性能比較したいとは思っていました。 1から違う言語で書き直すのは大変なので、Node.jsのまま入出力だけ修正して、GNU Parallelを使うというのは、 より短い時間で試せるいいアイデアです。

その他の感想

参考に次の記事を挙げました。 何人か「この記事を参考にしてスクレイピングに挑戦しています」と言ってくれる人がいました。 書いてよかったです。

qiita.com

*1:確率が低くても否定材料を集めておくことは、デバッグ最速理論的に大事なのです

*2:この手の並列はgo langのGoroutinesが強いイメージなので、実際に試してみたいです。

*3:rubexを使うとGILを超えて並列化できるという触れ込みなので実際に試してみたいです。

#rubykaigi 2018でLTしました

会社がお金を出してくれるのでRubyKaigiへの参加が決まっていました。 どうせ参加するなら何かしら発表したいです。 LTに応募したら通りました。

資料です。

speakerdeck.com

結論に被さるぐらいの勢いで銅鑼が鳴りました。

英語で発表

英語での発表にチャレンジしました。 「英語の資料を日本語で発表」はやったことがあります。 「資料も発表も英語」は初めてです。

何人かに、なぜ英語で発表したのか聞かれました。 これにはいくつか理由があります。

某所で「3年以内に海外カンファレンスで発表する」を目標にしました。 海外を国際に置き換えるだけで、ぐっとハードルがさがって実現可能なことに気づきました。

募集要項が全部英語でした。 「参加者は日本人が多いハズだが、国際カンファレンスだったはず・・・」日本語と英語の発表のどっちが良いのか空気が読めませんでした。 英語なら失敗しても「ナイスチャレンジ」と言われるだけなので、英語にしました。

github.com

を見ると、内容は大体伝わっていたようなので一安心です。

二夏続けて、総額90万円、英会話学校に課金しているので、成果が出て嬉しいです。

緊張

5分とはいえ、1000人いる会場で発表するのは緊張します。 そこで敬愛する右角ヒサシを見習いThee Michelle Gun Elephantの曲で己を鼓舞しました。 f:id:ledsun:20180613181640p:plain

さらに気持ちを盛り上げるためにTシャツはLAST HEAVEN TOUR 2003のライブTシャツです。

f:id:ledsun:20180603101351j:plain

15年前の遺物ですが、役に立つのです。

参考資料

R.I.P Abe...

Roy Fieldingの論文

qiita.com

RESTについてRoy Fieldingの論文原著を必ず読んでください

本文は読んでいません。周辺情報を調べてみました。

Architectural Styles and the Design of Network-based Software Architectures が論文全体です。 ネットワークを使ったアプリケーションのアーキテクチャを扱った長い論文の5章 「Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST)」にRESTの話があります。

タイトル「Architectural Styles and the Design of Network-based Software Architectures」でググると6780引用と出てきて驚きます。

Referencesを見ると

  1. Alexander, S. Ishikawa, M. Silverstein, M. Jacobson, I. Fiksdahl-King, and S. Angel. A Pattern Language. Oxford University > Press, New York, 1977.

Alexanderを参照していて、パターンランゲージにも関連があるようです。

英語の論文は読書会でもやらないと読める気がしません・・・。

面白かったけど役に立てられる気がしないセッション #rubykaigi

Hijacking Ruby Syntax in Ruby

クレイジーでした。Binding#local_variable_set や TracePoint を使ってRubyの言語仕様を拡張しようという趣旨です。 CRuby本体を拡張せずに、新文法のProof of conceptが書ける意味はわかります。 とはいえ、いくらなんでもやりすぎなのではという思いで一杯です。

tagomoris.hatenablog.com

TTY - Ruby alchemist’s secret potion

github.com

の紹介でした。

クレイジーでした。CLIを作るのに便利なgem群まではわかります。 とはいえ、なぜそこまでCLIなのでしょうか? プログレスバーやテーブル表示までは理解できるのですが、Markdownの表示までいくと、もうブラウザをUIに使ったほうが簡単なのではという思いです。

印象に残ったセッション #rubykaigi

Ferrari Driven Development: superfast Ruby with Rubex

RubyのC拡張を作るためのプログラミング言語の話でした。 グルー言語好きにはたまらないジャンルです。

機械学習方面の計算を早くするために並列計算をしたいのですが、CRuby上ではGILがあるのでCPUヘビーな計算を並列化できません。C拡張を書けばGILを避けて並列化できます。C拡張を書くにはC言語の知識が必要で、それがハードルになっています。Rubyライクな言語を作ることでC拡張を書くコストが下がります。

何がすごいって、こんなにミニマルで実践的なプログラミング言語を作る余地があることに気づいたことです。Rubyライクな言語からC言語に変換するだけなので実装量はそれほど大きくありません。コンパイル言語やインタプリタ言語の実装と違い、pure Rubyで書くことが出来ます。言語デザインを除けば似た実装を書くのはそこまでは難しくないと思います。それでいて、現実の問題をクリアするための言語として機能しているところ、すごいです。

もう一つは、作っている人は東工大の院生だし、ソースコードをみれば普通にちゃんとした言語処理系だしで、恐るべきです。

Parallel and Thread-Safe Ruby at High-Speed with TruffleRuby (Keynote)

CRubyの5倍速いデモのインパクトがすごすぎです。

チューニングの詳しい仕組みは、「isreading Chat Episode 07 – One VM to Rule Them All」を聞くとわかりやすいです。 misreading.chat 聞いてから振り返ると、発表前半のチューニングの仕組み部分は、GraalVMの仕組みのようです。

JITコンパイラJVMバイトコードだけであれば、インライン展開(関数呼び出しを展開するチューニング手法)できます。展開したい関数がRubyの埋め込み関数やライブラリ呼び出しの場合は、インライン展開できません。そこで、GraalVMのPartial Evaluationという機能を使い、実行しながらプロファイルを取得して、関数の呼び出し結果が定数になる場合は、定数に置き換えます。これでインライン展開を適用できる箇所を増やして、鬼のようなスピードアップを図っているようでうす。

それにしても、GraalVM用のRubyインタプリタ実装書くのめちゃくちゃ大変なのでは・・・?という気持ちです。

参考

アルゴリズムとデータ構造をたどるWebサーフィン

#rustfestの発表

yoshが #rustfest の発表が、すごい面白かったとツイートしているのを見ました。

B木の論文

リンクを貼ってある先が RRB-Trees: Efficient Immutable Vectors いきなりこれを読むのは辛いので、一番古い引用文献*1

[2] R. Bayer and E. McCreight. Organization and maintenance of large ordered indexes. Acta informatica, 1(3):173–189, 1972.

をググります。2300引用されている神文献のようです。

Organization and Maintenance of Large Ordered Indexes

どうやらB木の論文のようです。

AVL木の論文

ついでに更に最古引用文献

  1. Adelson-Velskii, G.M., Landis, E. M. : An information organization algorithm. DANSSSR, 146, 263-266 (1962).

をあさります。

G. M. Adel'son-Vel'skii, E. M. Landis, “An algorithm for organization of information”, Dokl. Akad. Nauk SSSR, 1962, Volume 146, Number 2,Pages 263–266

タイトルは違うのですが、著者と発表年を見るとこれのようです。 中身はロシア語なので読めませんでした。 ていうかSSSRって、何かと思ったら旧ソ連のことでした。驚きました。

AVL tree - Wikipediaを見るとAVL木の論文のようです。 AVLって何かと思ったら、Adel'son-Vel'skiiとLandisの頭文字でした。RSA的なネーミングですね。

この辺のことが話題になるということは、Rustは、新しいシステムプログラミング言語なので、まだデータ構造のライブラリが少なく、自分で実装するチャンスがあるのかもしれません。

素振るには?

アルゴリズムの解説はAVL木 - Wikipediaにありますが、理解するには実装するのが手っ取り早いです。 C言語やRustのような低レイヤーの言語で実装しないと理解できないのでしょうか? 使い慣れたJavaScriptで実装しても理解できるものなのでしょうか? と、悩んでいたら

AVL Treeの実装も含まれていました。 これを参考にすると良さそうです。

*1:古典なので既知の情報である確率が高い上に、わざわざ引用しているので読む価値が高いと仮定

fluxのstoreはMVCのモデルではない

結論

fluxのstoreは、(意味があって)「プレゼンテーションとドメインの分離」(PDS)に則っていないので、MVCのモデルではありません。

要約

MVCが考えられた時代では、プレゼンテーションロジックとドメインロジックが同等、もしくはドメインロジックの方が多かったです。その場合、PDSが有効でした。

現代のWebフロントエンドでは、プレゼンテーションロジックの方が圧倒的に多いです。プレゼンテーションロジックが9割ということも珍しくありません。この場合は、PDSは役に立ちません。

プレゼンテーションロジックの中で状態を持つ部分と、画面を描画する部分を分離する方が合理的に分割できます。この分離された「プレゼンテーションロジック中の状態を管理する部分」が「fluxのstore」です。

背景

以下の一連の議論を読んで、コンテキストに誤解がありそうなので、補完します。

blog.nkzn.info

nekogata.hatenablog.com

mizchi.hatenablog.com

本文

MVCが考えられた時代

Wikipediaの以下の記述を見ますと

1979年: パロアルト研究所にてTrygve Reenskaugが考案。[1][2]長い間、Smalltalk-80の実装のみが公開され、MVCに関する公開情報はなかった

MVCが考えられた時代は1979年頃のようです*1。 70〜80年代のアプリケーションの設計の話です。 Webアプリケーションはまだありません。 対象としているのはGUIアプリケーションです。 MVCは、Ruby on Rails等のWebサーバーアプリケーションでの設計にも活用されていますが、Webフロントエンドの話をする時に気にする必要はありません。

GUIアプリケーションといっても、現在のような複雑なプレゼンテーションは持っていません。 CUIに毛が生えたものと考えてください。 例えば、次の操作を想像してください。

  1. ユーザーは、GUIでパラメータを10個設定
  2. ユーザーは、実行ボタンを押す
  3. アプリケーションは、パラメータに基づいて計算をする(この間10分)
  4. アプリケーションは、結果を表示

複雑かつ重要なロジックは、3の部分にありこれをドメインロジックと呼んでいました。 それ以外の1, 2, 4は、記述は面倒さという意味の複雑さはありますが、重要度は低いです。 これをプレゼンテーションロジックと呼んでいました。

PDSMVC

このような構成のアプリケーションには、 プレゼンテーションとドメインの分離PDS)が大変有効機能します。 ドメインロジックをプレゼンテーションロジックから分離することで、入念にテストしたり、GUIの代わりにCUIをつけて他の環境で実行したりできます。

さらにMVCでは「ユーザーの入力」に関わる部分をController、「画面の表示」に関わる部分をViewと分割します。 上記の操作の

  • 1, 2がController
  • 3がModel
  • 4がView

と、綺麗に分割できました。

現代Webフロントエンドの複雑さ

現代のフロントエンドのもつプレゼンテーションロジックは遥かに複雑です。 ドメインロジックは変わらず単純です。 複雑なドメインロジックが必要な場合はサーバーで実行することが多いでしょう。

プレゼンテーションロジックは複雑です。 現代のアプリケーションでは、ユーザビリティを高めるためにユーザーの操作には即応することが可能であり、必要とされています。 例えば、ユーザーの操作に即座に反応してバリデーションの表示状態を変更します。 複数のパラメータから中間データのプレビューを作って表示するかもしれません。

次の操作をイメージしてください。

  1. ユーザーは、パラメータを変更する
  2. アプリケーションは、バリデーションを実行する
  3. アプリケーションは、バリデーション状態に変更があれば、バリデーション表示を更新する
  4. アプリケーションは、複数のパラメータから、プレビューデータを作る
  5. アプリケーションは、プレビューデータに変更があれば、プレビュー表示を更新する

これを何度も繰り返した上で、

  1. ユーザーは、保存ボタンを押す
  2. アプリケーションは、中間データをサーバに送る

このように、現代のWebフロントエンドのプレゼンテーションロジックは、MVC時代のプレゼンテーションロジックに比べて遥かに複雑です。 ユーザーの1操作ごとに、MVC時代のプレゼンテーションロジックと同等かそれ以上のロジックが実行されようなものです。

PDS適用の困難さ

前述の操作のうち、どこがドメインロジックで、どこがプレゼンテーションロジックでしょうか?

  • バリデーションはプレゼンテーションの状態を更新するので、プレゼンテーションロジックではないでしょうか?
  • 中間データの生成ロジックも、プレビューデータを更新するので、プレゼンテーションロジックではないでしょうか?

「バリデーションはサーバーと同じロジックを実行するので、ドメインロジックだ」と考えることもできます。一方で、「データをサーバに送るのは、永続化層であってドメインロジックではない」という考え方もできます。 PDSが簡単な判断基準ではなくなっています。

また、アプリケーションごとにPDSを拡張したルールを作成し、プレゼンテーションロジックとドメインロジックに分けたとしましょう。 さらにMVCに分割してみましょう。 一部は流用できるかもしれませんが、入力パラメータのバリデーションは、入力パラメータごとに実行する必要があります。 結局、ユーザーの操作1つに対して1つのMVCのセットを作ります。 これが設計の助けになるかというと、なりません。

私の経験では、ユーザー操作ごとプレゼンテーションロジックとドメインロジックの境界が揺らぎ、綺麗なレイヤードアーキテクチャにはなりません。 修正する時は、1つのMVCセットごとに、「これはここではプレゼンテーションロジックだっけ?ドメインロジックだっけ?」と悩みながら修正する羽目になります。

設計は、本来、アプリケーションの抽象度を階層化して、全体のアーキテクチャと個々のロジックを別々に悩めるために行うはずです。 PDSを適用してこれでは、教条主義であり、現場の助けになりません。

PDSの放棄とプレゼンテーションモデル

PDSを放棄します。 プレゼンテーションロジックとドメインロジックを分けることをやめ、両者を含んだものをプレゼテーションモデルとして扱います。

以上が、MVP / MVVMなどのアーキテクチャが提唱される背景です*2

結論

現代Webフロントエンドの複雑なプレゼンテーションにおいては、PDSに則ってアプリケーションを分割する意味が薄いです。 ですので、PDSに基づいているMVCのモデルは、現代Webフロントエンドの世界には居ません*3

宣伝

以上の話を、ソースコードを交えて「現代Webフロントエンドデザインパターン」の「第11章 プレゼンテーションモデルパターン」に書きました。

ledsun.booth.pm

また、「Android アプリ設計パターン入門」では、プラットフォームはAndroidですが、以上のようなUIパターンの議論がとても綺麗に整理されています。一読の価値があります。

Android アプリ設計パターン入門

Android アプリ設計パターン入門

  • 著者:日高 正博,小西裕介,藤原聖,吉岡 毅,今井 智章,
  • 製本版,電子版
  • PEAKSで購入する

*1:Wikipeidaに書いたのは私です。信用せずにWikipeida記載の参考文献を確認してください。

*2:MVP / MVVMについては詳しくありません。「Android アプリ設計パターン入門」などを読んでみてください。

*3:極々小さなアプリケーションでは、MVCが有効なこともあります。が、ほんの少しユーザー操作に反応する機能を増やすと破綻します。実例を「現代Webフロントエンドデザインパターン」に書きました。

RubyKaigi 2018 参加に関するお金の話

弊社では、今年からRubyKaigi 2018への参加支援を始めました。 今年は3人の参加者がいます。

主にお金周りの準備の仕方のメモを残します。

参加費

参加者個人で申し込んで支払いました。

以下の文書で経費精算

  • doorkeeperからの支払い確認メール
  • Paypalからの支払い確認メール

クレジットカードを持っていない社員もいたので、その人は会社のクレジットカードで支払いしました。

交通費

今回は仙台で移動時間が短いので、初日の朝と最終日の夜に移動します。

新幹線のチケットは、急な予定変更に対応できるように、 個人で取得し、後日精算します。

えきねっと(JR東日本)|新幹線・JR特急列車のきっぷ予約で予約できます。 いまどき、住所に半角文字を受け入れられない素敵なUIです。 指定席の前々日の取り消しでも310円*1なので、早めに申し込むのが良さそうです。

宿泊費

会場近くは栄えていないそうなので、仙台駅近くに、1人部屋を3部屋予約します。 会社で直接決済します。

#技術書典 #技術書典4 で「現代Webフロントエンドデザインパターン」を頒布しました

サマリ

完売。

赤字にならなかった点で成功、真の需要がわからなかった点で失敗でした。

電子版

在庫切れてしまったので、電子版を用意しました。

ledsun.booth.pm

bookwalker.jp こちらはEPUB形式です。

デザインパターンについて

GoFの23のデザインパターン

ほとんど同じでないです。被っているのはオブザーバーパターンの1つだけです。 クラス図は出てきません。サンプルコードでクラスは出てきますが、継承は出てきません。 JavaScriptでは継承、特にインタフェースの利用にほとんど旨味がありません。

GoFデザインパターンよりは広い対象に対して「デザインパターン」という言葉を使っています。 GoFデザインパターンではMVCも「デザインパターンではない」扱いでした。 現代のソフトウェア業界では、もう少し広い範囲を対象に「デザインパターン」を使っていると思っています。

デザインパターンの数

数は足りないと思っています。 パターンを考える会がやりたいです。

経費

  • サークル参加費 7,000円
  • 日光企画さん オンデマンド印刷 平とじ 68ページ 150部 58,300円
  • テーブルクロス 500円
  • コインケース 100円

よかったこと

  • 本文用紙を「淡クリームキンマリ」にしたら、「上質紙」よりかっこよくなった気がする
  • 「簡単後払い」のバーコードを印刷しておいた

次回やりたいこと

  • そろそろカラー表紙をつけたい
  • 300部に挑戦したい*1
  • オフセット印刷に挑戦したい

*1:客足と天候の因果の存在に恐怖しかない

#技術書典 #技術書典4 で「現代Webフロントエンドデザインパターン」を頒布します

techbookfest.org

モチベーション

Webフロントエンドの技術は、なかなか必要な状況や解決したい問題が明確にされないまま「流行っているからこの技術が良い」みたいな選択されることが大いように思っています。 もちろん、「新しくてかっこいい技術を使いたい」というモチベーションで新しい技術に取り組むのは良いのです。

それはそうとして、うまくマッチしない問題に適用してみて「イマイチな技術だ」っていう感想を抱いたり、 あれもこれも流行っているのに「どれにも自分は手を出せてないからイケてないエンジニアだ」って劣等感を抱いたり、 自分の環境ではレガシーな事ばかりやっていて、もう世間の流れについていけないと悲壮感を感じたりするのは、あまりハッピーではないと思います。

それもこれも、それぞれの技術がどういう状況のどういう問題を解決するのかが、上手く整理されていないからだと思います。最近、流行っていないみたいなのですが、そういうのを整理するのに最適なテクニックを僕は知っています。そうです、パターンランゲージです。

だから、デザインパターンの文法に則ってWebフロントエンドの技術や考え方を解体しました。

形態

  • A5
  • 68ページ (自己最長)
  • 700円(前作「受託開発インアクション 〜顧客の期待をコントロールする〜」と合わせてぴったり1000円)

内容

扱っているパターンは14種類。

  • タスクパターン
    • ポリフィルパターン
    • トランスパイラーパターン
    • スクランナーパターン
    • モジュールバンドラーパターン
    • パッケージマネージャーパターン
    • ユニバーサルモジュールパターン
  • 実装パターン
    • オブザーバーパターン
    • イベントデリゲーションパターン
    • テンプレートエンジンパターン
    • プレゼンテーションモデルパターン
    • 単方向データフローパターン
    • オニオンアーキテクチャパターン
    • 抽象インタフェースパターン
    • フレームワークパターン

ざっくりタスク系と実装系に分けました。 SPA/SSRみたいな、今ホットなところは扱っていません。 2010年頃からSPAに至るまでの、Webフロントエンド界隈で工夫されてきたテクニックを振り返る本です。

実装パターンに関してはソースコードを使った説明が多いです。 ライブラリに依存していない、現代的なWebブラウザ向けJavaScriptで書きました。 それぞれのパターンについて、理解するためのなるべく小さなサンプルのソースコードを用意しました。

それぞれのパターンについて、こんなに簡潔に説明している文章はなかなか読んだことがありません。自分で書いててなかなか良い本が書けたと思います。

Webフロントエンドの開発に従事している方、これから従事する方、外から見ていてどういうことなんだろかと疑問を抱いている方、ぜひ読んでみてください!

また又・エンジニアの採用面接対策 コメントへの回答

愛の告白

就職活動は愛の告白のような活動です。

「愛の告白」というメタファーに乗っ取れば、現職のある方の転職活動というのは、 既婚者が離婚して別の方と再婚するようなものです。

エンジニアの採用面接対策 - @ledsun blog

転職とは「現職の総合点が自身の能力と比較して下」だからするわけであり、面接(官)は「弊社総合点はあなたの能力と比較して上ですか?(転職先に値しますか?)」ということを確認する場でないといけない

2018/02/03 11:19
b.hatena.ne.jp

なれば、募集企業としては略奪婚を仕掛けているのですから、応募者に対しては丁寧に丁寧を重ねても、重ねすぎることはありません。

企業からすると、たくさんの結婚相手のなかの一人にです。 実際の結婚相手ほど大切にはできません。 採用が決まったあとは、他の婚姻者と同等に扱った方が、長期的な幸福度は高いでしょう。

応募者の立場も様々で、一刻も早く現職と別れるために、早く決まればどこでもいい人もいます。 様々な所に応募して一番条件が良いところを探している人もいます。

結婚にも「好きな人と結婚するより、愛してくれる人と結婚した方が良い」という意見や、 「婚前に熱烈な愛情を示した人が、結婚したら同じぐらいの情熱でDVに走る」という事件があります。

結婚も就職も、人と人の相性は事前にはわかりません。 迷っても何も新しい情報は得られないので、何かしら良いと感じるなら飛び込んだ方がいいでしょう。 悩むのであれば、何か小さな行動を起こしましょう。 少しでも良いと思う相手には、人事は尽くしましょう。 焦って結論を求めずに、思いつく限りの準備を丁寧にしましょう。

続々・エンジニアの採用面接対策 コメントへの回答

学習力無力感

エンジニアの採用面接対策 - @ledsun blog

組織内での過去の経験から、相談が無駄だと思われてしまったケースもありそう "一人で不満を溜め込んで、やめることを決めてしまう人"

2018/02/02 18:36
b.hatena.ne.jp

おそらく、大抵の場合は、学習性無力感なのだと思います。 ただ、面接の場では、自社に所属すれば回復するのか、長期的な治療を要するものなのかは判断がつきません。 個人的なイメージでは、優しい人が周りにいるだけで改善するものではないように思えます。 一人でも否定的な物言いをする人がいれば、防御姿勢をとってしまうのではないでしょうか?

このタイプの人は、周囲に愚痴を言いながら、自分傷つけない相手かどうか試そうとする傾向があるように思います。 これは本人にとっては防御のための行動なのだと思いますが、愚痴を言われる周りの人間にはストレスです。

新しく採用する人のために、同僚にストレスを強いるのはなかなか難しいです。 自分が上司に「今度入ってくる人は学習性無力感なので、気をつけて接してください」と言われたら「なんでそんな人をとるのか?」疑問に思います。

「取引先の社長の子供で、採用すると今後数年間数千万単位の売上が見込める」と言われれば納得します。 「弊社が、今抱えている課題にフィットした経験を持っている」と言われれば、特定の課題を解決するまでの期間の有期契約で良いのでは?と思います。

給与と待遇

エンジニアの採用面接対策 - @ledsun blog

「一人で不満を溜め込んで、やめることを決めてしまう人」それって単純に給与や待遇に不満があるケースのような

2018/02/02 12:28
b.hatena.ne.jp

戦略的な給与体系をとるのであれば、給与は実績に応じて上げるのではなく、今後の成果への期待に応じて昇給すべきです。 社員の離脱を防ぐのに大きな効果があります。

この戦略を取れるのは成長フェーズにある企業です。 原資が必要です。 仮に、特定の社員の給与を上げるために他の社員の給与を下げると、下げられた社員の心理的なダメージは大変なものです。 数年内に回復しないと離職する確率が高いです。 成長の踊り場にある企業にとっては、戦略的な給与体系をとることは難しいでしょう。

その結果、社員の成長が給与と待遇を追い抜く可能性があります。 企業としては、成長を生かして新しいビジネスに挑戦すべきですが、ここでも原資が問題になります。 エンジニアとしては、自分をより高く評価してくれる環境にチャレンジするのが良いように思います。

この時、社員が所属企業の売上・利益の状況を理解していて、給与と待遇の改善は見込めないと判断しているのであれば、 相談なしで辞めてしまうのは止むを得ないと思います。

志望動機

エンジニアの採用面接対策 - @ledsun blog

志望動機を聞いてくるようなところには自分なら行かないかな。

2018/02/02 11:24
b.hatena.ne.jp

「志望動機」の質問から知りたいのは、会社に期待することです。 会社に期待することを1つも満たせない場合は、お互い不幸になるので、お断りしたいからです。

新卒採用の時のように、実績のない、比べようのない人材を比べるために使うわけではありません。 あくまで、職歴やスキルを優先して評価しています。 その上で、価値観が会社と著しくズレている可能性を懸念しています。

質問としては、雑に「志望動機」と聞くよりは、丁寧に 「(入社後に社員として)弊社に期待すること」を聞いた方が良いと思います。

写経

エンジニアの採用面接対策 - @ledsun blog

写経のちょい先レベルで書き捨てた物があればなんぼか持ってきてもらう感じかな>学習能力

2018/02/02 09:37
b.hatena.ne.jp

この発想はありませんでした。

個人的な予想では、

  • ほとんどない人
  • 小さいツール類を山ほど書いている人

に、綺麗に分かれると思います。

シニアエンジニアの採用であれば、後者にフィルタリングするのはありかもしれません。

それでも、自社の事業領域と、職務経歴の一致度を優先して評価すると思います。 中途採用の場合は、雇用から成果を出すまでの期間が短い方が嬉しいからです。

業務時間外の学習の強要

続・エンジニアの採用面接対策 コメントへの回答 - @ledsun blog

業務外に何をしようが自由ではあるが、実際にはそういった勉強時間を取らないとやってけないのでは。。。それが嫌なら勉強しなくて良い会社に行けば良い。

2018/02/02 12:58
b.hatena.ne.jp
続・エンジニアの採用面接対策 コメントへの回答 - @ledsun blog

エンジニア側から「あっブラックだ、やめとこ」と足切りされるやつだ > 「現職で、仕事に関する調べものを、仕事以外の時間でしたかどうか」

2018/02/02 11:16
b.hatena.ne.jp

コメントを見て気づきました。 これは合法的に「業務時間外の学習を要求」する方法です。

時間外の学習の強要 ブラック - Google 検索 を検索すると、経営者から「時間外の学習」を要求すると違法か、ブラック企業と言った趣です。

(上司でなく)一緒に働く予定のエンジニアとの面接の結果 「この人は自習しなさそうだから一緒に働きたくない」と拒絶されれば合法です。 まあ、恣意的に「自習しないエンジニアが嫌いな人」を面接官に起用して運用したら、グレーになると気もします。

大人力を要求されない場面であれば「教えてもらうだけで、自習する気がない人とは働きたくないのだけど、あなたは大丈夫ですか?」 と率直に聞くのが良いと思います。