TypeScriptの型安全を殺す!? 個人的アンチパターン🔥
投稿日: 2025年08月09日
パワー系エンジニア・TAの吉本です。
みなさん、TypeScriptの恩恵受けてますか??
私はTypeScriptが好きです。型で私を導き守ってくれる感じがします。(伝わりますか?w)
今仕事で扱うバックエンドはRailsですが暗闇で不安感すごくて、このメソッド何返すんだ、、?ここの宣言は何を意味するんだ、、?import(include)していないのにどこからともなくやってきたこいつ誰?typoに気づかないでテストで落ちた、、教えてよ、、、みたいな感じで不安すぎます。
フロントエンドはTypeScript(Next.js)でコードレビューすることが多いのですが、「これじゃTSの安心感が得られんやん!」ってコードを見かけるので、型安全の観点からやるべきでない私的アンチパターンを挙げます!
any宣言
型アサーションの乱用
オプショナルチェーンの乱用
これらについて書きます!
やめてください。
anyはなんでも受け入れる型です。
any型であればstringだろうとnumberだろうとnullだろうとneverだろうとunknowだろうとunionだろうと()=>voidだろうとなんでもOKになります。
これTypeScriptの意味ある??って疑問です。
基本ES Lintでanyを許容しないようにしているプロジェクトが多いのではないかと思いますが、ES Lintのエラーを回避するためのコメントをするとエラーは吐かなくなるので、それでCIすり抜けようとすることもできます。(実務で見たことがあるのです・・・)
が、基本それするのはやめてください。
多分根本的な原因が別にあり、そちらを修正するとanyは消せると思っています。
この話ちらっと師匠と話したとき「as anyが最強のやつ」って言われて吹きましたw
any使ったらもはやJS書いてると思ってください。本当に同じことだと思います。
anyやないとどうしようもないんよって時はその手前に多分改善の余地あります(知らんけど)。
それか、型引数使ってください。だいたいそれで解決します!
なんでanyがダメなのか下に例を書きます。
// ❌ アンチパターン
const data: any = fetchData();
console.log(data.hoge); // 実行時エラーの可能性大(nameがdataに本当にある??anyだからなんでもあり。)
// ✅ 良い例
type User = { name: string };
const data: User = fetchData();
console.log(data.name); // 型で守られる
console.log(data.id); //idは存在しないためエディター上でエラー吐く(型安全)
型アサーションの乱用も見たことあり、コンポーネントを共通化することが目的になっている時でした。
明らかに型が違うのに無理やり同じコンポーネントを使おうとしたことで型エラー⇨型アサーション!!みたいな感じでした。
これは型ガードすることで解決するかもです。
そのほうが安全です。
コンポーネントの分け方で解決する問題のこともあるし、ケースバイケースですが、私が出会った型アサーション乱用では「型が全く違うフォームやからコンポーネント共通化せんでいい!型アサーションだらけやめてくれ」って伝えました。
型アサーション使われているところは本当にエラー吐かないかわからないのでレビュワーとしてapproveするの不安です。
とはいえ仕方ないこともあります。
例えば、環境変数の取得はstring|undefined
なので絶対ある!ってas string
書いたりしますよね。
こんなやつです。
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL as string;
こうするのも意味的にはほぼ同じです。(undefinedではない絶対ある宣言なので)
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL!;
設定し忘れたりtypoしたりサーバーオンリーな環境変数をクライアントコンポーネントで参照しようとしたりするとアサーションしてても実際に実行されるとundefinedで、string前提で処理は進むため先でエラー!!!って全然あり得ますよね。
これが「絶対にこの型である」という手動の宣言的な型アサーションの、型安全ではなくなるデメリットです。
仕方ない場面はある。
でもほんとに仕方ない場面なのかは考えて使用することが大事だと思います。乱用はだめ絶対。
私のスクール卒業当初くらいのレベル感の頃だったと思うのですが、普通にやってて「これ使えば型エラー回避できるから使用する」くらいに本当に思ってましたw
オプショナルチェーンを使用するとオブジェクトの参照がnullやundefinedでもエラーを起こさずに参照できます。
つまり参照結果はundefinedの可能性が残るということになります。
それを意図するならいいのですが、こうしないとエラー吐くから書いとく〜て状態は危ないです。
undefinedならこう!って処理を書いておく必要がないか?
その先でundefinedでも問題ないのか?
undefinedで問題がある場合は、「その先に処理を進めない」「エラーをスローする」などする必要はないか?しっかり考えましょう!
例えば、早期リターンも型ガード的になります。
可読性も上がり、型安全になりいいことだらけです。(後続の処理により早期リターンできないパターンもあります。)
// ❌ アンチパターン(実際にこんなコード見たんですよ)
if (hoge !== null) {
setName(hoge?.name ?? "");
setValues(hoge?.values?.map(v => v ?? ""));
}
// ✅ 型安全 + ネストせず可読性UP
if (!hoge) return;
//hogeがnullの場合はここまで到達しないのでhoge.nameは絶対ある
setName(hoge.name);
setValues(hoge.values.map(v => v));
このアンチパターンは色々ツッコミどころあり仕事で書いてる人のコードではないだろうと思って結構ビックリしましたw
オプショナルチェーンを使用するのは、undefinedの可能性が残ってて問題ないときですので、ここは使いどきなのか?としっかり考えて使用すべきだと思います。
これ最近TAとしてスクール生さんへお伝えした内容なのでついでに共有です。
型定義で使用する「?:」これは値があってもなくてもいいという意味合いです。
// hoge(プロパティ)はあってもなくても良いが、あるなら必ずstring
hoge ?: string
// hogeは必ずあるが、値の型はstringかundefined
hoge : string | undefined
ここ混在しがちな気がするので抑えておくと良いと思います!
またパワー系寄りな内容ですが、実際見かけるので、やらないで!と伝えたくて書きました。
型エラーを出さないようにはできるけど型安全性は落ちるコードになっている状態よりは、理解して使って「意図がありこうしている」と意図を説明できるようになるべきだと思います。
型安全性で言うと「JSみたいなTS」を書くこともできるわけなので、そうなってないか自分のコードを常に疑うようにしたいものです。
TSを使ってる≠型安全なコードを書いてる
正しく使わないと意味がないです。
意味不明やわって方はDMください!わかるまで付き合います🙌⭐️