if-eles 문은 타입을 체크를 하지 않는 이상 사용하는 것을 고려해야 합니다.
코드 스멜로 인식하는 이른 바인딩(early binding)이 있습니다. 즉 컴파일이 실행 된 후 결정되기 때문에 재실행 없이는 수정이 불가능합니다. if-else 문은 늦은 바인딩(late binding)에 해당이 됩니다. if문은 실행 시점에서 결정되기 때문에 오류를 감지가 가능합니다.
코드에서는 if-else 문을 사용해 의사결정을 뒤로 미루는 형태를 가지고 있습니다. 이를 객체지향적 설계로 제어 흐름 연산자로 리팩터링을 진행하겠습니다.
예제코드
enum TShirtSizes {
SMALL = 33, MEDIUM = 37, LARGE = 42,
}
const handleSelect = (tShirtSizes: TShirtSizes) => {
if (tShirtSizes === TShirtSizes.SMALL) {
return 33;
} else if (tShirtSizes === TShirtSizes.MEDIUM) {
return 37;
} else if (tShirtSizes === TShirtSizes.LARGE) {
return 42;
}
};
describe('shirtSize', () => {
context('셔츠 사이즈가 LARGE가 주어지면', () => {
it('42 Size를 반환해야 한다.', () => {
expect(handleSelect(TShirtSizes.LARGE)).toBe(42);
});
});
context('셔츠 사이즈가 MEDIUM가 주어지면', () => {
it('37 Size를 반환해야 한다.', () => {
expect(handleSelect(TShirtSizes.MEDIUM)).toBe(37);
});
});
context('셔츠 사이즈가 SMALL가 주어지면', () => {
it('33 Size를 반환해야 한다.', () => {
expect(handleSelect(TShirtSizes.SMALL)).toBe(33);
});
});
});
열거형)에 값을 interface(인터페이스)로 전환합니다.enum TShirtSizes {
SMALL = 33, MEDIUM = 37, LARGE = 42,
}
interface TShirtSizes2 {
isSmall(): boolean;
isMedium(): boolean;
isLarge(): boolean;
}
enum TShirtSizes {
SMALL = 33, MEDIUM = 37, LARGE = 42,
}
class Small implements TShirtSizes2 {
isSmall(): boolean {
return true;
}
isMedium(): boolean {
return false;
}
isLarge(): boolean {
return false;
}
}
class Medium implements TShirtSizes2 {
isSmall(): boolean {
return false;
}
isMedium(): boolean {
return true;
}
isLarge(): boolean {
return false;
}
}
...
handleSelect 함수 내 if 조건문 내에 매개변수를 새로운 매서드로 대체합니다.
const handleSelect = (tShirtSizes: TShirtSizes) => {
if (tShirtSizes === TShirtSizes.SMALL) {
return 33;
} if (tShirtSizes === TShirtSizes.MEDIUM) {
return 37;
} if (tShirtSizes === TShirtSizes.LARGE) {
return 42;
}
};
handleSelect 함수 매개변수를 TShirtSizes2 인터페이스로 변경합니다.
interface TShirtSizes2 {
isSmall(): boolean;
isMedium(): boolean;
isLarge(): boolean;
}
const handleSelect = (tShirtSizes: TShirtSizes2) => {
if (tShirtSizes.isSmall()) {
return 33;
} if (tShirtSizes.isMedium()) {
return 37;
} if (tShirtSizes.isLarge()) {
return 42;
}
};
tShirtSizes.isSmall() 값이 33이 반환 될 수 있는 이유는 조건문이 true 조건 때문입니다.
const handleSelect = (tShirtSizes: TShirtSizes2) => {
if (tShirtSizes.isSmall()) {
return 33;
} if (tShirtSizes.isMedium()) {
return 37;
} if (tShirtSizes.isLarge()) {
return 42;
}
};
class Large implements TShirtSizes2 {
isSmall(): boolean {
return false;
}
isMedium(): boolean {
return false;
}
isLarge(): boolean {
return true;
}
handleSelect(tShirtSizes: TShirtSizes2) {
if (tShirtSizes.isSmall()) {
return 33;
} if (tShirtSizes.isMedium()) {
return 37;
} if (tShirtSizes.isLarge()) {
return 42;
}
}
}
인터페이스에 handleSelect 메서드를 추가합니다.
interface TShirtSizes2 {
isSmall(): boolean;
isMedium(): boolean;
isLarge(): boolean;
handleSelect(): number;
}
! Tip
이때 동일한 속성이 할당 됩니다. 이때 handleSelect 함수 내에 매개 변수를 제거하고 변수를 this로 변환해줍니다. 각 클래스에 동일하게 변경해줍니다.
class Small implements TShirtSizes2 {
isSmall(): boolean {
return true;
}
isMedium(): boolean {
return false;
}
isLarge(): boolean {
return false;
}
handleSelect(): number {
if (this.isSmall()) {
return 33;
} if (this.isMedium()) {
return 37;
} if (this.isLarge()) {
return 42;
}
}
}
const handleSelect = (tShirtSizes: TShirtSizes2) => {
if (tShirtSizes.isSmall()) {
return 33;
} if (tShirtSizes.isMedium()) {
return 37;
} if (tShirtSizes.isLarge()) {
return 42;
}
};
const handleSelect = (tShirtSizes: TShirtSizes2) => tShirtSizes.handleSelect();
인터페이스 메서드로 호출이 완료가 되면 각 클래스의 해당 값만 뺀 나머지를 제거합니다.
class Large implements TShirtSizes2 {
isSmall(): boolean {
return false;
}
isMedium(): boolean {
return false;
}
isLarge(): boolean {
return true;
}
handleSelect() {
return 42;
}
}
class Small implements TShirtSizes2 {
isSmall(): boolean {
return true;
}
isMedium(): boolean {
return false;
}
isLarge(): boolean {
return false;
}
handleSelect(): number {
return 33;
}
}
class Medium implements TShirtSizes2 {
isSmall(): boolean {
return false;
}
isMedium(): boolean {
return true;
}
isLarge(): boolean {
return false;
}
handleSelect() {
return 37;
}
}
interface TShirtSizes2 {
isSmall(): boolean;
isMedium(): boolean;
isLarge(): boolean;
handleSelect(): number;
}
interface TShirtSizes2 {
handleSelect(): number;
}
class Small implements TShirtSizes2 {
handleSelect(): number {
return 33;
}
}
class Medium implements TShirtSizes2 {
handleSelect() {
return 37;
}
}
class Large implements TShirtSizes2 {
handleSelect() {
return 42;
}
}
클래스에 상속 된 인터페이스를 제거합니다.
class Small {
handleSelect(): number {
return 33;
}
}
class Medium {
handleSelect() {
return 37;
}
}
class Large {
handleSelect() {
return 42;
}
}
interface TShirtSizes {
handleSelect(): number;
}
class Small {
handleSelect(){
return 33;
}
}
class Medium {
handleSelect() {
return 37;
}
}
class Large {
handleSelect() {
return 42;
}
}
const handleSelect = (tShirtSizes: TShirtSizes) => tShirtSizes.handleSelect();
describe('shirtSize', () => {
context('셔츠 사이즈가 LARGE가 주어지면', () => {
it('42 Size를 반환해야 한다.', () => {
expect(handleSelect(new Large())).toBe(42);
});
});
context('셔츠 사이즈가 MEDIUM가 주어지면', () => {
it('37 Size를 반환해야 한다.', () => {
expect(handleSelect(new Medium())).toBe(37);
});
});
context('셔츠 사이즈가 SMALL가 주어지면', () => {
it('33 Size를 반환해야 한다.', () => {
expect(handleSelect(new Small())).toBe(33);
});
});
});
handleSelect 함수 내에 인터페이스 메서드를 통해 클래스에 구현 된 메서드의 값을 리턴합니다.
SeeAlso: