敗北者のTypeScript

TypeScriptはJavaScriptに静的型を導入したプログラミング言語で、登場から現在までその人気を増し続けています。

動的型付き言語であるJavaScriptに静的型の安全性（コンパイル時にバグ・間違いを発見することができる能力）を与えることで、TypeScriptはJavaScriptによる開発の効率を上げてくれます。

裏にJavaScriptがあるという特性もあり、TypeScriptは「部分的に静的型チェックをする」というような挙動をサポートしています[^no_runtime_check]。詳しくは後述しますが、これによりJavaScriptからTypeScriptへの移行が可能となっています。TypeScriptは`@ts-check`（あるいは`@ts-ignore`）などを通じてこのようなユースケースも手厚くサポートしています。

[^no_runtime_check]: この特徴から、TypeScriptは単なる静的型付けではなく「漸進的型付け」と見なされることもあります。個人的には、[ランタイムのチェックが何もないのに漸進的型付けと呼んでもよいのか怪しいと思っていますが](https://qiita.com/uhyo/items/df276348b966f0e9fe1c)。

このことの裏返しとして、TypeScriptを利用するときは注意すべき点があります。それは**使い方によってはTypeScriptの能力が十分に発揮されず、安全性を享受できない**という点です。記事タイトルにある**敗北者のTypeScript**というのはもちろん筆者が造った言葉ですが、これはTypeScriptが持つ安全性を最大限享受できずに、それどころか逆に危険な状態でTypeScriptを使っていることを指します。

この記事ではどのようなときに**敗北者のTypeScript**に陥ってしまうのか、そしてその対処法について議論します。

## 3行でまとめると
- TypeScriptで`--strict`を使わない人や、`any`とか`as`を濫用する人は敗北者です。
- これらを使っていいのはあなたがTypeScriptよりも賢くて真にそれが必要だと分かっている場合だけです。
- 「敗北者のTypeScript」だからといってTypeScriptのメリットが完全に無くなるわけではありません。JSからの移行段階での敗北は仕方がないし、「素のJavaScript」よりも「敗北者のTypeScript」のほうが安全です。どんどんTypeScriptを使って敗北していきましょう。

## 敗北者のTypeScriptとは何か

この記事でいう**敗北者のTypeScript**とは、ひとことで言えば**`any`や`as`に代表される危険な機能を濫用するTypeScriptコード**を指します。あとで詳説しますが、これらの要素はTypeScriptが本来保証してくれる安全性を破壊する機能であり、使えば使うだけTypeScriptを使うメリットが減少してしまいます。

これは筆者の意見ですが、最も理想的な状況としてはTypeScriptの`strict`オプションを有効にするのはもちろん（これは`noImplicitAny`や`strictNullChecks`などの厳しいチェックを有効にします）、`any`や`as`などの危険な機能はeslint[^tslint]の[no-explicit-any](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md)や[consistent-type-assertions](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md)[^type_assertion]などのルールを用いて原則禁止としなければいけません。これらがどうしても必要な場合はいちいち`// eslint-disable-next-line: no-explicit-any`のようなコメントを用いてルールを明示的に無効化する必要があり、さらにコメントで`any`などが必要となる妥当な理由を説明していない場合はレビューで弾かれなければいけません。

[^tslint]: TypeScript用のリンターとしてTSlintをご存知の方がいるかもしれませんが、現在はTypeScriptでもESlint（JavaScript用のリンター）に一本化されています。
[^type_assertion]: `type-assertion`はまだ提案のissueがあるだけで実装はされていません。当該のissueに応援コメントを書いておきました（メンテナの方にドン引きされてしまいましたが。）

TypeScriptを実際に使っている方の中には「**そんな運用は現実的ではない**」と思った方も多いでしょう。`any`や`as`という抜け道が封じられるということは、開発にかかる時間やその他のコストが増加することも考えられます。また、上記の運用をするには何が`any`を使う“妥当”な理由であり何がそうでないのかを理解・判断する必要がありますが、そのためにはコードを書く人もレビューする人もTypeScriptに精通していなければいけません。そのような運用ができるチームを組むのは並大抵のことではないでしょう。

ゆえに、**その通りです、TypeScriptユーザーの非常に多くはTypeScriptへの敗北に甘んじているのです**。筆者ですら例外ではなく、業務では`any`を使用するコードに対して「:japanese_ogre:」とだけコメントしてLGTMを出したこともあります。

なお、ここまで読んだ方は既に察しているとは思いますが、**筆者は型の信奉者であり過激派**であるという点はご理解ください。実際、[同じことをeslint-typescriptのリポジトリに書いた](https://github.com/typescript-eslint/typescript-eslint/issues/332)ら「（`any`を禁止するのはいいけど`as`を禁止するのは）**俺だったらそんなん絶対嫌だわ**（意訳）」と言われました。

ただ、思想はともあれ記事の内容は皆さんのTypeScriptコードをさらに安全にするのにきっと有用ですから、ぜひ読んでいってください。記事を読んで筆者と同じ思想に染まってくれたらさらに嬉しいです。

### 誰と誰が戦い、誰が負けたのか

ところで、ここまで読んだ方の中には**なにが敗北だ、俺は売られた喧嘩は買う主義なんだぜ**と思っている方もいるかもしれません。この記事のタイトルは、筆者の別記事**[TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#any%E5%9E%8B)**における`any`型の説明に由来します。

> ここで、any型という言葉が出てきましたので、これについても解説します。any型は何でもありな型であり、**プログラマの敗北です**。

つまり、負けたのは`any`を使ったプログラマです。そして、プログラマを打ち負かしたのは**TypeScriptコンパイラ**です。敗北したプログラマはTypeScriptコンパイラと知恵比べを行いましたが、`any`無しではコンパイルを通すことができませんでした。そこで、プログラマは妥協し、安全性を犠牲にしてコンパイルを通すという選択をしたのです。

`any`は、プログラムの実態（＝プログラムが持つバグ・誤り）を何も変えないままに多くのコンパイルエラーを消す力を持ちます。つまり、`any`を使うというのはTypeScriptがせっかく発見してくれた問題を放置し、TypeScriptを黙らせることを意味します。これこそがプログラマの**敗北**であり、プログラマは**敗北の代償**として**プログラムの安全性**を失ったのです。

敗北の理由には、**実力が足りない**、あるいは**時間が足りない**といったことが挙げられるでしょう。`any`を使わなくてもコンパイルが通るようなプログラムの設計や書き方ができないか、あるいはそのような良い書き方をする時間が無い場合に敗北が発生しがちです。

ここでは`any`が槍玉に挙がりましたが、`as`の場合も同様です。これらはTypeScriptの安全性を脅かす言語機能第1位と第2位であり、どちらも使うとプログラムの安全性が犠牲になります。どう危険なのかということはこの記事でじっくり説明しますから、ご安心ください。

### TypeScriptが敗北するとき

ところが、TypeScriptのスキルが一定以上ある方は、`any`や`as`を全く使わずにTypeScriptプログラムを書くのは不可能であるということをご存知でしょう。TypeScriptコンパイラも完璧ではなく、プログラムのロジックを理解できなかった結果として、実際にはありえない型エラーを報告してくることがあります。`any`や`as`はそのような場合にコンパイルエラーを消す手段として有効です。

これは**プログラマの勝利**、言い換えれば**TypeScriptの敗北**です。書かれたプログラムがTypeScriptの推論能力を上回った結果として`any`や`as`の使用を余儀なくされることがあるのです。これが、冒頭で触れた「`any`や`as`がどうしても必要な場合」です。

**TypeScriptの敗北**は型変数やconditional typeなどの複雑な機能を使う場合に多く発生しがちです。例えば、筆者の別記事[TypeScriptの型レベル連結リスト活用術：型を変えられるコンテナを作る](https://qiita.com/uhyo/items/ab65e79f7b0c786406f6)では、`any`や`as`がさりげなく使われているのを見ることができます。

`any`は`as`は使わないのが理想的ですが、実際のところそう上手くはいきません。**プログラマの敗北**か**TypeScriptの敗北**のいずれかの結果として、`any`や`as`は必要となります。この記事の主張は、`any`や`as`は**TypeScriptの敗北**の場合にのみ使うべきであるということです。それができなかった結果が、**敗北者のTypeScript**です。

別の言い方をすれば、`any`や`as`を**プログラマの力不足**を埋め合わせるために使ったのならばそれは**プログラマの敗北**であり、**TypeScriptの力不足**を埋め合わせるために使ったのならばそれは**TypeScriptの敗北**です。`any`や`as`の使用が妥当かどうか見極めるには、どちらの力不足が原因なのかを見極める必要があります。ただし、これをきちんと見極めるのは並大抵のことではありません（それがなぜかは記事中で追々解説していきます。）。

前置きが長くなりましたが、いよいよ本題に入りましょう。まず、`any`や`as`の何が危険なのか解説していきます。これを理解することは、`any`や`as`を避けるべき理由を理解するだけでなく、プログラマの敗北とTypeScriptの敗北を見分けるためにも重要です。

## `any`の危険性

`any`というのはTypeScriptが提供する特別な型です。`any`型は**TypeScriptを黙らせるための型**であり、`any`型に対して何をしてもTypeScriptは型エラーを出しません。

`any`型が使われる場面は以下のように分類できます。

- 正しいコードを書いたのにTypeScriptの型推論能力が足りなくてエラーになるのでTypeScriptを黙らせたい場合（**TypeScriptの敗北**）
- 実際コードが間違っていてTypeScriptがエラーを吐いているけど直せないのでとりあえずエラーを消したい場合（**プログラマの敗北**）
- どんな型を書けばいいのか分からないのでとりあえず`any`と書いている場合（**プログラマの敗北**）
- JavaScriptからの移行の途中でまだ型を書いていないのでとりあえず`any`にしている場合（**移行途中なら仕方ないけど敗北は敗北**）

最初のひとつ以外は望ましい`any`の使用ではありません。また、後述しますが、最初のものも基本的には後述の`as`で代用可能です。

具体的な`any`型の特徴は、**任意の型の値を`any`型として扱うことができる**こと、そして**`any`型の値に対してどんな操作を行っても型エラーが起きない**上、**その結果は`any`型になる**ことです。では例を見ましょう。

```ts
// 関数fooは適当なオブジェクトを受け取ることができるつもり
function foo(obj: any): number {
  // obj.numには数値が入っているはずなのでobj.numが存在すればそれを返すつもり
  if (obj.num != null) {
    return obj.num;
  }
  // obj.numが存在しない場合もあるのでその場合は0を返す
  return 0;
}
```

この例では、関数`foo`の引数`obj`の型を`any`としたことでこの関数の型安全性が崩壊しています。その結果、次のようなコードで問題が発生するでしょう。

```ts
foo(null); // これは型エラーは発生しないが実行時エラーが発生する

// 変数valはTypeScript上ではnumber型なのに実際には"12345"という文字列が入る
const val = foo({ num: "12345" });
// 文字列にtoFixedメソッドは存在しないのでここで実行時エラーが発生する
console.log(val.toFixed(20));
```

このように、`any`を使うと実際には危険なコードでも型エラーが起きません。まず、`foo`の引数`obj`は`any`型なので、どんな値も受け入れます。その結果、型エラーを出すことなく`foo`に引数として`{ num: "12345" }`を渡したり`null`を渡したりできます。

関数`foo`の中ではまず`obj.num`というアクセスが発生しますが、`obj`は`any`型なのでもちろんエラーは発生しません。また、`obj`は`any`型なので`obj.num`も`any`型として扱われます。

ここで`any`の危険性がひとつ露呈していますね。`obj`に`null`とか`undefined`が入った場合は`obj.num`というプロパティアクセスは実行時エラーとなりますが、TypeScriptはそれを型エラーとして検出してくれませんでした。これは`obj`が`any`型だからです。

さらに、`return obj.num;`にも問題があります。関数`foo`の宣言を見ると戻り値は`number`型として宣言されていますので`obj.num`は`number`型でなければなりませんが、`obj.num`は`any`型なのでそれを`number`型として扱っても型エラーは起きません。明らかに、実際`obj.num`に数値が入っている保証がありませんので、これは実行時のエラーに繋がります。

このように、**`any`型を使うとTypeScriptは本来防げたはずの実行時エラーを防いでくれなくなります**。これではTypeScriptを利用するメリットをプログラマ自ら殺していることになりますから、TypeScriptが提供する安全性を最大限享受するためには`any`を使うべきではありません。

特に、型をちゃんと書くのが面倒くさいから`any`にするというケースは最悪のパターンです。書こうと思えば書けた型を敢えて書かないというのは、（時間的要因など仕方ない部分があるかもしれませんが）明らかに**プログラマの敗北**です。

実際のところ、`any`を使っているコードでも問題なく動作するという場合も多いでしょう。そもそも人類は型のないJavaScriptで沢山のプログラムを書いてきたのですから、`any`の存在下で実際正しく動くプログラムを書くこともできるでしょう。そのようなコードを書いている方ならば、**こんなのちょっと考えれば安全だって分かるんだから`any`を使ってもいいだろ**と思うかもしれません。しかし、筆者はそのような考え方は推奨しません。なぜなら、**`any`はコードの負債になる**からです。

実際安全なコードであったとしても、`any`とコードに書くということは**そのコードが危険であると積極的に主張しているのも同然**ですから、コードに書かれたその`any`はこれからそのコードを見るすべての人を警戒させ、思考時間を奪います。`any`と書かなければTypeScriptが安全性を保証してくれたのに、`any`と書いてしまったばかりにTypeScriptは型チェックを放棄し、そのコードの安全性を保証するのは人間の責任となるのです。これこそがまさに、TypeScriptを使う意味が薄れるということです。

この`any`がいかに安全かをコメントに書き残しておくという手もありますが、それなら`any`を消してちゃんと型を書いたほうがよいです。`any`の安全性を証明するにはその`any`が関わるロジック全てを完全に精査し、コメントを読むだけで納得できるように分かりやすく説明しなければいけませんが、その労力はちゃんと型を書くために使うべきです。

もしどうしても正しい型が書けないとき、ほら見ろ**TypeScriptの敗北**だと思われるかもしれませんが、それは少し早計です。プログラムの設計変更によってよりロジックを単純化することで、型が書けるようになる可能性があるからです。TypeScript時代においては、ちゃんと型で表現できるようにプログラムを設計するということもTypeScriptのスキルの一つですね。

そもそも型をどう書けばいいか分からないようなプログラムは、ロジックが複雑すぎます。TypeScriptがどうとか以前に、そのようなプログラムは書くべきではありません。人間がロジックを理解できないということは、たとえテストを書いても意味がなく、安全性を保証するのがたいへん困難だからです。

### どうすればよかったのか

では、先ほどの`any`の例に話を戻して、これを正しく直してみましょう。いきなり正解を述べると、この場合は`obj`の型を`any`ではなく`{ num?: number }`とすべきです。この型の意味は、「オブジェクトであり、プロパティ`num`を持っていても持っていなくてもいいが、持っている場合はプロパティ`num`は`number`型でないといけない」という意味です[^primitive]。この型を用いることで、関数`foo`に変な値が渡されて実行時エラーとなるのを防止できます。

[^primitive]: 実は、この型は`3`や`"12345"`のような（`null`と`undefined`以外の）プリミティブ型も許容します。これらの値は`obj.num`のようなプロパティアクセスに対して実行時エラーを出さないため、その意味ではオブジェクトと同様に扱えるからです。

```ts
function foo(obj: { num?: number }): number {
  if (obj.num != null) {
    return obj.num;
  }
  return 0;
}

// ↓これはコンパイルエラーになる
foo(null);
// ↓これもコンパイルエラーになる
const val = foo({ num: "12345" });
```

この例では、`obj`の型に`any`を使うのをやめたことで実行時エラーに繋がるバグ（`foo(null)`とか）を検出することができました。つまり、TypeScriptが提供する安全性を正しく活用できたことになります。

繰り返しますが、`obj`に与えるべき型が分からずとりあえず`any`にしていたとしたら、それはプログラマの敗北としか言えませんね。正解が分からなかった方は、敗北者から抜け出すためにもっとTypeScriptの型を勉強するのが吉です。たとえば以下の記事などはその助けとなるでしょう（宣伝）。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)

もっと言えば、型が書けないときに**TypeScriptの敗北**であると主張するためには、TypeScriptでできることを全て知り尽くしていなければその資格がないということです。TypeScriptに勝利できるのはTypeScriptマスターだけなのです。

## `--noImplicitAny`を使わない場合の危険性

もうひとつ`any`に関連する話題として、`--noImplicitAny`の話をしましょう。これはTypeScriptのコンパイラオプションの一つで、`tsc`のコマンドラインオプションか`tsconfig.json`を通じて指定します。

このオプションは**暗黙に`any`型を推論するのを避ける**というオプションであり、**もちろん利用すべき**です。逆に、このオプションを使用しない場合は前述の危険な`any`型が暗黙のうちに発生してしまうことがあり、非常に危険です。

まず、TypeScriptでは**型宣言を省略**することができる機能があります。これは、何も型が書いていないただのJavaScriptコードも受け付けられるようにしてJavaScriptからTypeScriptへの移行を支援するという目的があります。

```ts
// 関数fooの引数numの型と返り値の型が省略されている
function foo(num) {
  return num.toFixed(3);
}

console.log(foo(123)); // "123.000"
```

では、上記のコードで`num`の型はどうなるでしょうか。我々の感覚だと、`foo(123)`という呼び出しがあるので`num`の型は`number`になってほしいですね。ところが、実際には**`num`の型は`any`となります**。すなわち、**関数引数の型が宣言されていない場合は問答無用で`any`になる**のです[^contextual_typing]。この挙動は、JavaScriptからTypeScriptへ移行途中でまだ型が書けていない場合には便利です。まだ書かれていない変数の型をとりあえず`any`としておくことで、まだ型を書いていない部分で型エラーが発生するのを抑止してくれるのですから。

[^contextual_typing]: 構文上の文脈から引数の型が推論可能な場合はこの限りではありません。

とはいえ、これはあくまで移行途中だから許される話であり、**安全性を求めるならこのような挙動は言語道断**です。そこで、このように**`any`と書いていないのに`any`型が発生する**事態を防いでくれるのが`--noImplicitAny`オプションなのです。このオプションを有効にすると、上述のように引数の型が宣言されていない場合は問答無用でエラーになります。TypeScriptは関数のインターフェースは明示すべきという方針を採用しているように思われますから、関数の引数の型はちゃんと明示し、`--noImplicitAny`オプションがオンの状態でもエラーが発生しないようにするのが正しいTypeScriptコードのあり方です[^return_type]。

[^return_type]: ちなみに、返り値の型については書かない場合`any`になるのではなく、関数の中身から推論してくれます。しかし、基本的には返り値の型も明示したほうがよいでしょう。コードリーディングの助けになるだけでなく、関数の中身にバグがあるときに型エラーでそのバグを発見できる可能性があります。

また、引数の型が書かれていないケース以外でもいくつかの場合（代表的なのは型定義の無いライブラリを読み込んだ場合）に`any`が発生することがあり、`--noImplicitAny`はそのような挙動を全て防止してくれます。

`--noImplicitAny`は安全にTypeScriptを使うなら**絶対に使うべき**オプションですが、一つでも型の書いていない引数があればコンパイルエラーとなり、型定義が用意されていないライブラリを使うのもコンパイルエラーになるため、このオプションは**JavaScriptからTypeScriptに移行している途中では使えない**という特徴を持ちます。それゆえ、一部には「`--noImplicitAny`を無理に使わなくてもいい」という主張をする人もいます。そのような考え方を否定はしませんが、それは`any`の自然発生という**大敗北**を受け入れるという、**もはや不戦敗**とも言えるやり方です。

筆者が危惧するのは、そのような**負け犬根性**が染み付いてしまい、それがTypeScriptの唯一のあり方であると思われることです。そのような主張が妥当性を持つのはあくまでJavaScriptからTypeScriptへの移行の途中の話であり、TypeScriptに移行が完了した後も**`--noImplicitAny`オプションありでコンパイルが通らないようなコード**は、**敗北者のTypeScript**であることを免れません。

そろそろ顔が赤く染まってきた読者も多いと思うので再度述べておきますが、**敗北者のTypeScriptを書くなと言っているわけではありません**。それでも**TypeScriptを使わないよりはずっと安全です**。

ただ、ここでお伝えしたいのは、`--noImplicitAny`を使わなくてもいいという主張は「**`--noImplicitAny`を使わないで達成できる程度の安全性で満足できるなら**」という枕詞が付きます。`any`が自然発生する状況では、例えば関数の引数の型を書き忘れるという単純なミスが、単なるコンパイルエラーでは済まずにランタイムエラー、壊れたロジック、その他重大なバグに繋がる可能性があります。`--noImplicitAny`を使わないというのは、このように本来TypeScriptがコンパイルエラーという形で保証してくれる安全性を放棄するという明示的な意思表示に他なりません[^explicit_any]。TypeScriptの安全性をどの程度享受し、そしてどの程度捨てるのかはプロジェクトが選択できることであり、TypeScriptの使われ方というのはその選択によって大きく異なったものとなります[^ide_support]。

[^explicit_any]: 一応述べておきますが、なるほど関数引数を省略してはいけないんだなと思って全部の引数に`any`と書いたとしても、何も状況は変わりません。`--noImplicitAny`はミスによる`any`の発生を防いでくれる有用なオプションですが、`any`という白旗を自ら上げるならば意味はありません。

[^ide_support]: 例えば、TypeScriptはその型情報によってエディタやIDEによる強力なサポートを可能にしています。これによる開発の効率化を主な目的とし、エラーを防ぐという安全性の方面は二の次ということもありえるでしょう。尤も、この場合でも型をちゃんと付ければ付けるほどIDEのサポートも良くなるのですが。

ですから、**`--noImplitiyAny`は使わなくてもいいという主張を常に真に受けるのではなく、自分たちがどの程度の安全性を達成したいのかと天秤にかけて判断する必要がある**のです。そして、自分たちの選択が自分たちにどの程度の安全性を齎し、また自分たちはどれくらい安全性を犠牲にしているのか、それを判断するのにこの記事は役に立つことでしょう。

この記事では以降も「〜すべき」などの言い回しを積極的に使いますが、それは「**敗北者にならないためには**」という前提の話です。ゆえに、あなたがどのレベルの安全性を求めるかによってはこの記事の主張は当てはまらないかもしれません。しかし、TypeScriptの真の力を知っていて敢えて使わないのか、それともそもそも真の力を知らないで使っているのかでは大違いです。では、次に進みましょう。

## `--strictNullChecks`を使わない場合の危険性

TypeScriptの他の代表的なコンパイルオプションとしては`--strictNullChecks`が挙げられます。これは`null`安全性（JavaScriptには`null`の他に`undefined`もあるのでそちらも）に関わるチェックを有効にするオプションです。

逆の言い方をすれば、**このオプションを指定しないとTypeScriptの`null`安全性が無に帰します**。こんなのオプションにしないで常に`null`安全にしろよとお思いだと思いますが、実は初期のTypeScriptには`null`安全性が無く、後から追加で実装されたためこのような形になっています。それまでTypeScriptでコンパイルが通ってきた非`null`安全なコードのビルドを壊さないためにデフォルトでオンにするのは避けられたのです。例えば、`--strictNullChecks`をオンにしないとこんなコードのコンパイルが通ります。

```ts
// fooは数値を受け取って文字列を返す関数
function foo(num: number) {
  return num.toFixed(3);
}

// fooにnullを渡すことができる！！！！！
foo(null);
```

このように、どんな型に対しても`null`（とか`undefined`）を渡すことができたのです。

`--strictNullChecks`オプションありならば、`null`は`number`型の値だと見なされなくなるのでこのコードはコンパイルエラーが発生します。嬉しいですね。

`--strictNullChecks`ありの状況では、`null`を扱う場面では明示的に**`null`型**を使用します。例えば、上記のコードを`foo`が`null`も受け取れるようにするには次のようにします。

```ts
// fooは数値またはnullを受け取って文字列を返す関数
function foo(num: number | null) {
  if (num == null) {
    // numがnullなら空文字列を返す
    return "";
  }
  // そうでなければnumはnumber型なので従来通り
  return num.toFixed(3);
}

// どちらもエラーが起きない
foo(null);
foo(123);
```

引数`num`の型を`number | null`としたことによって`num`が数値と`null`のどちらも受け取れるということを明示しました。また、コード中で`if (num == null)`という`null`チェックを行うことで`null`の処理を行います。TypeScriptはこのような処理を検知して、`num`がいつ`null`でありいつ`null`でないかを推論してくれます。その結果として、if文の次の`num.toFixed(3)`の部分では`num`が`null`である可能性は既に排除されていると判断されて`num`は`number`型となり、コンパイルエラーにならずに`toFixed`を呼び出すことができています。

この`number | null`は「`number`型または`null`型」ということを表す**union型**の記法です。TypeScript以外ではあまり見ないため戸惑う方もいるかもしれませんが、安全なTypeScriptプログラミングにおいては頻出の機能です。このように、TypeScriptの型の知識というのは安全にプログラムを書く能力に直結します。型の知識を付けるためには、例えば以下の記事がおすすめです（宣伝）。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)

## その他のコンパイルオプションたち

TypeScriptの安全性は日進月歩で改善されており、時折新しい安全性チェックが導入されます。`--strictNullChecks`と同様、それらは後方互換性を崩さないために原則としてコンパイルオプションの形で導入されます。その結果、これまで紹介した2つ以外にも安全性のチェックを強化するコンパイルオプションがいくつも提供しています。これらを有効にしないということはそれだけTypeScriptの安全性に穴を開けることになりますから、それらのコンパイルオプションは**当然全て有効にすべき**です。

そこで、これらのコンパイルオプション（前述の2つも含めて）をまとめて有効化できる素晴らしいオプションがあります。それが`--strict`です。ということは、**`--strict`オプションを有効化することが敗北者からの脱却の前提条件**です。TypeScript側としても`--strict`オプションの利用を推奨しており、`tsc --init`によって生成されるデフォルトの`tsconfig.json`ファイルは`--strict`オプションが有効化された状態となっています。

前述のような事情から、JavaScriptからTypeScriptへの移行案件の場合は`--strict`オプションをいきなり有効化するのは難しいでしょう。しかし、新規のTypeScript開発で`--strict`を有効にしないという選択はまず有り得ず、あったとすればそれは前述の**負け犬根性**の表れであると言わざるを得ません。敗北者のTypeScriptにすでに毒されています。[コードを書かないことがバグを生まない最善の方法である](https://github.com/kelseyhightower/nocode)ことは広く知られていますが、`--strict`を無効化するというのはまっさらの安全性100%の状態から**コードを書き始める前に安全性を50%くらいまで落とす**という行為に他ならないのです。

## `as`の危険性

`as`はTypeScriptに特有の、**型アサーション**を行うための構文です。これは、**型情報を強制的に修正する**ときに使います。まずは例を見てみましょう。

```ts
type MyObj1 = {
  id: number;
}
type MyObj2 = {
  id: number;
  name: string;
}
function foo(obj: MyObj1): string {
  // ↓これはエラー
  // return obj.name;

  // objはMyObj1型だけど無理やりMyObj2型に修正して利用
  return (obj as MyObj2).name;
}
```

関数`foo`の中で`as`が使われていますね。関数`foo`は`obj.name`を返したいのですが、`obj`は`MyObj1`型であり`name`プロパティが存在しないのでこれは型エラーとなります。そこで、エラーを回避するために一時的に`obj`の型を`MyObj2`型にしてしまうのが`(obj as MyObj2)`です。これにより`obj`は`MyObj2`型となり、`name`プロパティを持っていると見なされるので型エラーが消えます。

これのどこが危険かはお分かりですね。`foo({ id: 123 })`のように実際は`name`プロパティを持たないオブジェクトを`foo`に渡すと返り値は`undefined`です。これは、`foo`の返り値の型が`string`であることと合致していませんので、型情報と実行時の挙動が異なることになり実行時エラーに繋がります。

このように、`as`はTypeScriptに文句を言わせずに型に対して危険な修正を行うことができる（具体的には型をその部分型に修正できる）機能です。ですから、**正当な理由無く使うべきではありません**。

まあ実際のところ、`any`は絶対だめだけど`as`はまあ使ってもいいんじゃないという意見も見られます。それは、**TypeScriptの敗北**が発生したときに`as`を使って対応することが多いからです。というのも、TypeScriptの推論能力が足りないせいでコードに型エラーが発生する場合には、型が正しく推論されていない（実際のプログラムの動きと異なる・推論された型が実際に保証される条件よりも弱い）場合がほとんどです。この際に、`as`を用いて正しい型に修正することで型エラーをなくすことができます。

逆に、型は正しいのに、その使い方が間違っているために型エラーが出るということもあります。上の例はこちらのパターンです。この場合、型を間違ったものに修正してコンパイルエラーを消すために`as`を使うことができます。これは言うまでもなく**プログラマの敗北**です。

つまり、`as`を使いたい場合、それが**どちらの敗北なのか**を見極めるべきです。TypeScriptの敗北ならば`as`を使ってもよいですが、自分の敗北の場合に`as`を使うべきではありません。当然ながら、`as`を使うためには、どちらの敗北か見極められるだけのTypeScript力が求められるということです。

ちなみに、`as`は**実行時には何も行いません**。つまり、他の言語でいうキャストとは異なり、まずい型変換が行われたら実行時にエラーが出るわけではありません。ただ型チェック時に型が修正されるだけです。なので、型変換がまずいかもしれないので実行時にチェックしたいという場合は別途そのコードを書く必要があります。

### `as`を使ってもよい例

基本的に、`as`を使っていいのはあなたの書いたコードがTypeScriptの推論能力を凌駕したときです。ここではその一例を紹介します。

よくあるのは、オブジェクトをミュータブルなものとして扱う場面です。与えられたオブジェクトをコピーする関数を考えましょう。

```js
function shallowCopy(obj) {
  const result = {};
  for (const key in obj) {
    result[key] = obj[key];
  }
  return result;
}
```

素朴にTypeScriptで型をつけようとするとこうなるでしょう。

```ts
function shallowCopy<T extends object>(obj: T): T {
  const result = {};
  for (const key in obj) {
    result[key] = obj[key];
  }
  return result;
}
```

しかし、これはエラーとなります。その理由は、`const result = {}`によって`result`の型が`{}`と推論されるからです。これにより、`result[key] = obj[key]`は`result`に対して未知のプロパティを勝手に足そうとしているのでエラーになります。また、`return result;`は戻り値が`T`型なのに`{}`型の値を返そうとしているとしてエラーになります。

TypeScript側の主張も一理ありますね。実際、`const result = {};`の瞬間に`result`に入っているのは`{}`であり`result`はプロパティを何も持っていませんからこれの型は`{}`とせざるを得ません。

問題は、これを書いた人はこの関数内で`result`オブジェクトのプロパティがどんどん作られて最終的に`T`になることを意図しているのに、TypeScriptがそれを理解してくれないことです。実際のところ、コンパイラがこれを理解するのはかなり難しいでしょう。そこで、この場合は`as`を使ってエラーを消さなければなりません。

一番簡単なのは次のようにする方法です。

```ts
function shallowCopy<T extends object>(obj: T): T {
  const result = {} as T; // {} を {} as T に変更
  for (const key in obj) {
    result[key] = obj[key];
  }
  return result;
}
```

`as`を使うことで、`{}`の型を`{}`型ではなく`T`型だと思わせました。これにより変数`result`の型も`T`型になります。さっきの話からするとこれは嘘であり（変数`result`が作られた瞬間は`{}`が`T`型を満たすとは限らないため）、このコードは`as`によって型システムを欺く危険なコードとなっています。

この場合、**`as`による危険性が`shallowCopy`の中に閉じ込められている**のがポイントです。`shallowCopy`の内部でやっていることは危険ですが、`shallowCopy`のインターフェース（`T`型の引数を受け取って`T`型の値を返す）は正しいことは人間が注意深くチェックすれば確かめることができます。よって、`shallowCopy`を使う側は内部の`as`による危険性を気にせずにこの関数を使うことができます。

まとめると、`as`を正しく利用するためには以下のことに注意する必要があります。

- **TypeScriptの敗北を明らかにする**。この場合はTypeScriptが手続き的な操作でオブジェクトを作るコードを正しく型推論してくれないのが問題でした（やるのはかなり難しいでしょうから仕方ありませんが）。
- **危険性の影響範囲をできるだけ小さい関数に閉じ込める**。上の例で見たように、正しいインターフェースを持つ関数を定義し、`as`の危険性をその内部でしか露呈しないようにしましょう。これにより、危険性が影響する範囲を明確にして人間による安全性のチェックの負担を減らすことができます。また、危険な`as`を使う目的が明確になります。

## `!`の危険性

TypeScriptに独自の構文として後置の`!`が知られています。これは**値が`null`や`undefined`かもしれない可能性を無視する**という構文です。とりあえず例を見てください。

```ts
type MyObj = {
  name?: string;
}

function foo(obj: MyObj): string {
  // obj.nameの最初5文字を返す（存在しない可能性は無視）
  return obj.name!.slice(0, 5);
}
```

`obj.name!`というのは`obj.name`に後置の`!`がついた構文です。これにより、`obj.name`が存在しない可能性を無視しているのです。

というのも、`MyObj`型の宣言を見れば分かるとおり、`obj`の`name`プロパティは存在しないかもしれません。これにより、`obj.name`は`string | undefined`型（`string`型かもしれないし`undefined`型かもしれない値の型）となります。`undefined`に対して`slice`メソッドを呼んだら実行時エラーになってしまいますから、それを防ぐために当然TypeScriptは型エラーを出します。

ここで、**いやそんなの気にしないでいいからエラー出すなよ**と思った場合に`obj.name!`とすることで、これが`undefined`である可能性が排除されて`string`型として扱われます。それにより、型エラーを発生させずに`slice`を呼ぶことができるのです。

ちなみに`!`は`as`を使っても代用可能です。`as`を使う場合は次のように書けます。これは`obj.name`の型を`string | undefined`から`string`に修正するということですね。

```ts
type MyObj = {
  name?: string;
}

function foo(obj: MyObj): string {
  return (obj.name as string).slice(0, 5);
}
```

ということで、`!`の危険性は基本的に`as`のそれと同じです。`!`をいつ使えばいいかとかそういう話も、基本的に`as`と同じことが当てはまります。

一応言っておくと、上記の例は明らかに`!`の妥当な使い方ではありません。`foo`に渡される`obj`が必ず`name`を持っていることが分かっているならば`MyObj`を修正すべきですし、`name`を持っていないかもしれないなら`foo`の中の処理を修正すべきです。

### `!`を使ってもよい例

基本的に`!`に対する考え方は`as`と同じですが、わりと`!`を使いそうな妥当な場面というのはあります。

```ts
type MyObj = {
  name?: string;
}

function foo(obj1: MyObj, obj2: MyObj): string {
  if (obj1.name == null && obj2.name == null) {
    // 両方ともnameを持っていない場合の処理
    return "";
  }
  // （中略）
  if (obj1.name != null) {
    // obj1がnameを持っていた場合の処理
    return obj1.name;
  } else {
    // obj2がnameを持っていた場合の処理
    return obj2.name; // ←ここで型エラーが発生！
  }
}
```

関数`foo`は2つの`MyObj`型のオブジェクトを受け取り、それらが両方とも`name`を持っていなかったときの処理を最初に行います。ということは、`foo`の残りの部分では「`obj1.name`と`obj2.name`の少なくとも一方は存在する」という条件が満たされることになりますが、TypeScriptはこのような「どちらか一方が成り立つけどどちらなのかは不明」というような条件を理解できません[^sat_solver]。それにより、`if`文の`else`部でエラーとなっています。人間ならば、この位置では`obj1.name`が存在しないので`obj2.name`が存在するであろうことが理解できますが、TypeScriptはこれが理解できません。よって、この場合は`return obj2.name!;`としてもまあ許されるでしょう。

[^sat_solver]: これはまあTypeScriptの敗北なのですが、TypeScriptコンパイラがSATソルバになってしまっても困るので個人的にはこれは妥当な制約ではないかと思います。

なお、一応次のようにすることで`!`の使用を回避することはできます。

```ts
  if (obj1.name == null && obj2.name == null) {
    // 両方ともnameを持っていない場合の処理
    return "";
  }
  // （中略）
  if (obj1.name != null) {
    // obj1がnameを持っていた場合の処理
    return obj1.name;
  } else if (obj2.name != null) {
    // obj2がnameを持っていた場合の処理
    return obj2.name; // ←エラーが起こらない
  } else {
    // ここは絶対に通らないけどTypeScriptのために仕方なく書いた部分
    return "";
  }
```

これなら`!`を使わずにエラーが回避できますが、代わりに絶対に使われない無駄なコードが発生してしまいました。カバレッジが無駄に下がるといった問題点もありますからどちらも一長一短でしょう。

## その他の危険性

ここまでで紹介した`any`, `as`, `!`くらいがTypeScriptが持つ代表的な危険性です。言うまでもなく、これらはTypeScriptの安全性の限界とかではなく、我々を甘やかし負け犬奴隷とするためにTypeScriptが用意した穴です。我々は厳しい自制心でもって、これらの利用を最低限に留める必要があります。

しかしながら、TypeScriptが持つ危険性というのはこれだけではありません。これまで見てきたような甘ったるいものではなく、単なる罠としか思えないものもあります。基本的にはこれらの危険性も、利便性と安全性のトレードオフにおいてTypeScriptの言語デザインとして利便性のほうを選択したことで起こっています。利便性のために存在している危険性であるということは、つい危険な要素を使ってしまいがちであるということです、それら全てに目を光らせるのは難易度が高いですが、できるだけこれを防ぐためにぜひ知っておくべきです。

これについては以下の記事によくまとまっています（筆者が書いた記事ではありません）。

- [TypeScriptのunsafeな操作まとめ](https://qiita.com/kgtkr/items/1c136e1e4ccee8928bc8)

## `is`の活用

TypeScriptが提供する危険性のひとつが`is`です。`is`自体も正しく使わないとTypeScriptの安全性を破壊してしまう危険性を持ちますが、むしろ`is`は前述のように「危険性の影響範囲を小さくする」という目的で活用することができる諸刃の剣です。TypeScriptに勝利した機会に`is`を活用して危険性を最小に押し留めることができれば、勝利者の称号はあなたのものです。

では、まず`is`がどういう意味なのか見てみましょう。これは関数の返り値の型として使うことができる特殊な構文で、`引数名 is 型`という構文を持ちます。

```ts
function isStringArray(obj: unknown): obj is Array<string> {
  return Array.isArray(obj) && obj.every(value => typeof value === "string");
}

function foo(obj: unknown) {
  if (isStringArray(obj)) {
    // この中ではobjはArray<string>型になる
    obj.push("abcde");
  } 
}
```

この例では`isStringArray`の返り値の型が`obj is Array<string>`です。このような型宣言を持つ関数は、返り値が真偽値でなければなりません。そしてこれは、返り値が`true`のとき`obj`の型は`Array<string>`とみなして良いという宣言になります。

よって、関数`foo`の中の`if`文で`isStringArray(obj)`が真かどうか判断したことによって、その中では`obj`を`Array<string>`型の変数として使うことができるのです。

ちなみに、ここで登場している`unknown`という型は「どんな値でもよい」ことを表す型で、一見`any`と似ていますが`any`のような危険性を持たず安全に使える型です。引数の型を`unknown`型にするということは、どんな引数でも受け付ける（オブジェクトだろうと`null`だろうと何でも）ということを意味します。ですから、そのような引数を使いたい場合は`if`文などで型の絞り込みを行わければいけません。そのような場合に`is`が役に立ちます。

`is`の危険性は、その関数が誤った判断を行えば間違った型がついてしまう点にあります。上記の関数を次のように変えると`isStringArray`関数は嘘をついていることになりますが、TypeScriptは怒ってくれません。`is`を使う関数の中身は危険性の塊であり、嘘をつかないことはプログラマの責任なのです。

```ts
function isStringArray(obj: unknown): obj is Array<string> {
  return Array.isArray(obj) && obj.every(value => typeof value === "number");
}
```

#### 余談： `unknown`と`as`

実は、ここで出てきた`unknown`と`as`を併用することで`any`の必要性をほぼ消すことができます。
`as`は型を強制的に変えますが、全く無関係の型に変えることはできません。例えば`"foo" as number`のように文字列をそれとは無関係の数値型に変えることはできないのです（ここで無関係というのは、どちらの方向の部分型関係にもないことを意味します）。ただ、`unknown`を間に挟んで`"foo" as unknown as number`とすれば通ります。`unknown`を経由することで型を任意に変える（繰り返しますが、ランタイムには何も起こりません。あくまでTypeScriptを騙すだけです）ことができるのです。これができればだいたい`any`でやりたいことを達成できるため、実は`as`を優先して使うようにすれば本当に`any`を使う場面というのはほぼありません。

### `is`の活用例

では、`is`に話を戻してもうちょっと実際にありそうな活用例をご紹介します。

```ts
type MyObj = {
  name?: string;
}

// 渡されたオブジェクトたちの`name`プロパティの配列を返す。
// ただし`name`が存在しないものは抜かす。
function allNames(objs: Array<MyObj>): Array<string> {
  return objs.map(obj => obj.name).filter(name => name != null);
}

// ["Tanaka", "Yamada"] と表示される
console.log(allNames([{name: "Tanaka"}, {}, {name: "Yamada"}]));
```

このコードは型エラーとなります。なぜなら、`objs.map(obj => obj.name).filter(name => name != null)`の型が`Array<string>`ではなく`Array<string | undefined>`と推論されるからです。まず`objs.map(obj ==> obj.name)`の型は`Array<string | undefined>`となります。これは想定通りですね。

次に`.filter(name => name != null)`で`undefined`を弾いているのだから、その結果は`Array<string>`となってくれるのが嬉しいですが、`filter`はそこまで賢い推論をしてくれません。

この問題に対するひとつの対処法は`as`を使うことです。次のように、`as`を使って`Array<string | undefined>`を`Array<string>`に強制的に修正するとエラーが消えます。

```ts
function allNames(objs: Array<MyObj>): Array<string> {
  return objs.map(obj => obj.name).filter(name => name != null) as Array<string>;
}
```

この方法の残念な点は、`as`の危険性が`allNames`全体に広がっている点です。実は、ここで`is`を使うともっと危険性を狭い範囲に押し込めることができます。そのためには次のようにします。

```ts
// 引数がnullでもundefinedでもないことをチェックする
function isNotNull<T>(x: T): x is Exclude<T, null | undefined> {
    return x != null;
}

// 渡されたオブジェクトたちの`name`プロパティの配列を返す。
// ただし`name`が存在しないものは抜かす。
function allNames(objs: Array<MyObj>): Array<string> {
  return objs.map(obj => obj.name).filter(isNotNull);
}
```

新しい関数`isNotNull`は`is`型を使用しています。これは、返り値が`true`なら`x`が`Exclude<T, null | undefined>`型であるという意味です。`T`というのは引数`x`の型であり、`Exclude<T, null | undefined>`というのはざっくり言えば「`T`から`null`と`undefined`の可能性を排除した型」です。例えば`T`が`string | undefined`の場合は`Exclude<T, null | undefined>`は`string`になります。

これを使う側は`.filter(isNotNull)`となりました。なんと、これでエラーが消えてしまいましたね。実は、`filter`は返り値が`is`型の関数を渡すとそれを理解して返り値の型を調整してくれるように定義されています。

今回のポイントは、従来は`allNames`の中全体に広がっていた危険性を`is`を使うことで`isNotNull`の中に押し込めることができた点です。`Array#filter`を使っていて何か思い通りにいかないなあと思ったときは`is`のことを思い出してあげると解決するかもしれません。

## TypeScriptの型を活用する

ところで、さっきの`allNames`はこんな書き方をすることもできます。

```ts
type MyObj = {
  name?: string;
}

// 渡されたオブジェクトたちの`name`プロパティの配列を返す。
// ただし`name`が存在しないものは抜かす。
function allNames(objs: Array<MyObj>): Array<string> {
  return objs.filter(obj => obj.name != null).map(obj => obj.name) as Array<string>;
}

// ["Tanaka", "Yamada"] と表示される
console.log(allNames([{name: "Tanaka"}, {}, {name: "Yamada"}]));
```

すなわち、`map`してから`filter`するのではなく`filter`してから`map`するようになりました。この場合もやはり`is`を使って`as`を消すことができます。

```ts
type MyObj = {
  name?: string;
}
type MyObjWithName {
  name: string;
}

function hasName(obj: MyObj): obj is MyObjWithName {
  return obj.name != null;
}
// 渡されたオブジェクトたちの`name`プロパティの配列を返す。
// ただし`name`が存在しないものは抜かす。
function allNames(objs: Array<MyObj>): Array<string> {
  return objs.filter(hasName).map(obj => obj.name) as Array<string>;
}
```

ここでは、`filter`によって`name`を持たないオブジェクトが消えるという意図を型で表現するために`MyObjWithName`という型を作りました。やりたいことが型で明確に表されていて悪くないですが、ひとつ問題があります。それは、`MyObj`と似た型である`MyObjWithName`をわざわざ再定義したくないということです。上の例ではまだいいですが、これが次のようになったらどうでしょうか。

```ts
type MyObj = {
  name?: string;
  phoneNumber: string;
  address1: string;
  address2: string;
  address3: string;
}
type MyObjWithName {
  name: string;
  phoneNumber: string;
  address1: string;
  address2: string;
  address3: string;
}
```

安全性のためとはいえこんなコードを書いてしまったらそれはそれで敗北という気がします。

実は、TypeScriptの強力な型システムを使えばこんなことをする必要はなくなります。この場合は以下のようにすればいいのです。

```ts
type MyObj = {
  name?: string;
  phoneNumber: string;
  address1: string;
  address2: string;
  address3: string;
}
type MyObjWithName = MyObj & { name: string; };
```

`MyObjWithName`型を`MyObj & { name: string; }`型として定義することができました。この`&`というのは交差型（intersection型）の構文であり、左右両方の型の条件を満たすような値の型を意味します。つまり、`MyObjWithName`型というのは、「`MyObj`型であり、しかも`name`プロパティが`string`型である」という条件を表す型になります。これはちょうど我々がやりたいことに合致していますね。

このように、TypeScriptは既存の型をちょっといじった新しい型を作るのが得意です。これにより、同じ定義を何度も書くのを防ぐことができます。ここで紹介した交差型はその中でも簡単な部類であり、他にもmapped typeはconditional typeなどを使いこなせば型の表現力はぐんと上がるでしょう。これらの型について知りたい場合は以下の記事がおすすめです（宣伝）。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)

余談ですが、よりエクストリームな型を使って`hasName`を`MyObj`専用ではなく一般化することもでき、こんな感じになります。

```ts
function hasName<T extends { name?: unknown; }>(obj: T): obj is (T extends { name?: infer U } ? T & { name: U } : never) {
  return obj.name != null;
}
```

TypeScriptに勝利するためにここまでやる必要があるケースはあまり無いでしょうが、できると格好いいですね。まずこんなコードを（もちろんコメントで説明は書くべきですが）受け入れられるチームを組むところから始めないといけませんが。

## まとめ

この記事では、TypeScript開発においてむやみに`any`や`as`などの危険な機能を濫用するコードを**敗北者のTypeScript**と呼びその問題点を指摘しました。

これらの危険な機能を使うのはどうしても使う必要がある場合（TypeScriptに**勝利**した場合）に留めなければならず、その必要性は細心の注意をもって検討しなければいけません。検討とは、それがTypeScriptの**敗北**であることを明らかにすることです。そのためにはTypeScriptの限界を完全に理解し、危険な機能を使わずに済ますことが不可能であることを示さなければいけません。その壁を乗り越えて危険な機能を使用する場合も、記事中で説明したように、その危険性を最小限の範囲に閉じ込めるようにしなければいけません。

三度繰り返しますが、**敗北者のTypeScriptも立派なTypeScript開発の一形態です**。この記事はちゃんとTypeScriptを使えないならTypeScriptやめろといった主張をしたいわけではありません。ただ、**あなた方は安全性を犠牲にしている**ということを伝えたいのです。

意図的に安全性を犠牲にしているなら、筆者はやめたほうがいいと思いますがとやかくは言いません。それよりも、自覚せずに安全性を破壊し、TypeScriptが提供してくれる安全性が**空想上の存在**となっているにも関わらずその存在を信じ続けているという事態は避けるべきです。

また、TypeScript信者の観点からは、**敗北者のTypeScript**を書いている人がTypeScriptの安全性がこの程度であると見誤ってしまい、それがTypeScriptの限界であると思われることは避けたいところです。これが、この記事を書いた動機の一つです。

高いレベルの安全性を誰もが必要とするわけではないとしても、TypeScriptは“正しく”利用することで高い安全性を発揮することができる言語です。TypeScriptの型システムがこんなにエクストリームなのも、JavaScriptに型を付けて安全にするという難題に対してTypeScript開発チームが取り組んだ結果なのです。

ですから、この記事を読んでTypeScriptに勝利しようと思う方が一人でも増えることを願っています。