리팩토링 1장

jun·2024년 11월 30일

리팩토링

목록 보기
1/2

리팩토링 기법 (Refactoring Techniques)

코드를 더 깔끔하고 유지보수하기 쉽게 만드는 과정이 바로 리팩토링입니다. 소프트웨어 개발에서 리팩토링은 필수적인 작업인데요, 그 과정에서 사용되는 다양한 기법들이 있습니다. 이번 글에서는 그중 몇 가지 대표적인 리팩토링 기법에 대해 알아보겠습니다.

1. 함수 추출하기 (Extract Method)

설명: 코드의 특정 부분을 별도의 함수로 추출하여 코드의 가독성을 높이고 재사용성을 증가시키는 기법입니다.

적용 이유: 코드가 길어지고 복잡해질 때, 특정 기능을 수행하는 코드 블록을 새로운 함수로 만들어주면 더 이해하기 쉬워지고 테스트하기도 용이해집니다.

예시: 반복적으로 사용되는 로직을 독립적인 함수로 분리하여 코드의 중복을 줄이는 경우. 예를 들어, 여러 곳에서 동일한 데이터 검증 로직을 수행한다면 이를 별도의 함수로 만들어 재사용할 수 있습니다.

단계 1: 원래 코드

public class BankTransfer {
  private String sender;
  private String receiver;
  private double amount;

  public BankTransfer(String sender, String receiver, double amount) {
    this.sender = sender;
    this.receiver = receiver;
    this.amount = amount;
  }

  public String processTransfer() {
    double fee = 0;
    if (amount > 10000) {
      fee = amount * 0.02;
    } else if (amount > 5000) {
      fee = amount * 0.01;
    }
    double totalAmount = amount + fee;
    return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
  }
}

단계 2: 함수 추출하기 적용

public class BankTransfer {
  private String sender;
  private String receiver;
  private double amount;

  public BankTransfer(String sender, String receiver, double amount) {
    this.sender = sender;
    this.receiver = receiver;
    this.amount = amount;
  }

  public String processTransfer() {
    double totalAmount = amount + calculateFee();
    return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
  }

  private double calculateFee() {
    if (amount > 10000) {
      return amount * 0.02;
    } else if (amount > 5000) {
      return amount * 0.01;
    }
    return 0;
  }
}

위의 예시에서 calculateFee()라는 메서드를 별도로 추출하여 코드를 더 이해하기 쉽고 테스트하기 용이하도록 만들었습니다.

2. 변수 인라인하기 (Inline Variable)

설명: 의미가 없거나 코드의 가독성을 떨어뜨리는 임시 변수를 없애고, 그 값을 직접 사용하도록 바꾸는 기법입니다.

적용 이유: 변수가 코드 이해를 도와주는 역할을 하지 않거나 그저 값만 전달하는 경우, 해당 변수를 없애고 직관적으로 값 자체를 사용하면 코드의 단순성을 높일 수 있습니다.

단계 1: 함수 추출 전 코드

public String processTransfer() {
  double fee = calculateFee();
  double totalAmount = amount + fee;
  return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
}

단계 2: 변수 인라인하기 적용

public String processTransfer() {
  double totalAmount = amount + calculateFee();
  return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
}

변수 fee가 코드 이해에 별로 도움이 되지 않기 때문에 이를 제거하고 직접 사용하는 방식으로 개선하였습니다.

3. 문장 슬라이스 하기 (Split Phase)

설명: 하나의 함수나 메서드가 여러 단계의 작업을 수행할 때, 각 단계를 별도의 함수로 분리하는 기법입니다.

적용 이유: 각 단계를 별도로 관리하고 테스트할 수 있어 유지보수성이 향상되며, 단계마다 의미가 명확해져 가독성이 높아집니다.

단계 1: 원래 코드

public String processTransfer() {
  double totalAmount = amount + calculateFee();
  return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
}

단계 2: 문장 슬라이스 하기 적용

public String processTransfer() {
  double totalAmount = calculateTotalAmount();
  return generateTransferMessage(totalAmount);
}

private double calculateTotalAmount() {
  return amount + calculateFee();
}

