스터디/Five Lines of Code

Five Lines of Code 4장 타입 코드 처리하기

마디니 2023. 10. 14. 07:30
반응형

간단한 if 문 리팩터링

[규칙] "if 문에서 else를 사용하지 말 것"

정의

프로그램에서 타입(자료형) 검사를 할 때만 if else를 사용할 것

설명

  • if-else는 코드에서 결정이 내려지는 지점을 고정한다. 
  • 따라서 if-else이외의 위치에서 다른 변형작업을 할 수 없기 때문에 유연성이 떨어진다
  • if-else체인 문은 데이터의 입출력에 직접 연결되어야하고 애플리케이션이 나머지 부분과는 분리 되어야 한다.
  • if문은 검사(check)로 간주하고, if-else문은 의사결정(decision)으로 간주한다. if 절은 조기 return 을 시키므로 이 규칙은 if-else 문을 대상으로 한다.

스멜

  • early binding : if-else 는 의사결정 동작이 컴파일 시 처리되어 애플리케이션에 고정된다. <-> late binding
  • ealry binding 은 if문을 수정해야 변경할 수 있기 때문에 추가/변경이 어렵다.

의도

  • 제어 흐름을 if절이 아닌 객체(클래스)가 담당하도록 한다.
  • 인터페이스를 사용해 인스턴스화 하는 클래스에 따라 실해할 코드를 결정할 수 있다.

참조

  • 클래스로 타입 코드 대체(4.1.3)
  • 전략 패턴의 도입(5.4.2)

규칙 적용

1. 열거형에 있는 4개의 값에 대한 메서드와 함께 임시 명칭으로  Input2라는 새로운 인터페이스를 도입한다.

2. 4개의 열거값에 해당하는 4개의 클래스 만들기, 클래스에 해당하는 메서드를 제외한 모든 메서드는 false를 반환 한다.

3. 열거형의 이름을 RawInpu으로 변경 (컴파일 에러 발생)

4. 매개변수 타입을 Input에서 Input2로 변경하고 일치 여부 검사를 새로운 메서드로 대체

5. 변경으로 인한 오류 수정

6. Input2의 이름을 모든 곳에서 Input 으로 변경

 

[리팩터링 패턴] 클래스로 타입 코드 대체

설명

  • 열거형을 인터페이스로 변환하고 열거형의 값들은 클래스로 만든다. 
  • 각 열거형 값에 속성을 추가 하고 해당 특정 값과 관련된 기능을 특성에 맞게 만들 수 있다.
  • 즉, 타입별로 기능과 데이터를 그룹과 할 수 있다.
  • 열거형에 새 값을 추가 하는 것은 모든 파일 해당 열거형과 관련된 로직들을 확인해야 하지만, 인터페이스를 사용하면 새로운 구현체(클래스)만 추가하면 된다. 이 클래스를 사용하기 전까지 다른 코드를 수정하지 않아도 된다.
  • 정수 타입이나 일치 비교 연산자 === 를 지원하는 모든 타입은 타입 코드로 볼 수 있다 -> 타입 코드는 추적이 까다롭기 때문에 즉시 열거형으로 변환하는 것이 좋다.

절차

  1. 임시 이름을 가진 새로운 인터페이스를 도입한다. 인터페이스에는 열거형(enum)의 각 값에 대한 메서드가 있어야 한다.
  2. 열거형의 각 갑세 해당하는 클래스를 만든다. 클래스에 해당하는 메서드를 제외한 인터페이스의 모든 메서드는 false를 반환
  3. 열거형의 이름을 다른 이름으로 바꾼다. -> 컴파일러가 열거형을 사용하는 모든 위치에서 오류를 발생시킴
  4. 타입을 이전 이름에서 임시 이름으로 변경하고 일치성 검사를 새로운 메소드로 대체
  5. 남아있는 열거형 값에 대한 참조 대신 새로운 클래스를 인스턴스와 하여 교체한다.
  6. 오류가 더이상 없으면 인터페이스의 이름을 사용할 값으로 바꾼다.

 

