본문 바로가기
공부

리펙토링 2부

by 노오오오오옹 2019. 12. 4.

리펙토링 1부 : https://noooong1231.tistory.com/10

 

리펙토링 1부

개요 ▪ 정의 : 소프트웨어를 보다 쉽게 이해할 수 있고, 적은 비용으로 수정할 수 있도록 겉으로 보이는 동작의 변화없이 내부 구조를 변경하는 것이다. ▪ 이유 : 디자인을 개선하기 때문에 이해하기 쉬워진다...

noooong1231.tistory.com

 리펙토링 방법

 메소드 정리 (Composing Methods)

▪ 대부분의 문제는 긴 메소드에서 나온다. 긴 메소드는 많은 정보를 가지고 있기 때문에 복잡하다. 그래서 이해하기어렵다. 가장 중요한 리펙토링은 Extract Method(메소드 나누기)인데, 이것은 덩어리를 별도의 메소드로 뽑아내는것이다. 다만 지나치게 남발하면 너무 조각이나 Inlune Method(메소드 합치기)로 뭉치게된다.

 Extrat Method를 사용할 때 가장 큰 문제는 지역 변수를 다루는 것이고, 임시변수 또한 그렇다. 임시 변수를 제거하기 위해 Replace Temp with Query(함수 반환값)를 사용한다. 임시 변수가 여러 용도로 사용한다면 Split Temporay Variable(1개의 변수는 1개의 일을)를 사용하여 쉽게 바꾸도록 한다.

더보기
// # Split Temporary Variable (다양한 중간값을 가지는 변수가 있으면 각각 다른 변수로)

double temp = 2 * (height + width);
Console.WriteLine(temp);
temp = height * width;
Console.WriteLine(temp);

// # ...

readonly double perimeter = 2 * (height + width);
Console.WriteLine(perimeter);
readonly double area = height * width;
Console.WriteLine(area);

// 1. 코드는 한가지 일만 담당해야 한다. 
// 2. k, a2, val은 빨리 선언할 수 있지만, 이후 이해하기 어렵다.

 임시 변수가 너무 꼬여 있어 제거하기 어려운 경우는 Replace Method with Method Object가 필요하다. 이것으로 새로운 클래스를 만드는 단점이 있지만, 아주 복잡한 메소드라도 분해할 수 있는 강점이 있다. 

더보기
// # Replace Method with Method Object (지역 변수가 필드가 되도록 별도의 클래스로 변환해라.)

public class Order 
{
  public double Price() 
  {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    
    // Perform long computation.
  }
}

// # ...

public class Order 
{
  public double Price() 
  {
    return new PriceCalculator(this).Compute();
  }
}

public class PriceCalculator 
{
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) 
  {
    // Copy relevant information from the
    // order object.
  }
  
  public double Compute() 
  {
    // Perform long computation.
  }
}

// 1. 자체 클래스에서 긴 메소드를 분리하면 메소드의 풍선 모양을 막을 수 있습니다. 
// 2. 유틸리티 메소드로 원래 클래스를 오염시키지 않고, 클래스 내의 하위 메소드로 분할 가능 하다.

// a. 클래스가 추가되는 형태라 복잡해진다.

 파라미터의 값을 사용하지 않는다면, 파라미터는 임시 변수에 비해 문제가 크지 않다. 만약 파라미터의 값을 사용한다면, Remove Assignment to Parameters가 필요하다.

더보기
// # Remove Assignments to Parameters (매개 변수 말고 지역 변수를 쓴다.)

int Discount(int inputVal, int quantity) 
{
  if (inputVal > 50) 
  {
    inputVal -= 2;
  }
}

# ...

int Discount(int inputVal, int quantity) 
{
  int result = inputVal;
  
  if (inputVal > 50) 
  {
    result -= 2;
  }
}

// 1. 각 요소는 1개의 일만한다.
// 2. 반복적인 코드를 추출하여 메소드를 분리하는데 도움이 된다.

 

 메소드를 잘게 분할하면 더욱 쉽게 이해하고, 알고리즘을 개선하기 쉽다. 즉 Substitude Algorithm(다중 조건으로 return하지 말고 묶음을 만들어 반환)을 통해 더 명확한 알고리즘을 도입할 수 있는 것이다.

 

