はじめに
私は教科書やチュートリアルで学ぶのが得意ではなく、たいてい途中で飽きてしまう。
これまではAIに助けてもらいながらなんとかコーディングを続けてきたが、最近になってその限界を強く感じている。特に、業務で毎日触れているTypeScriptを「なんとなく書ける」レベルのままにしておくことに不安を感じていた。
そんなときに思いついたのが「仮説駆動学習」だ。飽きっぽい私でも楽しみながら学べそうである。
この記事では、この学び方で、業務でも使っているTypeScriptを基礎から学び直す記録を残していく。
仮説駆動学習のマイルール
① Webで調べたりAIに聞いたりする前に、自分の頭だけで仮説を立てる
なぜ?:自分の理解の甘い部分が明確になるから
② 見栄を張らずに、その時点での仮説や理解をありのままに書く
なぜ?:こんなことも分からないのか!恥ずかしい!という感情がモチベーションに繋がるから
問い1
コードの品質を確認するために使用する以下のコマンドの違いは何?
npm run lint
npm run build
過去に調べたことがあるが、忘れてしまった。今日はこの問いと向きあう。
仮説
npm run lintの方が表面的、npm run buildの方が深く型チェックをしている気がする。npm run buildの方が時間かかるし。
残念な仮説しか出てこないので、もう一つ砕いた問い2を設定する。
問い2:それぞれのチェックで、何の問題が検出できるの?
仮説
| 仮説ID | 仮説内容 | 自信度 |
|---|---|---|
| 1 | 文法エラー(カンマ忘れ、タイポなど)は lint で検出されるのでは? |
低め |
| 2 | 型の整合性が取れない問題も lint で検出されるのでは? |
低め |
| 3 | もし文法エラーや型エラーを lint が見るなら、build は別の観点(例えば実行時を意識したより厳しいチェック?)で問題を検出しているのでは? |
不明 |
検証1:カンマ忘れ
カンマ忘れのコードに対してチェックを実施した。
const user = {
name: "Alice"
age: 30
};
結果
| チェック | 文法エラーを検出? | 詳細 |
|---|---|---|
| Lint (ESLint) | 検出した | ただし Parsing error として。ESLint のルールではなく、パーサーが構文解析に失敗した。 |
| Build (tsc) | 検出した | TS1005: ‘,’ expected として 5 つのエラーを検出。 |
何と、ESLintやBuildの前にパーサーというものがあるらしい。聞いたことある。
パーサーとは何か?
AIに聞いた。
パーサーとは、ソースコード(文字の列)を
「文法に沿って構造化されたデータ」に変換するプログラム
「構文木(AST: Abstract Syntax Tree)」という木構造型のデータに変換し、それをESLintなどで解析するらしい。
文法エラーは「Lintルール」で検出されるのではなく、Lint/Build どちらでも「パーサー段階」で弾かれる
検証2:タイポ
"message" → "mesage" にタイポ
const message = "Hello";
console.log(mesage);
結果
| チェック | タイポを検出? | 何を検出した? |
|---|---|---|
| Lint (ESLint) | 直接は検出しなかった! |
message が未使用、calculateTotal が未使用…という間接的な警告のみ。「mesage なんて知らない」とは言っていない。
|
| Build (tsc) | 検出した! |
TS2522: Cannot find name 'mesage'. Did you mean 'message'? と、正しい名前まで提案してくれた。 |
発見: タイポはLintルールではなく、TypeScriptの型チェック(Build/tsc)で検出される
検証3:型の不整合
numberにstringを代入
function multiply(a: number, b: number): number {
return a * b;
}
const result1 = multiply("hello", 5);
結果
| チェック | 型の不整合を検出? | 何を検出した? |
|---|---|---|
| Lint (ESLint) | 検出しなかった! | 変数が未使用という警告のみ。型エラーは一切指摘していない。 |
| Build (tsc) | 検出した! | TS2345: 引数の型が違う |
ESLintは型の不整合を検出しない。
TypeScriptの型チェックでは検出される。
検証4:ではESLintによって検出できるものは?
AIに書いてもらった。
// Promiseを放置している(エラーハンドリング忘れ) ---
// これは「型としては正しい」が「バグの温床」
async function fetchData(): Promise<string> {
return "data";
}
// このPromiseを await していない!エラーハンドリングもしていない!
// 型としては問題ないが、実行時に問題が起きうる
fetchData(); // ← これが問題
結果
| チェック | 検出した? | 詳細 |
|---|---|---|
| Lint (ESLint) | 検出した! | no-floating-promises: Promiseが放置されている |
| Build (tsc) | 検出しなかった! | Buildは成功。型としては完璧に正しいため |
問い1・問い2の検証結果まとめ
問い1:「npm run lint」と「npm run build」の違い
検証の結果、「どちらが深いか」というよりも、見ている観点そのものが違うことが分かった。
- 共通点として、どちらも「パーサー」による構文解析を前提としているため、カンマ忘れなどの文法エラーは両方で検出された。
- 一方で、タイポ(
mesage)や型の不整合(multiply("hello", 5))のような「型システムが解決すべき問題」は、npm run build(= TypeScript コンパイラ)側で検出され、npm run lintでは基本的に検出されなかった。
つまり、当初の
npm run lintの方が表面的、npm run buildの方が深く型チェックをしている
という仮説は、「型に関しては build が担当する」という意味では大筋合っていたが、「深さ」の違いというより 役割分担の違い と捉えた方がしっくりくる。
-
lint: 構文解析結果(AST)をもとに、コードの書き方やバグの温床になりそうなパターンをルールベースで検出する。 -
build: 型情報を元に、そもそも成立しないコード(存在しない変数・引数の型不一致など)をコンパイル時に弾く。
問い2:「それぞれのチェックで、何の問題が検出できるの?」
問い2で立てた仮説と、検証結果を対応づけると次のようになった。
-
仮説1「文法エラー(カンマ忘れ、タイポなど)は
lintで検出されるのでは?」- 検証結果:
- カンマ忘れは、Lint というより「パーサーの段階」でエラーになり、ESLint でも tsc でも検出された。
- つまり「Lint のルールがえらい」のではなく、「パーサーが構文を理解できない」ために両方で失敗している。
- 学び:文法エラーは Lint のお仕事ではなく、「構文解析の前提が崩れている状態」だと理解した方がよい。
- 検証結果:
-
仮説2「型の整合性が取れない問題も
lintで検出されるのでは?」- 検証結果:
- 型の不整合(
"hello"をnumberに渡す)は ESLint では検出されず、tsc によって検出された。
- 型の不整合(
- 学び:ESLint は基本的に「型の正しさ」までは見ていない。型エラーは TypeScript コンパイラの責務。
- 検証結果:
-
仮説3「文法エラーや型エラーを
lintが見るなら、buildは別の観点で問題を検出しているのでは?」- 検証結果:
- 実際には逆で、「文法エラー」「型エラー」は tsc が強く、Lint は「型としては正しいが、実行時にバグの温床になりそうなパターン」を検出する役割を持っていた。
- 例えば、
fetchData()の Promise を放置しているコードは tsc 的には問題ないが、ESLint のno-floating-promisesがしっかり検出してくれた。
- 学び:
buildが「より厳しいチェック」をしているというより、buildは型安全性、lintは品質やバグのにおいを見る、という住み分けだった。
- 検証結果:
まとめ
ここまでの検証を通して、
- 文法エラー → パーサー
- 型エラー → TypeScript コンパイラ(
npm run build) - 実行時に問題になりそうなコードパターン → ESLint(
npm run lint)
という三層構造でコードがチェックされている、という全体像が見えてきた。
おわりに
勉強になった!でもタイパが悪い。
この学習方法のいいところは残しつつ、効率を良くしていきたい。