클래스로 코드 이관하기

1.handleInput을 복사해서 모든 클래스에 붙여 넣기, function 키 제거, 입력 매개변수를 this 로 변경

2.메서드 선언을 Input 인터페이스에 복사하고 원래 메서드 handleInput과 약간 다른 이름을 지정한다. 이 경우 Input이 이미 있으므로 두 번 쓰는 것은 의미가 없다.

3.네 클래스 모두에서 handleInput메서드 변경하기.

  1. if 조건의 논리 값은 isLeft, isRight.. 반환값
  2. 모든 if(false) {...}와 if(true)의 if 부분 제거

4.handleInput의 내용을 새로운 메서드에 대한 호출로 수정

[리팩터링 패턴] 클래스로의 코드 이관

설명

  • 기능을 클래스로 옮기기 때문에 클래스로 타입 코드 대체 패턴의 연장선
  • 항상 메서드 전체를 클래스로 옮긴다고 가정한다.

절차

  1. 원래 함수를 복사하여 모든 클래스로 붙여 넣는다.  메서드 이므로  function 키워드는 제거하고 컨텍스트를 this로 변경 후 사용하지 않는 매개변수를 제거한다. -> 메소드에 잘못된 이름이 존재하여 에러 발생
  2. 메서드 선언 부분의 메서드 이름과 매개변수 리스트(메서드 시그니처)를 인터페이스에 복사하고 원래 메서드와 약간 다른 이름을 지정
  3. 모든 클래스에서 새로운 메서드 점검하기
    1. 클래스에 맞게 조건식들의 true, false를 결정
    2. 미리 계산할 수 있는 모든 계산을 수행
    3. 메서드 처리가 완료되었음을 알릴 수 있는 적절한 이름으로 변경 -> 컴파일 에러 발생하면 안됌
  4. 원래 함수의 본문을 새로운 메소드에 대한 호출로 변경

예제

위 신호등 예제 계속

추가 자료

마틴 파울러의 '메서드 이동' 과 동일하다.

 

불필요한 메서드 인라인화

  1. 메서드릐 이름을 handleInput2로 변경 -> 함수 사용하는 모든 곳에서 컴파일 에러 발생
  2. 함수 내용인 input.handle(); 복사(input이 매개변수)
  3. handleInput 함수를 호출하는 곳을 복사한 내용으로 변경한다.
예제 코드
https://github.com/himj131/five-lines-of-code

[리팩터링 패턴] 메서드의 인라인화

설명

  • 가독성에 도움되지 않는 메서드를 제거하기 위한 목적
  • 메서드의 인라인화를 수행할 때는 모든 호출 하는 쪽에서 원래의 메서드를 제거해줘야한다.
  • 인라인화 해서는 안되는 메서드도 있다.
    • 메서드로 존재하는 것이 가독성에 도움이 되는 코드
    • 낮은 수준의 연산에 의존하는 코드 -> '동일한 추상화 수준에 있어야 한다'는 규칙을 위함

절차

  1. 메서드 이름을 임시로 변경한다. -> 함수 사용하는 모든 곳에서 컴파일러 오류 발생
  2. 메서드의 본문을 복사하고 매개변수를 기억해둔다.
  3. 컴파일러가 오류를 발생시킨 모든 곳의 호출을 복사된 코드로 교체하고 인자를 매개변수에 매핑한다.
  4. 오류 없이 컴파일되면 원래의 메서드가 더 이상 사용되지 않는 것이므로 원래 메서드를 삭제한다.

예제

돈을 인출해서 다른 계좌에 입금하는 은행 거래를 두 부분으로 분할하기

추가 자료

마틴 파울러의 리팩터링 책

긴 if 문 리팩터링

