Item 24 일관성 있는 별칭 사용하기
Alias
const borough = { name: 'Brooklyn', location: [40.688, -73.979] }
const loc = borough.location
borough.location
배열에 loc
이라는 별칭(alias)를 만들었다.
별칭을 변경하면 원래 속성값에서도 변경된다.
> loc[0] = 0
> borough.location
[0, -73.979]
별칭을 남발해서 사용하면 제어 흐름을 분석하기 어렵다.
Alias and Redundant Codes
다각형을 나타내는 다음과 같은 자료구조가 있다고 가정한다.
interface Coordinate {
x: number
y: number
}
interface BoudingBox {
x: [number, number]
y: [number, number]
}
interface Polygon {
exterior: Coordinate[]
holes: Coordinate[][]
bbox?: BoundingBox
}
어떤 속성이 다각형에 포함되는지 체크하는 함수를 만든다.
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
if (polygon.bbox) {
if (pt.x < polygon.bbox.x[0] || pt.x > polygon.bbox.x[1] ||
pt.y < polygon.bbox.y[0] || pt.y > polygon.bbox.y[1]) {
return false
}
}
// ...
}
이 코드는 타입 체크를 포함해 잘 동작하지만 반복되는 부분이 존재한다. 특히 polygon.bbox는 3줄에 걸쳐 5번이나 등장한다. 임시 변수를 도입해 중복을 없애보자.
임시 변수 도입 - 별칭은 타입 체커에게 혼동을 야기한다
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox
if (polygon.box) {
if (pt.x < box.x[0] || pt.x > box.x[1] ||
// ~~~ ~~~ 객체가 'undefined'일 수 있습니다.
pt.y < box.y[0] || pt.y > box.y[1]) {
// ~~~ ~~~ 객체가 'undefined'일 수 있습니다.
return false
}
}
// ...
}
코드는 동작하지만 편집기에서 오류로 표시된다. 그 이유는 별칭을 만듦으로써 제어 흐름 분석을 방해했기 때문이다.
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox
if (box) { // 속성 체크에 box를 사용하도록 변경
if (pt.x < box.x[0] || pt.x > box.x[1] ||
pt.y < box.y[0] || pt.y > box.y[1]) {
return false
}
}
// ...
}
위처럼 "별칭은 일관성 있게 사용한다"는 규칙을 지켜 속성 체크에 box를 사용하면 오류를 없앨 수 있다.
객체 비구조화
하지만 코드를 읽는 사람에게는 문제가 남아있는데, box와 bbox는 같은 값인데 다른 이름을 사용한 것이다.
객체 비구조화(Destructuing Assignment)를 사용하면 간결한 문법으로 일관된 이름을 사용할 수 있다.
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const { bbox } = polygon
if (bbox) { // 속성 체크에 box를 사용하도록 변경
if (pt.x < bbox.x[0] || pt.x > bbox.x[1] ||
pt.y < bbox.y[0] || pt.y > bbox.y[1]) {
return false
}
}
// ...
}
객체 비구조화를 사용할 땐 다음과 같은 두 가지를 주의하자.
전체
bbox
속성이 아니라x
와y
가 선택적 속성일 경우엔 속성 체크가 더 필요하다. 따라서 타입의 경계에null
값을 추가하는 것이 좋다. (Item 31)bbox
에는 선택적 속성이 적합했지만holes
는 그렇지 않다.holes
가 선택적이라면, 값이 없거나 빈 배열([]
)이었을 것이다. 차이가 없는데 이름을 구별한 것이다. 빈 배열은 'holes
없음'을 나타내는 좋은 방법이다.
별칭은 타입 체커뿐만 아니라 런타임에 혼동을 야기한다
const { bbox } = polygon
if (!bbox) {
calculatePolygonBbox(polygon) // polygon.bbox가 채워진다 - bbox는 not null
// 이제 polygon.bbox와 bbox는 다른 값을 참조한다.
}
제어 흐름 분석은 지역변수에는 꽤나 잘 동작하지만, 객체 속성에서는 주의해야 한다.
function fn(p: Polygon) { /* ... */ }
polygon.bbox // 타입이 BoudingBox | undefined
if (polygon.bbox) {
polygon.bbox // 타입이 BoudingBox
fn(polygon)
polygon.bbox // 타입이 BoudingBox
}
fn(polygon)
호출은 polygon.bbox
를 제거할 가능성이 있으므로 타입을 BoundingBox | undefined
로 되돌리는 것이 안전할 것이다. 그러나 함수를 호출할 때마다 속성 체크를 반복해야 하기 때문에 좋지 않다.
그래서 타입스크립트는 함수가 타입 정제를 무효화하지 않는다고 가정한다. 그러나 실제로는 무효화될 가능성이 있다.
polygon.bbox
로 사용하는 대신 bbox
지역 변수로 뽑아내서 사용하면 bbox
의 타입은 정확히 유지되지만, polygon.bbox
의 값과 같게 유지되지 않을 수 있다.
Summary
별칭은 타입스크립트가 타입을 좁히는 것을 방해한다. 따라서 변수에 별칭을 사용할 때는 일관되게 사용해야 한다.
비구조화 문법을 사용해 일관된 이름을 사용하는 것이 좋다.
함수 호출이 객체 속성의 타입 정제를 무효화할 수 있다는 점을 주의해야 한다. 속성보다 지역 변수를 사용하면 타입 정제를 믿을 수 있다.
Last updated
Was this helpful?