RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

「既存プロダクトに最小構成で TypeScript を導入」してみた感想

こんにちは。印刷のラクスルでフロントエンドを担当している菅野です。

 

以前本ブログで「既存プロダクトに最小構成でTypeScriptを導入する」という記事を投稿した者です。

上記記事はそれなりの数の方々に読んでいただけたようで嬉しく思います。

今回は、上記記事の方針で TypeScript を導入し 1 年以上開発してみて感じたことをつらつらと書いていきたいと思います。

なお、本記事は「この方針が間違っている・正しい」といった決めつけをするものではありません。あくまで各プロジェクトやチームに応じた決定をすべき、といった論調になっておりますので、ご了承ください。

TL; DR

  • TypeScript 導入したメリットは大きかった
  • ただ、導入方法は導入対象のプロダクトによってしっかり吟味すべき
    • 導入対象のプロダクトで今後、どういう方針で TypeScript を使っていくのか?
  • 直近で TypeScript に移行しないコードがいくらかあるなら、制約は強めで導入したほうがいい
    • 少なくとも strictNullChecksは true にしたほうがいい

前回記事のサマリ

詳細は過去記事を読んでいただければと思います。

  • 既存運用中のプロダクトに後から TypeScript を導入した
    • TypeScriptで記述できるようにする
      • .tsファイル及び.vueファイルでの <script lang="ts">を有効にする
    • 既存ロジックには影響をあたえない
    • 型検査は出来るようにする
  • mizchi さんの Gist を参考に、制約ゆるゆるの最小構成で導入した
  • 既存ロジックの変更は行っていないため、障害等も特になく導入できた

導入してみての感想

当然ながら TypeScript を入れることによる恩恵は概ね受けることが出来ました。

  • 型システムの恩恵が得られる
  • エディタの入力補完を受けられる
  • コード=ドキュメントという状況を作りやすい etc...

また、制約を緩くしたことでスピーディに TypeScript を導入することができました。「既存プロダクトへ途中から導入する」ことへのハードルを極力下げ、問題を起こすことなく導入できたのは良かったと思います。

ただ、制約を緩くしすぎることである程度のつらみといいますか、プロジェクトによってはこうした方がいいかもと感じた部分があるので、後のセクションで詳しく述べていきます。

他にも恩恵はたくさんありますが、この記事の想定読者の方々は TypeScript のメリットについて重々承知だと思いますので割愛します(断じてサボっている訳ではない)

tsconfig の制約を緩くして導入することの是非

本記事の主題はここです。結論から書くと

制約を緩くして導入するかどうかは、プロジェクト全体で今後どれくらい移行を本気でやるかによって決めるべき

です。

  • ある程度短期間で全てのコードを JavaScript から TypeScript に置き換えるのであれば、制約は緩めでいい。一旦移行しちゃってから徐々に強めていく。
  • もし TypeScript へ移行しないコードがいくらか存在する & 新規開発は TypeScript でガッツリやるようなケース(導入したプロダクトはまさにこちら)ではある程度制約を強くして導入したほうがいい。
    • 特に strictNullChecks は true にすべき。出来れば noImplicitAny も。

strictNullCheck: false だとどうなるのか

関数の戻り値や代入などで nullundefined をコンパイラがチェックしてくれません。

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 の場合の型 strictNullChecks: false の場合の型[/caption]

コンパイル時に nullundefinedが補足されないため、実行時エラーが発生しやすくなります。(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 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 というコメントが追加されています

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#-ts-expect-error-comments

こちらはどういうものかと言うと(以下引用)

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 導入を考えられている方々が本記事を読んで、少しでも参考になる部分があればいいなと思っております。

ラクスルでは、伝統的な業界のデジタル化を推進するフロントエンドエンジニアを募集しています。