private String generateTransferMessage(double totalAmount) {
  return "Transferring " + totalAmount + " from " + sender + " to " + receiver;
}

각 단계를 별도의 함수로 분리하여 로직의 흐름을 더 명확하게 만들었습니다.

4. 조건부 로직을 다형성으로 바꾸기 (Replace Conditional with Polymorphism)

설명: 조건문으로 구현된 로직을 다형성을 사용하여 각 조건에 따라 서로 다른 클래스를 만들어 처리하는 기법입니다.

적용 이유: 조건문이 많아지면 코드가 복잡해지고 읽기 어려워집니다. 이 문제를 다형성을 활용해 클래스 구조로 해결하면 각 클래스가 독립적으로 조건을 처리하게 되어 코드가 더 직관적이고 확장하기 쉬워집니다.

단계 1: 조건부 로직 사용 코드

private double calculateFee() {
  if (amount > 10000) {
    return amount * 0.02;
  } else if (amount > 5000) {
    return amount * 0.01;
  }
  return 0;
}

단계 2: 다형성 적용

public interface FeeCalculator {
  double calculate(double amount);
}

public class HighAmountFeeCalculator implements FeeCalculator {
  public double calculate(double amount) {
    return amount * 0.02;
  }
}

public class MediumAmountFeeCalculator implements FeeCalculator {
  public double calculate(double amount) {
    return amount * 0.01;
  }
}

public class NoFeeCalculator implements FeeCalculator {
  public double calculate(double amount) {
    return 0;
  }
}

private FeeCalculator getFeeCalculator() {
  if (amount > 10000) {
    return new HighAmountFeeCalculator();
  } else if (amount > 5000) {
    return new MediumAmountFeeCalculator();
  }
  return new NoFeeCalculator();
}

private double calculateFee() {
  FeeCalculator calculator = getFeeCalculator();
  return calculator.calculate(amount);
}

조건부 로직을 각 조건에 맞는 클래스로 대체하여 코드의 복잡성을 줄였습니다.

5. 함수 옮기기 (Move Method)

설명: 특정 클래스에 속해 있는 함수가 다른 클래스에 더 잘 맞는 경우, 그 함수를 적절한 클래스나 모듈로 옮기는 기법입니다.

적용 이유: 데이터와 로직이 서로 잘 맞아야 코드가 더 직관적이고 유지보수하기 쉬워집니다.

단계 1: 원래 코드

calculateFee() 함수가 BankTransfer 클래스에 존재하지만, 이는 수수료 계산 클래스에서 더 적절합니다.

단계 2: 함수 옮기기 적용

calculateFee() 함수를 각 수수료 계산 클래스 (HighAmountFeeCalculator, MediumAmountFeeCalculator, NoFeeCalculator)로 옮겨 응집도를 높였습니다.

6. 단계 쪼개기 (Split Phase)

설명: 복잡한 처리를 단계별로 나누어 각각의 단계가 명확히 드러나도록 하는 기법입니다.

적용 이유: 여러 단계를 처리하는 코드가 있을 때 이를 명확히 분리하면 각 단계가 독립적이고 테스트하기 쉬워지며, 로직의 흐름을 쉽게 파악할 수 있습니다.

단계 1: 원래 코드

public String processTransfer() {
  double totalAmount = calculateTotalAmount();
  return generateTransferMessage(totalAmount);
}

단계 2: 단계 쪼개기 적용

public void executeTransfer() {
  double totalAmount = calculateTotalAmount();
  String message = generateTransferMessage(totalAmount);
  printTransferMessage(message);
}

private void printTransferMessage(String message) {
  System.out.println(message);
}

각 단계가 명확히 드러나도록 함수를 분리하여 로직의 가독성을 높였습니다.

정리

GPT를 활용해서 코드를 작성하다보니 생산성이 많이 높아졌습니다. 하지만 생성된 코드를 그냥 쓰다보면 결국 리팩토링의 필요성을 느끼게 되었고 이를 해결하기 위해 리팩토링 기법을 숙지하면서 GPT의 생산성과 코드의 가독성을 함께 지켜나가며 좋은 서비스를 만들어 나가고자 합니다.

profile
사람들에게 긍정적 에너지와 즐거움을 주는 개발자

0개의 댓글