こんにちは。印刷のラクスルでフロントエンドを担当している菅野です。
以前本ブログで「既存プロダクトに最小構成でTypeScriptを導入する」という記事を投稿した者です。
上記記事はそれなりの数の方々に読んでいただけたようで嬉しく思います。
今回は、上記記事の方針で TypeScript を導入し 1 年以上開発してみて感じたことをつらつらと書いていきたいと思います。
なお、本記事は「この方針が間違っている・正しい」といった決めつけをするものではありません。あくまで各プロジェクトやチームに応じた決定をすべき、といった論調になっておりますので、ご了承ください。
TL; DR
- TypeScript 導入したメリットは大きかった
- ただ、導入方法は導入対象のプロダクトによってしっかり吟味すべき
- 導入対象のプロダクトで今後、どういう方針で TypeScript を使っていくのか?
- 直近で TypeScript に移行しないコードがいくらかあるなら、制約は強めで導入したほうがいい
- 少なくとも
strictNullChecks
は true にしたほうがいい
- 少なくとも
前回記事のサマリ
詳細は過去記事を読んでいただければと思います。
- 既存運用中のプロダクトに後から TypeScript を導入した
- TypeScriptで記述できるようにする
- .tsファイル及び.vueファイルでの
<script lang="ts">
を有効にする
- .tsファイル及び.vueファイルでの
- 既存ロジックには影響をあたえない
- 型検査は出来るようにする
- TypeScriptで記述できるようにする
- mizchi さんの Gist を参考に、制約ゆるゆるの最小構成で導入した
- 既存ロジックの変更は行っていないため、障害等も特になく導入できた
導入してみての感想
当然ながら TypeScript を入れることによる恩恵は概ね受けることが出来ました。
- 型システムの恩恵が得られる
- エディタの入力補完を受けられる
- コード=ドキュメントという状況を作りやすい etc...
また、制約を緩くしたことでスピーディに TypeScript を導入することができました。「既存プロダクトへ途中から導入する」ことへのハードルを極力下げ、問題を起こすことなく導入できたのは良かったと思います。
ただ、制約を緩くしすぎることである程度のつらみといいますか、プロジェクトによってはこうした方がいいかもと感じた部分があるので、後のセクションで詳しく述べていきます。
他にも恩恵はたくさんありますが、この記事の想定読者の方々は TypeScript のメリットについて重々承知だと思いますので割愛します(断じてサボっている訳ではない)
tsconfig の制約を緩くして導入することの是非
本記事の主題はここです。結論から書くと
制約を緩くして導入するかどうかは、プロジェクト全体で今後どれくらい移行を本気でやるかによって決めるべき
です。
- ある程度短期間で全てのコードを JavaScript から TypeScript に置き換えるのであれば、制約は緩めでいい。一旦移行しちゃってから徐々に強めていく。
- もし TypeScript へ移行しないコードがいくらか存在する & 新規開発は TypeScript でガッツリやるようなケース(導入したプロダクトはまさにこちら)ではある程度制約を強くして導入したほうがいい。
- 特に
strictNullChecks
は true にすべき。出来ればnoImplicitAny
も。
- 特に
strictNullCheck: false だとどうなるのか
関数の戻り値や代入などで null
とundefined
をコンパイラがチェックしてくれません。
https://typescript-jp.gitbook.io/deep-dive/intro/strictnullchecks
つまりはこういうことです。
interface SomeObj { someField?: string | undefined } function someFunc(someObj: SomeObj): string | undefined { return someObj.someField } // 戻り値として推論されるのは string だけなので、実行時エラーになる可能性がある someFunc().toLowerCase()
[caption id="attachment_4599" align="alignnone" width="1384"] strictNullChecks: false の場合の型[/caption]
コンパイル時に null
や undefined
が補足されないため、実行時エラーが発生しやすくなります。(Cannot read property 'someField' of undefined
などなど)
レビューで指摘すればある程度は担保できるかもしれませんが、それでも限界がありますし、コンパイル時にチェックするほうが何倍も楽です。
もちろん false にするメリットもあります。
既存 JavaScript コードの移行時には false のほうがすんなり進めることが出来ます。
ただ、前述したように導入したプロダクトでは
- 移行があまり進まなかった(移行するうまみが無い部分や、年単位で触っておらず障害も出ない部分は移行しない方針になった)
- 新機能開発は TypeScript でガンガン進めていた
という状況だったため、このオプションが false であることのデメリットが大きくなってしまっていました。
そのため、導入プロダクトの今後のロードマップや開発方針によって、少なくとも strictNullChecks
は true にすべきと感じました。
やっぱり strict: true にしよう
新規開発でのつらさに耐えかね、結局 strict: true にするという決断をしました。
[caption id="attachment_4601" align="alignnone" width="2486"] strict: true にする PR[/caption]
修正方針は以下としました。
- 基本ロジック修正はしない。
any
やts-ignore
を惜しまず使う。- 自明な型が欠如している部分や、
optional chaining
で問題なさそうな部分は修正。
- 自明な型が欠如している部分や、
ts-ignore
は今後の運用で外していく。
ここで大切なのは「基本ロジック修正はしない。any
や ts-ignore
を惜しまず使う。」の部分です
あくまでマイグレーションが目的だったので、スピード感や障害を出さないことを重視しました。any
や ts-ignore
は
すでにリリースが完了していますが、ロジックをほぼ修正していないため、特に問題なく制約強化を実施出来ました。
@ts-expect-error の話
TypeScript 3.9 では、新たに @ts-expect-error
というコメントが追加されています
こちらはどういうものかと言うと(以下引用)
When a line is preceded by a// @ts-expect-error
comment, TypeScript will suppress that error from being reported; but if there’s no error, TypeScript will report that// @ts-expect-error
wasn’t necessary. 前の行に// @ts-expect-error
がついている場合、TypeScript はエラーを報告しないようにします。しかし、もしエラーが無くなれば TypeScript は// @ts-expect-error
が不要であることを報告します。
とあります。エラーが無くなった場合に「もうここにはエラーはないよ」と教えてくれるわけですね。
// @ts-ignore
との挙動の違いは、// @ts-ignore
はただコンパイル時のエラーを抑制するだけです。対象の行エラーが無くなったとしても徳に何もレポートしません。
そのため、近いうちに直したい部分や一時的なワークアラウンドな箇所に適しています。
今回、対象プロジェクトではまだ TypeScript 3.9 にアップデート出来ていないため // @ts-expect-error
は使えませんでしたが、既に 3.9 であったり、近々アップデートする予定の方は使い分けることをおすすめします。(弊プロジェクトでもアップデート後使っていく予定です。)
最後に
印刷を担当するフロントチームでは、少なくとも「最小構成で導入したこと」を間違っていたとは思っていません。むしろ導入時のコストを最小限に抑えることができたため良かったと思っています。
ただ、当時の知識不足や議論の足りなさから、結果的に別の辛さを背負ってしまったのは間違いありません。
とはいえ、その分失敗から得られた知見は多く、社内フロントチームで共有して今後に活かしていこうと思っています。
これから既存プロダクトへの TypeScript 導入を考えられている方々が本記事を読んで、少しでも参考になる部分があればいいなと思っております。
ラクスルでは、伝統的な業界のデジタル化を推進するフロントエンドエンジニアを募集しています。