2024-05-28.md

๐Ÿก

DIL: ์ดํŽ™ํ‹ฐ๋ธŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ

์Šคํ„ฐ๋””: ์›”๊ฐ„ CS, https://github.com/monthly-cs/2024-05-effective-typescript
์ž‘์„ฑ์ผ: 2024-05-28
์ž‘์„ฑ์ž: dusunax


์•„์ดํ…œ 31: ํƒ€์ž… ์ฃผ๋ณ€์— null ๊ฐ’ ๋ฐฐ์น˜ํ•˜๊ธฐ Push Null Values to the Perimeter of Your Types

์˜ˆ์‹œ: null ๊ด€๋ จ๋œ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„ ๊ฒฐ๊ณผ๊ฐ’ ๋ฆฌํ„ด

  • ๋ฌธ์ œ ์ฝ”๋“œ
// @strictNullChecks: false
function extent(nums: Iterable<number>) {
  // num๋ฐฐ์—ด์ด ๋น„์–ด์žˆ์„ ์ˆ˜ ์žˆ์Œ
  let min, max; // undefined, undefined
  for (const num of nums) {
    if (!min) {
      // falsy๊ฐ€ ์•„๋‹ˆ๋ผ undefined๋ฅผ ์ฒดํฌํ•ด์•ผ ํ•œ๋‹ค
      // => 0์ผ๋•Œ ๋ง์”Œ์›Œ์ง
      min = num;
      max = num;
    } else {
      min = Math.min(min, num);
      max = Math.max(max, num);
    }
  }
  return [min, max];
}
  • ๊ฐœ์„  ์ฝ”๋“œ
function extent(nums: Iterable<number>) {
  let minMax: [number, number] | null = null;
  // ์‚ฌ์šฉํ•˜๊ธฐ ๋” ์ˆ˜์›”ํ•œ ์ฝ”๋“œ
  // ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ null ๊ฐ’ ์‚ฌ์ด์˜ ๊ด€๊ณ„๋ฅผ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค (๋‘ ๊ฐ’์ด ๋‹ค ์กด์žฌํ•˜๊ฑฐ๋‚˜, null์ž„)
  // ๋‹จ์ผ ๊ฐ์ฒด๋กœ ์„ค๊ณ„๋ฅผ ๊ฐœ์„ ํ–ˆ๋‹ค
  // null์ฒดํฌ๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค
  for (const num of nums) {
    if (!minMax) { // null์ด๋ƒ!
      minMax = [num, num];
    } else {
      const [oldMin, oldMax] = minMax;
      minMax = [Math.min(num, oldMin), Math.max(num, oldMax)];
    }
  }
  return minMax;
}
...
const [min, max] = extent([0, 1, 2])!; // null ๋‹จ์–ธ ์ž์ œํ•˜์ž!
const span = max - min;  // ์ •์ƒ
...
const range = extent([0, 1, 2]);
if (range) { // null ์ฒดํฌ~
  const [min, max] = range;
  const span = max - min;  // ์ •์ƒ
}

null๊ณผ null์ด ์•„๋‹Œ ๊ฐ’์˜ ๋น„๋น”๋ฐฅ

  • ๋„คํŠธ์›Œํฌ ์š”์ฒญ์˜ ๊ฒฝ์šฐ?
    • ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋œ ๋‹ค์Œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค(๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•œ๋‹ค)
    • null ์ƒํƒœ์— ๋Œ€ํ•œ ๊ฒฝ์šฐ์˜ ์ˆ˜๊ฐ€ ๋งŽ๋‹ค๋ฉด?? => null ์ฒดํฌ ๋‚œ๋ฌด, ๋ฒ„๊ทธ ์–‘์‚ฐ
  • null์ธ ๊ฒฝ์šฐ๊ฐ€ ํ•„์š”ํ•œ ์†์„ฑ์€ Promise๋กœ ๋ฐ”๊พธ๋ฉด ์•ˆ๋œ๋‹ค
    • ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ , ๋ชจ๋“  ๋ฉ”์„œ๋“œ๊ฐ€ ๋น„๋™๊ธฐ๋กœ ๋ฐ”๋€Œ์–ด์•ผ ํ•œ๋‹ค
    • ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ•˜๊ฒŒ ํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ธฐ๋„ ํ•œ๋‹ค

์˜ˆ์‹œ

๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ

interface UserProfile {
  id: number;
  name: string;
  email: string | null; // ์ด๋ฉ”์ผ์ด null์ผ ์ˆ˜ ์žˆ์Œ
}

// ๐Ÿ“Œ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” async ํ•จ์ˆ˜
// Promise๋ฅผ ๋ฐ˜ํ™˜
async function getUserProfileAsync(userId: number): Promise<UserProfile> {
  const users: UserProfile[] = [
    { id: 1, name: "Alice", email: "alice@example.com" },
    { id: 2, name: "Bob", email: "bibim@example.com" },
    { id: 3, name: "Jack", email: null },
  ];

  return users.find((user) => user.id === userId); // undefined ์ธ ๊ฒƒ๋ถ€ํ„ฐ ์—๋Ÿฌ
  // {id: -1, name: null, email: null} ๊ฐ™์ด ์ดˆ๊ธฐ๊ฐ’ ๋งŒ๋“ค๋ฉด? ๋ณต์žกํ•ด์ง
  // `|| null`๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํžˆํ•˜๊ณ , ๊ฐœ๋ฐœ์ž์™€ ํƒ€์ž… ์ฒด์ปค๊ฐ€ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๋„๋ก ํ•œ๋‹ค.
}

(async function () {
  const userB = await getUserProfileAsync(3);
  if (userB?.email) {
    console.log(`User email: ${userB.email}`);
  } else {
    console.log("User email not available");
  }
})();

Things to Remember

  • Avoid designs in which one value being null or not null is implicitly related to another value being null or not null.
    • ํ•œ ๊ฐ’์˜ null ์—ฌ๋ถ€๊ฐ€ ๋‹ค๋ฅธ ๊ฐ’์˜ null ์—ฌ๋ถ€์— ์•”์‹œ์ ์œผ๋กœ ๊ด€๋ จ๋˜๋„๋ก ์ž‘์„ฑํ•˜์ง€ ๋ง์ž (์ฐ”๋ฆฌ๋Š” ์  ์žˆ์Œ)
  • Push null values to the perimeter of your API by making larger objects either null or fully non-null. This will make code clearer both for human readers and for the type checker.
    • API ์ž‘์„ฑ ์‹œ์—๋Š”, ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ํฐ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด ์ „์ฒด๊ฐ€ null์ด๊ฑฐ๋‚˜ null์ด ์•„๋‹ˆ๋„๋ก ์ž‘์„ฑํ•ด ๋ช…๋ฃŒํ•˜๊ฒŒ ํ•˜์ž.
  • Consider creating a fully non-null class and constructing it when all values are available.
    • ๋ชจ๋“  ๊ฐ’์ด ์ค€๋น„ ๋˜์—ˆ์„ ๋•Œ, ํด๋ž˜์Šค ์ƒ์„ฑํ•˜๋„๋ก ํ•˜์ž.
๐Ÿ“Œ Using fully non-null APIs and classes is preferable to using null values in them.
``