cleanCode/FiveLinesOfCode

[FiveLinesOfCode] 4장 타입코드 처리하기

Mo_bi!e 2023. 10. 8. 12:25

4장 : 타입코드 처리하기

의문점

  • enum 을 클래스로 굳이 다 나눠야함? → 복잡도에 따라서 선택!
    • 대출부분

학습목표

  1. if 문에서 else를 사용하지말것과 switch문을 사용하지 말것으로 ‘이른 바인딩’ 제거란?
  2. ‘클래스로 타입 코드 대체’, 그리고 ‘클래스로의 코드 이관’으로 if 문 제거하기란?
  3. 메서드 전문화’로 문제가 있는 일반성 제거하기란?
  4. 인터페이스에서만 상속’받을 것으로 코드 간 커플링(결합) 방지하기
  5. 메서드의 인라인화’ 및 ‘삭제 후 컴파일하기’를 통한 불필요한 메서드 제거

키워드

1. 간단한 if문 리팩터링

  • 4.1 규칙 : if문에서 else를 사용하지 말것

    • 정의

      • 프로그램에서 인식 하지 못하는 타입인지를 검사하지 않는 한, if문에서 else사용하지 말것
      • if-else 를 사용하면 코드에서 결정이 내려지는 지점을 고정하게 됨 → if-else 위치 이후 다른 변형 도입할 수없기 때문에 코드 유연성 저하 (아직 이해X)
      • if-else문은 하드코딩의 결정
      • 어떤 input 된 key를 검사할때 else if 체인구문 피할 수 없음
        • 하지만 보통은 사용자 입력, DB값 가져오는 등 app외부 입력받는 프로그램 경계에서 발생하기 때문에 문제시 되지않음
        • 이러한 외부 데이터 타입을 내부에서 제어가능한 데이터 타입으로 매핑하기
      • 독립된 if문은 검사(check)로 간주, if-else문은 의사결정(decision)으로 간주함
      • 이 규칙은 else문 찾기로 검증이 가능함
    • 스멜

      • 이른 바인딩 (early binding)
        • 컴파일 할때 if-else같은 의사결정 동작은 컴파일시 처리가 됨 → 어플리케이션에 고정 → 재컴파일 없이는 수정 불가
        • if 문을 수정해야 변경할 수 있기 때문에, 추가에 의한 변경을 방해함
      • 늦은 바인딩 (late binding)
        • 코드가 실행되는 순간에 동작이 결정됨
    • 의도

      • if문은 조건 연산자로 흐름을 제어함 → 다음 실행코드 결정
      • OOP의 객체는 더 강력한 제어흐름 연산자임
        • 인터페이스 사용한 두가지 이상의 구현이 있으면, 인스턴스화 하는 클래스에 따라 실행하는 코드 결정 가능
        • 본질적으로 이 규칙은 더 강력하고 유연한 도구인 객체를 사용하는 방안 찾게함
    • 실습

      enum Input {
      UP, DOWN, LEFT, RIGHT
      }
      
      interface Input2{
      isRight() : boolean;
      isLeft() : boolean;
      isUp() : boolean;
      isDown() : boolean;
      
      }
      
      class RIGHT implements Input2{
        isRight() {return true;}
        .
        .
        .
      }
      class LEFT implements Input2{
         .
        .
        .
        .
      }
      class UP implements Input2{
         .
        .
        .
        .
      }
      class DOWN implements Input2{
        .
        .
        .
        .
      }
      function handleInputs(){
      while (inputs.length > 0) {
        let current = inputs.pop();
        handleInput(current)
      }
      }
      
      function handleInput(current: Input){
      if (current === Input.LEFT)
        moveHorizontal(-1);
      else if (current === Input.RIGHT)
        moveHorizontal(1);
      else if (current === Input.UP)
        moveVertical(-1);
      else if (current === Input.DOWN)
        moveVertical(1);
      }
      
      // handleInput()-> 일치여부검사 변경
      function handleInput2(input: Input){
        if (input.isLeft())
          moveHorizontal(-1);
        else if (input.isRight())
          moveHorizontal(1);
        else if (input.isUp())
          moveVertical(-1);
        else if (input.isDown())
          moveVertical(1);
      }
      window.addEventListener("keydown", e => {
      if (e.key === LEFT_KEY || e.key === "a") inputs.push(Input.LEFT);
      else if (e.key === UP_KEY || e.key === "w") inputs.push(Input.UP);
      else if (e.key === RIGHT_KEY || e.key === "d") inputs.push(Input.RIGHT);
      else if (e.key === DOWN_KEY || e.key === "s") inputs.push(Input.DOWN);
      });
      
      // -> 변경으로 오류 수정
      window.addEventListener("keydown", e => {
      if (e.key === LEFT_KEY || e.key === "a") inputs.push(new LEFT());
      else if (e.key === UP_KEY || e.key === "w") inputs.push(new UP());
      else if (e.key === RIGHT_KEY || e.key === "d") inputs.push(new RIGHT());
      else if (e.key === DOWN_KEY || e.key === "s") inputs.push(new DOWN());
      });
  • 4.1 리팩터링 패턴 : 클래스로 타입코드 대체

    • 설명
      • 열거형 → 인터페스로 변환 / 열거형값 → 클래스
      • 클래스로 코드이관과 함꼐 사용 (추가를 통한 변경)
      • 열거형 값 → 클래스 변경시
        • 해당값과 관련된 기능을 그룹화 가능
        • 기능과 데이터 함께 제공 → 기능을 해당 값의 특징에 맞게 만들기 가능
        • 열거형에 값 추가할때 연결 로직 확인해야하지만,
          인터페이스를 구현한 새 클래스 추가는 해당 클래스에 메서드의 구현이 필요할 뿐, 새로운 클래스 사용하기 전까지 다른 코드 수정하지 않아도 됨
        • 티셔츠 크기의 타입코드
          → 단순 int 는 타입코드 사용 추적 어려움
          → 항상 즉시! 열거형으로
          → 클래스로 타입코드 대체 래팩터링
    • 절차
      1. 임시이름 인터페이스 도입 & 인터페이스에는 열거형(enum)의 각값에 대한 메서드 존재
        • e.g. : interface TrafficLight2{}
      2. 열거형의 각 값에 해당 클래스 만듬 / 클래스에 해당 메서드 제외 메서드는 false 반환
        • e.g. : class red imperments TrafficLight2{ isRed() {retrun true;} } { isYellow() {retrun false;} }
      3. 열거형 이름 바꾸기 → 컴파일러 오류 발생
        • enum TrafficLight → enum RawTrafficLight
      4. 타입을 이전이름 → 임시이름으로 변경 → 일치성 검사 새 메서드로 변경
        • if(current === TrafficLight.RED) → if(current.isRed())
      5. 남아있는 열거형 값에대한 참조 대신 새로운 클래스를 인스턴스화하여 교체
      6. 오류 X ⇒ 인터페이스 이름을 모든위치에서 영구적으로 변경
        • interface TrafficLight2{} → interface TrafficLight{}
    • 참고로 여기서 isRedisYellow 는 임시적인 것임
  • 4.1 규칙 : 클래스로 코드이관하기

    • 마법임

      //1-1
      class Right implements Input {
        handleInput(){
          if (input === Input.RIGHT)
            moveHorizontal(1);
          else if (input === Input.LEFT)
            moveHorizontal(-1);
          else if (input === Input.DOWN)
            moveVertical(1);
          else if (input === Input.UP)
            moveVertical(-1);
        }
      }
      
      //1-2 this로
      class Right implements Input {
        handleInput(){
          if (input === this)
            moveHorizontal(1);
          else if (input === this)
            moveHorizontal(-1);
          else if (input === this)
            moveVertical(1);
          else if (input === this)
            moveVertical(-1);
        }
      }
      
      //2. interface
      interface Input {
      isRight(): boolean;
      isLeft(): boolean;
      isUp(): boolean;
      isDown(): boolean;
      handle(): void;
      }
      
      //3-1. is 메서드를 인라인메서드로 변
      class Right implements Input {
        handleInput(){
          if (false)
            moveHorizontal(1);
          else if (true)
            moveHorizontal(-1);
          else if (true)
            moveVertical(1);
          else if (true)
            moveVertical(-1);
        }
      }
      
      //3-2 true 부분의 메서드호출만 두기 나머지 제거
      class Right implements Input {
      isRight() { return true; }
      isLeft() { return false; }
      isUp() { return false; }
      isDown() { return false; }
      handle() {
        moveHorizontal(1);
      }
      }
      
      //3-4 이름 변경
      interface Input {
      isRight(): boolean;
      isLeft(): boolean;
      isUp(): boolean;
      isDown(): boolean;
      handle(): void;
      }
      
      //4. 메서드 호출로변경
      function handleInput(input: Input) {
      input.handle();
      }
    • 이 결과

      • 적은 인지부하
  • 4.1 리팩터링 패턴 : 클래스로 코드 이관하기

    • 기능을 클래스로 옮기기 때문에 → 클래스로 타입코드 대체패턴의 연장선
    • 그 결과 if문 제거 → 데이터에 더 가까이 이동
    • 특정 값과 연결된 기능이, 값에 해당하는 클래스로 이동 → 불변속성의 지역화
  • 4.1 불필요한 메서드 인라인화

    • hnadleInput() 함수를 앞에서는 도입

    • 리팩터링 순환적임 : 리팩터링 가능하게 하는 항목 추가 → 다시 제거

    • hnadleInput() 함수 목적은 가독성이지만, 이제는 X → 제거

      function handleInputs() {
      while (inputs.length > 0) {
        let current = inputs.pop();
        handleInput(current);
      }
      }
      
      function handleInput(input: Input) {
      input.handle();
      }
      
      // 변경 후
      function handleInputs() {
      while (inputs.length > 0) {
        let input = inputs.pop();
        input.handle();
      }
      }
  • 4.1 리팩터링 패턴 : 메서드의 인라인화

    • 이책의 주제는 코드추가와 코드 제거
    • 본 패턴은 후자임 → 가독성 도움X로서 제거
    • 방법
      • 호출하는 모든 곳으로 코드를 옮기기
    • 고려사항
      1. 메서드가 한줄만 있는경우 → 다섯줄 제한 규칙 때문
      2. 메서드 인라인화가 복잡하지 않은지 여부 → 작업은 동일한 추상화 수준일것
    • 예제
      • deposit() 메서드와 tranfer()메서드 분할 → deposit메서드 잘못호출시 출금없이 돈 입금 → 결합이 바람직