알고리즘 예시

더보기
// # Extract Method (그룹화 가능한 코드 조각은, 별도의 새로운 메소드에 옮기고 호출 형태로 바꾼다.)

void PrintOwing() 
{
  this.PrintBanner();

  Console.WriteLine("name: " + this.name);
  Console.WriteLine("amount: " + this.GetOutstanding());
}

// # ...

void PrintOwing()
{
  this.PrintBanner();
  this.PrintDetails(this.GetOutstanding());
}

void PrintDetails(double outstanding)
{
  Console.WriteLine("name: " + this.name);
  Console.WriteLine("amount: " + this.outstanding);
}

// 1. 더 읽기 쉬운 코드가 된다. 
// 2. 코드 중복이 줄어든다.
// 3. 코드의 독립적인 부분을 분리하기 때문에 오류 발생이 적다.

========================================================================================
// # Inline Method (메소드 호출을 메소드의 컨텐츠로 바꾸고 메소드 자체를 삭제한다.)

class PizzaDelivery 
{
  int GetRating() 
  {
    return MoreThanFiveLateDeliveries() ? 2 : 1;
  }
  
  bool MoreThanFiveLateDeliveries() 
  {
    return numberOfLateDeliveries > 5;
  }
}

// # ...

class PizzaDelivery 
{
  int GetRating() 
  {
    return numberOfLateDeliveries > 5 ? 2 : 1;
  }
}

========================================================================================
// # Inline Temp (변수에 대한 참조를 표현식 자체로 바꾼다.)

bool HasDiscount(Order order)
{
  double basePrice = order.BasePrice();
  return basePrice > 1000;
}

// # ... 
bool HasDiscount(Order order)
{
  return order.BasePrice() > 1000;
}

========================================================================================
// # Replace Temp with Query (전체 표현식을 별도의 메소드로 옮기고 리턴한다. 변수말고 메소드를 쿼리하자.)

double CalculateTotal() 
{
  double basePrice = quantity * itemPrice;
  
  if (basePrice > 1000) 
    return basePrice * 0.95;

  else 
    return basePrice * 0.98;
}

// # ...
double CalculateTotal() 
{
  if (BasePrice() > 1000) 
    return BasePrice() * 0.95;
  
  else 
    return BasePrice() * 0.98;
}

double BasePrice() 
{
  return quantity * itemPrice;
}

// 1. getTax()line보다 방법의 목적을 이해하는 것(코드 가독성)이 훨씬 쉽다. ex : orderPrice() * 0.2.
// 2. 교체되는 라인이 여러 방법으로 사용되는 경우 중복 제거를 할 수 있다.

========================================================================================
// # Introduce Explaning Variable (이해하기 어려우면 변수로 묶는다.)

bool isAnonyous = n.equals("anonyous") && p.equlas("");'
bool isGuest = n.equals("guest") && p.equlas("");

if(isAnonyous && isGuest)
{
	// function implementation...
}

========================================================================================
// # Split Temprorary Variable (다양한 중간값을 가지는 변수가 있으면 각각 다른 변수로)

double temp = 2 * (height + width);
Console.WriteLine(temp);
temp = height * width;
Console.WriteLine(temp);

// # ...

readonly double perimeter = 2 * (height + width);
Console.WriteLine(perimeter);
readonly double area = height * width;
Console.WriteLine(area);

// 1. 코드는 한가지 일만 담당해야 한다. 
// 2. k, a2, val은 빨리 선언할 수 있지만, 이후 이해하기 어렵다.

========================================================================================
// # Remove Assignments to Parameters (매개 변수 말고 지역 변수를 쓴다.)

int Discount(int inputVal, int quantity) 
{
  if (inputVal > 50) 
  {
    inputVal -= 2;
  }
}

// # ...

int Discount(int inputVal, int quantity) 
{
  int result = inputVal;
  
  if (inputVal > 50) 
  {
    result -= 2;
  }
}

// 1. 각 요소는 1개의 일만한다.
// 2. 반복적인 코드를 추출하여 메소드를 분리하는데 도움이 된다.

