動的型付け言語しか触ったことない人が静的型付け言語を触ってみた感想

# はじめに
普段はPHPとJavaScriptを使った開発を行っているエンジニアです。

色々な企業の技術スタックを見ている時に、TypeScript、Java等の静的型付け言語を使っている企業が多いな〜と感じ、興味が湧いて使ってみました。

実際に触ったのはTypeScriptとJavaなのですが、これらを使ってみて感じた、静的型付け言語で型を定義できることのメリット・デメリットについて書いています。

記事内のコードの例にはJavaScriptとTypeScriptを使用しています。

# メリットに感じたところ
次の2点がメリットに感じました。
* 可読性が高くなる
* バグが減る

それぞれ説明していきます。

## 可読性が高くなる
これが一番良いなと思いました。

下記の2つの点に絞って可読性が高くなることを説明したいと思います。
* 関数の概要が掴める
* 文字列型か数値型かの見分けがつく

JavaScriptとTypeScriptの例を交えながら順に説明していきます。

## 関数の概要が掴める
型が定義されている関数は、**関数名**・**引数**に加えて**引数の型**・**返り値の型**を関数の先頭で確認できます。

関数を読み始める最初の段階で多くの情報を持つことができるため、どんな処理があるかを予測しやすいです。

予測したものを踏まえて答え合わせをしながら読み進める方が、情報が少ない中で漠然と読むよりも捗るように思います。


以下は簡単な例になります。
**JavaScriptの例**
```js
function calculate(a, b, operator) {
    switch (operator) {
        case 'add':
            return a + b
        case 'subtract':
            return a - b
        case 'multiply':
            return a * b
        case 'divide':
            return a / b
        default:
            throw new Error("Invalid operator. Use 'add', 'subtract', 'multiply', or 'divide'.")
    }
}
```
**TypeScriptの例**
```ts
function calculate( 
    a: number,
    b: number,
    operator: 'add' | 'subtract' | 'multiply' | 'divide'
): number {
    switch (operator) {
        case 'add':
            return a + b
        case 'subtract':
            return a - b
        case 'multiply':
            return a * b
        case 'divide':
            return a / b
        default:
            throw new Error("Invalid operator. Use 'add', 'subtract', 'multiply', or 'divide'.")
    }
}
```
上記では特に引数の`operator`の型を確認できれば、「これは四則演算だな？」となり、その時点で関数についてほぼ理解したようなものです。

この例の関数は少ない記述で簡単な処理であるためメリットを感じにくいかもしれませんが、関数が複雑になるほど威力を発揮するように思います。

## 文字列型か数値型かの見分けがつく
動的型付け言語を使っていると、`id`や`code`という変数の値が文字列型なのか数値型なのかがすぐに判断できず困ることがあります。

例えば次のような場合です。

**JavaScriptの例**
```javascript
function getUserById(id) {
    const users = [
        { id: 1, name: "田中 太郎" }, // ①idは数値型
        { id: 2, name: "山田 花子" },
        { id: 3, name: "鈴木 一郎" }
    ]
    return users.find(user => user.id === id) // ②厳密等価
}

console.log(getUserById('1')) // 実引数が文字列型
```
上記では関数の実引数が文字列型のため、undefinedが返されます。そしてこのようなことを無くすためには、少なくとも①、②を確認する必要があります。つまり関数の中を見ないと`id`という仮引数が文字列、数値型のどちらを前提としているのかわかりません。

ところがTypeScriptを利用して型を定義すると、③だけを見ればよいです。わざわざ関数の中を見る必要がないため、余計な手間を省けます。

**TypeScriptの例**
```typescript
function getUserById(id: number) // ③idは数値型
: { id: number, name: string } | undefined {
    const users = [
        { id: 1, name: "田中 太郎" },
        { id: 2, name: "山田 花子" },
        { id: 3, name: "鈴木 一郎" }
    ]
    
    return users.find(user => user.id === id);
}

console.log(getUserById(1))
```

## バグが減る
こちらも使ってすぐに感じたメリットになります。
以下が具体例です。

**JavaScriptの例**
```javascript
function getUserById(id) {
    const users = [
        { id: 1, name: "田中 太郎" },
        { id: 2, name: "山田 花子" },
        { id: 3, name: "鈴木 一郎" }
    ]
    return users.find(user => user.id === id)
}

const user = getUserById('1')
console.log(user.name) // エラーが発生する
```
上記のようなケースではエラーが出るのでまだマシですが、エラーが出ずに処理が進んでしまうような場合、バグの原因を探すのが難しくなり厄介です。