2. 긴 if문의 리팩터링

  • 개요

      function drawMap(g: CanvasRenderingContext2D) {
        for (let y = 0; y < map.length; y++) {
          for (let x = 0; x < map[y].length; x++) {
            if (map[y][x] === Tile.FLUX)
              g.fillStyle = "#ccffcc";
            else if (map[y][x] === Tile.UNBREAKABLE)
              g.fillStyle = "#999999";
            else if (map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE)
              g.fillStyle = "#0000cc";
            else if (map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX)
              g.fillStyle = "#8b4513";
            else if (map[y][x] === Tile.KEY1 || map[y][x] === Tile.LOCK1)
              g.fillStyle = "#ffcc00";
            else if (map[y][x] === Tile.KEY2 || map[y][x] === Tile.LOCK2)
              g.fillStyle = "#00ccff";
    
            if (map[y][x] !== Tile.AIR && map[y][x] !== Tile.PLAYER)
              g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
          }
        }
    • if문은 함수 시작에만 배치 위반 & else if 체인구문

      //메서드 추출 하기
      function drawMap(g: CanvasRenderingContext2D) {
      for (let y = 0; y < map.length; y++) {
        for (let x = 0; x < map[y].length; x++) {
          colorOfTile(g, y, x);
      
          if (map[y][x] !== Tile.AIR && map[y][x] !== Tile.PLAYER)
            g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
      }
      }
      
      // else 사용하지 말것 규칙 위반
      function colorOfTile(g: CanvasRenderingContext2D, y: number, x: number) {
      if (map[y][x] === Tile.FLUX)
        g.fillStyle = "#ccffcc";
      else if (map[y][x] === Tile.UNBREAKABLE)
        g.fillStyle = "#999999";
      else if (map[y][x] === Tile.STONE || map[y][x] === Tile.FALLING_STONE)
        g.fillStyle = "#0000cc";
      else if (map[y][x] === Tile.BOX || map[y][x] === Tile.FALLING_BOX)
        g.fillStyle = "#8b4513";
      else if (map[y][x] === Tile.KEY1 || map[y][x] === Tile.LOCK1)
        g.fillStyle = "#ffcc00";
      else if (map[y][x] === Tile.KEY2 || map[y][x] === Tile.LOCK2)
        g.fillStyle = "#00ccff";
      }
    • 열거형 각 값에 해당 클래스 생성 & 열거형이름 변경 & 일치성 검사 메서드로 변경

      enum RawTile {
      AIR,
      FLUX,
      UNBREAKABLE,
      PLAYER,
      STONE, FALLING_STONE,
      BOX, FALLING_BOX,
      KEY1, LOCK1,
      KEY2, LOCK2
      }
      
      interface Tile2 {
      isAir(): boolean;
      isFlux(): boolean;
      isUnbreakable(): boolean;
      isPlayer(): boolean;
      isStone(): boolean;
      isFallingStone(): boolean;
      isBox(): boolean;
      isFallingBox(): boolean;
      isKey1(): boolean;
      isLock1(): boolean;
      isKey2(): boolean;
      isLock2(): boolean;
      }
      
      class Air implements Tile2 {
      isAir() { return true; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Flux implements Tile2 {
      isAir() { return false; }
      isFlux() { return true; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Unbreakable implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return true; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Player implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return true; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Stone implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return true; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class FallingStone implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return true; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Box implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return true; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class FallingBox implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return true; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Key1 implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return true; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Lock1 implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return true; }
      isKey2() { return false; }
      isLock2() { return false; }
      }
      
      class Key2 implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return true; }
      isLock2() { return false; }
      }
      
      class Lock2 implements Tile2 {
      isAir() { return false; }
      isFlux() { return false; }
      isUnbreakable() { return false; }
      isPlayer() { return false; }
      isStone() { return false; }
      isFallingStone() { return false; }
      isBox() { return false; }
      isFallingBox() { return false; }
      isKey1() { return false; }
      isLock1() { return false; }
      isKey2() { return false; }
      isLock2() { return true; }
      }
      
      //일치성 검사 새 메서드로 경
      function colorOfTile(g: CanvasRenderingContext2D, y: number, x: number) {
      if (map[y][x].isFlux())
        g.fillStyle = "#ccffcc";
      else if (map[y][x].isUnbreakable())
        g.fillStyle = "#999999";
      else if (map[y][x].isStone() || map[y][x].isFallingStone())
        g.fillStyle = "#0000cc";
      else if (map[y][x].isBox() || map[y][x].isFallingBox())
        g.fillStyle = "#8b4513";
      else if (map[y][x].isKey1() || map[y][x].isLock1())
        g.fillStyle = "#ffcc00";
      else if (map[y][x].isKey2() || map[y][x].isLock2())
        g.fillStyle = "#00ccff";
      }
    • 하지만 let map 의 자로형이 여전히 문제임

        let map: Tile[][] = [
          [2, 2, 2, 2, 2, 2, 2, 2],
          [2, 3, 0, 1, 1, 2, 0, 2],
          [2, 4, 2, 6, 1, 2, 0, 2],
          [2, 8, 4, 1, 1, 2, 0, 2],
          [2, 4, 1, 1, 1, 9, 0, 2],
          [2, 2, 2, 2, 2, 2, 2, 2],
        ];
      
        let inputs: Input[] = [];
      
        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 오류가 여전히 있음 → 임시이름 사용하는 이유

    • 임시이름 사용 X → 문제 발견 X & 잘 작동한다고 생각

  • 4.2 일반성 제거

    • remove 함수 문제는 타일의 타입으로 map 의 모든위치에 주언타입의 타일 제거 → 하지만 실제로는 Lock1 또는 Lock2 만 제거

    • Tile 의 특정인스턴스인지 확인하지않고 유사한지만 확인함 → 너무 일반적임

      //remove복제 
      //-> ===tile 을 ===Tile.Lock1으로 변경 
      //->  ===Tile.Lock1 을 .isLock1()로 변
      
      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();
          }
        }
      }
      }
      //새함수 호출하도록 변경
      
      function moveHorizontal(dx: number) {
      if (map[playery][playerx + dx].isFlux()
        || map[playery][playerx + dx].isAir()) {
        moveToTile(playerx + dx, playery);
      } else if ((map[playery][playerx + dx].isStone()
        || map[playery][playerx + dx].isBox())
        && map[playery][playerx + dx + dx].isAir()
        && !map[playery + 1][playerx + dx].isAir()) {
        map[playery][playerx + dx + dx] = map[playery][playerx + dx];
        moveToTile(playerx + dx, playery);
      } else if (map[playery][playerx + dx].isKey1()) {
        removeLock1();
        moveToTile(playerx + dx, playery);
      } else if (map[playery][playerx + dx].isKey2()) {
        removeLock2();
        moveToTile(playerx + dx, playery);
      }
      }
  • 4.2 리팩터링 패턴 : 메서드 전문화

    • 프로그래머의 본능에 반하는 패턴

      • 일반화 & 재사용본능 → but 책임 흐려지고, 다양한위치에서 호출시 문제됨
    • 메서드 전문화는 적은위치에서 호출되어 필요성이 없어지면 더 빨리제거

    • 절차

      1. 전문화하려는 메서드 복제
      2. 메서드 중 하나 이름을 새로 사용할 메서드의 이름으로 변경 → 메서드 제거
      3. 매개변수 제거에 따라 메서드 수정해서 오류 없도록
      4. 이전의 호출을 새로운것을 사용하도록 변경
    • 결국 해당 메서드에 대해서 훨씬 더 이해하기 쉬워짐 (e.g. 체스)

  • 4.2 switch 가 허용되는 유일한경우

    • 동작하지 않는 열거형 인덱스로 map 생성

    • 인덱스는 일반적으로 DB나 파일 에 저장시 사용

    • 전체 map 변경하는 대신 열거형 인덱스에서 새로운 클래스 사용하도록 함수만들기가 좋음

      let map: Tile[][] = [
      [2, 2, 2, 2, 2, 2, 2, 2],
      [2, 3, 0, 1, 1, 2, 0, 2],
      [2, 4, 2, 6, 1, 2, 0, 2],
      [2, 8, 4, 1, 1, 2, 0, 2],
      [2, 4, 1, 1, 1, 9, 0, 2],
      [2, 2, 2, 2, 2, 2, 2, 2],
      ];
      let rawMap: RawTile[][] = [
      [2, 2, 2, 2, 2, 2, 2, 2],
      [2, 3, 0, 1, 1, 2, 0, 2],
      [2, 4, 2, 6, 1, 2, 0, 2],
      [2, 8, 4, 1, 1, 2, 0, 2],
      [2, 4, 1, 1, 1, 9, 0, 2],
      [2, 2, 2, 2, 2, 2, 2, 2],
      ];
      let map: Tile2[][];
      function assertExhausted(x: never): never {
      throw new Error("Unexpected object: " + x);
      }
      function transformTile(tile: RawTile) {
      switch (tile) {
        case RawTile.AIR: return new Air();
        case RawTile.PLAYER: return new Player();
        case RawTile.UNBREAKABLE: return new Unbreakable();
        case RawTile.STONE: return new Stone();
        case RawTile.FALLING_STONE: return new FallingStone();
        case RawTile.BOX: return new Box();
        case RawTile.FALLING_BOX: return new FallingBox();
        case RawTile.FLUX: return new Flux();
        case RawTile.KEY1: return new Key1();
        case RawTile.LOCK1: return new Lock1();
        case RawTile.KEY2: return new Key2();
        case RawTile.LOCK2: return new Lock2();
        default: assertExhausted(tile);
      }
      }
      function transformMap() {
      map = new Array(rawMap.length);
      for (let y = 0; y < rawMap.length; y++) {
        map[y] = new Array(rawMap[y].length);
        for (let x = 0; x < rawMap[y].length; x++) {
          map[y][x] = transformTile(rawMap[y][x]);
        }
      }
      }
    • 본 switch 는 예외케이스로 위반하지않음

  • 4.2 규칙 : switch 를 사용하지 말것

    • 정의
      • default 케이스가 없고, 모든 case에 반환 값이 있는 경우가 X이면 switch 사용하지말것
      1. switch는 모든값에 대한 처리 실행필요 X
        1. 문제점
          • default 지원함 → 중복없이 여러값 지정하지만 불변속성임
          • 새로운값 추가시 여전히 유요한지 컴파일로러 판단X
          • 컴파일러는 새로 추가한 값의 처리 잊은건지, default 에 지정하고자한것인지 구분 불가
        2. 해결
          • default 사용 X
      2. fall-through 로직임
        1. 문제점
          • break 사용 누락할수도
        2. 해결
          • 모든 케이스에 return 지정
    • 스멜
      • switch는 스멜의 이름임
      • 불변속성의 전역화
    • 의도
      • else if 체인문으로 변환하고 이를 다시 클래스로 만듬
  • 4.2 if 제거하기

    • 이것도 마찬가지로 ‘클래스로 코드이관’ 패턴 적용

      function colorOfTile(g: CanvasRenderingContext2D, y: number, x: number) {
      if (map[y][x].isFlux())
        g.fillStyle = "#ccffcc";
      else if (map[y][x].isUnbreakable())
        g.fillStyle = "#999999";
      else if (map[y][x].isStone() || map[y][x].isFallingStone())
        g.fillStyle = "#0000cc";
      else if (map[y][x].isBox() || map[y][x].isFallingBox())
        g.fillStyle = "#8b4513";
      else if (map[y][x].isKey1() || map[y][x].isLock1())
        g.fillStyle = "#ffcc00";
      else if (map[y][x].isKey2() || map[y][x].isLock2())
        g.fillStyle = "#00ccff";
      }

