4. 타입 코드 처리하기
4.1. 간단한 if 문 리팩터링
4.1.1. if문에서 else 문 사용하지말기
- if문은 검사(check)이고, if-else문은 의사결정(decision)으로 간주합니다. (캬.. else문을 쓰면 빠르게 "결정"되니까, 하드코딩느낌이고, 코드의 유연성이 떨어진다)
- if-else같은 동작은 컴파일때 빠르게 결정되는 이른바인딩 스멜. 별루다. if문을 수정해야 변경할 수 있기 때문에 전에 배운 추가에 의한 변경을 방해한다. 반면 늦은 바인딩은 추가에 의한 변경을 가능케 한다.
- 여기서 또 멋있는 말이 등장하는데, if는 조건연산자로 흐름을 제어하지만, 객체지향 프로그래밍에서는 객체라는 훨씬 더 강력한 제어 흐름 연산자가 있다!. 즉 if를 쓰지말고 객체를 쓰자는 말인데, 이게 무슨 말인고 하니...
인터페이스를 사용하는 두가지 다른 구현(클래스라던지)이 있는 경우, 인스턴스화하는 클래스에 따라 실행할 코드를 결정할 수 있따는 말. - 늦은 바인딩은 클래스로 타입코드대체(4.1.3)이나 전략패턴도입(5.4.2)이라는 리팩터링 패턴을 볼때 볼 수 있다.
4.1.2. if문의 열거형을 인터페이스로 바꾸기(살짝 이해안되는데? 왜바꾸지. if문도 그대로 있네)
- 열거형이 있다?(if문 조건들이겠찌?) 그럼 일단 인터페이스로 바꿔!. 그담에 구현체로 열거형 애들 하나씩 있다고 치고, 구현체에 따라 다르게 실행되게 하라는 뜻.
enum Input { UP, DOWN, LEFT, RIGHT }
- 이런 열거형 친구들 하나하나는 결국 if의 조건이라고 할수 있는데, 얘네를 인터페이스화 시켜보자.
아무튼 이런식으로 바꿔서, 열거형이 들어가는 모든곳에input.isRight()
식으로 집어넣던지,new Right()
이런식으로 세팅하던지가 가능하다. 코드가 너무 길어지는데...?interface input2{ isRight() : boolean; isLeft() : boolean; isUp() : boolean; isDown() : boolean; } class left implements Input2{ isRight(){ return false;} isLeft(){ return true;} isUp(){ return false;} isDown(){ return false;} } class right implements Input2{ isRight(){ return true;} isLeft(){ return false;} isUp(){ return false;} isDown(){ return false;} } class up implements Input2{ isRight(){ return true;} isLeft(){ return false;} isUp(){ return true;} isDown(){ return false;} } class down implements Input2{ isRight(){ return true;} isLeft(){ return false;} isUp(){ return false;} isDown(){ return true;} }
- 혹시 코드가 너무 길어진다고 생각했다면? 정상이라고 한다.. 5장에서 저 is메서드들을 잔뜩 혼내줄 예정이라고 한다. (혹시 그 유명한 전략 패턴의 등장?!)
4.1.3을 보면서 이 과정을 한번더 곱씹자.
4.1.3. 클래스로 타입 코드 대체(이것도 어렵네)
- 열거형에 값을 추가할때는 수많은 파일에 거쳐서 해당 열거형과 연결된 로직들을 확인해야 한다.
- 헌데 인터페이스를! 구현한 새로운 클래스를 추가하는 것은 해당클래스에 메서드 구현이 필요할 뿐이다.
- 타입 코드를 볼 때는 바로 열거형으로 바꾼뒤, 인터페이스로 바꾸는 리팩터링을 생각하자.
4.1.4. 클래스로 코드 이관하기
- 이제 마법이 일어납니다. function이 붙어있떤 함수에서, 메소드가 되어버립니다.
- 순서를 기재하자면.
- 함수를 메서드로 만든다.
- 메서드 선언을 인터페이스에 넣는다. 기존 메서드와 약간 다른 이름을 짓고, 매개변수는 의미가없어질경우 제거한다.
- 인터페이스를 구현한 구현체에서 알맞게 메서드를 변경한다.
- 메서드가 된 함수원형에, 메서드를 호출하도록 코드를 변경한다.
- 필자가 가장 좋아하는 리팩터링 패턴이라고 한다. 깐깐한 내가 봐도 이건 깔끔하다는 생각이 든다.4.1.5. 리팩터링패턴 : 클래스로 코드 이관.
- 위의 방식의 연장이다.4.1.6. 불필요한 메서드 인라인화.( <-> 메서드 추출과 정확히 반대)
- 리팩터링을하다보면, 가독성을 해치고 공간만 차지하는 메서드는 과감하게 인라인화 하자. 흔히 한줄짜리 메서드를 인라인화한다. 물론, 한줄 초과의 메서드도 가능하지만, 인라인화하기에 복잡하다던지, 동일한 추상화수준인지 생각해보자.4.1.7. 리팩터링패턴 : 메서드의 인라인화
- 다음 코드는 절댓값을 구하는 한줄짜리 메서드인데, 지금도 이해를 못했다. 이런건 인라인화 하지말자
const NUMBER_BITS = 32; function absolute(x: number){ reutrn( x ^ x >> NUMBER_BITS-1) - (x >> NUMBER_BITS-1); }
- 또, 입/출금시 동시에 값을변경해야된다면, 값의 update를 하나의 메소드로 만들지말고, 입출금을 하나의 메서드로 인라인화해서 만드는게 좋겠지?
4.2. 긴 if문의 리팩터링
- 리마인드 : if문은 함수의 시작에만 배치, else if 쓰지말기, 열거형 인터페이스/클래스로 바꾸기,
와근데 여기서
enum Tile{
AIR,
FLUX,
UNBREAKABLE,
PLAYER,
STONE, FALLING_STONE,
BOX, FALLING_BOX,
KEY1, LOCK1,
KEY2, LOCK2
}
이거 하나를 리팩터링을 위해가지고 엄청 쓰려니까 좀 힘들다...☆
4.2.1. 일반성 제거(말이 뭐이리 어려워 ㅋㅋ) -> 메서드 전문화
- 실제로 하는일에비해, 메서드 기능 정의는 범용적으로 되어있을때 일반적인 메서드라 함. 예를들어 특정 구현체에 대해서만 제거하는
remove(Tile) 함수가 있다고 생각해보자. 매개변수로 Tile 인터페이스 구현체들이 올 수 있지만, 실제로는 특정 구현체Lock만 오도록 쓰인다고 생각해보자.
이럴때 메서드 전문화를 한다.
4.2.2. 리팩터링 패턴 : 메서드의 전문화
function remove(tile: Tile){
for(let y = 0; y < map.length; y++){
for(let x = 0; x< map[y].length; x++){
if(map[y][x] === tile){
map[y][x] = new Air();
}
}
}
}
위 코드를 보자. 어차피 실제로 쓸때는 Tile에 Lock1,Lock2 구현(클래스)만 들어간다. 그래서 이걸 메서드 전문화를 통해 다음과 같이 바꿀 수 있다.
function removeLock1() {
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x].isLock1()) {
map[y][x] = new Air();
}
}
}
}
////
function removeLock2() {
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x].isLock2()) {
map[y][x] = new Air();
}
}
}
}
사실 메서드 하나를 메서드 2개로까지 바꿔가면서 전문화를 해야하나.. 싶긴하다.
암튼 너무 일반화하면 책임이 흐려지고 다양한 위치에서 코드를 호출할 수 있어 문제가 될 수 있다고한다.
메서드 전문화 과정을 순서대로 나열하고 넘어가자.
-
- 전문화하려는 메서드를 복제
-
- 메서드중 하나의 이름을 새로 사용할 메서드의 이름으로 변경, 전문화하려는 매개변수를 제거(또는 교체)합니다.
-
- 매개변수 제거에 따라 메서드를 수정해서 오류가 없도록 합니다.
-
- 이전의 호출을 새로운 것을 사용하도록 변경합니다.
4.2.3. 스위치가 허용되는 유일한 경우는??(궁금하네)
인덱스(배열)은 객체보다 직렬화하기 쉽다. 즉 논리적일수 있다. 전체 map을 변경 하느 대신 열거형 인덱스에서 새로운 클래스를 사용하도록 새로운 함수를 만드는것이 좋다. -> 이때 스위치가 허용된다는건가..?
deafault케이스가 없고, 모든 case에 반환 값이 있는 경우가 아니라면 switch를 사용하지말자.
4.2.4. 스위치를 사용하지말것(이랬다 저랬다좀 하지말지.)
4.2.5. if를 제거해보자.
4.3. 코드 중복 처리
4.3.1. 인터페이스말고 추상클래스는 왜 잘 안쓸까?
4.3.2. 인터페이스에서만 상속받을것.
4.3.3. 클래스에 있는 코드의 중복은 다 무엇일까
분기조장
4.4. 복잡한 if 체인 구문 리팩터링
4.5. 필요없는 코드 제거하기(너무 뻔한데?)
4.5.1. 리팩터링 패턴: 삭제 후 컴파일하기.
4장 요약
- 타입 코드 처리하기라고 쓰고 if문 혼내주기
- else 사용말자(분기가빨리결정되잖아)
- switch 사용말자(왜)
- 지나친 메서드 일반화 no. 메서드 전문화
- 인터페이스로만 상속받기(불필요한 긴밀한 커플링 방지)
- 리팩터링 후 리팩터링 : 메서드 인라인화 + 삭제후 컴파일하기.
P.S. 메소드 어디에쓰이는지 보려고 일부러 이름 슥 바꿔보는거 되게 꿀팁인듯.
'독서' 카테고리의 다른 글
책01. 파이브라인즈 오브 코드 06. 데이터보호 (0) | 2024.03.03 |
---|---|
책01. 파이브라인즈 오브 코드 05. 유사한 코드 융합하기 (1) | 2024.02.18 |
책01. 파이브라인즈 오브 코드 03. 긴코드 조각내기 (1) | 2024.02.01 |
책01. 파이브 라인즈 오브 코드 02. 리팩토링 깊게 들여다보기 (1) | 2024.01.31 |
책01. 파이브 라인즈 오브 코드 01. 리팩토링 리팩토링 (0) | 2024.01.31 |