TypeScriptの危険性

## はじめに

筆者は現在、ESMベースの素のJavaScriptにJSDocで型情報を付与し、`tsc` によって型定義（`.d.ts`）のみを出力する構成で開発を行っています。

この構成は、静的解析や補完といった恩恵は享受しつつ、

- 視認性の良さ
- 型の複雑化
- 言語的な矛盾
- ビルドの煩雑さ
- チーム運用コストの肥大化

といったTSの構造的な問題を回避する、極めてバランスの取れた開発スタイルであると思い採用しています。

私も現場を指揮する上でTSへの移行についてはずっと考えてきましたが、次にTSで書きたいと言われた際には **「開発コスト食い潰すだけだからやめて」と切り捨てます。**  

> ＊ 採用の際にこの現場ではTSを使わない旨はしっかり伝えています。

本稿では、**現在もTypeScriptを信仰的に使用している開発者・チームに対し、10の視点から冷静かつ厳密に問題提起をしていきます**。

> ＊ あくまで ESM + JSDoc との比較です。

## 1. TypeScriptは「スーパーセット」であって「上位互換」ではない

TSを盲信している方々の言動を見ていると「TypeScriptはJavaScriptのスーパーセットである」という表現が、「上位互換」「より優れている」という誤解を招いているような気がしています。

実際には、

「JavaScriptの構文や動的特性に依存しながら、それを無理に静的型で包み込むという設計」

であり、**「静的型付け言語のように見えるが調和していない」ように見えます**。  

## 2. 型が「あること」と「信頼できること」は別

TypeScriptには、`any`, `unknown`, `as`, `@ts-ignore` といった**型システムの抜け道**が大量に存在します。  
言語仕様を考えると不可避な機能ですが、これらを許容しながら「型があるから安心」と語るのは、論理的に破綻してします。  

> 静的型はあくまで**補助的な安全性**であり、設計の不備や思考停止を防ぐものではありません。

## 3. TypeScriptの型システムは「後付け」であり、整合性に欠ける

JavaやSwiftのように、クラスベースで言語の設計段階から型を中心に組み上げられた静的型言語と異なり、TypeScriptはJavaScriptに**後付けで型を適用するアプローチ**を取っていることを忘れてはいけません。

その結果として、

- 実行時に型が存在しない（完全に消える）
- 構造的型（Structural Typing）による曖昧な一致
- 関数の返り値や構造が動的に変わる設計との乖離

が発生しやすく、**型が保証するはずの「正しさ」が成立しづらいです**。  

特に、「**JSライクにTSを書いている**」 のであれば、すぐに ESM + JSDoc の使用を検討した方が良いと思います。  
Objectに対して動的に型の違うキーを追加し、利用者がどこから引っ張れば良いか分からないような型引数を指定しないと扱えないような実装を公開することがどれだけレベルの低いことか理解しておきましょう。