========================================================================================
// # Replace Method with Method Object (지역 변수가 필드가 되도록 별도의 클래스로 변환해라.)

public class Order 
{
  public double Price() 
  {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    
    // Perform long computation.
  }
}

// # ...

public class Order 
{
  public double Price() 
  {
    return new PriceCalculator(this).Compute();
  }
}

public class PriceCalculator 
{
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) 
  {
    // Copy relevant information from the
    // order object.
  }
  
  public double Compute() 
  {
    // Perform long computation.
  }
}

// 1. 자체 클래스에서 긴 메소드를 분리하면 메소드의 풍선 모양을 막을 수 있습니다. 
// 2. 유틸리티 메소드로 원래 클래스를 오염시키지 않고, 클래스 내의 하위 메소드로 분할 가능 하다.

// a. 클래스가 추가되는 형태라 복잡해진다.

========================================================================================
// # Substititude Algorithm (경우마다 반환하지않고 리스트를 통해 반환시켜 간단히 만든다.)

string FoundPerson(string[] people)
{
  for (int i = 0; i < people.Length; i++) 
  {
    if (people[i].Equals("Don"))
      return "Don";
    
    if (people[i].Equals("John"))
      return "John";
    
    if (people[i].Equals("Kent"))
      return "Kent";
  }
  
  return String.Empty;
}

// # ...

string FoundPerson(string[] people)
{
  List<string> candidates = new List<string>() {"Don", "John", "Kent"};
  
  for (int i = 0; i < people.Length; i++) 
  {
    if (candidates.Contains(people[i])) 
      return people[i];
  }
  
  return String.Empty;
}

 

 객체간의 기능 이동 (Moving Features Between Objects)

▪  객체 디자인에서 가장 중요한 것은 책임을 어디 둘지 결정하는 것이다. 이런 문제는 Move Method와 Move Field를 사용해 동작을 옮김으로 해결 할 수 있다. 둘다 사용할 경우 먼저 Move Field후에 Move Method를 사용한다.

▪  클래스는 종종 책임으로 비대해진다. 이럴 경우 Extract Class로 책임을 줄인다. 일이 없다면 Inline Class로 합치자. 만약 다른 클래스가 사용중이라면, Hide Delegate를 써서 사실을 숨긴다. 때로는 위임 클래스를 남발해 Middle Man이 생길 수 있다.

▪  어떤 클래스의 소스 코드에 접근할 수는 없지만, 수정할 수 없는 클래스로 책임을 옮기고 싶을 때만 이 방법을 사용한다. 옮길 것이 Introduce Foreign Method(메소드 하나를 랩퍼 형태로 반환 값을 사용)를 사용하고, 많다면 Introduce Local Extension(랩퍼 형태로 반환 값을 사용)을 사용한다.

더보기
// # Introduce Foreign Method (메소드를 해당 클래스에 추가하고, 반환값을 사용하는 방식으로 만든다.)
// 즉 랩퍼형태로 만듬.

class Report 
{
  void SendReport() 
  {
    DateTime nextDay = previousEnd.AddDays(1);
  }
}

// # ...

class Report 
{
  void SendReport() 
  {
    DateTime nextDay = NextDay(previousEnd);
  }
  
  private static DateTime NextDay(DateTime date) 
  {
    return date.AddDays(1);
  }
}

// 1. 코드 여려 곳에서 사용할 수 있기 때문에 중복을 방지한다. 
// 2. 외래 호출이 복제보단 낫다.
메소드를 포함하는 새 클래스를 만들고, 이를 유틸리티 클래스의 하위나 랩퍼 형태로 제작한다.

 

 데이터 구성 (Organizing Data)

▪  Self Encapsulate Field은 필요하다. 객체가 자신의 데이터에 직접 접근하도록 해야 하는지, 접근자로 접근하는지는 중요하다. 접근자 사용시 Self Encapsulate Field를사용한다.직접 접근하는 방식을 보통사용하는데,이는리펙토링이쉽기때무이다.

더보기
// # Self Encapsulate Field ()

class Range 
{
  private int low, high;
  
  bool Includes(int arg) 
  {
    return arg >= low && arg <= high;
  }
}

