성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Typescript

더 나은 타입스크립트 작성하기 3 - readonly

제이콥(JACOB) 2020. 1. 1. 17:48

이번 글은 스킬이기보다는 타입스크립트 작성 시에 추천 사항이다.


readonly를 사용하자

타입스크립트로 프로젝트를 작성하더라도 readonly는 한 번도 작성을 안 하는 경우가 많다. 이유인즉슨 꼭 써야 되는 상황이 있는 것이 아니기 때문이다. 그렇다면 왜 쓰자고 권장하는 것일까. readonly는 Mutation(수정)으로 부터 발생할 수 있는 에러를 막아주기 때문이다. 예제를 통해 살펴보자.

 

Trianular numbers 예제

Trianular number(=triangle number)는 삼각형 Dot Pattern을 만들 수 있는 숫자를 의미한다.

https://www.mathsisfun.com/definitions/triangular-number.html

이제 수학 공부도 할겸 이거를 계산하는 것을 typescript를 이용해서 코드로 짰다고 가정해 보자.

function arraySum(arr: number[]) {
  let sum = 0;
  let num;

  while ((num = arr.pop()) !== undefined) {
    sum += num;
  }
  return sum;
}

function trianularNumber(n: number) {
  const nums = [];

  for (let i = 0; i <= n; i++) {
    nums.push(i);
  }
  return arraySum(nums);
}

console.log(trianularNumber(5)); // 15
console.log(trianularNumber(3)); // 6
console.log(trianularNumber(4)); // 10
console.log(trianularNumber(1)); // 1

결괏값이 너무 잘 나와서 만족했다.

그런데 문제의 요구사항이 바뀌어서 0~n까지의 trianularNumber를 콘솔로 보여달라고 했다. 결괏값도 잘 나왔으니 "for문에 콘솔만 찍으면 되겠지?" 하고 trianularNumber()의 for문 안에서 arraySum()함수의 콘솔을 찍었다. 

function trianularNumber(n: number) {
  const nums = [];

  for (let i = 0; i <= n; i++) {
    nums.push(i);
    console.log(arraySum(nums)); // 바뀐 부분
  }
}

trianularNumber(4);

콘솔에는 어떻게 나왔을까?

뭐지하고 코드를 다시 봤다면 문제점을 발견할 수 있을 것이다.

function arraySum(arr: number[]) {
  let sum = 0;
  let num;

  while ((num = arr.pop()) !== undefined) {
    sum += num;
  }
  return sum;
}

계속 배열을 값을 비우는 side effect를 만들고 있었던 것이다. 이것을 해결할 수 있는 가장 좋은 방법은 arraySum() 함수에서 배열을 수정하지 못하게 하는 것이다. 그것을 타임스크립트에서는 readonly가 도와준다.

number[] 타입 선언 앞에 readonly 키워드를 붙여주었더니, 바로 에러 표시가 나고 있다.

'readonly number[]' 형식에 'pop' 속성이 없습니다.

readonly 키워드를 붙여주면 해당 엘리먼트는 아래와 같은 특징이 생긴다. 

  • 읽을 수는 있지만 쓸 수는 없다.
  • length를 읽을 수는 있지만, 프로퍼티를 추가할 수 없다. 
  • mutate를 할 수 있는 모든 메서드들을 사용할 수 없다.

에러 표시 덕분에 어디가 잘못되었는지 파악했고 로직을 바로 수정할 수 있다.

function arraySum(arr: readonly number[]) {
  let sum = 0;

  for (const num of arr) {
    sum += num;
  }
  return sum;
}

function trianularNumber(n: number) {
  const nums = [];

  for (let i = 0; i <= n; i++) {
    nums.push(i);
    console.log(arraySum(nums)); // 바뀐 부분
  }
}

trianularNumber(4);

다시 실행해 보면 내가 원하는 결과가 나왔음을 확인할 수 있다. 여기서 중요한 점은 내가 프로퍼티를 수정할 마음이 없다면 가능한 readonly를 사용하는 것이 좋다는 것이다. 결과가 잘 나오기 때문에 그냥 넘어갔다면 저 side effect는 훗날 어떤 에러를 발생시킬지 모르고 저 위치에서 발생했다는 것도 찾기 힘들 것이다. 

 


참고

  • readonly는 array와 tuple literal에만 적용 가능하다. object에는 Readonly<>라는 동일한 기능을 하는 제네릭을 사용해 주면 된다.
  • readonly는 shallow하다.
type Person = {
  name: string;
  address: {
    address1: string;
    address2: string;
  };
};

const person: Readonly<Person> = {
  name: "jacob",
  address: {
    address1: "korea",
    address2: "seoul"
  }
};

// Cannot assign to 'address' because it is a read-only property.ts(2540)
person.address = { address1: "korea", address2: "Busan" };

person.address.address2 = "Busan";
console.log(person.address.address2); // Busan

 

반응형