しかし、`TypeScript`だとコンパイルした時点でエラーが発生するため、バグを残しにくいです。

**TypeScriptの例**
```typescript
function getUserById(id: number): { id: number, name: string } | undefined {
    const users = [
        { id: 1, name: "田中 太郎" },
        { id: 2, name: "山田 花子" },
        { id: 3, name: "鈴木 一郎" }
    ]
    
    return users.find(user => user.id === id);
}

console.log(getUserById('1'))  // コンパイルエラーが発生する
console.log(user.name)
```
それとJavaScriptだと実行時までエラーに気づけないですが、TypeScriptだとコンパイル時に気付けるので、早い段階で対応できるのも良いですね。

# デメリットに感じだところ

* 学習コストが高い
* コードの量が多くなる

それぞれ説明していきます。

## 学習コストが高い
静的型付け言語では動的型付け言語で学習するようなことに加えて「型」や「コンパイル」についても学習する必要があります。そしてそれだけでも意外と覚えることが多いように感じます。

試しにChatGPTさんにTypeScriptの型関連のキーワードを出力してもらいましたが、それが下記になります。（実際に表示されたものの一部を抜粋しています）
1. Primitive Types (プリミティブ型)
1. Special Types (特殊な型)
1. Literal Types (リテラル型)
1. Intersection Types (インターセクション型)
1. Tuple Types (タプル型)
1. Array Types (配列型)
1. Object Types (オブジェクト型)
1. Function Types (関数型)
1. Type Aliases (型エイリアス)
1. Interfaces (インターフェース)
1. Enums (列挙型)
1. Classes (クラス)
1. Generics (ジェネリクス)
1. Type Assertions (型アサーション)
1. Mapped Types (マップド型)
1. Conditional Types (条件型)
1. Utility Types (ユーティリティ型)

上記の全てを理解する必要があるかはさておき、これらをある程度理解したり使いこなしたりする必要があると考えると、動的型付け言語より学習することが多くなることはわかっていただけると思います。

学習コスト高いと、その分扱える人も少ないと思いますので、エンジニア採用で困るかもしれません。また、社内に扱える人が少ない状態だと、新たに導入するハードルも高くなり、気軽には使えないと思います。

何にせよ学習コストが高いことに付随して、様々なデメリットが発生するように思えます。

## コードの量が多くなる
型を定義するので仕方ないですが、やはりコードの量は増えてしまいます。動的型付け言語に慣れた自分としても型を定義するのが少し面倒にも感じてしまいます。

やや大袈裟に書いているかもしれませんが、以下はコード量が増える例です。
**JavaScriptの例**
```javascript
const users = [
  { name: "田中 太郎", age: 25 },
  { name: "山田 花子", age: 30 }
]

function printUserNames(users) {
  users.forEach(user => {
    console.log(user.name)
  })
}

printUserNames(users)
```

**TypeScriptの例**
```typescript
type User = {
  name: string
  age: number
}

const users: User[] = [
  { name: "田中 太郎", age: 25 },
  { name: "山田 花子", age: 30 }
]

function printUserNames(users: User[]): void {
  users.forEach(user => {
    console.log(user.name)
  })
}

printUserNames(users)
```
少ない記述量ですが、コード量に差が出ることを実感していただけると思います。

当たり前ですがコードの量が増えると、その分書くのにも時間がかかるため、このデメリットを超えるメリットが無い場合は静的型付け言語を利用する必要がありません。

この辺りの判断は難しいと思いますが、小規模なシステムであったり、事前に保守・改修の頻度が低いとわかっているシステムにおいては、静的型付け言語を利用するとオーバースペックになる可能性がありそうです。

# 終わりに
最後まで読んでいただきありがとうございました。

静的型付け言語を利用すると可読性が高くなりバグも少なくなります。このような特徴から大規模なシステムや頻繁に保守・改修するようなシステムではより恩恵を受けられるように思います。

この記事に書いたメリット・デメリットは一般的に言われているようなことと差はなく、新鮮味には欠けるとは思います。ただ、具体的なコードの例等を見ることによって、よりそれを肌で感じていただけたかと思っています。