[이펙티브 타입스크립트] 아이템34 ~ 아이템37

Yongwoo Cho·2022년 6월 7일
0

TIL

목록 보기
84/98
post-thumbnail

[아이템34] 부정확한 타입보다는 미완성 타입을 사용하기

정확하게 타입을 모델링할 수 없다면, 부정확하게 모델링하지 말아야 한다. 또한 any와 unknown을 구별해서 사용해야 한다.

[아이템35] 데이터가 아닌, API와 명세를 보고 타입 만들기

$npm i --save-dev @types/geojson
import { Feature } from "geojson";

function calculateBox(f: Feature): BoundingBox | null {
  let box: BoundingBox | null = null;
  const helper = (coords: any[]) => {
    // ...
  };
  const { geometry } = f;
  if (geometry) helper(geometry.coordinates); // ❌ 'Geometry'형식에 'coordinates'속성이 없습니다.
}

geometry에 coordinates 속성이 있다고 가정한 것이 문제, GeoJSON은 GeometryCollection(coordinates 속성이 없음)일 수 있다.

✔️ 해결 방법

const geometryHeloper = (g: Geometry) => {
  if (geometry.type === "GeometryCollection") {
    geometry.geometries.forEach(geometryHelper);
  } else {
    helper(geometry.coordinates); // 정상
  }
};
const { geometry } = f;
if (geometry) geometryHelper(geometry);

명세를 기반으로 타입을 작성한다면 현재까지 경험한 데이터뿐만 아니라 데이터에 드러나지 않는 예외적인 경우와 사용 가능한 모든 값에 대해서 작동한다는 확신을 가질 수 있다.

[아이템36] 해당 분야의 용어로 타입 이름 짓기

동물들의 데이터베이스 구축

interface Animal {
  name: string;
  endangered: boolean;
  habitat: string;
}
const leopard: Animal = {
  name: "Snow Leopard",
  endangered: false,
  habitat: "tundra",
};

👎 문제점

  • name은 매우 일반적인 용어로 동물의 학명인지 일반적인 명칭인지 알 수 없음
  • endangered 속성이 멸종위기를 표현하는데 멸종된 동물이 true인지 false인지 판단이 힘듬
  • 서식지를 나타내는 habitat 속성이 너무 범위가 넓은 string 타입
  • 객체의 변수명이 leopard지만 name 속성의 값은 Snow Leopard로 의도가 불분명함

✔️ 개선 후 코드

interface Animal {
  commonName: string;
  genus: string;
  species: string;
  status: ConservationStatus;
  climates: KoppenClimate[];
}
type ConservationStatus = "EX" | "EW" | "CR" | "EN" | "VU";
type KoppenClimate = "Af" | "Am" | "As" | "Aw" | "BSh" | "BSk" | "BWh" | "BWk";

const snowLeopard: Animal = {
  commonName: "Snow Leopard",
  genus: "Panthera",
  species: "Uncia",
  status: "EW",
  climates: ["Af", "BSk"],
};

👍 개선점

  • name은 commonName, genus, species 등 구체적인 용어로 대체
  • endangered는 보호 등급 표준 분류 체계인 ConservationStatus 타입의 status로 변경
  • habitat은 기후를 뜻하는 climates로 변경

엄선된 타입, 속성, 변수의 이름은 의도를 명확히 하고 코드와 타입의 추상화 수준을 높여 준다.

✔️ 타입, 속성, 변수에 이름 붙힐 때 지켜야할 규칙

  • 동일한 의미를 나타낼 때는 같은 용어를 사용하기
  • data, info, item 같은 모호하고 의미 없는 이름은 피하기
  • 작명할 때는 포함된 내용이나 계산된 방식이 아니라 데이터 자체가 무엇인지 고려하기

[아이템37] 공식 명칭에는 상표를 붙이기

interface Vector2D {
  x: number;
  y: number;
}
function calculateNorm(p: Vector2D) {
  return math.sqrt(p.x * p.x + p.y * p.y);
}
calculateNorm({ x: 3, y: 4 }); // 정상, 5
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // 정상, 5

위 코드는 구조적 타이핑 관점에서는 문제가 없지만 수학적으로 따지면 2차원 벡터를 사용해야 이치에 맞는다. 3차원 벡터를 허용하지 않게 하려면 공식 명칭을 사용하면 된다. 공식 명칭 개념을 타입스크립트에서 흉내 내려면 '상표(brand)'를 붙이면 된다.

interface Vector2D {
  _brand: "2d";
  x: number;
  y: number;
}
function vec2D(x: number, y: number): Vector2D {
  return { x, y, _brand: "2d" };
}
function calculateNorm(p: Vector2D) {
  return math.sqrt(p.x * p.x + p.y * p.y);
}
calculateNorm(vec2D(3, 4)); // 정상, 5
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // ❌ '_brand'속성이 ... 형식에 없습니다

상표(_brand)를 사용해서 calculateNorm 함수가 vector2D 타입만 받는 것을 보장한다.

profile
Frontend 개발자입니다 😎

0개의 댓글