Refactoring & CleanCode

중복코드 (Duplicated Code)

Tommy__Kim 2023. 4. 5. 11:06

똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다.

코드가 중복되면 각각 코드를 볼 때마다 차이점이 존재하는지 주의 깊게 봐야 한다.

만약 중복 코드 중 하나를 변경하려고 할 때에는 다른 비슷한 코드들도 모두 살펴보고 적절히 수정해야 한다.

  • 함수 추출하기
    • 클래스 내에서의 메서드가 똑같은 표현식을 사용하는 경우
  • 문장 슬라이드하기
    • 클래스 내에서의 메서드가 비슷한데 완전히 똑같지 않은 경우
  • 메서드 올리기
    • 같은 부모로부터 파생된 서브 클래스들에 코드의 중복이 있는 경우

함수 추출하기

여기서 함수란 객체지향 언어의 method 혹은 절차형 언어의 procedure / subroutine 에도 동일하게 적용된다.

함수 추출하기란 코드 조각을 찾아 무슨 일을 하는지 파악한 다음, 독립된 함수로 추출하고 목적에 맞는 이름을 붙이는 것이다.

이 책에서는 목적구현을 기준으로 독립된 함수를 추출할 것을 추천한다.

코드를 보고 무슨 일을 하는 지 파악하는데 시간이 오래 걸린다면 그 부분을 함수로 추출한 뒤 "무슨 일"에 해당하는

이름을 짓는다.

이렇게 한다면 나중에 코드를 읽을 때 함수의 목적이 눈에 확 들어온다.

[예시]

{리팩터링 전}
public class Printer{
  private void print(){
      System.out.println("***");
      System.out.println("this is Printer");
      System.out.println("***");    

    ...
  }
  ...
}

{리팩터링 후}

public class Printer{

    private void print(){
    printInformation();

    ...
  }

  private void printInformation(){
    System.out.println("***");
      System.out.println("this is Printer");
      System.out.println("***");    
  }

}

[절차]

  1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.
    • "어떻게" 가 아닌 "무엇을" 하는지가 드러나야 한다.
  2. 추출할 코드를 원본 함수에서 복사해 새 함수에 복사한다.
  3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다.
    • 있다면 매개변수로 전달한다.
  4. 변수를 다 처리했다면 컴파일한다.
  5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다.
  6. 테스트
  7. 다른 코드에 방금 추출한 것과 똑같거나 유사한 코드가 있는지 살핀다.
    • 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.

함수를 짧게 만드는 것에 대해 성능이 느려질까 큰 고민을 하지 말자, 요즘에는 그런 고민을 안 해도 된다.


문장 슬라이드 하기

관련된 코드들이 가까이 모여있다면 이해하기가 더욱 수월하다.

하나의 데이터 구조를 사용하는 문장들은 다른 곳에 흩어져 있기보다는 한데 모여있어야 좋다.

관련 코드끼리 모으는 작업은 함수 추출하기 와 같은 리팩터링 방법의 준비단계로 행해진다.

애초에 관련된 코드들이 모여 있어야 함수를 추출할 수 있다.

[예시]

{리팩터링 전}
private class memberService{

  private void join(MemberDto memberDto){
    String memberName = memberDto.getName();
    int age = memberDto.getAge();

    ... {다른 비즈니스 로직 수행 코드}
    if (memberName.startsWith("kim")){
      ...
    }
    if (age < 10){
      ...
    }
  }
}
{리팩터링 후}
private class memberService{

  private void join(MemberDto memberDto){

    ... {다른 비즈니스 로직 수행 코드}

    String memberName = memberDto.getName();
    if (memberName.startsWith("kim")){
      ...
    }

    int age = memberDto.getAge();
    if (age < 10){
      ...
    }
  }
}

[절차]

  1. 코드를 이동할 목표 위치를 찾는다.
  2. 코드 조각의 원래 위치와 목표 위치 사이의 코드들을 훑어보면서 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다.
    • 다음과 같은 간섭이 있다면 리팩터링 포기
      • 코드 조각에서 참조하는 요소를 선언하는 문장 앞으로는 이동을 할 수 없다.
      • 코드 조각을 참조하는 요소의 뒤로는 이동할 수 없다.
      • 코드 조각에서 참조하는 요소를 수정하는 문장을 건너뛰어 이동할 수 없다.
      • 코드 조각이 수정하는 요소를 참조하는 요소를 건너뛰어 이동할 수 없다,
  3. 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.
  4. 테스트

메서드 올리기

중복된 두 메서드는 당장은 문제가 없을지 몰라도 미래에는 문제가 생길 수 있는 여지가 있다.

무언가 중복되었다는 것은 한쪽의 변경이 다른 한쪽에는 변경되지 않을 수 있다는 위험을 수반한다.

메서드들의 본문 코드가 같을 때에는 메서드를 올리기가 쉽다.

만약 서로 다른 클래스에 있는 두 메서드를 매개변수화했을 때 같은 메서드가 된다고 한다면, 매개변수화를 진행하여 상속 계층의 위로 올리면 된다.

만약 매당 메서드의 본문에서 참조하는 필드들이 서브클래스에만 있는 경우 필드들을 먼저 슈퍼클래스로 올린 후 메서드를 올린다.

[예시]

{리팩터링 전}
class Toy{
  ...
}

class Lego extends Toy{
  getSize(){
    ...
  }
}

class ToyCar extends Toy{
  getSize(){
    ...
  }
}

{리팩터링 후}
class Toy{
  getSize(){...}
}

class Lego extends Toy{
  ...
}

class ToyCar extends Toy{
  ...
}

[절차]

  1. 똑같이 동작하는 메서드인지 확인
  2. 메서드 안에서 호출하는 다른 메서드와 참조하는 필드들을 슈퍼클래스에서도 호출하고 참조할 수 있는지 확인
  3. 메서드 시그니처가 다르다면 함수 선언 바꾸기로 슈퍼클래스에서 사용하고 싶은 형태로 통일
  4. 슈퍼클래스에 새로운 메서드를 생성하고, 대상 메서드의 코드를 복사해 넣는다.
  5. 정적 검사를 수행
  6. 서브 클래스 중 하나의 메서드를 제거
  7. 테스트
  8. 모든 서브클래스의 메서드가 없어질 때까지 다른 서브클래스의 메서드를 하나씩 제거

'Refactoring & CleanCode' 카테고리의 다른 글

전역 데이터 (Global Data)  (0) 2023.04.10
기이한 이름 (Mysterious Name)  (0) 2023.04.05