**AWS Amplify** さん、私はあなたの defineBackend() 関数とどう向き合えば良いのでしょうか？  
Gen1で簡単にできたREST APIを追加する為のGen2の酷いドキュメントが [こちら](https://docs.amplify.aws/react/build-a-backend/add-aws-services/rest-api/set-up-rest-api/) です。

```typescript:amplify/backend.ts
import { defineBackend } from "@aws-amplify/backend";
import { Stack } from "aws-cdk-lib";
import {
  AuthorizationType,
  CognitoUserPoolsAuthorizer,
  Cors,
  LambdaIntegration,
  RestApi,
} from "aws-cdk-lib/aws-apigateway";
import { Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
import { myApiFunction } from "./functions/api-function/resource";
import { auth } from "./auth/resource";
import { data } from "./data/resource";

const backend = defineBackend({
  auth,
  data,
  myApiFunction,
});

// ..この後クソ長いCDKスタックを記述がある
```

これって普通に変数: backend を引数にした createApi(backend) 関数を api/resource.ts に定義して使えば api の定義をまとまられます。  
普通はそうしますが出来ない理由がこれです。

```typescript
const backend: Backend<{
    auth: ConstructFactory<BackendAuth>;
    data: ConstructFactory<AmplifyGraphqlApi>;
    myApiFunction: ConstructFactory<ResourceProvider<...> & ResourceAccessAcceptorFactory & AddEnvironmentFactory & StackProvider>;
}>
```

これが変数: backend にカーソルを合わせると出てくるヒントですが、そもそもキーが動的で、```type Backend<T>``` は ```@aws-amplify/backend``` から取り出せますが、その型引数はどこからどう引っ張れば取り出せるのか謎。

こう言う場合、createApi() 関数はこう書くしかありません。（CDKの内容なんて ```Backend<any>``` ではとても対応できません）

```typescript:amplify/api/resource.ts
import {backend} from '../backend'

export function createApi() {
  //...
}
```

はい、普通は引数で入れるものを export / import で直接持っていきました。

**もうTS終わってませんか？ 頭大丈夫ですか？**

そもそも論、AWSチームの AWS SDK も Amplify もどうして常に完全リニューアルしてしまうのでしょうか？  
Amplify Gen2 に至っては、完全に作りかけのモック段階なのにどうしてAWSコンソールをGen2という全く別物の仕様に作り変えてしまうのでしょう？  
信頼できるベンターとして論外ですし、普通にAWSを辞めるレベルの粗悪さです。

、、、話を戻しましょう。

## 4. 開発者の補完体験のために、プロジェクト全体が犠牲になっている

TypeScriptの最大の魅力は、補完、ジャンプ、インテリセンスといった**IDE体験の向上**ですね。

しかしその代償として、

- 複雑な型設計
- ビルドパイプラインの増加
- 型定義の保守コスト

が発生しており、**チーム全体の生産性や開発速度を確実に損なっていると思います**。

> テメーが気持ちよくなるために開発コストを何倍も注げる訳がありません。  
> そう言う場合は家で一人でデュフっていて下さい。

## 5. 型のための型、ユーティリティのためのユーティリティという本末転倒

TypeScriptでは、型を再利用・合成するために、さらに抽象化されたユーティリティ型が必要になる。

これにより、

- コードの視認性低下
- 実装とは直接関係のないコードが大量に生まれ
- 読みづらく、壊れやすく、テスト不能な構造

がプロジェクト内に蔓延する。

「型を守るために実装が犠牲になる」ような状態は、**本末転倒以外の何物でもない**。  

Amplifyの例もこれに当てはまりますし、AWS SDKも現在は（不要な）改善により少なくなっていますが、実装ファイル内に型の 定義/import/export が大量に書かれ、本質的な実装が1/10程度しか無いようなコードがありました。

とても見られるようなファイルではなく、私がTSを否定するようになったのはこのコードを見て確信を得たからです。

## 6. 「TSがあればスケールする」という幻想

TypeScriptの導入理由としてよく挙げられるのが、「大規模開発に耐えられる」というものです。  
まるでJavaでも使っているような言い方ですね。お笑いです。

確かに、型による静的検査は一定の安心材料になるります。  
しかし、実際に大規模開発において求められるのは、

- 明確な仕様と要件の定義
- 密結合の排除（ドメイン駆動設計（DDD）やモジュール設計の力）
- テスト戦略の充実
- 組織的なコードレビュー体制と運用ルール

といった、**言語に依存しない設計と運用の実力**の部分であり、全然素のnode.jsでも問題ありません。

---

### 📌 さらに深刻な問題：TypeScriptがシステムを硬直化させることすらある

TypeScriptは型の整合性を強く求めるがゆえに、ある段階を超えると**変更コストが異常に高くなる**傾向があります。  
3で言った、TSの言語仕様と型の不整合さも相まって、

- 型の再設計が必要になる
- 影響範囲が不明確になり、ビルドすら通らなくなる
- 型定義が他のモジュールに波及し、修正が伝播する

その結果、
> **「型があるから安心してスケールできる」はずが、いつの間にか「型のせいで一切変更が効かない」状態に陥ることがあるはずです。**

ここまで来ると、チームは「**一部を書き直すより、リプレイスした方が早い**」という選択を迫られます。  
実際に、TypeScriptによって柔軟性を失ったコードベースが、**長期的な保守困難性を生み、プロダクトの寿命を縮める例もあるようです**。

> 極端な笑い話と思うかもしれませんが、TSを採用することによってこうなる可能性がある限り、ESMを選択する判断は合理的であると思います。

### 💡 スケーラビリティの鍵は型ではなく設計

つまり、TSはスケーラビリティの「保証」ではなく「条件の一部」に過ぎません。  
むしろ導入と運用を誤れば、**型という名の足かせ**になり得ることを知ってもらいたいです。

> 「スケールする開発」と「TypeScriptを使うこと」はイコールではない。  
> 真のスケーラビリティは、設計力と運用体制に宿る。

## 7. tscの通過が安心材料になってしまう危険性

「tscが通っているから大丈夫」という認識は、開発チーム全体の**思考停止を招く最悪の兆候**であると思います。

実際には、

- テストがない
- 実行時の型保証がない
- バリデーションが不十分

といった問題を覆い隠し、**型だけで品質が担保されたかのような幻想を生みます**。  
特に「**型でバリデーションは出来ない**」と言うことは覚えておいてほしいものです。

### 👿 tsconfigの黒魔術

余談ですが、tsconfigの「なぜか分からんけどこれを追加すると問題が解決する」というような黒魔術にStackOverflowで何度か出会いました。地盤をコントロール出来ていないと言うのはかなり危ういよう思います。

ついでに、Deno ではTypeScriptがネイティブサポートされていますが、tsconfigは無く「Denoのビルドシステムに合わせて書いとけ」という設計。筆者にはこれが **TS軽視** に思えました。

## 8. 柔軟性を捨ててまで得られるものが少ない

TypeScriptを導入することで、JavaScript本来の持つ柔軟性——すぐに書けて、すぐに試せて、すぐに変えられるという特性——は大きく損なわれます。

結果として、

- 書けるはずのコードが「型的にNG」で書けない
- 小さな修正にも型定義の修正が必要

といった事態が発生し、**設計と変更に対するストレスが不自然に高くなります**。

## 9. 現場レベルでは高コスト構造になりやすい

TSは「正しく使えば」強力なツールであることは否定しません。  
しかしそれには、

- 適切な型設計の知識
- 組織全体での合意形成
- 一貫した設計ルール

といった**極めて高度な運用が前提**となります。（それこそJavaスタイルで書くような...）  
中途半端な理解と運用では、**むしろ開発速度と品質の両方が損なわれる**可能性あることを覚えておいて下さい。

> **TSを採用して得られるものとは一体何なのでしょうか？**

## 10. ESM + JSDoc + tsc はTypeScriptの理想を抽出した形である

筆者が採用している `ESM + JSDoc + tsc` という構成は、

- JSの柔軟性と実行速度を損なわず
- 補完・型チェックの恩恵だけを享受でき
- 型定義の配布（.d.ts）も可能

という点で、静的型付けの「良い部分」だけを抽出した理想的なアプローチなように思います。

TSの導入で苦しんでいるチームがあれば、**この構成は非常に有効な代替手段となるはずです**。

> JSDoc は TypeScript と比較すると簡易的な型システムです。  
> しかし、大抵の型は表現出来ますし、何よりAltJSでなく.jsファイル内のコメントで型を表現する手法が素晴らしいと考えています。

---

## 結論：TypeScriptを採用する際は慎重な判断が必要

TypeScriptは、静的型付けによる補完や型チェックの恩恵を提供する一方で、運用や設計において多くの課題を抱えています。特に、以下の点を考慮せずに導入することは、プロジェクト全体の柔軟性や生産性を損なうリスクを伴います。

1. **型の信頼性の限界**  
   TypeScriptの型システムは万能ではなく、`any` や `@ts-ignore` といった抜け道が存在するため、型があることが必ずしも安全性を保証するわけではありません。

2. **運用コストの増加**  
   型設計や保守にかかるコストが、プロジェクトの規模やチームのスキルセットに見合わない場合、かえって開発効率を低下させる可能性があります。

3. **柔軟性の喪失**  
   JavaScript本来の持つ柔軟性が制約され、小さな変更にも型定義の修正が必要になるなど、開発のスピード感が失われることがあります。

4. **設計力の重要性**  
   真にスケールする開発を実現するためには、言語に依存しない設計力や運用体制が不可欠であり、TypeScriptの導入がそれを補完するものではないことを認識する必要があります。

5. **TypeScriptに開発コストを割けるか**
   TypeScriptはSaaSなどWebの現場が多いと思いますが、開発スピード、プロダクトの増加、人員の確保など総合的に考えて本当に合理的な選択かを考える必要があります。

### 最後に

私のチームでは絶対に採用しませんが、TypeScriptを採用するかどうかは、プロジェクトの特性やチームの状況に応じて慎重に判断しましょう。  
脳死でJavaScriptを軽視し、TypeScriptは正義であるとは思わず、「**TypeScriptには一定のリスクが伴う**」ことを理解して頂きたいです。
今回の問題提起で、今一度 TypeScript について見直して頂けますと幸いです。