JSConf 2019 Postmortem
JSConf 2019で Your benchmark may not guide real application performance という話をしてきました。 寒い中お越しいただいた皆様。ありがとうございました。
話さなかったこと
「そうは言ってもベンチマークを組み込むのつらいよね」な話
自分の体験談、身の回りの同僚知人各位の体験談も合わせていくとそれだけの一本のコンテンツにできますが、今回は単純にスコープ外かつ30分に収まらないので諦めました。私の力量の問題もあります
後からベンチマーク足そうと思うと本当に大変。まあ最初からやっても大変だとは思うけども、ゼロからやる方が"ちょっとだけ"簡単だと思う。
詰まるところカルチャーの問題なので、うまくチームや会社を巻き込まないといけない。「ソフトウェアパフォーマンスは究極的には組織文化である(要約)」というJoe Duffyの主張はとても正しい。「パフォーマンス専任担当者・チーム」が、どこからともなくやってきて、ベンチマーク足して帰って行ってもあんまりうまくいかない。文化として根付いていないからだ。
プロダクトを主に書いているチームの人間が自分たちで取り組まない限り、途中から現状維持でさえキツくなったり、ベンチマークスコアがどんどん遅くなるのを眺めて何もできないままとなっていくみたいな話。「遅い変更は強制revert」ルールとかも、ちゃんと(評価など含めて)カルチャー化してないと、ひたすら疲弊するだけですし。
再現性は手軽さも重要みたいな話
動かすだけで一苦労みたいな、手軽に再現できないベンチマークは誰も試さなくなる。「一回動かすのにセットアップで1人/週かかって、実行に5時間かかります」みたいなのは絶対に続かない。
わかりやすいボトルネックが本当に見当たらなくて「全体的に遅い」みたいなことになってる問題に対処する話
わかりやすいボトルネックを1~2つ潰したらこうなることが多い気がする。最初からそうなってることも、それなりにある(気がする)。
原因はクリティカルパスが隠れている以外にも、「どうしようもなく全体が遅い」「全体を速くしないと速くならない」みたいなこともあって、それはプログラミング慣習の問題だったり、ソフトウェアアーキテクチャだったり、そもそもの機能要求に起因したり、まあ色々。徹底して分析するしかないと思っている。
この手の話で好きなのはAndroidの豪傑Dianne HackbornがGoogle+で書いていた話なのだけど、もう消えちゃったんだよねえ.....(と思ったらアーカイブが残っていた)。これは少々極端だしOSやフレームワーク層の意見で、アプリケーション層とはまた少し違うと思うけれども、態度として敬意を払いたい。
「単純なボトルネックなんて早々に消え失せてしまった」系の話だとMozillaのQuantum Flowの時の話とかも個人的には好きです。
そもそもコアバリューってなんだよ・ちゃんと自分たちのコアバリュー考えて仕事してるんだっけ話
複数の友人から指摘されたけど、これ言い始めるともう少しメタな話になるので省きました。指摘をくれた友人各位には「俺よりもお前の方が適任だと思うぞ」と返信していたので、彼らがやってくれるかもしれません。
Q&A形式のまとめ
友人に勧められたのですが、(英語力の問題で)面白いやりとりが思い浮かばなかったor身も蓋もなさすぎるのでボツになりました。
よかったこと
話が刺さってくだすった観客が何人かいたこと。 その後、直接感想をくださった皆様、ありがとうございます。楽しんでもらえらた人がいるだけで何よりです。
やっちまったなと思うこと
動画が公開されるかもしれませんので、現時点で気づいたことを予め懺悔しておきます
- 観客の目を見て喋る機会が少なかった
- 「下ばっかり見て何だコイツ」と思われた方もいると思います。申し訳ありません。カンペ読んでました
- しゃべるの速すぎる
- すいません。練習が足りませんでした。
- 序盤、「自分たちでコントロールできない指標」のくだりでしどろもどろになってしまったこと
- うっかり口が滑ってしまった
- 多分意味不明なこと言ってます。本当に申し訳ない。
- ベンチマークは指標に過ぎないという話が弱かったかもしれない
- あくまでも実現したいコアバリュー・目標としての速度があって、ベンチマークはそこに向かって進むためのテストケースに過ぎないという話です
- Causal Profilingの話への理解が微妙に甘い。たぶん変なこと言ってる気がする
- 理解が甘いのに無理に言及してしまった
- 詳しく知りたい方は元論文を参照してください
謝辞
運営の皆様、二日間開催お疲れ様でした。来年はUDXカンファレンスや大田区産業プラザPioでの開催となれば駅近なので嬉しいです。途中で帰ってしまったため、もしアンケートフォームなどありましたら公式Twitterでシェアいただければ幸いです。
当日お越しいただいた皆様、ありがとうございました。楽しんでいただけたなら幸いです。
観客の皆さんは金と時間を使って観に来てくださっているわけで、やはり楽しんでもらえないことには自分が発表する意味はないわけです。無闇に客に媚びれば良いというものでもありませんが、ちゃんと一人でもいいから需要のある話を提供できないといけないわけです。そうした中で何かしら刺さってくれる人がいたのであれば、登壇者冥利に尽きます。
資料のレビューやアイデア出しに参加いただいた友人各位、本当にありがとうございました。無事形になりました。皆さんのおかげです。本当にありがとうございました。
Leave my work
I was fired 😫
Sorry, this is a bit clickbait phrase. My contract was ended due to various reasons. This primary reason is that I could not pass the interview by an industrial doctor and I ended over the period my employer allows me to rest.
Thank you for my colleagues.
By the way, I'm finding my next job. I don't have any concrete next plan. I consider all options about my next position since I can no longer to work for CyberAgent Inc. If you have an opportunity which I may suite for, I’ll appreciate to let me knows about it. Please contact me via my email address which I listed to GitHub.
I'd like to work to continue software engineer and I'm interested in to challenge to shape a platform service provides high availability, performance, and reliability.
However, this my hope might too long jump if you know my career. But I'd like to challenge it.
Finally, I appreciate my colleagues. I’m looking forward to see you again. Bye.
css-modulesを止めようとしている話(具体的な解決編)
BEMでいいじゃん話の続きその2にして, 具体的な解決編.
多分ここが気になる人が多いと思うのでなるべく箇条書きで済ませることにする.
背景
前回書いてた内容をまとめると以下のようになる.
無駄話が多いので前回は読まなくてもいいです.
追記: 前回読んでもらった方がいい気がしてきた. 時間が無い方はいったん飛ばしてくれて構わないのは変わらず.
状況
- サービスの初期からwebpackのcss-loader(を用いたcss-modules)を使用していた
- サービス開始時の鉄火場の中で, cascadingに基づく暗黙的なスタイルの継承を用いて, UI component treeにおいて祖先側が子孫側のスタイルを上書きしている箇所が多々あった
起きた問題
- リファクタリングの一環でディレクトリ構造を転置し, 併せてimportの順序も変更・整列した結果, css-loaderによるモジュール間依存グラフの構築順序が変わり,
最終成果物たるCSSのcascading順序が変わり, 先述の暗黙的な上書きと相まって, ES Module間の依存構造の先の先に位置するような,
ほぼ全く予期せぬ箇所のスタイルがいきなり崩れるような事故が発生し始めた
- この時点でreftest(visual regression test)は無い
- reftestがあったとしてもいきなり全く関係ない箇所のスタイルが壊れるコードはメンテがしんどい
- リファクタリングを妨げるような道具立ては長期運用するプロダクトにおいて選択する理由がない
- webpackの上にCSSのビルドまで依存させた結果, ビルドパイプラインが複雑化の一途を辿っていた.
解決方法
要求事項
- 最終的に生成されるCSSが予測可能(透過的)なアプローチであること
- 他のツール群(Lintなど)との相互運用性が取れていること
- できればwebpackに依存しないこと
- css-loaderベースの既存コードからのmigrationが容易であること
- css-loaderベースの既存コードのメリットである「UIコンポーネントのコードとそれに関連するCSSファイルの物理的距離が近い(siblingである)」ことが保たれること
具体的な解決法
importの方法
元々, 以下のようなファイル構成になっていた
/ ├ featA/ │ ├ ComponentA.css │ └ ComponentA.jsx └ featB/ ├ ComponentB.css └ ComponentB.jsx
ComponentA.jsx
のスタイル指定は原則的にはComponentA.css
に記述され,
css-loaderを用いることでwebpackでのbuild時にimport styles form './ComponentA.css';
のように解決される.
これを以下のようにする
/ root.css ├ featA/ │ ├ featA.css │ ├ ComponentA.css │ └ ComponentA.jsx └ featB/ ├ featB.css ├ ComponentB.css └ ComponentB.jsx
/root.css
は, featA.css
とfeatB.css
を任意の順序で@import
経由でimportし, featA.css
はComponentA.css
を同様にimportする. featB.css
も同じ.
これにより, 既存の記述方式からの緩やかな移行が可能になりつつ, ディレクトリ単位での順序関係の定義がCSS wayで明確に可能になった.
また, 現実的にはUIコンポーネントは粒度の細かいものから大きなものに沿って, 祖先方向(caller)なUIコンポーネントが子孫方向(callee)となり, 従属関係があるように設計されるため, セレクタ名さえユニークであればconflictすることはないし, ディレクトリ構造を少しいじった程度で規則が変わるという問題もない.
これは元々css-loaderを採用していた時から「理想的・本来的にはそうなっているはず」だったのだけど, 現実にはcascading順序が先述の理由から偶発的に変わることがあり, 厳密な保証を取りにくい状況にあった.
それをroot.css
でのimport順序に用いてシンプルなcascading順序解決にすることで最終的なコードにおける記述順序が透過的な形式で記述できるようにできた.
そのため, 万が一に順序の問題が起きたとしても(そしてそれはレガシーコードとの釣り合いの問題や, css-loaderからの移行, リファクタリングの中間過程においては頻繁に発生しうるが), 以前よりは順序問題として容易にコントロールが可能になる.
尚、コードはわかりやすくするとこんな感じになる.
/* desktop/mobileで切り替え可能になっている */ @import './variables.css'; @import './component/atom.css'; @import './component/mole.css'; @import './component/org.css'; /* template */ @import './featA/featA.css'; @import './featB/featB.css'; /* page */ @import './page1/page1.css'; @import './page2/page2.css';
これにより
はなんとかなった.
セレクタの命名規則
前項の前提を達成するためにセレクタをユニークにするにはBEMを下敷きに以下のように設定した.
.アプリケーションドメイン-コンポーネント名__コンポーネント内の要素--修飾子
アプリケーションドメイン の箇所は, 当該cssが配置されているディレクトリまでのパスが-
で繋がれる形式になることが多い.
一方で汎用コンポーネントは.com-a
や.com-m
のように簡略表記を許容していた.
同時に, あとでgrepで検索しやすくするため, アプリケーションドメイン-コンポーネント名
の部分をJSコードなどで動的に構築するのは(事実上)禁止することにした.
これは将来的にセレクタ名をrenameするのを容易にするための方針. そもそもcssファイルのsiblingに位置するjsファイルの中でしか使われないので必ずしも重要ではないのだけれども, 原理上, 上位(粒度の粗い側)のコンポーネントからセレクタ経由でdirty hackとして上書きされる可能性を許容してしまっているため, それを「やらざるを得なくなった」場合への対処の余地も含んでいる(現実にはコードレビューで弾かれることの方が多いので存在はしないだろうと思っているが).
結果(中間報告)
一番最初にも述べたけど最終的な移行完了を自分は見届けたわけではないので中間報告になるけれども.
- postcssベースで実現
- node-sassを使わなかったのは, Node.jsのmajor version upへのnode-sassの対応が他に比べて遅れやすく, Node.jsの更新の妨げになるケースが過去多々あったために避けた
- 要求事項は達成
- コード上の使用箇所とスタイル定義が紐づくため, 不要コードの削除も比較的容易なcss-loaderのメリットを引き継げたのは非常に大きい
- 何よりも生成コードの予測がつきやすくなった
- easyではないがsimpleは達成できた
ので結果は上々(と言えると思っている).
Performance Impacts
- minifyしないことで相対的なファイルサイズの増加は起こり得る
- SpeedCurveでの計測を見ていると移行による大規模なperf regressionは起きていない
- 長期的にはむしろ通常の機能追加に基づくコードの増加の影響の方が多い....
未解決または諦めた問題たち
- class属性のmangling
- critical rendering pathに必要なCSSだけの読み込み
- css-loaderべったりの時から, 最終成果物は独立したCSSファイルにしていたため, これ自体はregressionではない
- 将来的にこれが問題になった時に改めて検討する
- plainなCSSにした結果, 必要なスタイル定義だけを切り出してhtml中に埋め込むなどの最適化の余地が広がっているんじゃないかと思っているものの, その調査と最適化を必要とする段階には至っていないので未確認.
- 現代の多くのwebサイト・アプリケーションでは, この問題が現実の問題になるよりも先に, initialで読み込むJSのサイズの方がよっぽどperformance bugになると推測しているし, このとあるアプリケーションA(仮)では事実そうだった
- renameがめんどい
- 変更する箇所が多い
- これはある程度固まったプロダクトでは問題ない
- 立ち上げ期の試行錯誤の続くコードベースではめんどくさいかもしれない
- grep一発で解決可能ではあるが...
余談
- 元々CSS-in-JS嫌いな自分が面白半分で実験していたものを, regression鎮火のために, UIコード周りのオーナーと煮詰め直してworkaroundとして急遽投入したものだけど, 案外うまくいった(と思っている).
- easyではないがcascading style sheetの記述としてsimpleな所に落ち着いたと思うし, 自分としてはBEMで問題が解決していたという屁理屈が実証を伴ったという認識.
- いくつかのunresolved question含め、そのうち同僚の誰かがもう少し詳細に話してくれる....はず. 多分.
教訓・まとめ
- class属性を上書きしてどうにかするのは忙しくても止めた方が良い
- reftestは早期からあると良い
- 壊れたことを検知するには当然テストですよ
- CSSはcascading style sheetsでありES Moduleとは諸々の解決規則が違うので, 道具としては別物として考えるべき
追記: ちょっとした返答・反論とか
CSS Moduleを止めようとしている話(具体的な解決編) - saneyuki_s log“cascadingに基づく暗黙的なスタイルの継承を用いて, UI component treeにおいて祖先側が子孫側のスタイルを上書きしている” コレを食い止めなられなかったことが致命的であって、BEMかCSS Modulesか、という話はあんまり関係な
2019/03/15 03:32
根本的かつ理想的にはそうです. それで解決できていたならば何を採用しても苦労しません.
しかし残念ながら,
- レビューの際の見逃しやby designや時間的な制約による場当たり的な対応という意味の事故も含め, そのような問題が容易に発生しうるのがCSS Scopingのない(WebComponentsに頼れない)世界のCascading Style Sheetsの限界である
- そうした問題が発生していないのであれば, そもそもBEMだstyled-componentsだのという論争も存在しない.
- 現実にそれが当該アプリケーションのコードベースでは発生していた
- 今後の運用の過程で継承や上書きに頼る必要のある箇所が(偶発的であっても)現れるのが予見しうる状況にあったし, 仮にゼロから理想的な設計かつ負債の無いコードベースで構築したとしても, プロダクションにおけるコーナーケースも考慮すると, どこかの層で原理的に担保しない限り「発生しない」という前提に立つのは難しい.
- CSSの継承に頼らず, 関連するpropertyを個別に
unset
またはinitial
で埋めつつ作業をするのも解法としてあり得るが現実的ではない. - そして本文の繰り返しになりますが, リファクタリングなどに際してそこの依存が思わぬ箇所に出てくる結果となっていた. また, css-modulesの枠内で対処を試みると, 取り扱っている対象と記法のセマンティクスのミスマッチから却ってmonkey patchを増やすばかりであった
ため, それらの課題に対して
- そもそも暗黙的な上書きをしうるケースを減らす方向にコーディングスタイルやミクロなUIツリーの設計を変えるようにした
- 最終的に生成されるCSSコードを透過的に扱えるようにすることで, 以下の課題への対処を容易にする
- 1の結果発生しうるコーナーケースの緩和
- 1を志向してもどうしてもうっかり発生しうる将来の問題へのworkaround
- すでに負債と化していたコードベースで発生していた, css-module依存のcascadingに依存したコードからの移行
というのが, 当該アプリケーションで採用した解決案であり, 本エントリで主に言及していたのは2になります.
CSS Moduleを止めようとしている話(具体的な解決編) - saneyuki_s log無秩序なCSS Modulesの使用から、秩序あるBEMにしたら、秩序を得た。CSS Modulesはなにも問題ないのでは?
2019/03/15 20:27
改めて説明すると,
- css-module時代のコードはそれはそれで秩序はあった.
- 付け加えていうのであれば
- リファクタリングを妨げるような道具立ては長期運用するプロダクトにおいて選択する理由がない
- 最終的にcascading style sheetsのセマンティクスで表現されるものを, ESModule(ないしwebpackのloaderセマンティクス)で構築するような, 異なるセマンティクスを道具で構築するのはやり方が悪い
- 最終的に規約や秩序や運用でカバーといった言葉で解決するしかないのであれば, よりsimpleな運用で済む手段で良い
- とりあえずこちら と, 本エントリを改めて読んでいただければと思います
色々書いてなかったこと
Twitterでやりとりした中で、やっぱり書いたほうがいいなと思った話。
込み入りすぎててうまく説明できる気がしなかった初稿で説明していなかったが, className
地獄への門の一端として, 現状の課題たるコーナーケースも色々あった(全部覚えていない + 全部列挙する前にCSS Module路線を諦めた)。例えば「本来であれば:nth-child()
擬似クラスで解決するのが綺麗なはずだが、実際には適用したい対象がReact Componentかつ孫よりも下位に位置するため, 結局何かしらの上書きが必要になっている」など。この辺りについては「そもそものUI ComponentのAPI設計・粒度がおかしい」と断じてしまえばそれまでなのだが, 目の前にあるコードをmigrationしようとするには避けて通れない問題であった。
また、CSS custom propertiesなどはレガシー環境を考慮するとコンパイル時に解決するものになってしまう。つまり, 実行時制約を転化には処理系でのネイティブサポートが必須になるもののIE11などのサポートを含めている以上はそちらを前提にした設計に切り替えることは難しい(丸々IE11向けとそれ以外のコードベースを用意するか、そこも含めてサポートする互換レイヤーを何かしらで用意する必要があった)。
また、WebComponents一式が使えるようになったとしても, ShadowDOM境界を跨ぐ前提に立つと諸々の設計パターンは変わる可能性があり, WebComponents周りが未だ発展途上気味であるため(form control周りなどは最たる例だし, 残念ながら「今年はWebComponents元年」と年初に叫ぶはもはや年中行事になりつつある), より「モダン」なapproachへ将来的に移行したいことや最も強固なエコシステムであるWeb標準のなるべく近くに位置しておきたいことを考慮すると, メンテナブルかつ保守的(Web標準の近くに寄る)手段を採用するほうが長期的には払うコストが少ないだろうと予想し. ゆえに, シンプルに振ることになった.
ゼロから全てを描き直せるのであれば, おそらく強固な規約を浸透させるなどの運用戦術を取りつつ, 別の手段を考慮する可能性もあるだろう. しかし, 我々にそのような富豪的な選択肢はありえないし, 仮に書き直せるとしても真っ先にレガシー環境を切り捨てる方向に体力を注ぐだろうし, 一連の記事で述べている過程は「書き直しやすくする」ための前段階としての措置であった. そもそも頻繁に書き直しできるのであれば, 長期的にメンテナブルにすべきかなどといった問題はない. 困ったら書き直せばいいのだから.
それとstyled-components/CSS-in-JSに関する話については, 「最終的にCSSというシステムに落ちるのだから」自分は否定的にすぎない(あれで本当に多くのcascadingの課題を難なく解決できるのであればそちらで全く問題ない). ゆえに, React Nativeのように最終形がAndroidやiOSのスタイルシステムに落ちるフレームワークに関しては意見するつもりはない.そっちは今の所よく知らないし.
css-modulesを止めようとしている話(長々とした状況説明編)
BEMでいいじゃん話の続きその1にして, とあるアプリケーションが困っていた話.
書き味は最良ではないけれど, 設計思想を持った上で長期メンテを考えると結構いい感じだと思っているアプローチに至った. しかしながら, 放っておくと誰も試行錯誤の過程を書かないままになってしまいそうだし, それを書きそうな同僚も(多忙のあまり)書きそうな雰囲気はなさそうだし, もしかすると永遠に出てこない気さえするので書いてみる.
多分完遂の暁にはどこかで話として総括が出てくると思うけど, 中間報告ということで.
経緯
とあるアプリケーションは, 元々はCSS Moduleをcssのビルドシステムに用いてAtomic Designとして作っていた. CSS Moduleを選定した理由は自分はよく知らないが, ものの試しで使ってみようと思ったんだと思う. ここまでは別に間違ってはいない. (自分は選ばないが)styled-componentsに比べれば自分の好みではあるし, そこまでガミガミと言うつもりはない.
しかし, 肝心のコードがやらかしていた.
セレクタの黒魔術こそ殆どなかったものの, いたるところに存在するReact製のUI componentがclass属性の上書きを許容する形になっていた. この時点で開かれすぎたcomponent指向なので色々破綻しているのだが, おそらく汎用的なcomponentを作ろうとする麻疹の一種だったのだろうと推察している. そしてそれは同時に, initial release前の炎上の鉄火場のなかでダクトテープを貼り付けてデザインレイアウトとの不整合をどうにか鎮火しようとするプログラマーのメンタルモデルとハマってしまったのだろう(その時自分はその場にいないので、真相は藪の中だけど). 結果, アプリケーションサービスがリリースされて1年以上が経過した頃(自分が関わるようになった頃)には, いたることで上位のcomponentが思い思いにclass属性を上書きすることで帳尻を合わせるコードが散見されるようになっていた.
複雑なセレクタの黒魔術による上書きではなく, class属性の上書きで対処しようとした理由はわからない. だが、おそらくはCSS Moduleを使っている関係上, production buildでclass属性がmangleされてしまい, そのmangle結果もコードの成長に応じて必ずしも一定しないためにセレクタでの解決が不可能だったのではないかと推測する. とはいえセレクタを使っていても問題は後述の問題は回避できなかったと思うけど.
さて, そのような不安定なコードベースの現状に対して, アプリケーションの属するサービスは順調に成長を重ねていた. もし「実は伸びないのでcloseします」みたいなサービスであったら、ガタガタでも何の問題も無かっただろう. どうせ死ぬんだし. だが, それとは真逆でむしろ伸ばす方向にするにはどうするかを考える必要があった. そのような状況で, やばいなーと思いつつもガタガタになっているとはつゆも知らない自分(と同僚たち)はうっかりパンドラの箱を開けてしまうことになる.
発端はコードベース内のディレクトリ構造をリファクタリングしているときに起こった. そもそもディレクトリ構造をリファクタリングしていたのはコードやモジュールの責務境界を明瞭にするために実施したのだが, その過程でES Moduleのimport文の順序関係や格納されるパスのアルファベット順を変えてしまうケースがあった. 変えてもアプリケーションはちゃんと動いているし読み込む側の順序に依存するようなことはないだろうと思っていた.
読み込む側での順序を変えた結果, アプリケーション内で読み込まれる側のモジュールの依存関係に基づくDAG上でトラバースされ評価されるタイミングは変わることになる. しかもその変わった対象は変更したモジュールから遠く離れた箇所であったりもするため, changesetから読み取ることも難しい. これがJSの世界に閉じただけならば問題なかったのだが, 変更前後でグラフ構造の比較が必要になる上, 「変更箇所から10モジュールくらい跨いだ先のJSファイルが読み込んでいるCSSファイルの評価順序が変わった」のを上手く検知しながら他を壊さないように直す方法を探す必要がある. さらに悪いことに, その評価順序の変更の適用される対象が「最終的にはフラットな構造を上から下まで読み込んだ順序に評価する」cascading style sheetsなのであった.
結果を先に述べると, 上の方で述べた「上位のcomponentがclass属性を介して下位のcomponentのclass属性を上書きして微妙なレイアウトを調整する」ために必要なcascading順序が大きく破綻して, 「注意して確認すると壊れている」「大きく破綻するケースもあれば小さくずれているだけも両方ありうる」状況になってしまった.
それでもreftestがあれば早期に問題には気づけたであろうが, 当時の自分たちはreftestを持っておらず, しかも悪いことにリファクタリング作業が半分以上進んだ段階でその問題が発覚したために(それまで気づくことができなかった), 今更ディレクトリ構造の変更を中断して全てをrollbackするのも難しい状態になっていた.
では仮にreftestを持っていた場合は早期に問題が検知できて堅実に進めることができたのだろうか? その場合は先にも述べたようにモジュール間の依存グラフ構造を黒ひげ危機一発的におっかなびっくりと壊さないように変更を進めることになる. それが嫌であれば、コード行数10~15万行の中から, まず安易な上書きをしているところをすべて取り除く必要があり,それは縦横無尽に鎮火を前提として書かれたhackを全て取り除くだけのチームの体力が要求される. どちらにせよ, コードの責務分界点を分けるという目的に到達するまでに年単位の労力が必要になる. そして繰り返すが当時の自分たちにはreftestはない. 事実上, この路線のままでは軽微なcleanupにも等しいリファクタリングさえもロクにできない状況に追い込まれていた. リファクタリングできないコードベースとか敗北宣告にも等しかった.
解決に向けて
話を戻す, 今更rollbackして後にも引けない, かといって放っておくと前進すら難しい自分たちは以下の強行着陸を採用することになる..
- 可能な限りモジュール依存グラフの評価順序を変えないために, JSのimport文のsortを諦める
- CSS Module by css-loaderを使うのではなく, 素のcssをconcatすることでcascadingの評価順序を壊さないようにする
- class属性の上書き順序を保持またはコントロール可能にする
- postcss-loaderを用いる.
1は, 評価順序を変えなければ最終成果物が変わらないという保証が取れたので, 2で払うべきコストを先送りしてディレクトリ構造の変換を継続するための手段. 2は, すでに壊れてしまったものを制御可能な状態に戻すための処置. 手間は増えるしダルいものの, 明示的にcascadingの順序を制御できるために, もっともシンプルな解決方法となる.
この2つを, 最終的に2に持っていく合意のもとに壊れた箇所を修正しながら適用することになった. そもそもclass属性上書きしているのを直すべきなのだが, 影響範囲が大きすぎる + regressionの鎮火が優先であるため, 次のフェーズの課題とした.
結果的に多数のregressionは鎮火し, その後細々と変え続けた結果, 開始から数カ月(機能実装などで一時中断していたので実働としてはもっと短い)ののちにディレクトリ構造の変更は完了したのであった(CSS Module撤廃はside work的に現在も進行中)..
......同僚各位、その節は本当にご迷惑をおかけしました....
なんか話が長くなってきたので具体的にどうしたかは次のエントリで書くことにする.
「OSSのコードが綺麗」という先入観
先週、Twitterでまるっきり別のクラスタの知人が全く別個の「Open Source Softwareのコードは綺麗。業務のコードは汚いが動くコード」みたいな誰かのtweetをRTしていてちょっとそれ違うよなーと思った次第。
Open Source Softwareのコードが綺麗かどうかというのは非常に大雑把かつ間違った先入観で、 仮にそう思っているのであれば、それはその人がロクでもないコードや大雑把な単位のコミットを普段から見すぎているか、 運よくめちゃくちゃコードの出来がいいプロジェクトのコードを読んでいるだけだと思った方がいい。
Open Source Softwareのコードが綺麗になるとするならば、
- コードを公開する側にとっては綺麗なコードを公開したいという気持ちが生まれ、せめて見せかけだけでもと頑張っている(恥の概念)
- 綺麗なコードを書けるプログラマーが参加している・集まっている
- プロジェクトのcheck-in policyが(時には潔癖なまでに)しっかりしている
のどれかでしかない。
Open Sourceだろうがプロプラだろうが先ず重要なのは問題を解いていることであって、 コードが綺麗かどうかは重要じゃない(クソコード無罪と積極的に言っているわけではないので悪しからず)。
それに、パッと見は設計もスタイルも綺麗だけど全然問題に適してない設計だったり、 そもそもインデントが揃っててて綺麗なだけで全部グチャグチャとか、コミットログがロクでもないとか、 設計やAPI designが腐ってるとか、そういうコードはOpen Source Softwareを多数探せばいっぱいあるし、 逆にプロプラでもメチャクチャしっかりしたコードと出会うこともある。
Open Source Softwareに期待するべきなのは、コードが公開されているとか再利用可能とか困ったら自分で修正できるとかにすべきで、 コードの綺麗さを期待してはいけない。
仮にそういうことを褒めたいときは特定のプロジェクトを名指しで褒めるべき。OSS一般が綺麗というのは幻想もいいところ。
そもそも綺麗なコードって何だよっていう話ではある。
BEMで底に達した問題を探す問題のために生まれる問題
最近、社内のいろんなプロジェクトのリポジトリを眺めているとスタイルシートの記述にstyled-componentsとかwebpackのcss-loaderとかで頑張っているものを頻繁に目にする。
んで、Lintとかどうしてるの?みたいな話をすると「〜はこの『A(どこかのCSS-in-JS派閥の一つ)』は対応してないんだよねー」という返答が返ってくる。
そのたびに思う。「BEMで問題解決してたんだからBEMでいいじゃん」と。
このようなことを言うと「JVMのJITコンパイラの仕組みを聞いた後に『アセンブラを生で書けばいいじゃん』と言い出す痛いおじさん」感がするので自分でもあんまり好きじゃない。ただ、CSSに関してはBEMで問題が底に達してしまっていて、そこから先の標準化されてないwebdevツール群は問題を再発明しているだけに過ぎないなと思う。
書き味をどう頑張ろうが結局我々はCascadingという宿命からは逃れられないのだし、それぞれのツールや処理系の最適化は依然として最終成果物がcascading style sheetsに落ちることを期待しているという事実を無視してもしょうがない。であるならば如何に素のCSSに近い状態で、エコシステム内の多くのサービスやツールとの相互運用性を諦めずに済むかの方がよほど現実的であったり長期的に残るんじゃないかな。「このサービスはハズレたらすぐ死ぬから大丈夫?」、だいたいそういうプロジェクトに限って、儲かってもいないが赤でもない、みたいな感じでうっかり長生きしちゃったりするんだぜ.......?
「文書構造と見た目を分離する」という目的でCSSがHTMLのツリー定義と分かれているというのが常に便利とは限らない、というのが現状であるものの、依然としてCSSというモデルは生きているのであり、それは好む好まざるに関わらない事実である。それを無視するのはクレバーじゃないなーと思う。業界全体的になんかおかしくない?
さて、かつての我らがCSSに困っていた問題は場当たり的なセレクタの複雑化と、それに伴う優先順位の破滅であった(今も困っている)。
BEMやその一門はclass名は複雑でもセレクタはシンプルに保つ方向でそれを解決しようとした。あの命名規則は問題を解決したと言っていいが、我々は慣れ親しんだセレクタの奥義を捨てるのはまだまだ惜しかった。
ただ、BEMが一定の知名度を得た頃には、あまりUI component指向みたいなものは(webデザイナー業界・Webフロントエンド業界)では一般的ではなかったような記憶があるし、体系を作るための設計語彙みたいなものもそんなに統一されていなかった。
それから時は流れて。Atomic Designを始めとする設計語彙は随分と人口に膾炙するようになり、UI component指向は大なり小なり考えるのが前提の時代になった。今や黒魔術的なセレクタをdirty hack以外で使う方が珍しい。そんな今だからこそ、やっぱりBEMでいいんじゃないか。いいというか、「あれでよくなったんじゃないか」。
それゆえに思う。「BEMで問題解決してたんだからBEMでいいじゃん」と。
EdgeHTMLを悼む
久々に色々書きたい気持ちになった + 矢倉さんの書かれたものを見て、彼とは微妙に考えることは違うかなあと思ったので書くだけ書いてみる。意見似てるなと思ってるところは書かないようにはした(標準化方面周りとか)。あと、Webブラウザ周りの現状に明るくない同僚や友人向けのテイストは含んでいる。
そもそもの大前提
まず、Webという文書・アプリケーションプラットフォームの価値は「標準仕様に基づく相互運用性」「インストールせずとも使える」の二点に集約されると自分は思っている。
最近はずいぶん聞かなくなった「Webは簡単に作りやすい」というメリットは、「Win32のデスクトップアプリに比べると」という但し書き付きで、90年代は事実だったと思うけど.NET Frameworkの進化とかモバイルOSアプリが出たりとか業界の成熟に伴って事実ではなくなって久しいと思う。
この「標準仕様に基づく相互運用性」というのは、標準仕様を実装しているソフトウェアがあればあるほど担保されやすくなっている。単一の実装と単一のテストケースだけでは、どうしても特定の実装に依存する標準仕様になっていることがあって、まず相互運用性なんてものは実現されないと思っていい。複数の実装があって、それぞれで相互に検証して、どれが実装のバグなのかどうかを試しかめるフェーズが非常に重要になってくる。
ここで問題になるのが、「今日の商用利用に耐えうるWebブラウザをゼロから新規に作り直すのは現実的には不可能である」ということ。理由は
- Webブラウザに求められる機能が非常に多いため、開発には多大なる人間や金や時間が必要であるため
- 歴史的経緯に基づく挙動や標準仕様、意図的に無視する必要のある仕様もいくつか存在するため、それらを含めて相互運用可能な状態にするには本当に大変な労力が必要となる
- 今日の商用利用に耐えうるWebブラウザは化石ではないため、日進月歩で進化を続けていく。仮に再実装する場合は、彼らに追いつく速度で進化させつつ足りない歴史的な機能を実装し続けていく必要がある
- そのような大事業を新規でやることに経済合理性がある会社は世界中のどこにも存在しない。
などが挙げられる。
おもちゃ程度であれば一人でも十分に再実装は可能だし、00年代の中盤に求められた程度の機能であればエース級エンジニアを10人ほど集めて2~3年やれば近しいところまで実現できるというのはMozillaのServo Projectが2013~15年ごろに証明したけれども、アクセシビリティとかコーナーケースなどの対応を考えるともっと人数がいるし時間もいる。
そこまでやってもゼロからの新規開発では15年近く前の環境しか作れないので、何も経済合理性が存在せず、むしろ既存の実装を捨てることで影響力が消えたり、イニシアチブを取れなくなったり、相互運用性を損なう業界へのリスクを考慮して継続しているのがWebプラットフォームないしWebブラウザ開発の現場だったりする。
Opera (Presto)の思い出とwebでコードを書く話
そんな状況に陥ったWebブラウザの開発が中断されるのは別に今回が初めてじゃない。2013年には組み込み向けビジネスを当時のWebKitに破壊され、収益の柱を失ったOperaが自社製エンジンの開発を断念してChromiumに移っている。
自分はPrestoの死を悼むし、今回のEdgeHTMLの死も悼むけれども、それらが別にコードを書いていて楽しい相手だったかといえば必ずしもそうではない。
ソフトウェアなのでもちろんバグってることはあるし、「このブラウザ向けに対応しても喜ぶユーザーは滅茶苦茶少ないかもなあ」と思ってそれら専用のworkaroundを書くときは複雑な心境ではあった。だけどそれはPrestoやEdgeHTMLに限った話ではない。あらゆるソフトウェアにおいてバグが存在しているので、それに出会うかどうかの運の問題でしかないし、ユーザーが多いブラウザの方がバグが見つかりやすくて直されやすいというだけでしかない。Google ChromeだってSafariだってFirefoxだって、ブラウザの実装バグに悩まされなかったブラウザなんて一つもない。むしろインターネットの声の大きな人たちから好かれて絶賛されているメジャーなブラウザがバグっていて、普段は嫌われ者のあいつがちゃんと動いてるときは本当に複雑な気持ちだった。だけど、それが相互運用性のあるプラットフォームというものなのであるし、そうしたバグを踏んでしまった場合に誰が悪いかを決めるのが標準仕様なんだ。僕たちはそのメンドくささを飲み込んで、その上にあるメリットを掴み取ることにした。だからそこに愚痴は言えども、ちゃんとissue trackerにバグを報告して次のリリースで直ってくれればいい。それで終わりだ。
そんな心持ちでいたからこそOperaがPrestroを止めたときはショックだったし、その後にBlinkに移行した結果、Presto時代のOperaの開発者の多くがレイオフないしは自主的な退職でOperaを去り、Opera Software所属の社員の姿を標準化のメーリングリストや議事録から消えるのを見るのは複雑な心境だった。もちろんAnneみたいに他のベンダに転職して継続して活動している人もいるけど、全体としては標準化の要である「目」と実装の数が減ってしまったのは事実だ。
そうした過去や、MicrosoftがIEで市場を一旦破壊・独占した結果として標準仕様の価値が無に帰した時代を知っているだけに、少なくともWebでコードを書いて銭を稼いでいた人間としてはこれ以上エンジンが消えることはWebの相互運用性に悪影響を及ぼす可能性というものは心配していた。
ましてやGoogle Chromeが、Googleの多大なる投資の結果、彼らのWebサービスとの強い結合とともにWebにおいて、かつてのIEの権勢を思い出すような強いシェアをモバイルとデスクトップの両面で持ち始めている。私企業の経済活動が「うっかりやり過ぎてしまって」、結果として全体としては不幸なことにになリかねないという懸念の矢先にEgdeHTMLの開発停止が発表されてしまった。
EdgeHTML
EdgeHTMLは、TridentをforkしてIE時代の互換コードをバッサリと落としたり、内部の文書構造がWebブラウザ向けではなかったのをDOMベースにするなどのリファクタリング話をある時期までよく発表していた。Chromium互換っぽく見せることに注力する傾向があったため、悪い意味でChromiumのダメな箇所を引きずったりもしているなと思ったし(Extension周りのAPIの作り方とか。あれは今やるならMozillaみたいにPromise使わないとダメだよ)、Windows 10のややこしすぎるサポートポリシーのために厳密な市場シェアの高いバージョンを調べるにはサービスごとのユーザー動向を見る必要はあったけれども、総体としてはずいぶんサクサクと動いてくれるし、IEと比べてwebdevとしてもすごくユーザーに勧めやすいブラウザの中身になっていたと思う。
動画系のAPIのように標準化の隙間が結構見え隠れするAPIの挙動は不安定だったりするし、Microsoft Connectに登録しても本当に直してくれるのか不安なところはあった。けれども、それは各OSに特化して作ったブラウザのあるあるなところで、Safariみたいなものだと思えば先述の通り問題なかった。少なくともEdgeHTMLについては、標準仕様に基づく相互運用性が高く、かつ時代や業界の要求してくるAPIについても大きなタイムラグなく実装してくれていたので、開発者としてもユーザーとしても期待が持てるブラウザだった。実家の父親がパソコンを買った時に「新しくGoogle Chromeをインストールして....」とヘルプしなくても済むし、サポートしがいのある(ナーバスな気持ちにならずに積極的にコードを書いていいと思える)ブラウザだと思っていた。
しかしながら、MicrosoftがWindows Phoneを止め、Android向けに出したEdgeはBlinkベースになり、Microsoft自体はナデラがOpenを謳う一方でconsumer clientへの会社としてやる気の薄さとAzure/Officeの稼ぎぶりが目立つようになっていったあたりから次第にEdgeHTMLやChakraCoreも新機能開発してる話も細々とアナウンスされるようになったり、かつてはやる気に満ち溢れていたリファクタリングの話も聞こえなくなり、Chromiumが押しに押しているWeb Componentもなかなか実装されないなあと感じるようになった。
正直、FirefoxOSに全賭けして大敗北した間にAndrod版の投資をサボりにサボった結果の果てにモバイルにおける影響力をほぼ全て失ったMozillaよりも先にWebブラウザを諦めても不思議じゃないなと思うようになっていた。それでも、WebViewをGUI環境のAPIとしてWindowsのいろんなところで使っていたり、WebベースのOffice製品やVisual Studio関連製品を出しており、収益も株価も決して不調とは言えないMicrosoftが今更ブラウザエンジンを止めるとも思えず、杞憂に終わるのではないかと思っていた。そんなことを1年ほどぼんやりと心の片隅に置いていたところで、MicrosoftがEdgeHTMLが止めるかもしれないという噂が報道され、そして数日後にそれは現実のものとなった。
止める理由は漠然とわかる。Web標準への準拠ならびに市場最大シェアとなっていたChromiumとの互換性を強く押し出し、Windows 10のデフォルトブラウザとしての地位を使い、あの手この手でユーザーに使わせようとしていた割にはシェアが零細のまま伸び悩んでいたし、スマートフォンやタブレットの普及の進展に伴い、モバイルで大敗北したWindowsはインターネット接続デバイスの総数における影響度は落ち始めていた。それにいくらスマートフォン市場で全く勝てなかったにしてもモバイルOSは時代の華でであり、Holo LensなどのXRの橋頭堡としての利用価値だってあったはずのWindows PhoneのGUIシェルのチームすらも解散させるような前科のある、今のMicrosoftがPersonal Computingをいつ諦めても不思議ではないという雰囲気は1ユーザーからも見てわかるように漂っていた。それ故に驚きはなく、ただただ切なさだけがこみ上げてくるだけだ。ましてや実装のdiversityの懸念がPrestoの脱落以後ずっと気にされていた中で、KHTML出身なWebKitやBlinkとは由来が全く違うTrident由来の、最も実装のdiversityにとって重要なエンジンの一つと目されていたものの脱落だけに、なおさらに切ない。今後、ORTCのようなcounter proposalが出る機会も失われるのだろうと思うと、単純に実装の一角が落ちたという以上の悲しみがある。
Mozillaの公式声明について
この件についてMozillaがChris Beard名義で以下のような内容を出している.
Goodbye, EdgeHTML - The Mozilla Blog
自分はこの声明の語調の強さには少々顔をしかめたくなるのだけれど、独立系ベンダーとしてのOperaも今は無く、WebKitも(胴元のAppleの方針も相まって)プロジェクトとして何か言うことは無いだろうし、Googleは当然何もいうはずがない。今や Google ChromeのVPであるGoogleのDarin Fisherは、彼には彼のポジショントークがあるにしても、こんな感じのことを言ってしまう状況だからね...... そのような状況を踏まえると、もはやMozillaしか言えないからMozillaが語調を荒げていうことに対しては一定の理解はできる。
だけど、この状況において(ある意味ではチャンスとも言える状況において)、FirefoxOSの失敗と同時にFirefox Androidに投資を殆どしていなかった結果、ことモバイルにおいてはMozillaは影響力が皆無に等しい状態にある。また、Web Engineのコアバリューの一つである速度の面で、総合的な結果として他二つに後塵を拝することになっているGeckoがユーザーに選んでもらえるかというと、短期的には厳しいんじゃないか。一番速い必要はなくとも、競合と比較した場合に遅いというのは選び続ける理由としては厳しいものがある。
そして、MicrosoftがChromium Projectに(どの程度かは知らないけれども)参加する意向を示している以上は、少なくともWindows向けのIntegrationくらいはやるだろう。そうなるとMozillaは最後にして唯一のプロプラOSベンダーからの支援が名実ともに一切無いProjectになるわけで、モバイルやWeb全盛の中では開発チームを動態保存するだけでも精一杯とまで揶揄されるようなデスクトップOS向けアプリケーション開発としては長期的には競合比で不利になるのではないかと懸念する。
なので、頑張っては欲しい。自分はcontributorとしては退いてしまった身なのであんまり偉そうなことは言えないけども......
WebKitについて
Mozillaについて書いたので、一応こちらにも触れておく。
従来、WebKitについては結構心情としては複雑。彼らのsoftware engineeringにおけるuser first主義とそのためのperformance first主義、そして「Web BrowserはOSの1コンポーネントであるが、同時に1コンポーネントに過ぎない」と言わんばかりの姿勢には大変なる尊敬を自分は抱いている。だいたい2015年ぐらいからは標準化されている範囲に関しても、きっちりと作り込んで来ている印象もある。
一方で、specに書かれていないことの合間を縫っていると思しき最適化の結果や、モバイル環境向けにメモリリソースなどを削りこんでいると思しき最適化、彼らのprivacy重視の実装の結果として発生するバグについてはwebdevとしては本当に厄介。しかし標準化されていない・解釈次第ともいうべき箇所であったりするので強く文句も言えないし、標準化されていない範囲に関してはそれぞれの実装方針は自由であるというのがWebの多様性を支えている重要な点でもある。地道に「せめてデバッグしやすくしてくれ」「せめて動かない理由がわかるようにしてくれ」とbugzillaに要望を書いたりしていくとかするしかないんだろう。面倒だけど仕方ないね。
めんどくさい奴だけど、現状においてはそのシェアの問題とスタンスの違いから、WebUSB/WebBluetoothのように、WebのOS化の夢を諦めていないGoogle Chromeに対する非常に重要なカウンターパートなので、Webのdiversityの観点からは非常に重要なキーファクターであり、当面は応援したいところ。
まさか2012年にWebKitに市場が席巻されるのを心配していた頃に、6年後の2018年の今ではApple WebKitがWebの相互運用性のための一角を担うことになるとは誰が思っていただろうか。
Microsoftについて
あんまりWeb関係ない感想なんだけど。
2014~2015年ごろのMicrosoft Buildの様子とは大きく変わって、最近のMicrosoftは次々とconsumer client deviceについては少なくともWindowsとしては放置を決め込んでいるように見える。Windows Phoneはシェルの開発チームごと消えてしまったし、捨てるに捨てられない戦略物資だったはずのWeb Engineは今回の件で捨ててしまったし、相変わらずHolo Lensは生きているのか死んでいるのかわからないし...... CortanaもAlexaとコラボするみたいな話があったし、Windows 10 on ARMもやる気あるのかないのかわからないし、Surfaceはハードウェアの話でSotfwareとしてのWindowsそこまで関係ないし。AzureがあるからNTカーネル自体は維持するんだろうけれども、GUIシェルよりも上に関してはもうやる気がないのかな....と邪推してしまう。
ちょっと脱線した。
webdevとして
webdevとしては「Edgeを冠するWindowsのデフォルトブラウザのリリース間隔が短くなる(かも)」という点に関してのみは嬉しい。非常に惜しむらくはそれがEdgeHTMLによって為されなかった点。半年に一度のWindows 10のUpgradeに紐付く上に、そのUpgrade対象ポリシーが非常にややこしいために「だいたい最新のEdgeが使われていると信じる」ような推定からは脱出できるからだ。しかし、それがEdgeHTMLによって為されていないという点が悔やまれる。
率直に言って、現在のWebクライアントサイドの開発においてEdgeHTMLの対応に苦心するというのは例外を除いてそんなにない。彼らがChromium互換を狙っていたというのもあるけれども、後述する例外を除いて苦心するようなケースは、経験上、そもそもの問題として何かしらcross browserで動くことを一切想定していないような事例が多い。
故に、EdgeHTMLが消失したことに対してシャンパン祝賀会を開こうと主張している、どこぞのカメラアイコンの人は、少なくとも直近数年においてはEdgeHTMLのサポートを含むまともなWebクライアントサイドの開発をしていなかったか、そもそもまともなコードを書いたことがないか、後述する例外ケースばかり泥臭くやっていて心を病んでいたんじゃないかな?と思った。そういえばiOS黎明期に一発当てて以後の最近はもっぱらUI/UXコンサルタントでしたっけ? 私は彼を詳しく存じあげていないのでよくわかりませんが。Web周りは専門外なんじゃないかな? 間違ってたらごめんなさいね。
一応例外はある。それはWebRTCであったり動画配信のように、OSやベンダーとブラウザの組み合わせごとに、バックエンドの実装が変わってしまう場合。自分はWebRTCについては詳しくはないけれども、少なくとも動画周りに関してはDRMやデコーダバックエンド、再生しているフォーマットやコンテンツファイルの相性というものがいまだに残っている。しかし、ライトに使っている限りは顕在化しないし、ヘビーに使っている場合はEdgeHTMLとWindowsの組み合わせに限った問題ではなく、どのWebブラウザでも大なり小なり抱えている問題で、それを踏み抜くかは運の問題でしかない。何度も繰り返すが、相互運用が価値のプラットフォームにおいてその問題は日常茶飯事であり、瞬間的には怒りこそすれども、そのようなプラットフォーム向けにコードを書いている以上は仕方ないのないことで、統合という方向性よりもそのようなバグが修正させる方向に向かった方が健全ではある。非常に面倒くさいけどね。。。。
EdgeブランドがChromiumに移行した結果であっても、おそらく類似の問題は発生するだろう。Microsoftが今後ChromiumベースのEdgeをリリースする時に、もしEdge向けのバックエンドがWindowsとの強固なintegrationを志向して、Google Chromeとは別の実装である場合、もしかするとEdgeHTMLの時以上にブラウザのバグ回避が困難な可能性だってありえる。故に、相互運用性だのWebの価値だのと言った問題をすべて外した上でも、一概にChromiumに移行することがwebdevにとって幸せだとは言えないと自分は思っているし、むしろGoogle Chromeに真の意味で一本化されていない以上、実装のモノカルチャーが中途半端に進展して中途半端な差異が生まれるだけで不幸でさえあるかもしれない。
長くなってきた
もう少し色々ダラダラ書きたかったんだけど、いったん切ることにする。
EdgeHTMLを諦めるというMicrosoftの判断は残念ではならない。非常に悲しい。