class Range 
{
  private int low, high;
  
  int Low // 프로퍼티
  {
    get { return low; }
  }
  
  int High //프로퍼티
  {
    get { return high; }
  }
  
  bool Includes(int arg) 
  {
    return arg >= Low && arg <= High;
  }
}

// 1. getter/setter 보다 유연하고 쉽게 구현 가능하다.
// 2. getter/setter를 재정의 할 수 있다.

▪ 객체의 장점은 새로운 타입을 정의할 수 있는 것이다. Replace Data Value with Object(데이터 타입을 객체로)로 멍청한 데이터 타입을 똑똑한 객체로 바꿀수 있다. 이 객체가 많이 활용되는 인스터스라면, Change Value to Reference(객체와 객체를 참조로 연결)를 사용해 참조 객체로 만든다.

 

▪ 객체간 연결은 단방향이거나 양방향 일 수 있다. 단방향 연결은 구현하기 쉽지만, 새로운 기능을 넣을때 Change Unidirectional Association to Bidirectional를 사용해야 한다. Change Bidirectional Association to Unidirectional는 양방향 링크가 더이상 필요없다는 것을 알게 되었을 때 복잡성을 제거할 때 사용한다.

더보기
Change Unidirectional Association to Bidirectional : 양방향 연관

 

  1. 역 연관을 보유하기위한 필드를 추가하자.

  2. 어떤 클래스가 "주요"가 될지 결정한다. 이 클래스에는 요소가 추가되거나 변경 될 때 연관을 작성하거나 업데이트하는 메소드가 포함되어 클래스에서 연관을 설정하고 연관된 오브젝트에서 연관을 설정하기위한 유틸리티 메소드를 호출한다.

  3. "비 우성"클래스에서 연관을 설정하기위한 유틸리티 메소드를 작성하십시오. 메소드는 필드를 완성하기 위해 매개 변수에 제공된 것을 사용해야합니다. 나중에 다른 용도로 사용되지 않도록 방법에 명확한 이름을 지정하십시오.

  4. 단방향 연관을 제어하기위한 이전 메소드가 "주요"클래스에있는 경우 연관된 오브젝트의 유틸리티 메소드 호출로이를 보완하십시오.

  5. 연결을 제어하는 ​​이전 방법이 "비우량"클래스에있는 경우 "우성"클래스에서 메소드를 작성하고 호출 한 후 실행을 위임하십시오.

Change Bidirectional Association to Unidirectional : 양방향 제거

 

  1. 다음 중 하나에 해당하는지 확인해라.

    • 연관이 사용되지 않음.

    • 데이터베이스 쿼리를 통해 연결된 개체를 얻는 또 다른 방법이 있습니다.

    • 연관된 객체는이를 사용하는 메소드에 인수로 전달 된다.

  2. 상황에 따라 다른 개체와의 연결이 포함 된 필드를 다른 방법으로 개체를 가져 오기위한 매개 변수 또는 메서드 호출로 바꿔야합니다.

  3. 관련 개체를 필드에 할당하는 코드를 삭제하십시오.

  4. 현재 사용하지 않는 필드를 삭제하십시오.

종종 GUI 클래스가 비즈니스 로직을 처리할 수 있는데, 그렇게 하면 안된다. 동작을 적절한 도메인 클래스로 옮겨야한다. Duplicate Observed Data를 사용하여 GUI를 지원해라. 데이터 중복은 안좋지만, 이는 어쩔수 없는 상황이다.

 

객체에서 가장 중요한건 캡슐화이다. 그러니 public 데이터들은 Encapsulate Field를 사용해 덮어주자. 컬랙션은 Encapsulate Collection, 전체 레코드가 들어난다면 Replace Record With Data Class를 사용한다.

 

▪ 타입 코드는 특별 취급이 필요한 데이터이다. 열거형이나 static final integer로 구현되기도 않다. 만약 코드가 정보를 위한 것이고 클래스의 동작을 바꾸지 않는다면 Replace Type Code with Class를 사용하여, 타입 체크를 보다 확실히 하고 동작을 바꿀 수 있는 플랫폼을 제공하도록 할 수 있다. 만약 타입 코드에 의해 클래스의 동작이 영향을 받는다면, 가능하면 Replace Type Code with Subclasses를 사용하라. 만약 그렇게 할 수 없을 때는 좀더 복잡한(그러나 더 융통성 있는) Replace Type Code with State/Strategy를 사용하라. -> 먼말인지 모르겠음

 

 조건문 단순화 (Simplifying Conditional Expressions)

