Categories

  • uuuuujin

4장 - 타입 코드 처리하기

4.1 간단한 if문 리팩터링

📌 if문에서 else를 사용하지 말 것

  • 사용자의 입력을 받거나 DB에서 값을 가져오는 등 애플리케이션 외부에서 입력을 받는 프로그램의 경계에서 발생하기 때문에 문제가 되지 않음.

    ➡️ 외부의 데이터 타입을 내부에서 제어 가능한 데이터 타입으로 매핑하기

  • 독립된 if문은 검사로 간주 & if-else 문은 의사결정으로 간주
  • enum대신 interface 사용하기 ➡️ 값들이 클래스로 바뀜



📌 클래스로 타입 코드 대체

  • 각 값에 속성을 추가하고 해당 특정 값과 관련된 기능을 특성에 맞게 만들 수 있음
  • 열거형에 새 값을 추가하는 것은 수많은 파일에 걸쳐서 해당 열거형과 연결된 로직들을 확인해야 함
  • 인터페이스를 구현한 새로운 클래스를 추가하는 것은 해당 클래스에 메서드의 구현이 필요할 뿐, 새로운 클래스를 사용하기 전까지 다른 코드를 수정하지 않아도 됨
  • 타입코드: 정수타입, 일치 비교 연산자 ===를 지원하는 모든 타입 (일반적으로 int와 enum 사용)
  • 타입코드를 보면 즉시 열거형으로 변환
// 변경 전
const SMALL = 33;
const MEDIUM = 37;
const LARGE = 42;

// 변경 후
enum TShirtSizes {
  SMALL = 33,
  MEDIUM = 37,
  LARGE = 42,
}


절차

  1. 임시 이름 가진 새로운 인터페이스 도입. 인터페이스에는 열거형의 각 값에 대한 메서드가 있어야 함.
  2. 열거형의 각 값에 해당하는 클래스 만들기. 클래스에 해당하는 메서드를 제외한 인터페이스의 모든 메서드는 false 반환.
  3. 열거형 이름 바꾸기. 컴파일러가 오류 낼 것.
  4. 타입을 임시 이름으로 변경하고 일치성 검사를 새로운 메서드로 대체.
  5. 남아있는 열거형 값에 대한 참조 대신 새로운 클래스를 인스턴스화하여 교체.
  6. 오류가 더이상 없으면 인터페이스의 이름을 모든 위치에서 바꾸기.


  • 모든 값에 대한 메서드를 갖는 것도 스멜이긴 함


🌈 리팩토링을 할 때 기존의 변수명이나 함수명을 그대로 두고 안에 내용만 바꿀 때가 있었는데 그러다보니 중간에 뭔가 꼬일 때가 종종 있었다. 책에서는 의도적으로 변수명을 기존의 것과 살짝 다르게 해놓음으로써 컴파일 에러를 내고, 그 에러가 없어질 때까지 하나씩 수정해 나가면서 “에러가 없으면 수정 완료 된 것” 이라는 결론으로 도달하는 과정이 신기했다.



📌 클래스로 코드 이관

  • 기능을 클래스로 옮기기 때문에 클래스로 타이 코드 대체 패턴의 자연스러운 연장선
  • 특정 값과 연결된 기능이 값에 해당하는 클래스로 이동하기 때문에 이는 불변속성을 지역화 하는 데 도움됨
  • 메서드 전체를 클래스로 옮긴다고 가정
  • is로 시작하는 메서드가 남아 있는 것이 스멜이다..!!!
class Red implements TrafficLight {
  isRed() {
    return true;
  }
  isYellow() {
    return false;
  }
  isGreen() {
    return false;
  }
  updateCarForLight() {
    // 1단계 - 소스 함수 복사해서 붙여 넣고 this로 대체
    if (this.isRed()) {
      car.stop();
    } else {
      car.drive();
    }

    // 2단계 - 클래스에 맞게 조건식들의 true, false 결정
    if (true) {
      car.stop();
    } else {
      car.drive();
    }

    // 3단계 - 미리 계산할 수 있는 계산 수행
    car.stop();
  }
}



📌 불필요한 메서드 인라인화