일반성 제거

  • 너무 일반적인 함수는 변경하기 어렵다.
  • 일반화를 줄이고 좀 더 특정화한 버전의 함수를 도입하는 과정을 '메서드 전문화' 라고 한다.

[리팩터링 패턴] 메서드 전문화

설명

  • 코드를 일반화하고 재사용하면 할 수록 책임이 흐려지고 다양한 위치에서 사용되면서 문제가 될 수 있다
  • 전문화된 메서드는 더 적은 위치에서 호출되어 필요성이 없어지면 더 빨리 제거 할 수 있다.

절차

  1. 전문화하려는 메서드를 복제한다.
  2. 메서드 중 하나의 이름을 새로 사용할 메서드의 이름으로 변경하고 전문화하려는 매개변수를 제거(또는 교체)한다.
  3. 매개변수 제거에 따라 메서드를 수정해서 오류가 없도록 한다.
  4. 이전의 호출을 새로운 것을 사용하도록 변경한다.

예제

체스게임 구현 - 체스 말의 움직임을 검사하는 작업의 일부로 말의 이동이 문제 없는지 검사하기

  1. 초기 코드
  2. 전문화하려는 메서드를 복제한다.
  3. 메서드 중 하나의 이름을 새로 사용할 메서드의 이름으로 변경하고 전문화하려는 매개변수를 제거(또는 교체)한다.
  4. 매개변수 제거에 따라 메서드를 수정해서 오류가 없도록 한다.
  5. 이전의 호출을 새로운 것을 사용하도록 변경한다.

추가  자료

'독립 게임을 프로그래밍하는 방법' 이라는 연설에서 전문화된 메서드의 장점이 나옴

 

switch 가 허용되는 유일한 경우

  • 더 이상 동작하지 않는 열거형 인덱스를 사용해서 map을 생성하고 있다.
  • 실제로 리팩토링을 위해 기존의 외부 데이터를 변경하는 것은 불가능한 경우가 많기 때문에 전체 map을 변경하는 대신 열거형 인덱스에서 새로운 클래스를 사용하도록 새로운 함수를 만든다.

[규칙] "switch를 사용하지 말 것"

정의

default 케이스가 없고 모든 case에 반환 값이 있는 경우가 아니라면 switch를 사용하지 말자.

설명

  • switch는 default 로 중복 없이 여러 값을 지정할 수 있는데, 새로운 값이 추가될 때 다른 조건들이 여전히 유효한지 컴파일러로 알 수 없다.
  • 지원되는 언어의 경우라면 default 키워드를 사용하지 않는 것이 좋다. default 생략이 불가하다면 switch를 사용하지 말아야 한다.
  • break 키워드를 만나기 전까지 연속해서 실행하는 fall-through 로직인데 실수로 break 키워드를 누락하는 경우 효율성이 떨어진다.
  • 모든 케이스에 return을 지정해서 fall-through 문제를 해결할 수 있다.

스멜

  • switch는 컨텍스트, 즉 값 X를 처리하는 방법에 초점을 맞춘다.

의도

  • switch를 else if 체인문으로 변환하고 이를 다시 클래스로 만든다

참조

  • 마틴파울러의 'switch'라는 스멜

if 제거하기

  1. colorOfTitle을 복사해서 모든 클레스에 붙이기, function 키워드 제거하기, 매개변수 x,y를 제거하고 map[x][y]를 this로 변경
  2. 메서드 선언부를 Title 인터페이스에 복사하고 이름을 color로 변경
  3. 모든 클래스에서 새로운 메서드를 살펴본다.
    1. is로 시작하는 메서드들은 인라인화 한다
    2. if (true) 및 if (false) {...}를 제거한다. 대부분의 새로운 메서드는 한 줄만 남고 Air 및 Playersms 비어있다.
    3. 이름을 color로 변경하여 이 메서드가 완성됨을 알리기
  4. colorOfTitle의 본문을 map[y][x].color의 호출로 변경