▪ 핵심 리펙토링은 Decompose Conditional(리턴값 이용)이며, 조건문을 조각으로 분해하는 것을 의미한다. 이것은 조건 로직을 세부 동작에서 분리한다.

더보기
// # Decompose Conditional (리턴값을 이용한다.)

if (date < SUMMER_START || date > SUMMER_END) 
{
  charge = quantity * winterRate + winterServiceCharge;
}

else 
{
  charge = quantity * summerRate;
}

// # ...

if (isSummer(date))
{
  charge = SummerCharge(quantity);
}

else 
{
  charge = WinterCharge(quantity);
}

모두 같은 효과를 나타낼 때는 Consolidate Conditional Expression를 사용하고, 조건문 안의 중복 코드는 Consolidate Duplicate Conditional Fragments를 쓴다.

더보기
// # Consolidate Conditional Expression (모든 조건은 단일 표현식으로 적어라.)

double DisabilityAmount() 
{
  if (seniority < 2) 
  {
    return 0;
  }
  
  if (monthsDisabled > 12) 
  {
    return 0;
  }
  
  if (isPartTime) 
  {
    return 0;
  }
  
  // Compute the disability amount.
}

// # ...

double DisabilityAmount() 
{
  if (IsNotEligableForDisability()) 
  {
    return 0;
  }
  
  // Compute the disability amount.
}

// 1. 동일한 대상을 가진 여러 조건을 결합하면, 1개의 작업으로 1개만 수행하고 있는 구조로 된다.
// 2. 복잡한 표현식을 조건의 목적을 설명하는 이름으로 분리할 수 있다.

===============================================================================================
// # Consolidate Duplicate Conditional Fragments (중복된 함수는 조건의 외부로 이동한다.)

if (IsSpecialDeal()) 
{
  total = price * 0.95;
  Send();
}

else 
{
  total = price * 0.98;
  Send();
}

// # ...

if (IsSpecialDeal())
{
  total = price * 0.95;
}

else
{
  total = price * 0.98;
}

Send();

하나의 종료점을 가져야할 필요는 없다. 즉 컨트롤 플래그를 굳이 사용할 필요는 없음. 조건문에서 특별한 경우는 Replace Nested Conditional with Guard Clauses를 사용하고, 다루기 힘든 컨트롤 플래그를 제거하기 위해서는 Remove Control Flag를 쓰자.

 

더보기
// # Replace Nested Conditional with Guard Clauses (평평한 형태로 조건문을 만들어야한다.)

public double GetPayAmount()
{
  double result;
  
  if (isDead)
  {
    result = DeadAmount();
  }
  
  else 
  {
    if (isSeparated)
    {
      result = SeparatedAmount();
    }
    
    else 
    {
      if (isRetired)
      {
        result = RetiredAmount();
      }
      
      else
      {
        result = NormalPayAmount();
      }
    }
  }
  
  return result;
}

// # ...

public double GetPayAmount() 
{
  if (isDead)
  {
    return DeadAmount();
  }
  
  if (isSeparated)
  {
    return SeparatedAmount();
  }
  
  if (isRetired)
  {
    return RetiredAmount();
  }
  
  return NormalPayAmount();
}

 

더보기
// # Replace Conditional With PolyMorphism (하나의 추상클래스와 하위 클래스들을 만든다.)

public class Bird 
{
  public double GetSpeed() 
  {
    switch (type) 
    {
      case EUROPEAN:
        return GetBaseSpeed();
        
      case AFRICAN:
        return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
        
      case NORWEGIAN_BLUE:
        return isNailed ? 0 : GetBaseSpeed(voltage);
        
      default:
        throw new Exception("Should be unreachable");
    }
  }
}

// # ...

public abstract class Bird 
{
  public abstract double GetSpeed();
}

class European: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed();
  }
}

