『現場で役立つシステム設計の原則』感想
読んだ。
雑多に感想を書く。
いわゆる Rails Way において到達できるところと、実際にシステムが要求するところの水準に乖離があるような現場はそこそこあると思っていて、Rails から入ったけどそういうところに困っている人(= つまり僕だ)にいい本だと思った。
ふだんなんとなく気にしていることが明文化されているのがよかった。
とりあえず本記事の読者層をRailsさわったことありますという人として、第8章(アプリケーション間の連携)以降はあまり読む必要はなさそうだった。本書のよさはそこにはなく、蛇足だと感じたが、それは普段どういった情報に触れているかという話でもあるので読む人によるのだろう。
たとえばいわゆる Rails Way の何が厳しいのか。それを語るには Rails 歴がたりなさすぎるのであるがそれはおいておくとして、1つの指摘として「REST表現におけるリソースとDBのテーブルが1対1に対応する」という素朴な前提がよく育ったアプリケーション(あるいは対象ドメインによっては)当てはまらないということがあると思う。
bin/rails g scaffold Post でうごいたわーいというのはプロトタイピングとして優秀であるのですが、たとえば post を作るのは1つのクチでいいのか、という話。
たとえば有料課金ユーザーにのみに許されるアフィリンク追加みたいな機能があるかもしれない、あるいは記事を「書く」のではなくツイートの切り貼りで1つの記事を作りたいかもしれない。もしかしたら記事自体を売りたいというような要求が出てくるかも。こういった posts テーブルにまつわる種々の事柄1をどう捌いていけばいいのかというのが Rails Way を超えた先にあるような気がしている。
Rails Way 以外のナニカに触れる機会として本書は非常によかったように思う。いま対峙しているアプリケーションにおいてどう設計するのがよいか、Rails Way と違う点は何かを考えるための機会を得たという感触がある。
-
posts 以外のテーブルに入れたほうがいいような例もあった気はしつつ↩
秒速でサイト作ったら反響がよかったので振り返り
きっかけは、隣に座ってた人がいきなりやり始めたんだけど、検索ロジックとかが ガバそう 改善の余地がありそうだったので口出ししてたところから。
やったことは主にこんな感じです。
- (PRになってないけど) 検索ロジックに口出しした
- とりあえず
\s+
で分けて、それぞれに対してマッチするかでANDとればいいと思うよ、と話した - むかーし slack の検索を自前でやろうとしたときに似たようなことをした
- そのときは単語の順番も考慮したいので
new RegExp(str.split(/\s+/).join('.*'))
みたいな感じだった - 当時も今もパフォーマンスは気にしない前提
- とりあえず
- bulma 導入
- element-ui を使おうとしてたっぽいけど設定ミスって中国語になってた
- だったらもう bulma 使おうぜと提案して勝手に導入
- use strict した
- IE11対応
- placeholder 追加
{{}}
が見えないようにv-text
を使用- favicon 追加
- strong とかを bulma 化
最初のほうはやることたくさんあって楽しいですね。スタイルはまだまだ貧弱なのですがそこは苦手な分野なので PR をお待ちしております。
その rescue_from ほんとに必要ですか
だいたい必要ないと思ってますよ、という話。
Rails アプリケーションの開発において rescue_from を追加することが正しいことは少ないと思っています。
rescue_from とは実際にはアクションおよびコントローラをまたいだ begin - rescue ( try - catch )であると言えます。
begin - rescue は大きく分けて2種類の使い方があると思っています。1つは、局所的な例外処理を書くとき。もう1つが例外からの復帰を諦めた場合に最後に(最初に)囲んでおくやつです。
ここでは前者を「復帰用rescue」、後者を「覆い隠し用rescue」と呼びます1。
class Foo def fetch AwesomeHTTP::Get("https://example.com") rescue AwesomeHTTP::Error => e # ライブラリのエラーを拾っておく # 「復帰用rescue」 ( 局所的な例外処理 ) raise MyApp::APIError, e end end begin Foo.new.fetch rescue MyApp::Error => e # 「覆い隠し用rescue」 # 復帰できないときには MyApp::Error あるいはそのサブクラスを投げるという規約にしておく warn "エラーが発生しました" warn e exit 0 end
rescue_from はこの使い分けでいうと「覆い隠し用rescue」であると言えます。根拠は、どこで起きるかは関係なくすべて拾ってユーザー側には通知しないようにしている点です。
Rails にはある程度のエラー処理を自動的にやってくれる仕組みがあります。Rails guide の rescue_responses を参照してください。
Configuring Rails Applications — Ruby on Rails Guides
rescue_responses を使わずに rescue_from のブロックで何か動的に処理をしたくなっている時点で「復帰用rescue」に該当していると思います。
しかし、復帰時の方法は果たしてコントローラ単位で本当に共通なのでしょうか?どこで起きた例外かによって処理を変えたいのでは?
というわけで rescue_from を書いておいたほうがいいことってほっとんどないと思うんですけどどうなんですかね。 ただの500ページより情報量が多いエラーページを出すために ApplicationController に書いとく、とかはアリなのかなあ。
-
よい名前、あるいはこれに該当する分け方が既にあったら教えてください :bow:↩
jewel polisher
最近自分たちのプロジェクトが依存する gem 関連で何かが起きて対応が必要だったことが続いたのでメモっておく。
rails-html-sanitizer 0.4.0
CVE-2018-3741 対応。これは依存が多いので追うのがちょっとめんどかった。情報としては以下の issue が非常によくまとまっていたので置いておく。
libxml2
<- nokogiri
<- loofah
<- rails-html-sanitizer
という依存関係で、大本は libxml2 の脆弱性。
属性に HTML コメントとして解釈できそうな文字列が含まれたときに適切にサニタイズしないことがあるように読めた。1
これはかなり前に報告済み2 だがlibxml2 では修正されてないっぽい? とりあえず loofah 側ではこれを対応できるようにpublic なメソッドをはやしてくれている。
https://github.com/flavorjones/loofah/commit/56e95a6696b1e17a242eb8ebbbab64d613c4f1fe
rails-html-sanitizer はそれを使うようにした、という感じ
https://github.com/rails/rails-html-sanitizer/commit/f3ba1a839a35f2ba7f941c15e239a1cb379d56ae
該当コミットで追加されたテストケースを対応前のバージョンで走らせたらこんなだった。
def test_uri_escaping_of_href_attr_in_a_tag_in_white_list_sanitizer html = %{<a href='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = white_list_sanitize(html) assert_equal %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, text end # -"<a href=\"examp<!--%22%20unsafeattr=foo()>-->le.com\">test</a>" # +"<a href=\"examp<!--\" unsafeattr=foo()>-->le.com\">test</a>"
対応としては普通に update すればおk
mysql2 0.5.0 リリース
リリースされたのだが Rails5 で mysql のバージョンをうまく制限していないと以下のコードで死ぬ。
gem "mysql2", ">= 0.3.18", "< 0.5"
普通にしてると mysql2@0.5.0 が入ってきて「バージョンを満たす gem がない」などのエラーになる
対応は簡単で mysql2 のバージョンを固定しておけばよい。Railsと同様に "< 0.5"
をつけておけばよいだろう。
現在の stable の先っちょでは対応済みっぽい3。 github.com
0.5.0 対応して大丈夫か、という判断があるので 5-1-stable ではバージョン指定で対応するかと思ったけど普通にゆるめてくれましたね。ありがたや。
普段自分の仕事内容を思い出そうとしても、こういう活動は忘れがちなので残しておいた。
アプリケーションというのは、機能を追加すれば複雑性が増すし、何もしなくても部品のメンテが必要である。
Rails バージョンアップなどの目立つものもあるが、目立たないこういったところで追随する努力も必要であるし意味のある仕事なんだぞと自戒をこめて書いておく。
-
同僚は https://github.com/GNOME/libxml2/blob/960f0e275616cadc29671a218d7fb9b69eb35588/HTMLtree.c#L714-L718 から
a
タグのhref
,action
,src
,name
属性のみかな?と言っていたが検証していない↩ -
2016-08-11 18:29 とのこと。 https://bugzilla.gnome.org/show_bug.cgi?id=769760 をみると “Can I ask again that the libxml2 maintainers consider addressing this issue?” などと言われているのが見える↩
-
ライフサイクルよくわからんけど 5-0-stable には backport されてなくて気になる(のでコメントしている)↩
TaPL読書録 #3
今回は4章終わりまで。
私物ノートPCのエンターキーがぶっ壊れているため今回のメモは控えめ。
定理3.5.12.
整礎集合の1要素へとマッピングできる関数 f を用いて無限降下列にならないことを示す論法は他の箇所でも見た気がする。
演習3.5.13.
(1) 雑に一意性を損ねること、正規形に関して影響を及ぼさないことがわかればよい。
(2) 1ステップ評価の一意性は損ねるが、多ステップ評価の一意性を損ねないところがミソ。
これは解答の補題とその訳注が面白い。
補題A.1.
ダイヤモンド性について。停止尺度の観点から評価によりかならず停止尺度が下がるのにもかかわらず、s -> t
, s -> u
において t -> u
となるようなことがあるのか疑問に感じて発言した。が、停止尺度として示されていた項のサイズは必ず1ずつ下がるわけではないので十分にありえる話だった。
訳注について具体例をもって示す。 E-IFFALSEとE-FUNNY2が同時に成り立つような項について考える。
t2 -> t2'
において if false then t2 else t3
E-IFFALSE を用いて評価した場合は t3
E-IFFUNNY2 を用いて評価した場合は if false then t2' else t3
であるがこれは再度 E-IFFALSE を用いて評価することで t3
となる。
これはダイヤモンドではなく、「三角形」であると言える。
本では次に数値という概念で拡張する。
prod 0 -> 0
なる評価に意図を感じる。
演習3.5.16.
演習としては行き詰まり状態と wrong が多ステップ評価の元同値であること、すなわち 「行き詰まり状態であれば wrong と評価されること」と「wrong と評価される前の項は行き詰まり状態であること」が示せればよい。
演習3.5.17.
こちらも iff であることを示せばよい
演習3.5.18.
E-IF, E-IFTRUE, E-IFFALSE を取り除いて5つの評価規則を追加する。画像参照。
t3の評価を行う評価規則の左辺で t2 ではなく v2 であるところに注意。
補題A.8.
succ t ->* succ v
のときに t ->* v
とステップ数が変わってないので「他の項構築子についても同様である」が誤りでないかという指摘があった。のだけど succ の付け外しも評価に含むんじゃねーかなといまは思いつつある。
次回は5章全部。
どうかくE22とjit
実装: github.com
とりあえずナイーブに解いたやつ。鍋谷さんは「Rubyでは解けないくらいの計算量にしたつもりだった」と言っていたが案外解けている。
$ ruby -v ruby 2.6.0preview1 (2018-02-24 trunk 62554) [x86_64-darwin17]
MacBook Retina, 12inch, 2017 1.4GHz Core i7 メモリ16GB
$ time ruby test.rb ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 ok 8 ok 9 ok 10 ok 11 ok 12 ok 13 ok 14 ok 15 ok 16 ok 17 ok 18 ok 19 ok 20 ok 21 ok 22 ok 23 ok 24 ok 25 ok 26 ok 27 ok 28 ok 29 ok 30 ok 31 ok 32 ok 33 ok 34 ok 35 ok 36 ok 37 NG: 0 ruby test.rb 829.58s user 107.90s system 91% cpu 17:05.94 total
jit 版
$ time ruby --jit test.rb ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 ok 8 ok 9 ok 10 ok 11 ok 12 ok 13 ok 14 ok 15 ok 16 ok 17 ok 18 ok 19 ok 20 ok 21 ok 22 ok 23 ok 24 ok 25 ok 26 ok 27 ok 28 ok 29 ok 30 ok 31 ok 32 ok 33 ok 34 ok 35 ok 36 ok 37 NG: 0 ruby --jit test.rb 752.21s user 114.54s system 95% cpu 15:06.41 total
約10%の高速化、な気がする (あまりベンチマークに詳しくない)
別解法を考えた。枝刈りバンバンしていくことでなんとかならないか、というやつ。
そもそも実装がよくわからんけどミスっているのと「xが大きいときにはやくならないですね」という指摘を受けてばたんきゅ〜
n進数なのに木構造を使わなかったのはちょっとダメな気がした。
Ruby の ! について
はいどうも
! メソッド
Ruby における !
ってご存知ですか?
Ruby は演算子っぽいようなものもメソッドとして実装されているという話があったりします。
1 + 1 # Integer の + メソッドが呼ばれている class Integer # 上書き def +(other) 42 end end 1 + 1 # => 42
!
も BasicObject のメソッドとして定義されています。
/** * call-seq: * !obj -> true or false * * Boolean negate. *-- * \private *++ */ MJIT_FUNC_EXPORTED VALUE rb_obj_not(VALUE obj) { return RTEST(obj) ? Qfalse : Qtrue; } // ...略 rb_define_method(rb_cBasicObject, "!", rb_obj_not, 0);
class BasicObject def !(*) raise 'bang dayo-nn' end end !1 # Traceback (most recent call last): # 1: from basicobject.rb:7:in `<main>' # basicobject.rb:3:in `!': bang dayo-nn (RuntimeError)
ところで
クラス内でのメソッド呼び出しについては以下のようになりますよね。
class Foo def foo bar(1) # Foo#bar への呼び出しになる end def bar(arg) puts arg end end Foo.new.foo # => 1 が出力される
ということはこれで Foo#!
が呼ばれる気がするじゃないですか
class Foo def foo !(true) end def !(arg) puts '! called' false end end Foo.new.foo # なんもでない puts 'done'
でもそうはならないんですよね。
なんで?
parseされたときの結果を見ればわかりますが、 !
メソッドのレシーバーは nd_lit: 1
であることがわかります。
$ ruby --dump=parse -e '!(1)' ########################################################### ## Do NOT use this node dump for any purpose other than ## ## debug and research. Compatibility is not guaranteed. ## ########################################################### # @ NODE_SCOPE (line: 1, location: (1,0)-(1,4)) # +- nd_tbl: (empty) # +- nd_args: # | (null node) # +- nd_body: # @ NODE_OPCALL (line: 1, location: (1,0)-(1,4))* # +- nd_mid: :! # +- nd_recv: # | @ NODE_LIT (line: 1, location: (1,2)-(1,3))* # | +- nd_lit: 1 # +- nd_args: # (null node)
!(1)
は メソッド !
呼び出しの引数が 1
ではなく、(1)
(= 1
)への !
メソッド呼び出し扱いになるんですね。
parserの気持ちがわからない。
以上、昔この issue を見てよくわかんないなーと思って調べたことの dump です。
なおこの issue を理解するには更に if + regex literal のときの挙動を理解しておく必要があります。
https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fcontrol.html#if
補足
Ruby の動作は ruby 2.6.0preview1 (2018-02-24 trunk 62554) [x86_64-darwin17]
、
CRubyのソースコードは ac44ae58c697971e14e72493da2cfdd9a4e3b458 時点のものを基準としています。