！をTypeScriptで使うことの是非について考えてみた

## はじめに
TypeScriptでは、変数の後ろに「!」を置くことで、その変数が`null`または`undefined`ではない、と宣言する記法があります。

これは**non-null assertion(非nullアサーション)** と呼ばれます。

一般的に非推奨とされていますが、用意されているからには使い所があるはずです。
どのような場面で使うべきなのかを考えます。

:::note warn
「!」を変数の後ろに置く記法として「definite assignment assertion（明確な割り当てアサーション）」もありますが、本記事ではこちらは扱いません。
:::

## non-null assertionとは
### 概要
`null`または`undefined`が設定される可能性のある変数に対して、末尾につける「！」がnon-null assertionと呼ばれます。

これはTypeScript独自の記法であり、JavaScriptには存在しません。
TypeScriptからJavaScriptに変換する際、「！」は削除されます。


### 使用例
non-null assertionは以下のような場合に使用します。
#### 未使用時の例
```typescript:未使用時の例
interface User {
  name: string;
  age: number;
  gender?: string;
}

const func = (user: User) => {
  const gender = user.gender.toUpperCase();
  console.log(gender);
};
```

任意項目である`gender`に対して、`toUpperCase`メソッドを呼び出しています。

VS Code上では以下のようにエラーが発生します。
<img width='80%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/37c180c4-3f3b-d11f-f2fc-e104f9b0c06e.png'>
この処理のように、`null`または`undefined`が設定される可能性のある変数に対して、何らかの処理を呼び出す場合、TypeScript上ではエラーになります。

13行目で確実に`gender`を設定していますが、コンパイルの際には値の設定有無の判定まではできません。

#### 使用時の例
この変数に対して「！」をつけることで、「確実に値が設定される」ということをassertion（主張/宣言）し、エラーを抑止するのがこの記法の役割です。
```typescript:使用時の例
interface User {
  name: string;
  age: number;
  gender?: string;
}

const func = (user: User) => {
  // genderの末尾に!を設定
  const gender = user.gender!.toUpperCase();
  console.log(gender);
};
```

以下のようにエラーが消えます。
<img width='80%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/0a170333-c623-20a2-0662-43d70f2b0526.png'>

しかし、この記法は値が入ることを保証しません。
あくまでも宣言であるので、実際にどうなるかは実装次第です。

現に、12行目では`gender`を設定していませんが、この時点ではエラーかどうかの判定はできません。
実行時にエラーとなります。

## non-null assertionの使い所
### 使うべきでないパターン
基本的に、多くの場合使用するべきではありません。
なぜなら、バグの温床となる可能性があるからです。

「多くの場合」というのは抽象的すぎるので、より具体的に表現すると、「TypeScriptの実装の範疇である場合」には使用するべきではないと考えています。

しかし、使うべきではないとはいえ、発生しているエラーは解消しなければなりません。
使用しない場合のエラー回避の方法を確認していきます。

#### エラーを回避する:型ガードでチェックする
一番手っ取り早いのはこの方法です。
```typescript
const func = (user: User) => {
  if (typeof user.gender === 'string') {
    const gender = user.gender.toUpperCase();
    console.log(gender);
  }
};
func({ name: "John", age: 28 });
```
`gender`の型は`string`と`undefined`のユニオン型なので、`string`型のときのみ処理を実行するようにします。

このように、条件分岐を書くことでエラーを回避するのが一番確実で早いです。

ほかにはオプショナルチェーンを使ったり、null合体演算子を使うのもよいかと思います。

#### エラーを回避する:型の設計を見直す
「!」は`null`または`undefined`でないことを宣言するものです。

裏を返せば、「！」を使っている状況というのは、確実に値が設定されていることがわかっているにも関わらず、変数の型に`null`または`undefined`が含まれている、ということかと思います。

値が設定されるとわかっているのであれば、そもそも元の変数の型を適切なものに見直すことで、「！」を使う状況自体をなくしてしまうのも方法の一つです。

### 使っても良いパターン
「TypeScriptの実装の範疇である場合」には使うべきではない、と書きました。
では、「実装の範疇外」であれば使っても良いことになりますが、これはどのようなパターンが考えられるでしょうか

### DOM要素の操作
HTMLはTypeScriptの実装とは別の部分になるので、値が存在するかどうかをTypeScriptで検知することはできません。

しかし、確実にHTML上に当該要素があると分かっている場合には、non-null assertionを使うことで、シンプルに実装することができます。

```typescript
const button = document.querySelector('#submit-button')!;
button.addEventListener('click', handleClick);
```
条件分岐を使って値の有無をチェックすることで、「！」の使用を回避することもできますが、冗長になる場合には、このように書くのが有効な場合もあります。


### 外部ライブラリとの連携
外部ライブラリの型定義が不完全で、実際には値が存在すると分かっている場合にも使ってよいかと思います。

```typescript
import { someFunction } from 'external-library';
const result = someFunction().someProperty!;
```

外部ライブラリの場合には、自身の実装の範疇を超えているので、このように使用することでシンプルに書くことができます。

## まとめ
自分が携わっている開発の現場で「！」をよく見かけるのですが、個人的にあまり好きではなく、どのような場合に使うべきなのかについて、考えてみました。

公式サイトにおいてnon-null assertionについて言及されている箇所が多くなく、またベストプラクティスも見つけられなかったので、不正確な部分もあるかもしれません。

ただ、やはり基本的には極力使うべきではなく、自分の実装が及ばない部分にのみ限定して使うべきなのでは、と考えています。