코드 중복 처리

메서드 추출

 

인터페이스 대신 추상 클래스를 사용할 수는 없을까?

"있다"

  • 인터페이스 대신 추상 클래스를 사용해서 중복되는 공통 코드들을 넣을 수도 있다.
  • 인터페이스를 사용하면 잘못해서 속성을 잊거나 잘몬된 오버라이드를 방지할 수 있다. -> 코드 작성후 시간이 흐른 후 더 문제가 될 수 있다.
  • 따라서 인터페이스에서만 상속 받을 것

[규칙] "인터페이스에서만 상속 받을 것"

정의

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

설명

  • 추상클래스는 중복을 줄이고 코드줄을 줄일 때 편리하지만 단점이 더 많다.
  • 단점 
    • 코드 공유는 커플링(결합)을 유발한다. 
    • 추상클래스에 methodA 와 methodB가 구현되어 있는데 한 하위클래스에서는 methodA 만, 다른 하위 클래스에서는 methodB만 필요하다면 메서드중 하나를 빈 버전으로 재정의 해야 한다.
    • 기본 구현된 메소드가 있지만 재정의 해야하는 경우 재정의가 필요한지 컴파일러를 통해 알 수 없다.

스멜

  • 상속보다는 컴포지션(객체 구성)이 더 좋다

의도

  • 상속을 받기위해 다른 객체를 참조함으로써 코드를 공유해야 한다.

참조

  • <<GoF의 디자인 패턴>>
  • 전략패턴의 도입 (5.4.2)

클래스에 있는 코드의 중복은 다 무엇일까?

  • 중복된 코드의 변경이 필요할 때 한곳만 변경하면 두 가지 다른 기능이 존재하게 된다 -> 코드 중복은 분기를 조장하게 되어 나쁘다

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

  1. Tile 인터페이스에 isEdible 메서드를 새로 만든다.
  2. 각 클래스에 이 메서드를 추가하는데, 이름을 임시로 약간 변형해서 isEdible2라고 한다.
  3. 본문에 return this.isFlux() || this.isAir();를 입력한다.
  4. isFlux와 isAir 값을 인라인화 한다.
  5. 이름에서 임시로 넣은 2를 제거
  6. 여기서만 변경, 다른 || 연산 절에서도 동일한 속성을 참조하는지 모르기 때문에 모든 곳을 바꿀 수는 없다.

필요없는 코드 제거하기

  • IDE가 표시해주는 사용하지 않는 함수 제거
  • 인터페이스에서 사용하지 않는 함수 제거하기
    1. 컴파일을 한다. 오류가 없어야 한다.
    2. 인터페이스에서 메서드를 삭제한다.
    3. 컴파일한다.
      1. 컴파일러 오류가 발생하면 실행을 취소하고 계속 진행
      2. 그렇지 않으면 각 클래스를 살펴보고 오류 없이 해당 메서드를 삭제할 수 있는지 확인

[리팩터링 패턴] 삭제 후 컴파일하기

설명

  • 주 용도는 인터페이스에서 사용하지 않는 메서드를 제거하는 것 (인터페이스의 전체 범위를 아는 경우에만)
  • 사용하지 않는 메서드의 경우 읽거나 무시하는데 시간이 들고 컴파일과 분석이 느려지며 테스트를 어렵게 만든다.
  • 인터페이스 범위 밖에서 쓰이는 메소드중 안쓰는 것도 제거하고, 안쓰는 메소드 범위 안에서 사용되는 메소드도 함께 지우자. 

절차

  1. 컴파일 한다. 오류가 없어야 한다.
  2. 인터페이스에서 메서드를 삭제한다.
  3. 컴파일 한다
    1. 컴파일러 오류가 발생하면 실행을 취소하고 계속 진행
    2. 그렇지 않으면 각 클래스를 살펴보고 오류 없이 해당 메서드를 삭제할 수 있는지 확인
반응형