class African: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
  }
}

class NorwegianBlue: Bird
{
  public override double GetSpeed() 
  {
    return isNailed ? 0 : GetBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.GetSpeed();

// 1. Tell-Don't-Ask 원칙을 준수한다. 필요한 작업을 알려주고 스스로 결정하는 방식
// 2. 새롭게 변형하는 경우 기존 코드를 건드리지 않고 새 하위 클래스를 추가해 만들 수 있다.

// 방법
// 대체 동작을 포함 할 준비가 된 클래스 계층이 있어야 한다. 계층이 없는 경우 계층을 만든다. 
// 타입 코드를 서브 클래스로 교체한다 . 특정 객체 속성의 모든 값에 대해 서브 클래스가 생성한다. 
// 이 방법은 객체의 다른 속성에 대한 하위 클래스를 만들 수 없으므로 간단하지만 유연성이 떨어진다..

// 유형 코드를 State / Strategy로 바꾼다.
// 클래스는 특정 객체 속성 전용이며 하위 속성은 속성의 각 값에 대해 생성된다.. 
// 현재 클래스는이 유형의 객체에 대한 참조를 포함하고 실행을 위임한다.

// 리팩토링 단계
// 조건이 다른 조치를 수행하는 메소드에있는 경우 추출 메소드를 수행한다.
// 각 계층 구조 서브 클래스에 대해 조건부를 포함하는 메소드를 재정의하고, 해당 조건부 분기의 코드를 해당 위치로 복사한다.
// 조건부의 분기를 삭제한다. 이를 조건이 비워 질 때까지 바놉ㄱ한다.
// 그런 다음 조건을 삭제하고, 메소드 abstract를 선언한다.

 

 메소드 호출의 단순화 (Making Method Calls Simpler)

인터페이스 리펙토링에 관한 얘기다.

가장 간단한 방법은 메소드, 변수, 클래스의 이름을 이름을 바꾸는 것이다. 

파라미터는 Add Parameter나 Remove Parameter로 간단히 리펙토링한다. 객체를 처음 접한 프로그래머는 종종 긴 파라미터 리스트를 사용하는데, 이를 짧게 바꿔야한다. 객체에서 여러 값을 전달하면, 객체를 건내주는 Preserve Whole Object 방식을 사용한다. 객체가 없을 경우 Introduce Paraemter Object를사용해만든다.메소드가이미접근하고있는ㅐㄱ체에서 데이터를 얻으면 Replace Parameter with Method 로 파라미터를 제거하자. 조건에 따라 결정되는데 사용 되는 파라미터는 Replace Parameter with Explicit Methods를 사용할 수 있다 Parameterize Method(매개 변수로 결합한다.)로 파라미터를 추가해 여러 개의 비슷한 메소드를 하나로 합칠 수 있다.

 

더보기
// # Preserve Whole Object (여러 값을 얻은 다음 매개변수로 넘기지 말고, 전체 개체를 보낸다.)

int low = daysTempRange.GetLow();
int high = daysTempRange.GetHigh();
bool withinPlan = plan.WithinRange(low, high);

// # ...
bool withinPlan = plan.WithinRange(daysTempRange);

// 1. 어려운 매개변수 대신 이해하기 쉬운 오브젝트로 표시된다.
// 2. 더 많은 데이터를 필요할 경우, 모든 위치에서 추가할 필요 없다. 개체로 묶은 함수만 고치면 된다.

// a. 다만 유연성이 떨어진다.

===============================================================================================
// # Replace Parameter with Method Call (변수를 전달하지 말고, 쿼리 호출을 사용하자.)

int basePrice = quantity * itemPrice;
double seasonDiscount = this.GetSeasonalDiscount();
double fees = this.GetFees();
double finalPrice = DiscountedPrice(basePrice, seasonDiscount, fees);

// # ...

int basePrice = quantity * itemPrice;
double finalPrice = DiscountedPrice(basePrice);

===============================================================================================
// # Replace Parameter with Explicit Methods (메소드의 개별 부분을 자체 메스드로 추출하고 호출 방식으로)

void SetValue(string name, int value) 
{
  if (name.Equals("height")) 
  {
    height = value;
    return;
  }
  
  if (name.Equals("width")) 
  {
    width = value;
    return;
  }
  
  Assert.Fail();
}

// # ...

void SetHeight(int arg) 
{
  height = arg;
}

void SetWidth(int arg) 
{
  width = arg;
}

  수정자와 질의자가 결합 된 경우는 Separate Query from Modifier(get과 set을 구분)로 상태를 바꾸는 메소드(수정자)와 상태를 묻는 메소드(질의자)를 분리한다.

어떤 데이터를 숨길지, 보여야할지 구분해야한다. 그후에는 Hide Method나 Remove Setting Method로 숨길 수 있다.

에러 발생시 예외 처리(Replace Error Code with Exception)를 사용하면 좋을 수 있다.

더보기
# Replace Error Code with Exception(특수 값 반환을 하지말고, 예외를 던져라)

int Withdraw(int amount) 
{
  if (amount > _balance) 
  {
    return -1;
  }
  
  else 
  {
    balance -= amount;
    return 0;
  }
}

// # ...

///<exception cref="BalanceException">Thrown when amount > _balance</exception>
void Withdraw(int amount)
{
  if (amount > _balance) 
  {
    throw new BalanceException();
  }
  
  balance -= amount;
}

 

 

일반화 다루기(Dealing With Generalization) 

  일반화는 하나의 리펙토링 군을 만들거나, 상속 구조에서 메소드를 옮길 때 다룬다. Pull Up Field와 Pull Up Method는 둘다 상속 구조의 위쪽으로 기능을 옮기고, Down으로 내릴 수 있다. 생성자는 상속 구조 위로 옮기기 어렵기 때문에, Pull Up Constrouctor Body에서 이 문제를 다룬다. 생성자를 아래로 옮기는 것 보다, Replace Constructor with Factory Method로 내리는게 유용하다.

더보기
// # Pull Up Constructor Body (슈퍼 클래스 생성자를 작성하고, 서브에서 슈퍼의 생성자를 호출한다.)

public class Manager: Employee 
{
  public Manager(string name, string id, int grade) 
  {
    this.name = name;
    this.id = id;
    this.grade = grade;
  }
}

// # ...

public class Manager: Employee 
{
  public Manager(string name, string id, int grade): base(name, id)
  {
    this.grade = grade;
  }
}

===============================================================================================

// # Replace Constructor with Factory Method (팩토리 메소드로 작성하고, 생성자를 호출한다.)

public class Employee 
{
  public Employee(int type) 
  {
    this.type = type;
  }
}

// # ...

public class Employee
{
  public static Employee Create(int type)
  {
    employee = new Employee(type);

    return employee;
  }
}

기능을 위아래로 옮기는 것 말고, 새로운 클래스를 만들어 구조를 변경한다. Extract Subclass, Extract Superclass Extract Interface(동일한 부분은 인터페이스로)는 타입 시스템에서 기능의 작은 부분에 표시 하고 싶을 때 중요하다. 상속 구조에서 불필요한 것은  Collapse Hierachy(클래스 차이가 없으면 합쳐라)로 제거한다. 

 

 대규모 리펙토링 (Big Refactorings)

▪ 2가지 작업을 한번에 처리하는 상속 구조가 있으면 (Tease Apart Inheritance) 

더보기
2개의 상속 구조를 만들고, 하나를 호출하도록 위임해라.

▪ 절차적 스타일로 작성된 코드가 있다면 (Convert Procedural Design to Objects)

더보기
데이터 레코드를 객체로 바꾸고, 동작을 쪼개 객체로 옮겨라.

 

▪ 도메인 로직을 포함한 GUI 클래스를 가지고 있다면

더보기
도메인 로직을 분리하여 도메인 클래스를 만들어라.

 

▪ 너무 많은 작업을 하거나, 부분적으로라도 많은 조건문이 있는 클래스는(Extract Hierarchy)

더보기
각각의 서브클래스가 특정 작업을 담당하도록 클래스의 상속 구조를 만들어라.

 

사용 스킨 : dracula

 

참고 사이트 

https://refactoring.guru/

'공부' 카테고리의 다른 글

리펙토링 1부  (0) 2019.12.02

댓글