파이브 라인스 오브 코드
4. 타입 코드 처리하기
규칙: if 문에서 else 를 사용하지 말 것
-
프로그램에서 이해하지 못하는 타입인지를 검사하지 않는 한 if 문에서 else 를 사용하지 않는다.
-
if-else
를 사용하면 코드에서 결정이 내려지는 지점을 고정하게 된다. -
독립된 if 문은 검사로 간주하고, if-else 문은 의사 결정으로 간주한다.
// AS IS
function average(ar: number[]) {
if (size(ar) === 0) {
throw new Error("Cannot take average of empty array");
}
return sum(ar) / size(ar);
}
// TO BE
function average(ar: number[]) {
assertNotEmpty(ar);
return sum(ar) / size(ar);
}
function assertNotEmpty(ar: number[]) {
if (size(ar) === 0) {
throw new Error("Cannot take average of empty array");
}
}
스멜
- 이른 바인딩: if-else 같은 의사결정 동작은 컴파일 시 처리되어 애플리케이션에 고정되면 재컴파일 없이는 수정할 수 없다.
- 늦은 바인딩: 코드가 실행되는 순간 동작이 결정됨
의도
- if 는 흐름을 제어 -> 다음에 실행할 코드를 결정한다는 뜻
리펙터링: 클래스로 타입 코드 대체
- 열거형을 인터페이스로 변환하고 열거형의 값들을 클래스가 되도록 변환한다.
- 해당 리펙터링 패턴은 자체적으로 많은 가치를 가지지 않지만, 추후 개선을 가능하기 한다.
리펙터링: 클래스로 코드 이관하기
- 기능을 클래스로 옮기면서 클래스로 타입 코드 대체 패턴의 자연스러운 연장선
- 특정 값과 연결된 기능이 값에 해당하는 클래스로 이동하기 때문에 불변속성을 지역화
리펙터링: 메서드의 인라인화
-
프로그앰에서 더 이상 가독성에 도움이 되지 않는 메서드를 제거
-
단, 메서드를 인라인으,로 사용하는 것과는 다른 개념
-
메서드가 인라인화 하기 너무 복잡한가? -> 낮은 수준의 연산에 의존하여 가독성에 도움이 되는 경우에는 인라인화하지 않는다.
리펙터링: 메서드 전문화
- 일반화하고 재사용하려는 본능적인 욕구가 있지만 그렇게 하면 책임이 흐려지고, 다양한 위치에서 코드를 호출할 수 있기 때문에 문제가 될 수 있음
- 전문화된 메서드는 더 적은 위치에서 호출되어 필요성이 없어지면 더 쉽게 제거 가능함
규칙: switch 를 사용하지 말것
- default 케이스가 없고 모든 case 에 반환값이 존재하는 경우에는 switch 를 사용하지 않는다.
switch 의 문제점
- 컴파일러 입장에서 새로 추가한값의 처리에 대한 누락을 알 수 없다.
- break 키워드 누락으로 인한 버그
스멜
- switch 문은 값을 처리하는 방법에 초점, 클래스에 기능을 추가할때는 값이 상황을 처리하는 방법에 초점
- 컨텍스트에 초점을 맞춘다는 것은 불변속성을 전역화하는 것을 의미
의도
-
switch -> else if -> 클래스로 변환
-
인터페이스 대신 추상 클래스를 사용할 수 없을까?
- 사용할 수 있다. 코드의 중복을 피할 수 있다.
- 하지만 인터페이스를 사용하면 개발자가 능동적으로 무엇인가를 해야함. 즉, 누락으로 인한 오류를 방지할 수 있음
규칙: 인터페이스에서만 상속받을 것
-
상속은 오직 인터페이스를 통해서만 받는다.
-
추상 클래스를 사용하는 것은 일부 메서드의 기본 구현을 제공하고, 다른 메서드를 추상화 하기 위함
- 중복을 줄이고 코드의 줄을 줄이고자 할 경우 편리
-
하지만 추상 클래스의 코드 공유는 커플링을 유발하고, 기본 구현으로 인해 재정의가 필요한 메서드 인지 컴파일러를 통해 확인이 어려움
스멜
상속보다는 컴포지션이 더 좋다 <<GoF의 디자인 패턴>>
리팩터링: 삭제 후 컴파일하기
- 인터페이스에서 사용하지 않는 메서드를 제거하는 것
- 메서드를 삭제하고 컴파일러에서 허용하는지 확인하는 것