🌈 여기까지 책 읽으면서 느낀점:
하나 가르쳐줘서 공부했더니 “사실 그거는 이제 필요없고 이렇게 해야해” 이래서 또 공부했는데 “사실 그거는 또 이렇게 바뀐단다?” 하는 방식이 조금 지친다🥲
이런 리팩토링 기법을 처음 익히는 거니까 어쩔 수 없는 거겠지… 완전히 내 것으로 만드려면 반복해서 읽고, 코드를 쳐보는 수밖에 없다는 것을 다시 한 번 느끼게 되는 순간ㅠㅠ

  • 메서드에서 이를 호출하는 모든 곳으로 코드 옮기기 ➡️ 가독성 떨어지던 메서드 제거할 수 있음
  • 메서드의 인라인화 리팩토링 패턴과 메서드를 인라인으로 사용하는 것은 다름


메서드 인라인화를 하면 안되는 경우

  • 분기 없이 성능에 최적화되어 있는 경우
  • 메서드로 존재하는 것이 가독성에 도움됨
  • 인라인화 했을 때 ‘작업은 동일한 추상화 수준에 있어야 한다’에 반하는 스멜이 생기는 경우
// 인라인화 하면 안되는 경우
const NUMBER_BITS = 32;
function absolute(x: number) {
  return (x ^ (x >> (NUMBER_BITS - 1))) - (x >> (NUMBER_BITS - 1));
}



4.2 긴 if문의 리팩터링

📌 메서드 전문화


  • 너무 일반적인 함수는 유연성이 떨어지고 변경하기 어렵게 만듬
  • 메서드 전문화: 일반화를 줄이고 좀 더 특정화한 버전의 함수를 도입하는 과정
  • 일반화하고 재사용성을 높이면 책임이 흐려지고 다양한 위치에서 코드를 호출할 수 있어서 문제가 될 수 있음
  • 좀 더 전문화된 메서드는 더 적은 위치에서 호출되어 필요성이 없어지면 더 빨리 제거할 수 있음


🌈 내 생각

  • 일반적이면 유연성이 높은 것 아닌가..? 여기저기 쓰일 수 있으니까..? 아직 “유연성”을 제대로 이해하지 못하고 있는건가 😭
  • 바로 뒤에 “이 리팩터링은 프로그래머의 본능에 반하기에 좀 난해합니다” 라고 나오네 ㅋㅋㅋㅋ
  • 근데 remove를 removeLock1,2로 쪼개는 예제는 좀… 차라리 if문 조건을 map[y][x].isLock1() || map[y][x].isLock2() 이렇게 주면 안되나



🌈 생각해보기
“재사용성”은 무조건 높을수록 좋은 걸까??
메서드는 아니었지만…) 전에 사이드 플젝에서 모든 모달에서 사용할 수 있는 Modal 컴포넌트를 만든 적이 있음.
처음 몇 개는 괜찮았지만 점점 모달의 종류가 다양해지면서 props와 조건문이 많아졌고 사용하기가 불편해짐.
책에서 말하는 “일반화하고 재사용성을 높이면 책임이 흐려지고 다양한 위치에서 코드를 호출할 수 있어서 문제가 될 수 있음” 이 부분이 공감이 되면서도, 그럼 어느 정도의 재사용성을 가지는 것이 좋은지 고민이 됨.



📌 switch를 사용하지 말기

  • default 케이스가 없고 모든 case에 반환 값이 있는 경우가 아니라면 switch를 사용하지 말기
  • 사용하는 언어가 default의 생략을 허용하지 않으면 switch를 사용하지 말기
  • 타입스크립트에서는 컴파일러가 switch 문에서 모든 열거 값을 매핑했는지 확인할 수 있어서 switch문이 유용함



4.3 코드 중복 처리

📌 인터페이스에서만 상속받을 것

  • 인터페이스를 사용하면 이를 통해 도입한 각각의 새로운 클래스에 대해 개발자는 능동적으로 무엇인가 해야함
  • 따라서 잘못해서 속성을 잊어버리거나 해서는 안되는 오버라이드를 방지할 수 있음
  • 추상클래스를 사용하지 못하도록 인터페이스에서만 상속받아야 함


📌 코드 중복은 왜 안좋은가

  • 복제된 코드가 있고 한곳에서 변경하면 두 가지 다른 기능이 존재함 ➡️ 분기 조장하기 때문에 나쁨




4.4 복잡한 if 체인 구문 리팩터링

4.5 필요 없는 코드 제거하기