7. 코드를 오용하기 어렵게 만들라
1. 불변 객체로 만드는 것을 고려해보세요.
객체가 생성된 후 상태를 변경할 수 없으면 그 객체는 불변입니다. 불변성이 필요한 이유를 이해하기 위해서는 가변 객체가 어떤 문제를 일으킬 수 있는지 알아야 합니다. 가변 객체의 문제점은 다음과 같습니다.
- 가변 객체는 추론하기 어렵습니다.
- 불변 객체라면 여러 곳에서 전달해도, 객체가 변경되거나 무언가 추가되지 않았다는 것을 확실하게 알 수 있습니다.
- 가변 객체는 다중 스레드에서 문제가 발생할 수 있습니다.
- 한 스레드가 객체를 읽는 동안 다른 스레드가 그 객체를 수정하면 오류가 발생할 수 있습니다.
가변 객체는 코드의 복잡성을 높이고 문제를 일으킬 수 있기 때문에, 기본적으로는 불변 객체를 만들어 사용하는 것이 좋습니다. 하지만 필요한 경우에는 가변 객체를 사용할 수 있도록 해야 합니다.
가변 클래스는 오용하기 쉽습니다.
해당 인스턴스를 전달받는 모든 코드가 이 객체를 변경할 수 있고, 이로 인해 오용의 위험이 있습니다.
해결책 1) 객체를 생성할 때만 값을 할당하세요. 모든 값이 객체의 생성 시에 제공되고 그 이후로는 변경할 수 없도록 함으로써 클래스를 불변적으로 만들 수 있고 오용도 방지할 수 있습니다.
해결책 2) 불변성에 대한 디자인 패턴을 사용하세요. 빌더 패턴, 쓰기 시 복사 패턴 등이 있습니다.
2. 객체를 깊은 수준까지 불변적으로 만드는 것을 고려하세요.
깊은 가변성 때문에 클래스가 실수로 가변적이 될 수 있습니다. 이 문제는 멤버 변수 자체가 가변적인 유형이고 다른 코드가 멤버 변수에 액세스할 수 있는 경우에 발생할 수 있습니다.
깊은 가변성은 오용을 초래할 수 있습니다.
어떤 객체에 대한 참조를 클래스 외부에서도 가지고 있으면 깊은 가변성과 관련된 문제가 발생할 수 있습니다. JavaScript에서 배열이나 객체와 같은 자료구조들은 참조 값이므로 세터 함수를 제공하지 않더라도 멤버 변수의 참조에 접근해서 값을 변경할 수 있습니다. 이는 코드 오용이 쉬워집니다.
해결책) 방어적으로 복사하세요. 게터 함수를 통해 객체가 반환될 때 객체의 복사본을 반환함으로써 가능합니다.
방어적으로 복사하면 불변적인 클래스를 만드는 데 효과적이지만, 다음과 같은 단점도 명확합니다.
- 복사하는 데 비용이 많이 들 수 있습니다.
- 클래스 내부에서 발생하는 변경을 막아주지 못하는 경우가 많습니다.
해결책2) 불변적인 자료구조를 사용하세요. JavaScript에서는 Immutable 모듈이나 Immer를 사용할 수 있습니다.
3. 지나치게 일반적인 데이터 유형을 피하세요.
정수, 문자열, 리스트 같은 간단한 데이터 유형은 코드의 기본적인 구성 요소 중 하나입니다. 그러나 이러한 유형은 너무 일반적이기 때문에 데이터 유형 자체만으로는 무언가를 설명하기에는 충분하지 않을 수 있습니다.
정수나 리스트와 같은 유형으로만 표현한다고 해서 항상 좋은 방법은 아닙니다. 설명이 부족하고 허용 범위가 넓을수록 코드 오용이 쉬워집니다.
지나치게 일반적인 유형은 오용될 수 있습니다.
특정 정보를 완전히 표현하려면 종종 둘 이상의 값이 필요합니다. 예를 들어 2D 지도의 위치는 위도와 경도가 필요합니다. 단순히 배열로 관리한다면 너무 일반적이기 때문에 코드를 오용하기 쉽게 만드는 단점이 있습니다. 어떤 항목이 위도, 경도인지 혼동하기 쉽고 배열의 요소가 두 개만 존재한다는 보장이 없습니다.
해결책) 전용 유형을 사용하세요. 위의 지도 예시에서는 위치를 나타내는 전용 클래스를 정의하는 것이 좋습니다.
4. 시간 처리
정수로 시간을 나타내는 것은 문제가 될 수 있습니다.
시간을 나타낼 때 일반적으로 정수를 사용합니다. 이것은 어느 한 순간을 의미하는 시각과 시간의 양 두 가지를 모두 나타냅니다. 정수는 매우 일반적인 유형이기 때문에 시간을 나타내는 데 사용하는 경우 코드가 오용되기 쉽습니다.
해결책) 적절한 자료구조를 사용하세요. JavaScript에서는 내장 Date 객체를 사용할 수 있습니다. 그러나 부족한 경우에는 서드파티 라이브러리를 사용할 수 있습니다.
5. 데이터에 대한 진실의 원천은 하나여야 합니다.
데이터는 보통 두 가지 형태로 제공됩니다.
- 기본 데이터: 코드가 처리하기 위해 필요한 데이터입니다. 코드에 이 데이터를 제공하지 않으면 처리할 수 없습니다.
- 파생 데이터: 주어진 기본 데이터를 기반으로 코드가 계산하여 만들어낸 데이터입니다.
하지만 두 개 이상의 진실의 원천을 가지는 경우 문제가 발생할 수 있습니다. 예를 들어, 은행 계좌의 잔고는 대변과 차변 두 가지 데이터에 의해 결정됩니다. 하지만 대변이 5달러이고 차변이 2달러인데 잔액이 10달러라고 한다면 논리적으로 맞지 않습니다. 이러한 상황에서 코드의 오용이 발생할 수 있으므로, 기본 데이터를 유일한 진실의 원천으로 사용하는 것이 좋습니다. 대변과 차변만 전달하고 파생 데이터인 잔액은 그때 그때 계산하여 사용할 수 있습니다. 계산 비용이 많이 드는 경우에는 캐싱을 고려해볼 수도 있습니다.
6. 논리에 대한 진실의 원천은 하나여야 합니다.
논리에 대한 진실의 원천도 마찬가지입니다. 코드의 한 부분에서 수행되는 일이 다른 부분에서 수행되는 일과 일치해야 합니다. 중복된 논리가 있는 경우, 개발자가 하나만 수정하고 다른 하나를 수정하지 않아 문제가 발생할 수 있습니다. 따라서 논리에 대한 진실의 원천을 하나만 유지하는 것이 중요합니다.