4.3 코드 중복처리

  • 개요

    • drawMap 의 중간에 if가 존재 여전히 규칙위반

      1. 메서드 추출

        function drawMap(g: CanvasRenderingContext2D) {
        for (let y = 0; y < map.length; y++) {
         for (let x = 0; x < map[y].length; x++) {
           map[y][x].color(g);
           if (!map[y][x].isAir() && !map[y][x].isPlayer())
             g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
         }
        }
        function drawMap(g: CanvasRenderingContext2D) {
        for (let y = 0; y < map.length; y++) {
         for (let x = 0; x < map[y].length; x++) {
           drawTile(g, x, y);
         }
        }
        }
        
        function drawTile(g: CanvasRenderingContext2D, x: number, y: number) {
        map[y][x].color(g);
        if (!map[y][x].isAir() && !map[y][x].isPlayer())
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
      2. ‘클래스로 코드이관패턴’으로 메서드를 Tile클래스를 옮길 수 있음

        //코드이관
        function drawTile(g: CanvasRenderingContext2D, x: number, y: number) {
        map[y][x].draw(g, x, y);
        }
        interface Tile {
        isAir(): boolean;
        isFlux(): boolean;
        isUnbreakable(): boolean;
        isPlayer(): boolean;
        isStone(): boolean;
        isFallingStone(): boolean;
        isBox(): boolean;
        isFallingBox(): boolean;
        isKey1(): boolean;
        isLock1(): boolean;
        isKey2(): boolean;
        isLock2(): boolean;
        color(g: CanvasRenderingContext2D): void;
        draw(g: CanvasRenderingContext2D, x: number, y: number): void;
        }
        
        class Air implements Tile {
        isAir() { return true; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) { }
        draw(g: CanvasRenderingContext2D, x: number, y: number) { }
        }
        
        class Flux implements Tile {
        isAir() { return false; }
        isFlux() { return true; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#ccffcc";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#ccffcc";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Unbreakable implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return true; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#999999";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#999999";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Player implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return true; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) { }
        }
        
        class Stone implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return true; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#0000cc";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#0000cc";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class FallingStone implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return true; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#0000cc";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#0000cc";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Box implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return true; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#8b4513";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#8b4513";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class FallingBox implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return true; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#8b4513";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#8b4513";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Key1 implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return true; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#ffcc00";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#ffcc00";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Lock1 implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return true; }
        isKey2() { return false; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#ffcc00";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#ffcc00";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Key2 implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return true; }
        isLock2() { return false; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#00ccff";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#00ccff";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
        
        class Lock2 implements Tile {
        isAir() { return false; }
        isFlux() { return false; }
        isUnbreakable() { return false; }
        isPlayer() { return false; }
        isStone() { return false; }
        isFallingStone() { return false; }
        isBox() { return false; }
        isFallingBox() { return false; }
        isKey1() { return false; }
        isLock1() { return false; }
        isKey2() { return false; }
        isLock2() { return true; }
        color(g: CanvasRenderingContext2D) {
         g.fillStyle = "#00ccff";
        }
        draw(g: CanvasRenderingContext2D, x: number, y: number) {
         g.fillStyle = "#00ccff";
         g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
        }
        }
      3. 메서드 인라인화

        //인라인화 전
        function drawMap(g: CanvasRenderingContext2D) {
        for (let y = 0; y < map.length; y++) {
         for (let x = 0; x < map[y].length; x++) {
           drawTile(g, x, y);
         }
        }
        }
        
        function drawTile(g: CanvasRenderingContext2D, x: number, y: number) {
        map[y][x].draw(g, x, y);
        }
        
        //인라인화 후
        function drawMap(g: CanvasRenderingContext2D) {
        for (let y = 0; y < map.length; y++) {
         for (let x = 0; x < map[y].length; x++) {
           map[y][x].draw(g, x, y);
         }
        }
        }
  • 4.3 인터페이스 대신 추사클래스 사용은 불가능?

    • 사용할수있음 → 코드 중복을 피할 수있음
    • 단점
      • 인터페이스 사용시 개발자는 능동적으로 해야함 → 잘못해서 속성을 잊거나, 하면안되는오버라이딩 방지가능
      • 즉 내용잊어버리고 시간이 흐르고 만든 Tile 의 유형을 추가해야할 때 문제시됨
    • 결국 ‘인터페이스에서만 상속’받을것 규칙으로 공식화함
  • 4.3 규칙: 인터페이스 에서만 상속받을것

    • 정의

      • 상속은 오직 인터페이스를 통해서만 받음

      • 추상클래스 상속 이유

        1. 일부 메서드에서는 기본구현 제공, 다른 메서드는 추상화
        2. 중복을 줄이고, 코드 줄 줄이기 경우 편리
      • 추상클래스 단점

        • 코드 공유는 커플링(결합)을 유발함 → 커플링은 추상클래스의 코드의미
        1. 추상클래스경우에 두가지 메서드 가 있음 → 하나의 하위클래스에는 전자, 다른것은 후자 → 유일한 옵션은 메서드 중 하나만 빈 버전으로 재정의
        2. 기본 구현 메서드 경우 두가지 버전
          1. 모든 하위클래스에 해당 메서드가 필요한 경우 → 메서드를 클래스 밖으로 쉽게 이동 가능
          2. 일부 하위 클래스에서 메서드 재정의 → 기본구현이 있기 때문에 컴파일러로 재정의가 필요한 메서드 못잡아냄
    • 스멜

      • Gof 디자인패턴의 ‘상속보다 컴포지션(객체구성)이 더 좋다’ 원칙에서 규칙 도출
    • 의도

      • 우리가 상속받기위해 다른 객체를 참고함으로써 코드를 공유
  • 4.3 클래스에 있는 코드 중복은 무엇?

    • 코드 복제는 변경이 필요할 때 수정내용을 프로그램 전체에 반영하는 방식으로 변경 → 유지보수성 저해
    • 코드 중복은 분기(divergence)를 조장함 → 해결방법? 다음장으로

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

  • 개요

    • 아래 두 함수에 두개의 ‘||’ 표현식이 있음 → 이 구조를 보존하면서 리팩토링 해야 함

        function moveHorizontal(dx: number) {
          if (map[playery][playerx + dx].isFlux()
            || map[playery][playerx + dx].isAir()) {
            moveToTile(playerx + dx, playery);
          } else if ((map[playery][playerx + dx].isStone()
            || map[playery][playerx + dx].isBox())
            && map[playery][playerx + dx + dx].isAir()
            && !map[playery + 1][playerx + dx].isAir()) {
            map[playery][playerx + dx + dx] = map[playery][playerx + dx];
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey1()) {
            removeLock1();
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey2()) {
            removeLock2();
            moveToTile(playerx + dx, playery);
          }
        }
      
        function moveVertical(dy: number) {
          if (map[playery + dy][playerx].isFlux()
            || map[playery + dy][playerx].isAir()) {
            moveToTile(playerx, playery + dy);
          } else if (map[playery + dy][playerx].isKey1()) {
            removeLock1();
            moveToTile(playerx, playery + dy);
          } else if (map[playery + dy][playerx].isKey2()) {
            removeLock2();
            moveToTile(playerx, playery + dy);
          }
        }
    • 변경

        //함수선언
        function moveHorizontal(dx: number) {
          if (map[playery][playerx + dx].isEdible()) {
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isPushable()
            && map[playery][playerx + dx + dx].isAir()
            && !map[playery + 1][playerx + dx].isAir()) {
            map[playery][playerx + dx + dx] = map[playery][playerx + dx];
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey1()) {
            removeLock1();
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey2()) {
            removeLock2();
            moveToTile(playerx + dx, playery);
          }
        }
      
        function moveVertical(dy: number) {
          if (map[playery + dy][playerx].isEdible()) {
            moveToTile(playerx, playery + dy);
          } else if (map[playery + dy][playerx].isKey1()) {
            removeLock1();
            moveToTile(playerx, playery + dy);
          } else if (map[playery + dy][playerx].isKey2()) {
            removeLock2();
            moveToTile(playerx, playery + dy);
          }
        }
        // 인터페이스 만들어주기
        interface Tile {
          isAir(): boolean;
          isFlux(): boolean;
          isUnbreakable(): boolean;
          isPlayer(): boolean;
          isStone(): boolean;
          isFallingStone(): boolean;
          isBox(): boolean;
          isFallingBox(): boolean;
          isKey1(): boolean;
          isLock1(): boolean;
          isKey2(): boolean;
          isLock2(): boolean;
          color(g: CanvasRenderingContext2D): void;
          draw(g: CanvasRenderingContext2D, x: number, y: number): void;
          isEdible(): boolean;
          isPushable(): boolean;
        }
      
        class Air implements Tile {
          isAir() { return true; }
          isFlux() { return false; }
          isUnbreakable() { return false; }
          isPlayer() { return false; }
          isStone() { return false; }
          isFallingStone() { return false; }
          isBox() { return false; }
          isFallingBox() { return false; }
          isKey1() { return false; }
          isLock1() { return false; }
          isKey2() { return false; }
          isLock2() { return false; }
          color(g: CanvasRenderingContext2D) { }
          draw(g: CanvasRenderingContext2D, x: number, y: number) { }
          isEdible() { return true; }
          isPushable() { return false; }
        }
      
        class Flux implements Tile {
          isAir() { return false; }
          isFlux() { return true; }
          isUnbreakable() { return false; }
          isPlayer() { return false; }
          isStone() { return false; }
          isFallingStone() { return false; }
          isBox() { return false; }
          isFallingBox() { return false; }
          isKey1() { return false; }
          isLock1() { return false; }
          isKey2() { return false; }
          isLock2() { return false; }
          color(g: CanvasRenderingContext2D) {
            g.fillStyle = "#ccffcc";
          }
          draw(g: CanvasRenderingContext2D, x: number, y: number) {
            g.fillStyle = "#ccffcc";
            g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
          }
          isEdible() { return true; }
          isPushable() { return false; }
        }
      
        //생략...
    • 클래스로의 코드 이관 실시

        //전
        function moveHorizontal(dx: number) {
          if (map[playery][playerx + dx].isEdible()) {
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isPushable()
            && map[playery][playerx + dx + dx].isAir()
            && !map[playery + 1][playerx + dx].isAir()) {
            map[playery][playerx + dx + dx] = map[playery][playerx + dx];
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey1()) {
            removeLock1();
            moveToTile(playerx + dx, playery);
          } else if (map[playery][playerx + dx].isKey2()) {
            removeLock2();
            moveToTile(playerx + dx, playery);
          }
        }
      
        //후
        function moveHorizontal(dx: number) {
          map[playery][playerx + dx].moveHorizontal(dx);
      
        // 클래스로 코드 이
        interface Tile {
          isAir(): boolean;
          isFlux(): boolean;
          isUnbreakable(): boolean;
          isPlayer(): boolean;
          isStone(): boolean;
          isFallingStone(): boolean;
          isBox(): boolean;
          isFallingBox(): boolean;
          isKey1(): boolean;
          isLock1(): boolean;
          isKey2(): boolean;
          isLock2(): boolean;
          color(g: CanvasRenderingContext2D): void;
          draw(g: CanvasRenderingContext2D, x: number, y: number): void;
          isEdible(): boolean;
          isPushable(): boolean;
          moveHorizontal(dx: number): void;
        }
      
        class Air implements Tile {
          isAir() { return true; }
          isFlux() { return false; }
          isUnbreakable() { return false; }
          isPlayer() { return false; }
          isStone() { return false; }
          isFallingStone() { return false; }
          isBox() { return false; }
          isFallingBox() { return false; }
          isKey1() { return false; }
          isLock1() { return false; }
          isKey2() { return false; }
          isLock2() { return false; }
          color(g: CanvasRenderingContext2D) { }
          draw(g: CanvasRenderingContext2D, x: number, y: number) { }
          isEdible() { return true; }
          isPushable() { return false; }
          moveHorizontal(dx: number) {
            moveToTile(playerx + dx, playery);
          }
        }
      
        class Flux implements Tile {
          isAir() { return false; }
          isFlux() { return true; }
          isUnbreakable() { return false; }
          isPlayer() { return false; }
          isStone() { return false; }
          isFallingStone() { return false; }
          isBox() { return false; }
          isFallingBox() { return false; }
          isKey1() { return false; }
          isLock1() { return false; }
          isKey2() { return false; }
          isLock2() { return false; }
          color(g: CanvasRenderingContext2D) {
            g.fillStyle = "#ccffcc";
          }
          draw(g: CanvasRenderingContext2D, x: number, y: number) {
            g.fillStyle = "#ccffcc";
            g.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
          }
          isEdible() { return true; }
          isPushable() { return false; }
          moveHorizontal(dx: number) {
            moveToTile(playerx + dx, playery);
          }
        }
    • 인라인 메서드 리팩토링

      //전
      class Right implements Input {
      isRight() { return true; }
      isLeft() { return false; }
      isUp() { return false; }
      isDown() { return false; }
      handle() {
        moveHorizontal(1);
      }
      }
      
      class Left implements Input {
      isRight() { return false; }
      isLeft() { return true; }
      isUp() { return false; }
      isDown() { return false; }
      handle() {
        moveHorizontal(-1);
      }
      }
      
      //
      class Right implements Input {
      isRight() { return true; }
      isLeft() { return false; }
      isUp() { return false; }
      isDown() { return false; }
      handle() {
        map[playery][playerx + 1].moveHorizontal(1);
      }
      }
      
      class Left implements Input {
      isRight() { return false; }
      isLeft() { return true; }
      isUp() { return false; }
      isDown() { return false; }
      handle() {
        map[playery][playerx - 1].moveHorizontal(-1);
      }
      }

4.5 필요없는 코드 제거하기

  • 개요
    • 새로운 메서드 도입, 일부 삭제 했지만 더 제거가능
    • IDE는 사용하지 않는 함수 표시함 → 즉시 함수 제거!
    • 인터페이스는 공용이므로 인터페이스 메서드가 사용되는지 여부 알려주는 IDE 없음 → 쉽게 삭제할 수없음
  • 4.5 리팩터링 패턴 : 삭제 후 컴파일하기
    • 설명
      • 용도는 인터페이스 전체범위를 알고 있을 때 인터페이스에서 사용하지 않는 메서드 제거하기
      • 메서드 삭제하고 컴파일러에서 허용하는지 확인
      • 구현 중에는 X
      • 코드 베이스가 만료된 코드가 있으면 코드의 위치를 아래로 끌어내림
        • 코드 읽고 무시하는데 시간소요
        • 컴파일과 분석이 느려짐
        • 테스트 어렵게함
      • 사용하지 않는 메서드 식별가능한데 이를 속이는것이 인터페이스
        • 메서드가 인터페이스에 있다?
          1. 범위 밖의 코드 사용위한 것?
          2. 범위 내의 코드에 대한 메서드가 필요
          3. 편집기는 차이점을 구분할 수없음 → 유일 안전한 옵션은 모든 인터페이스 메서드가 범위 밖에서 사용된다고 가정
      • 인터페이스가 범위 내에서 사용된다는것 알고있다면 수동으로 정리할것

요약

  1. if 문에서 else를 사용하지말것과 switch문을 사용하지 말것으로 ‘이른 바인딩’ 제거란?
    • else, switch 는 프로그램의 가장자리에 있어야함
    • 모두 낮은 수준(추상화 수준)의 제어흐름 연산자임 → 2번으로
  2. ‘클래스로 타입 코드 대체’, 그리고 ‘클래스로의 코드 이관’으로 if 문 제거하기란?
    • 클래스로 타입코드 대체, 클래스로 코드이관 패턴으로 switch와 else구문을 높은 수준의 클래스와 메서드로 대체해야함
  3. 메서드 전문화’로 문제가 있는 일반성 제거하기란?
    • 지나치게 일반화된 메서드는 리팩터링 방해
  4. 인터페이스에서만 상속’받을 것으로 코드 간 커플링(결합) 방지하기
    • 추상클래스와 클래스 상속을 사용해 코드 재사용 방지위함
    • 불필요하게 긴밀한 커플링발생시
  5. 메서드의 인라인화’ 및 ‘삭제 후 컴파일하기’를 통한 불필요한 메서드 제거
    • 가독성에 도움이 되지않는 메서드 제거

나눔