2024-05-31.md
๐กDIL: ์ดํํฐ๋ธ ํ์ ์คํฌ๋ฆฝํธ
์คํฐ๋: ์๊ฐ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์์ฑ์ผ: 2024-05-31
์์ฑ์: dusunax
์์ดํ 34: ๋ถ์ ํํ ํ์ ๋ณด๋ค๋ ๋ฏธ์์ฑ์ ์ฌ์ฉํ๊ธฐ Prefer Imprecise Types to Inaccurate Types
- ํ์ ์ด ๊ตฌ์ฒด์ ์ผ ์๋ก, ๋ฒ๊ทธ๋ฅผ ๋ง์ด ์ก๊ณ ๋๊ตฌ๋ฅผ ํ์ฉํ ์ ์๋ค.
- ํ์ ์ ์ธ์ ์ ๋ฐ๋๋ฅผ ๋์ฌ์ผ ํฉ๋๋ค. ์๋ชป๋ ํ์ ์ ์๋ ๊ฒ๋ณด๋ค ๋ชปํ๋ค.
interface Point {
type: "Point";
coordinates: number[];
}
interface LineString {
type: "LineString";
coordinates: number[][];
}
interface Polygon {
type: "Polygon";
coordinates: number[][][];
}
type Geometry = Point | LineString | Polygon;
...
// โ๏ธ ํ์
์ ์ธ์ ๋ ์ธ๋ฐํ๊ฒ ๋ง๋ค๊ธฐ ์ํ ์๋
// ํ์ง๋ง Point์ ์๋ ๊ฒฝ๋๋ฅผ ์ ์ธํ ๊ณ ๋๊ฐ ์๊ฑฐ๋, ๋ค๋ฅธ ์ ๋ณด๊ฐ ์๋ค๋ฉด, ํ์
์ด ๋ถ์ ํํด์ ธ ๋น๋๋ฅผ ๊นจํธ๋ฆด ์ ์๋ค.
// ์ฌ์ฉ์๊ฐ ํ์
๋จ์ธ์ ํ๊ฑฐ๋, as any๋ก ํ์
์ฒด์ปค๋ฅผ ๋ฌด์ํด์ผ ํ๋ ์ํฉ์ด ์๊น
type GeoPosition = [number, number];
interface Point {
type: 'Point';
coordinates: GeoPosition;
}
์์: Mapbox ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ์ ๋ฐ๋ ์ ์งํ๋ฉด์ ์ค๋ฅ ์ก๊ธฐ
// โ๏ธ Mapbox ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์๋์ ๊ฐ์ ์์คํ
์ผ๋ก ์ง๋ ๊ธฐ๋ฅ์ ํํ๋ฅผ ๊ฒฐ์
["+", 1, 2];
[("/", 20, 2)];
[("case", [">", 20, 10], "red", "blue")];
[("rgb", 255, 0, 127)]; // 3 // 10 // "red" // #FF007F
// ๐พ ํ
์คํธ ์ผ์ด์ค!
const okExpressions = [
10,
"red",
["+", 10, 5],
["rgb", 255, 128, 64],
["case", [">", 20, 10], "red", "blue"],
];
const invalidExpressions = [
true, // (boolean ์๋จ) ~~~ Type 'boolean' is not assignable to type 'Expression2'
["**", 2, 31], // (์๋ FnName) Should be an error: no "**" function
["rgb", 255, 0, 127, 0], // (RGB ์์๊ฐ์ด 3๊ฐ์ฌ์ผํจ) Should be an error: too many values
["case", [">", 20, 10], "red", "blue", "green"], // (๋ถ๊ธฐ๊ฐ์ด 2๊ฐ์ฌ์ผ ํจ) Too many values
];
// ๋์์ ๋ชจ๋ธ๋งํ ์ ์๋ ์
๋ ฅ๊ฐ
// ๐ ์ ๋ถ ํต๊ณผ ๐
type Expression1 = any;
// ๐ ์๋ฌ ์ผ์ด์ค ์ค boolean๋ง ๊ฑฐ๋ฅผ ์ ์์
type Expression2 = number | string | any[];
const invalidExpressions: Expression2[] = [
true, // โ
["**", 2, 31],
["rgb", 255, 0, 127, 0],
["case", [">", 20, 10], "red", "blue", "green"],
];
// ๐ ์ ๋ฐ๋ ๋์ด๊ธฐ~!
type FnName = "+" | "-" | "*" | "/" | ">" | "<" | "case" | "rgb"; // ๋ฌธ์์ด ๋ฆฌํฐ๋ด์ ์ ๋์จ ์ฌ์ฉ
type CallExpression = [FnName, ...any[]];
// ์ฒซ๋ฒ์งธ expression์ FnName์ด๊ณ , ๋๋จธ์ง๋ Any์ผ ๊ฒ
type Expression3 = number | string | CallExpression;
const okExpressions: Expression3[] = [
10, // number!
"red", // string!
["+", 10, 5], // CallExpression!
["rgb", 255, 128, 64], // CallExpression!
["case", [">", 20, 10], "red", "blue"], // CallExpression!
];
const invalidExpressions: Expression3[] = [
true, // โ
["**", 2, 31], // โ
["rgb", 255, 0, 127, 0],
["case", [">", 20, 10], "red", "blue", "green"],
];
// ๐ ๋งค๊ฐ ๋ณ์ ๊ฐฏ์ ์ฒดํฌํ๊ธฐ
// - ์ฌ๊ท๋ก ๋ชจ๋ ํจ์ ํธ์ถ์ ํ์ธํ๋ค? nope
// - ์ธํฐํ์ด์ค ์ถ๊ฐํ๊ธฐ (์ฌ๋ฌ ์ธํฐํ์ด์ค๋ฅผ ํธ์ถ ํํ์์ผ๋ก ๋ฌถ์ ์ ์์)
// => type Expression4 = number | string | (MathCall | CaseCall | RGBCall) ์ด๋ฐ ๋๋์ผ๋ก ์๋๋ค๋ ๋ป
type Expression4 = number | string | CallExpression;
type CallExpression = MathCall | CaseCall | RGBCall; // ํจ์์ข
๋ฅ ์ ๋์จ
// MathCall ํ์
์ ์ฒซ ๋ฒ์งธ index๋ ๋ ๊ฐ์ ์ฐ์ฐํ๋ FnName์ด๋ค.
type MathCall = ["+" | "-" | "/" | "*" | ">" | "<", Expression4, Expression4];
// CaseCall์ Array likeํ ํ์
์ด๋ค.
// ์ฒซ ๋ฒ์งธ index๋ "case" ๋ฌธ์์ด์ด๊ณ , ํน์ ๊ธธ์ด์ ์ ๋์จ์ length๋ก ๊ฐ์ง๋ค.
interface CaseCall {
0: "case";
[n: number]: Expression4;
length: 4 | 6 | 8 | 10 | 12 | 14 | 16; // etc.
}
// RGBCall ํ์
์ ์ฒซ ๋ฒ์งธ index๋ "rgb"์ด๋ค.
// ํํ ํํ์ด๋ฉฐ, 3๊ฐ์ Expression4์ ํ์
์ ๊ฐ์ง๋ค.
type RGBCall = ["rgb", Expression4, Expression4, Expression4];
const invalidExpressions: Expression4[] = [
true, // โ
// ~~~ Type 'boolean' is not assignable to type 'Expression4'
["**", 2, 31], // โ
// ~~~~ Type '"**"' is not assignable to type '"+" | "-" | "/" | ...
["rgb", 255, 0, 127, 0], // โ
// ~ Type 'number' is not assignable to type 'undefined'.
["case", [">", 20, 10], "red", "blue", "green"], // โ
// Types of property 'length' are incompatible.
// Type '5' is not assignable to type '4 | 6 | 8 | 10 | 12 | 14 | 16'.
];
// ํ์
์ ๋ณด๊ฐ ์ ๋ฐํด์ก์
// ๋๋ถ์ ์๋ชป๋ ์ฝ๋์์ ์ ๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง๋ง, ํ์
์๋ฌ ์ ๋ฉ์์ง๊ฐ ๋ถ์ ํํ๋ค.
// ๋ ๊ตฌ์ฒด์ ์ด์ง๋ง, ์๋ ์์ฑ์ ๋ฐฉํดํ๋ฏ๋ก ๊ฐ๋ฐ ๊ฒฝํ์ ํด์น๊ฒ ๋๋ค.
ํ์ ์ ์ธ์ด ๋ณต์กํด์ง๋ฉด?
- ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง๋ค.
- ๋ถ์ ํํจ์ ํ์ ์ผ๋ก ๋ฐ๋ก์ก์ง ์๊ณ , ํ ์คํธ ์ธํธ๋ฅผ ์ถ๊ฐํ์.
- ์ ๋ฐํ ํ์ ์ ์ฝ๋์ ๋ฒ๊ทธ๋ก ์ด์ด์ง ์ ์๋ค(ํ๋ ์ ์์)
- ํ์
์ ์ ์ refineํ ๋, ๋ถ์พํ ๊ณจ์ง๊ธฐ ์์ ๋ฅผ ์๊ฐํ์?
- ๋ฌผ๋ก any๊ฐ์ ํ์ ์ ์ ์ ํ๊ธฐ
- ํ์ ์ ๊ตฌ์ฒด์ ์ผ๋ก ์ ์ ํ๋ค๊ณ ํด์? ์ ํ๋๊ฐ ๋น๋กํด์ ์ฌ๋ผ๊ฐ์ง ์๋ค.
- ํ์ ์ ์์กดํ๋ฉด ๋ถ์ ํํจ์ผ๋ก ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ ๋๋ฉ์ด
const moreOkExpressions: Expression4[] = [
["-", 12], // ๐พ ์
๋ ฅ์ ์์๋ก ๋ฐ๊ฟ์ฃผ๋ ๊ธฐ๋ฅ ์ฌ์ฉ ์ ์๋ชป๋ ์ค๋ฅ
// ~~~~~~ Type '["-", number]' is not assignable to type 'MathCall'.
// Source has 2 element(s) but target requires 3.
["+", 1, 2, 3], // ๐พ 2๊ฐ ์ด์์ ์๋ฅผ ์ฐ์ฐํ ๋ ์๋ชป๋ ์ค๋ฅ
// ~ Type 'number' is not assignable to type 'undefined'.
["*", 2, 3, 4],
// ~ Type 'number' is not assignable to type 'undefined'.
];
Things to Remember
- Avoid the uncanny valley of type safety: complex but inaccurate types are often worse than simpler, less precise types. If you cannot model a type accurately, do not model it inaccurately! Acknowledge the gaps using any or unknown.
- ํ์ ์ด ๋จ์ํ๊ณ ๋ถ๋ถ๋ช ํ ๊ฒ๋ณด๋ค, ๋ณต์กํ๋ฉด์ ์ ํํ์ง ์์ ๊ฒ์ด ํจ์ฌ ๋ ๋์๋ค.
- ์ ํํ๊ฒ ๋ชจ๋ธ๋งํ ์ ์๋ค๋ฉด? ๋ถ์ ํํ๊ฒ ํ์ง๋ ๋ง์. (any๋ unknown๊ณผ๋ ๋ ๋ค๋ฅธ ์ด์ผ๊ธฐ์)
- Pay attention to error messages and autocomplete as you make typings increasingly precise. It's not just about correctness: developer experience matters, too.
- ํ์ ์ ๋ณด๋ฅผ ๊ตฌ์ฒด์ ์ผ๋ก ์์ฑํ๋ ค ํ ์๋ก >>> ์๋ ์์ฑ๊ณผ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ฃผ์๊น๊ฒ ๋ณด์. ์ ํ๋ & DX ํฅ์!
- As your types grow more complex, your test suite for them should expand.
- ํ์ ์ด ๋ณต์กํด์ง๋ ๋งํผ ํ ์คํธ ์ผ์ด์ค๋ ๋ง์์ ธ์ผ ํ๋ค.