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.
    • ํƒ€์ž…์ด ๋ณต์žกํ•ด์ง€๋Š” ๋งŒํผ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋„ ๋งŽ์•„์ ธ์•ผ ํ•œ๋‹ค.