1. kkeethのエンジニア雑談チャンネル
  2. No.125 朝活「Migrating milli..
2022-11-02 21:19

No.125 朝活「Migrating millions of lines of code to TypeScript」をダラダラ読む回

はい.第125回は


Migrating millions of lines of code to TypeScript
https://stripe.com/blog/migrating-to-typescript


を読みました💁

今回も Stripe さんのエンジニアブログを読ませていただきましたが,相変わらずエンジニアのレベルが高そう…そして読み応え抜群!今回は実に370万行を超える Flow ベースのコードを TypeScript に置き換えたそうで,サラッと書かれていましたが中の人はかなり大変だったと推察されます((((;゚Д゚))))

きっちりテストも書いているし,移行計画にあたって関係各所としっかりコミュニケーションも取るのはさすがで,更にFlow から TypeScript への変換 codemod も公開されているのはとてもクールな対応ですね😆そのマインドも参考にしたいと思います!


ではでは(=゚ω゚)ノ


See Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.

00:06
はい、10月30日日曜日ですね。 時刻は昨日もありました。
昨日は、うちの相方と一日中、実はキャンプに行ってましてですね。 日課の方に行ってました。
そのせいで今日はちょっと足が筋肉痛だと、 若干寝不足で疲れてるんですけど、まぁちょっと頑張っていきたいなと思います。
はい、ではおはようございます。ひめめのkeethことくわはなです。 はい、本日も朝活を始めていきたいと思います。
今日はですね、またストライプさんのエンジニアブログで面白そうなのを見たかったので、 読んでいきたいと思います。
Migrating millions of lines of code to TypeScript と、100万行以上のコードをTypeScriptに置き換えたらしいですね。
というところで、これは早速読んでいこうと思います。 今日のブログ筆者の方はアンドリュー・ルーニーさんって方ですかね。
で書かれたそうです。 では行きましょう。
KさんとMPさんとプテラノドさんですね。 はい、おはようございます。ご参加いただきありがとうございます。
だらだら読んでいくので、片手間で聞いてもらえれば幸いです。 はい、では行きます。
このブログ自体は2022年の5月20日に書かれたものですね。 3月6日日曜日、ストライプ最大のJavaScriptコードベース
ストライプダッシュボードっていうのを動かしているものがありますけど、 これをフローからTypeScriptに移行しました。
一応フローは書いてたんですね。 1回のプロレイリクエストで370万行以上のコード変化しました。
100万じゃなかった、300万言ってた。 その翌日には何百人ものエンジニアが自分のプロジェクトでTypeScriptを書き始めるためにやってきました。
本当に非現実的です。少し前までTypeScriptがストライプに上陸することはないと笑っていたのを覚えていますが、
クリスマスの月曜日の朝、目を覚ますとそれはここにありますと。 ストライプエンジニアのマイク・フィックス氏がこういうことをおっしゃってたらしいですね。
TypeScriptはJavaScriptの型チェックのデファクトスタンダードであり、私たちのエンジニアはこの移行に大喜びしています。
私たちはTypeScriptの変換ツールをGitHubで共有し、他の人が同様の移行を行えるように支援しています。
ほう、いいですね。 この変換ツールが実際にリンクも貼られているので、もし興味ある方は見てみてください。
すごいですね。GitHubでちゃんと公開しているのがなかなか熱いですね。
ストライプアーカイブというオーガニゼーションがあって、その中にFlow to TypeScript Code Modというリポジトリが入っています。
ここにあります。コミットは10コミットしかないんですけど、10コミットでガッと作ったんですね。
しかもわざわざちゃんとロゴまで入っていますね。 これアルパカかな?の背中にTSって文字が入った布をかけてあるような感じですね。
ちょっとかわいいですけど、このブログも皆さんにTwitterでシェアするので、ちょうど見てみてくださいというところです。
では本題に入っていきましょう。
A Brief History of JavaScript Checking at Stripeですね。
ストライプにおけるJavaScriptの型チェックの簡単な歴史です。
ストライプというのは2012年から、stripe.com、stripe.js、stripe.dashboardなどの大規模なフロントエンドアプリケーションを構築してきました。
03:06
会社の成長とともにJSコードのタイプチェックを行うことで、製品の品質と信頼性を高めてきました。
2016年当時はメタ、当時Facebookですね。
当社はメタで開発されたJavaScriptのオプション型システムであるフローをいち早く採用しました。
それ以来フローは当社のフロントエンドアプリケーションの大部分に型安全性を提供しています。
型自体の導入はさっさとやったということですね。
その当時フローが必要なかったのでフローを入れたと。
しかしエンジニアはフローを使いこなすのに苦労していた。
タイプチェッカーのメモリ使用量によってラップトップがロックされたり、
エディター内の統合が頻繁に遅くなって信頼性が低下したということですね。
僕ちょっとフローは名前は知ってますけど実際に導入したことなかったんで、結構メモリ使用しちゃうんですねこれ。
一方マイクロソフトが開発した代替の型システムであるTypeScriptというのはそのツールや強硬なコミュニティーのおかげで爆発的な人気を博しましたと。
TypeScriptを利用できるようにすることはStripeのエンジニアの間でも最も重要な要求となりました。
Stripeの開発者生産性チームというのはエンジニアのキャリアにおいて最も生産的な開発環境を提供することを目指しており、
そのために私たちのツールを喜んでもらうことが本当に重要ですと。
開発者生産性チームというのがStripe社にあるんですね。
それ専門のチームがあるというのは結構熱い話ですね。
私たちは開発者に影響を与えるもとも差し迫った問題を特定するために努力しています。
例えば摩擦を報告するために統合機能を全ての開発ツールに組み込んだりとか、
担当チームに迅速にリューティングして優先順位をつけたりしています。
TypeScriptのサポートはそのような緊急の問題の一つであり、
フロントエンドエンジニアをサポートするチームは全社的なTypeScriptのサポートについて計画を立て始めましたよと。
続いて正しい移行戦略の選択をしようということですね。
Stripeの最大のフロントエンドコードベースはStripeダッシュボードやその他のユーザー向け製品を動かしています。
ダッシュボードのコードベースは一種コンポーネント間の結合がやっぱり強く、
依存関係グラフをきれいにリファクタリングすることができません。
TypeScriptへの段階的な移行というのは共通のタスクを達成するために、
開発者に両方の言語での作業を強いることになります。
また、両言語間で型定義を同期させ、開発プロセスを通じて一貫性を保つための総合運用性レイヤーが必要になります。
2020年後半、私たちは新たに水平方向のJavaScriptインフラストラクチャーチームというのを結成しました。
このチームはStripeにおけるJSの記述体験向上を支えることだけを焦点に当てたエンジニアのグループだそうです。
このチームの最初の課題の一つは、長くて不確実な移行説にフローをTypeScriptに置き換えることにしました。
私たちはまず同様の移行を行った企業に話を聞き、
AirTableとかZapierとかがその経験を記した記事を読むことから始めました。
これらの企業は、ある言語を別の言語に変換する自動化スクリプトを開発し、
コードベース全体に対して実行し、その出力を単一のコミットとしてマージしていました。
06:01
単一のコミットとしてマージしたんや。
それ結構すごいですね。
AirTableはフローコードを解析して、TypeScriptを生成するソース間変換ツール、
コードモードとして変換スクリプトをGitHubに公開していました。
この方法で移行すれば、同じ製品の動作について両方の型システムを扱う必要がなくなるので、
エンジニアの認識オーバーヘッドを大幅に減らすことができます。
フローとTypeScriptの間をきれいに分けることもできるようになりましたよということですね。
これを多分、流用というか参考にして、同じようなものを自分たちも作っていったってことなんでしょうね。
続きまして、計画、準備、そして反復というところですね。
ここからもプランニングとプレイプレーションとイテレーションということです。
私たちはAirTableの変換コードの品質に非常に感銘を受け、
それを私たちの移行作業の基礎として使用することに一応決定しました。
オープンソースコミュニティはこのような例から多くの利益を得ています。
私たちはまずAirTableのコードモードをStripeのモノレポにコピーし、
私たちの内部コードに対して実行することから始めました。
私たちのJavaScriptプロジェクトというのは、厳密に片付けされたリアクトコンポーネントの共有デザインシステムである
セールというのと対応しているので、最初に注目したのはこの分野でした。
フローで書かれたアプリケーションを引き続きサポートするため、
コードをTypeScriptに変換するのではなく、セール用のTypeScript定義というのを生成しました。
両方の型システムを安全にサポートするために、基盤となるフローのコードに変更があった場合にTypeScriptの定義を検証するためのテストを書きました。
このアプローチは大規模なコードベースでは結構面倒ですが、ありがたいことにセールコンポーネントのインターフェースは明示的であり、かなり厳格なものだった。
コードモードのコアは強固でしたけど、包括的ではなかった。
多くのファイルでクラッシュしたり、不完全な出力を生成したりしました。
数ヶ月かけて、より多くの公文的意味的な一致形式を処理するために繰り返し行いましたよと。
例えば簡単な例として、JavaScriptのアロー関数は、リターンステートメントなしで単一の式を書くことができる。
例えば、次のようなもので、コンスト、LinesOfCodeイコール括弧アローの7みたいな感じですね。
リターンが書いてないような一行で書かれたやつですね。
JavaScriptのオブジェクトリテラルは、中括弧を使ってプロパティ定義を囲みます。
中括弧はステートメントとブロックを区切るために使用されるので、
アロー関数からオブジェクトのリテラルを返すには曖昧さをなくすために追加の括弧のセットが必要になりますよね。
例えば、コンスト、コンカレンシーマップイコール括弧のアローで括弧の中にさらにオブジェクトみたいな感じですね。
ちゃんと明示的にリターンするところに、外に丸括弧で中にオブジェクトを入れてあげるっていうような行動が必要ですよねってことでした。
私たちはコードモードが誤ってこれらのアロー関数から余分な括弧を削除していることに気づきましたよと。
しかしこれはジェネリック関数、型の引数を取る関数ですね。
倍伸びで標準のJavaScriptでは使用できない項目になりますよと。
というところではBATケースがガンと入れるソースコードが入ってますね。
悪いソースコードですけど、コンストラッパーイコール&ltコロンみたいな感じでいわゆる括弧のところを
09:08
何だっけこれ。
エスケープしてるソースコードが張られてますと。
これは確かに見づらいですね。
そういうふうにコードモードが誤って変換をしていたってことですね。
私たちはこの問題を修正して、さらなるリグレッションを防ぐためにテストを追加することができましたと。
コードベースの幅を広げるために同様の構文の修正は何十個もございました。
セールがTypeScriptから使えるようになると、数百のJSモジュールを含むいつかの内部アプリケーションに取り組みました。
また、生成されたコードのエラーを抑制するためにコードモードに2回目のパスを追加して、
TypeScriptのattsextractconerrorというコメントを使って、これらのエラーにタグ付けをするようにしましたよと。
全てのエラーを前もって解決するのではなくて、できるだけ早くフローを排除することに集中し、
TypeScriptのエラー制御、エラー抑制というのを追跡して、変換後に対処するようにしましたよと。
とにかく、エラーは出るんですけど、フローの引っペガすことの方に注力をしたということですね。
まあそれは単純に他社のコードモードを使ったからすぐにスパッとうまくいくなんて話はもちろんないと思いましたけど、
案の定何十回も修正を加えたということですね。
続いて、ダッシュボードのコードベースに対する最初のパスでは9万7千円を超えるエラー抑制というのが発生しましたよと。
しかしコードモードのアップデートを繰り返すことで、その数は3万7千円に減少、6万も減らしたんですね。
占業に一つの割り上げ前もなりましたと。
ちなみにフローのコードでは約5000エラー抑制がありました。
フローとTypeScriptの両方が型カバレッジの計測をサポートしていますけど、
TypeScriptがこのような抑制を行った場合でも、フローよりも高いカバレッジを報告していることに私たちは非常に驚きましたよと。
これはTypeScriptで利用できるサードパーティーの型定義の数と品質が向上したためだとも考えています。
ああ、でもそうかもですね。
何万ものモジュールを持つダッシュボードに移行すると、私たちのアプローチはTypeScriptコンパイラに大きなメモリー不足を生じさせました。
これに対処するための主要なツールがTypeScriptのプロジェクト参照でございましたよと。
ダッシュボードは個別のモジュールとして構造化されているわけではありませんが、
モジュール構造を推測し、それに基づいてプロジェクト参照を作成することができますよと。
このアプローチによってアプリケーションコードの大きな塊をリファクタリングすることなく、
コードベース上でTypeScriptを実行するためにも余裕が生まれましたよということです。
あといくつか記事のリンクが貼られているので、それも後ほど記事から追っていただければと思います。
参考になるそうなものがいくつかありました。
続いてGoing Liveというところに入っていきたいと思いますが、
本稼働に向けてということですね。
毎週何百人ものエンジニアがダッシュボードに投稿しています。
そんな大規模な変更というのは、通常の勤務日にマージするのは非常に困難です。
私たちのチームは3月6日の日曜日にStripeのモノレポをロックしてブランチを立ち上げることを約束しました。
マージする前の1週間はCIシステムにビルドを通し、QA環境にデプロイすることに集中しました。
12:04
TypeScriptはプロジェクトを正常にチェックすることができましたが、
ソースコードを処理する他のツール、例えばESLint、JEST、Webpack、Metronも更新が必要になりますよと。
特に問題だったのはJESTのスナップショットテストです。
JESTはスナップショットファイルを生成する際に、それを生成したテストファイルへの参照をハードコードして生成します。
コードモードはTypeScriptファイルに対して.tsまたは.tsxの拡張子を生成するので、
スナップショットファイルはそのテストソースへの無効な参照を持つことになります。
そこで.tsxのみを使用するように生成方法を変更することで、これを簡略化しましたよと。
これによってスナップショットを一括して書き換えることができて、100%のテストが通過されましたよと。
確かにそれで良さそうですね。
TypeScriptの互換性を保つためにコードを修正すると、スケジュールに何週間もかかることが分かっているケースもありましたよと。
その一つが私たちのカスタムESLintルールですと。
ファイル間の一貫性を保つためにインポートを並び替えるルールがありましたが、
そのルールはバベルのFlowParserに対して書かれており、TypeScriptParserとは微妙に異なる抽象項分岐を作っていましたよと。
このような場合、いくつかのチェックを無効にして、変換後に復元する作業を行うことを選択しましたよと。
ビルドが成功した段階で、私たちはダッシュボードのユーザー向け機能を担当する製品チームに連絡を取りました。
ダッシュボードには後半のUnitテストと機能テストがありますけど、End-to-Endのテストカバレッジは限定的でしたと。
そのため、製品関係者による手動テストが非常に重要でしたと。
ちゃんと最後に手動テストもやるんですね。
これらのテストには、よりいくつかの小さなバグが浮き彫りになり、再収集には解決することもできました。
あるケースでは、翻訳を読み込むコードにハードコードされた.js拡張子が原因で、
英語以外のダッシュボードユーザーの翻訳を読み込むことができませんでしたみたいなケースもあったそうですね。
このプロセスによって高い信頼性を得ることができましたが、これほど大規模な変更には常に不確実性が伴います。
開発者用のツールとビルドプロセスがしっかり把握していたものの、
コードベース内のすべてのファイルに変更を加えていたのですよと。
変換スクリプトというのは、微妙なエラー、
例えば複数のコンポーネント間で共有されているオブジェクトから空のフィールドを削除するみたいなことですね。
などなどは既存の自動テストのいずれかでもカバーされないままユーザー向けのエラーを引き起こす可能性がありましたよと。
このようなエラーは下流の開発ツールの問題からビルドの失敗まで様々な形で現れる可能性があります。
私たちはデプロイの自動化とアンビエントモニタリングを利用して予期せぬ問題を認識し、
スラックのチャンネルを作成してロールアウトを調整し、
ユーザー側のチームが受け取ったレポートをすぐにエスカレーションできるようにもしましたよと。
3月5日土曜日ですね。これ前日かな。
チームは新しい移行用のブランチを作成して自動化されたスクリプトというのを実行しました。
その後、そのブランチをQAにデプロイし、製品チームから提案された手動テストも含めて検証プロセスを繰り返しました。
15:00
その結果、新たな問題は見つかりませんでした。
そして、マージ当日を迎えることができましたよと。
3月6日日曜日の朝、ストライプのモノレポをロックし、
移行用ブランチをもう一度QAパスし、変更を提出しました。
きれいにマージされ、自動テストもパスしました。
そして、TypeScriptを本番環境に投入するためのデプロイメントを開始しました。
前年度の慎重かつ厳格な作業のおかげで、
トラフィックを新しいコードに移行させる際に不快な驚きはありませんでしたよと。
私たちは、リポジトリのロックを解除し、
ダッシュボードがTypeScriptになったことを開発者に知らせましたよと。
この最後のお知らせ、すごい厚かったんでしょうね、本当に。
なんせ370万ものコードを変更したって言ってますからね。
私が面接を受けたとき、フローからTypeScriptへの移行が進行中だと聞きました。
全員のチームが小さなコードベースの複雑さと労力で苦労しているのを見ていたので、
確かに半信半疑でした。
月曜日に数分で元通りに戻ったのは屈辱的だったそうです。
すごい屈辱的だったんですね。
エリック・グレマンというソフトエンジニアの方がこういう一言を言いました。
まっすぐに圧倒的な反響がありましたよと。
あるエンジニアはこのマイグレーションがStripeに入社して以来、
唯一最大の開発者の生産性向上であると述べています。
1年間の努力が報われ、
Stripeのコードベースがこれほどまでに的確かつ明確に改善されたことは、
私たちにとって大きな喜びですよということでした。
続いてTypeScript、2ヶ月後です。
変換は完璧ではありませんでした。
残念ですね。
その後数週間の間にJSインフラチームは発生した問題に対処していました。
私たちが想定しなかった例として、
CIとローカルのTypeScriptの実行の間に矛盾があることを報告するエンジニアがいました。
TypeScriptではNPMからインストールされる多くのサードパーティーの型定義を使用することができ、
それらが更新された場合、エンジニアは新しいバージョンをインストールする必要があります。
これはフローの構成とは異なり、依存関係の更新で型が変わることはほとんどないため、
エンジニアへのデバッグのステップとして、
ERMインストールを実行するよう教育する必要がありました。
まだまだやるべきことがあります。
より詳細なプロジェクト参照によってパフォーマンスをさらに向上させることができますし、
より良いキャッシュによってCIの実行を高速化することもできます。
しかしその恩恵は道のりの凹凸を遥かに凌駕しています。
エンジニアは自動依存性インポートだったりとかコード補完などの機能や、
TypeScriptコミュニティのサードパーティーの型定義や統合の広範なコーパスにも享受しています。
新しいエンジニアがフロントエンドのコードを書くために、
Stripeに参加していたとき、初日からより快適で駄目した新たな言語で成功することができますよということです。
ラストですね。
ダッシュボードに関する作業が完了した後も、JSインフラチームは全社的にTypeScriptの採用を拡大し続けています。
同じツールを使ってStripeチェックアウトなどの全てのPayments UIを含む多くのコードベースともどんどん変化をしていっていますよと。
Stripeのフロントエンドエンジニアは、どのプロジェクトを開発するにしてもすぐにTypeScriptを書くようになります。
私たちが最初にこのストーリーを公にしたとき、この反応は同じように熱狂的でした。
18:03
業界全体からもっと知りたい、同じ改良を自分のコードベースにも適用したいという開発者が集まってきました。
こうした開発者を支援するために、私たちはTypeScriptの変換コードをGitHubで公開し、各チームが自分のプロジェクトに適用できるようにしています。
JavaScriptやFlow、TypeScriptの特殊性はさておき、今回の移行から私たちが得た大きな教訓というのは、
大規模なコードベースに対する劇的な改善は、勤勉さとコミットメント、そして楽観主義があれば可能であるということです。
この考え方は、私たちのエンジニアをより効率的にするために他の機会にも適用していきますし、
他の人も同じようにすることを願っていますという言葉で締めくくられております。
一応ですね、Twitterにシェアドストーリーというツイートが貼られてますね。
一応同じこのブログの書かれている筆者の方がツイートしてますね。
週末に私のチームであるStripeのチームは会社最大のJSコードベースをフロートからTypeScriptを渡した。
約350万行のコードを修正し、そして何百人もの開発者が月曜日の朝、DSで書くためにやっていました。
今、この記事の冒頭で書かれたことをツイートして、そこから連継でずっとひたすらストーリーが語られてますね。
結構連継長いですけど、そこが書かれているので、興味ある方は見てみてくださいというところで、このブログは終了になりました。
すごいですね。
フローが入っていたというのはすごくいい話であるんですけど、
これをさらにこの変換、大変だと思いますけど、この変換の行動モードというのはしっかりOSSにも貢献するという意味で公開しているというのはとても素晴らしい話だと思いますね。
今後、新しくプロジェクトを立ち上げる時もフローでやることはほぼないので、あんまりこれを使うことはどんどん減っていくとはもちろん事実としてあると思いますけど、
まだまだ全世界でフローを使っている会社さんというのはいるでしょうし、僕もたまに聞きますね。
やっぱりフローで使っているんですよというのを聞くので、そこでTSに依存じゃないですね。
移行したい場合はこいつを使ってみると結構良さそうな感じがしますね。
というところでございます。
中身は軽くソースコードに似ていますけど、結構大変だったのだろうなというのはよくわかります。
でもしっかりこの変換ツールの中にもテストコードがしっかり書かれていて、その辺のチェックがされているので、わりと信頼性が高そうには見えますね。
ではちょっと早いかもしれないですけど、今日の朝活はこちらで以上にしようかなと思います。
相変わらずStriveさんは技術力高いエンジニアさんが本当に多くいらっしゃるなというのはつくづく感じますね。
さらっと述べられてますけど、すごい中の人エネルギーが大変だったし、コミュニケーションをいろんなところに人たちにしっかり確認を取ったりしたというところで、
作業量が膨大だった気はしますけど、これを最終的にスパッと終わらせて、かつツールも出しているというのもめちゃめちゃクールなブログだったなという感じがしました。
参考になれば幸いですし、他の記事内に貼られているリンクのところもいろいろ参考になるところがありました。
他社さんのところですね、Airtableさんだったりとかの移行フローの話もあったりするので、そこら辺も興味があったら読んでみたくなりましたね。
というところで、朝活はこちらで以上にしたいと思います。
日曜日の朝から多くの方参加いただき、大変にありがとうございました。
21:03
また明日、ゆるーくエンジニア的な記事を読んでいこうと思いますので、興味ある方はご参加いただけば幸いです。
じゃあ、日曜日ですね。今日もしっかりゆっくり休んでいただいて、また明日から頑張っていければなと思います。
では、以上で終了したいと思います。お疲れ様でした。
21:19

コメント

スクロール