こんにちは、@mugi_unoです。
GWはリスと遊んできました。たのしかったです。
さて、長きに渡ってコツコツと手を入れてきたMisocaのフロントエンドですが、 先日、新たに大きな改善を行いました。
というわけで、令和一発目のエントリーは MisocaのフロントエンドにTypeScriptを導入したお話です。
🤔なぜTypeScriptを?
金額処理触るの怖すぎ問題
Misocaは請求書の発行・管理サービスという性質上、各所で金額に関する処理があります。 そして、最近はさまざまな事情により修正が頻繁に行われていました。 以前のエントリでもご紹介したレガシーコードのリファクタリングなども該当します。
エンジニアの方なら「おおぅ...」となりそうですが、金額を触る処理というのは怖いものです。 そしてフロントエンドが絡んでくると「ここは文字列?数値?」といった疑心暗鬼も発生し、気軽に足し算もできません。
そのため、機能修正やリファクタリングを行うにあたって、あらかじめ大量のテストコードを用意するなど、安全のために多くのコストが発生していました。
そのようなテストコードや目視確認といった手法で安全を確保するアプローチに対し、TypeScriptなどの型システムによるアプローチでは、設定や記述によっては完全にすべての値や計算結果を保証することはできないとはいえ、「ある程度」は静的なチェックで保証することができます。
今後も似たような変更をする状況は容易に想像できますので、つらさを感じてから出来るだけ早いうちに、 そのつらさを解消するための仕組みを入れておいたほうがいいと判断しました。
時代の流れにもちゃんと乗りたい
「そんな理由で!」と怒られそうですが、 個人的には「世の中の流行りを追う」も技術選定において重要な要素だと考えています。
一瞬の流行りに飛びつくと怪我をするので慎重さも必要ではありますが、 トレンドとなっている技術を利用することで開発者(主に私かもしれない)のモチベーションが上がりますし、 そこから発展した新しい技術要素が登場した場合にも検証・導入しやすくなります。
主観ですが、最近のフロントエンドではTypeScriptが主流になっている感覚があり、 「Misocaでも追従すべきだろうな」と以前から考えていました。
そんな中、上記の金額処理怖い問題も重なり「やるか!」と決意したのでした。
💪TypeScript導入のながれ
実験用PRを作ってみる
何はともあれ、一旦実験用PRを作ってみました。
方針を決める前に、現状のコードベースにTypeScriptを導入した場合にどういったことが起きるのかを知るのが目的です。 最終的にはこのPRがそのままマージされましたが、試しに小さく一回やってみるのは大事ですよね。
方針をきめる
TypeScript導入時に意見が大きく分かれそうなのが、noImplicitAny
の存在ではないでしょうか。
型が不明な場合にany型に推論させるかどうかという設定です。
これを無効化することは敗北だという意見を目にしたこともあります。
私も最初は「型でバッチリ固めて守るぞ〜!」と意気込んでいましたが、 上記の実験用PRを作ってみたところスクロールしきれないほどのエラーが噴出し、「あっ、これ無理だわ!!」 と感じたので noImplicitAnyはfalse(any型に推論させる)ではじめることにしました。 また、noImplicitAnyに限らず、基本的にanyの利用は禁止しないことにしています。
このあたりはSlackなどでチームメンバーとも相談し
- any推論を許容しても、自動推論で受けられる恩恵は十分大きい
- まずは全体をTypeScriptコンパイルが通っている状態にすること自体が大事
- 徐々に型を整えていって固めていけばいい
- それでも現状と比べると遥かに良くなる
といった考えに基づいています。
@t_snzkさんや@__gfx__さんの提唱されている「がんばらないTypeScript*1」も参考にさせていただきました。(ありがとうございます!)
なお、strict
オプション自体は trueのままで、noImplicitAny以外のstrict関連のオプションはすべて有効化した状態でスタートしました。
これも実際に有効にして型チェックを通してみた結果「これぐらいならサクっと対応できそうだな」という肌感を得ての判断でした。
ADRを書く
Misocaでは開発時の意思決定をADR(Architecture Decision Records)*2という形でソースコードと一緒にドキュメント化して残しています。
いきなりTypeScriptを導入するのではなく
- 現在はどういった背景事情があるのか
- なぜ導入するのか
- どういった形で導入するのか
- 何が変わり、何が影響を受けるのか
- どういったメリット・デメリットがあるのか
といった情報を整理して残しておくことで、問題意識の共有もできますし、 後から新しく入ったメンバーも過去の意思決定を遡って知ることができます。
参考までに、実際のADRをまるごと貼っておきます。
(内部共有仕様なので、もし不備とかがあったら見逃してください。)
実験PRを通して見えてきたさまざまな方針をADRとして整理し、PR上でチームでレビューした内容が反映されています。
テストコードから対応していく
方針が決まったので、まずはテストコードから置き換えを行いました。
テストコードなので本番への影響もほぼありませんし、 わりと単純なコードが多いので差分の理解もしやすいです。 作業者自身だけでなく、レビュワーの目を慣らすためにもテストの書き換えから行うのはアリでしょう。
Vue.jsをどうしたか
Vue.jsでTypeScriptの恩恵を受けるため、
を導入し、クラス&デコレータを利用した形に全コンポーネントを書き換えることにしました。
Vue&TypeScript導入方法は、Vue.extends
を利用する方法もありますが、
この場合は型で保証できる範囲に一部制約が発生します。
どうしようか悩みましたが、Propsはしっかり型で守りたいな〜と考えていたため、
vue-property-decorator
を使うことにしました。
実際置き換えをやってみるとなかなか大変で、クラスコンポーネント形式固有で注意すべき挙動なども対応していく中で踏み抜きました。 このあたりは知見として、最終的にはesaにガイドラインとして整理しておきました。
(踏み抜いたundefinedによる初期化に関する記録の例)
エンジニアへの知見共有
全体をTypeScript化するにあたって、コード自体の変更だけではなく、 エンジニア全体への知見の共有も併せて検討していかねばなりません。
TypeScript自体は良いものだと思いますが、 今まで利用していなかったのであれば、もちろん学習コストが発生します。
そこで、作業時には常に社内でライブ配信することにしました。
変更前後をリアルタイムで比較することができますし、 何よりも修正と共有がまとめて出来てお得ですね!
終わった後は学びを簡単にまとめてみるなど、なかなかよい試みでした。
🏃♀️プロジェクト化して一気にゴールまで走る
コツコツと一人で書き換えを続けていましたが、 残り40〜50%程度になった段階で@KawamataRyoがJoinし、 二人でプロジェクト化して一気にすべてを片付けることにしました。
そこからはおおよそ一ヶ月弱程度で、すべてのJSファイルをTypeScriptに書き換えることができました!
🎉TypeScript化(ひとまず)完了
というわけで、anyを使って緩めている箇所は残っていますが、無事全体をTypeScript化することに成功しました。 現在は新たにフロントエンドのコードを書く場合はすべてTypeScriptを利用しています。
VSCodeでコードを書いているときにメソッドや引数の補完が出るのもありがたいですし、 単純なタイポも即座に指摘してくれているので非常に快適です。
がんばってよかった!
次の課題
いや〜、導入できてよかったよかった、と言いたいところですが、課題は残っています。
やはり最大の問題はanyで、noImplicitAnyを無効化しているのに加えて、ひとまずany定義をして逃げたコードも多く残っています。 直近でやるべき作業は次のようなものかな〜と考えています。
- noImplicitAnyの有効化
- anyはunknown型利用に置き換え
理想の構成を手に入れるまで、引き続きコツコツやっていきたいと思います💪
...というわけで!
採用
Misocaではコツコツ改善するのが好きなエンジニアを募集しております!