본문 바로가기
공부

리펙토링 1부

by 노오오오오옹 2019. 12. 2.

 

 개요

▪ 정의 : 소프트웨어를 보다 쉽게 이해할 수 있고, 적은 비용으로 수정할 수 있도록 겉으로 보이는 동작의 변화없이 내부 구조를 변경하는 것이다.

이유 :  디자인을 개선하기 때문에 이해하기 쉬워진다. 즉 버그를 찾기 쉬워지고, 프로그램을 빨리 작성할 수 있다.

▪ 시기 :  다른 기능을 추가할 때 리펙토링을 하면 코드에 대한 이해도가 높아져 속도가 빨라진다. 버그를 수정할 때 해라.

▪ 문제 : DB, 인터페이스 변경

▪ 방법 : 그럴듯한 솔루션을 만들고 나서 코딩을 하고 리펙토링 한다. 정확한 디자인을 하는 것이 아니라 '적절한 솔루션'을 먼저 찾는 것이다.

 

 Bad Smeels in Code

어떠한 리펙토링을 해야 할 지 확신히 없을 때 참조할 것.

 

 중복된 코드 (Duplicated Code)

중복된 코드 구조가 나타난다면, 그것을 하나의 함수로 합쳐서 개선한다.

▪ 코드는 똑같지만 완전히 똑같지 않은 경우 (Extract Method)

더보기
// # 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. 코드의 독립적인 부분을 분리하기 때문에 오류 발생이 적다.

▪ 다른 알고리즘 내에서 같은 일을 하는 메소드 (Extract Method -> PullUp Field)

더보기
서브 클래스가 같은 필드를 가질 경우, 슈퍼 클래스로 옮긴다.

▪ 같은 클래스내 2개 이상의 메소드에 중복 코드 존재 (Extract Method)

더보기
// # 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. 코드의 독립적인 부분을 분리하기 때문에 오류 발생이 적다.

▪ 2개 이상의 서브 클래스내 중복 코드 존재 (Substitute Algorithm)

더보기
// # Substitute 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;
}

▪ 2개 이상의 연관 없는 클래스 내의 중복코드 (Extract Class)

더보기
// # Extract Class (한 학급이 두 학급의 일을 한다면, 관련 기능들을 가각의 필드와 메소드로 구분한다.)

class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}

// # ...

class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}
한 학급이 두 학급의 일을 하지말고, 각각의 필드와 메소드를 구분하자.

 

 긴 메소드 (Long Method)

긴 메소드는 이해하기 어려워 수정이 힘들다. 즉 메소드는 짧아야한다. 또한 메소드의 이름을 코드 내부를 보지 않아도 이해할 수 있도록 명확하게 해야한다.

 

대부분은 이 경우(코드 덩어리를 별도의 메소드로)다. (Extract Method)

더보기
// # 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. 코드의 독립적인 부분을 분리하기 때문에 오류 발생이 적다.

▪ Extract Method 하는중 parameter를 많이 넘겨야 하거나, 임시 변수를 많이 사용하게 되는 경우

(Replace Temp With Query, Introduce Parameter Object, Preserve Whole Object)

더보기
// # 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 Parameter Object (그룹은 하나의 객체로)

function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}

// # ...
 
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}

// 1. 어려운 매개 변수 대신 이해할 수 있는 단일 오브젝트로 표시되어 읽기 쉽다.
// 2. 흩어져있는 동일한 매개 변수 그룹은 코드 복제를 만든다. 
반복되는 매게변수가 있다면, 객체로 교체한다.
// # 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. 다만 유연성이 떨어진다.

▪ 주석(작동 원리, 설명)이 붙은 코드는, 주석에 기반한 이름의 메소드 명칭으로 바꾼다.

▪ 조건문이나 루프가 자주 보일 때 (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);

// 1. 명확하게 명명된 메소드로 시간이 지난후 쉽게 유지보수가 가능하다.
// 2. 짧은 표현에도 적용할 수 있고, 가독성이 좋다.

 

 거대한 클래스 (Large Class)

1개의 클래스가 다양한 행동을 할때 많은 변수를 사용한다. 많은 변수를 사용하면 코드 중복이나 혼란이 올 확률이 높다.

 

▪ 수 많은 변수 및 임시변수, 사용할 필요 없는 인스턴스 변수들 (Extract Class, Extract Subclass)

더보기
// # Extract Class (한 학급이 두 학급의 일을 한다면, 관련 기능들을 가각의 필드와 메소드로 구분한다.)

class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}

// # ...

class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}

// 1. 단일  책임 원칙을 준수하는데 도움이 된다. 즉 코드는 명확하고 이해하기 쉬워진다.
// 2. 단일 책임 클래스라 보다 신뢰성 있고 변경이 허용되지 않는다. 

// a. 과도하게 사용할 경우 'Inline Class'를 사용해야 한다.
한 학급이 두 학급의 일을 하지말고, 각각의 필드와 메소드를 구분하자.

 

특정 경우에 사용되는 클래스의 기능은, 서브 클래스로 작성한다.

▪ GUI 클래스에서 데이터부가 중복될때, AWT -> Swing Component로 바꿀때 (Duplicate Observed Data)

더보기
클래스의 저장된 도메인 데이터를 GUI가 담당하지 않을때, 별도의 클래스로 분리하여 도메인과 GUI간 연결 및 동기화를 한다.

 

 긴 파라미터 리스트 (Long Parameter List)

긴 리스트는 이해하기 어렵거나, 일관성이 없어 사용하기 불편하다. 다른 데이터가 필요할 때 마다 계속 고쳐야 하니 적어야한다. 대부분은 객체로 넘기자.

 

▪ 이미 알고 있는 객체에 요청하여 파라미터의 데이터를 얻을 수 있으면 (Replace Parameter With Method)

더보기
// # Replace Parameter With Method (일일히 값을 전달하지말고 메소드 본문 내에서 쿼리 호출 방식으로 만들자.)

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);

// 1. 불필요한 매개 변수를 제거해 단순화 한다.

// a. 다만 요구 변수가 다르면 다양한 메소드가 필요한다.

▪ 한 객체로부터 주워 모은 데이터 뭉치를 그 객체 자체로 바꾸기 위해서 (Preserve Whole Object)

더보기
// # 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. 다만 유연성이 떨어진다.

 

▪ 객체와 관계없는 여러 개의 데이터 아이템을 가지는 경우 (Introduce Parameter Object)

더보기
// # Introduce Parameter Object (그룹은 하나의 객체로)

function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}

// # ...
 
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}

// 1. 어려운 매개 변수 대신 이해할 수 있는 단일 오브젝트로 표시되어 읽기 쉽다.
// 2. 흩어져있는 동일한 매개 변수 그룹은 코드 복제를 만든다. 
반복되는 매게변수가 있다면, 객체로 교체한다.

 

 확산적 변경 (Divergent Change)

1개의 클래스를 변경할 때 다른 클래스들을 변경해야한다.

 

▪ 데이터 베이스 변수명을 바꾸면, 거기에 딸려오는 것들을 바꿔야한다. (Extract Class)

더보기
// # Extract Class (한 학급이 두 학급의 일을 한다면, 관련 기능들을 가각의 필드와 메소드로 구분한다.)

class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}

// # ...

class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}

// 1. 단일  책임 원칙을 준수하는데 도움이 된다. 즉 코드는 명확하고 이해하기 쉬워진다.
// 2. 단일 책임 클래스라 보다 신뢰성 있고 변경이 허용되지 않는다. 

// a. 과도하게 사용할 경우 'Inline Class'를 사용해야 한다.
한 학급이 두 학급의 일을 하지말고, 각각의 필드와 메소드를 구분하자.

 

 산탄총 수술 (Shotgun Surgery)

확산적 변경인데, 1개의 클래스가 다양한 클래스에  연관된 것이 많다. 여기저기 널려있는 것인데, 이러면 찾기 어렵거나 변경해야할 중요 사항을 까먹게 된다.

 

▪ 바뀌는 부분을 (Move Method, Move Field) 하여 하나의 클래스에 넣거나 새로 만든다.

더보기
다른 클래스에서 많이 사용되면, 이동해라. 필드도 마찬가지다.

 

▪ 모든 동작을 하나로 모은다. (Inline Class) 

더보기
Extract Class와 반대 개념으로, 별로 안쓰이면 뭉쳐라

 

 기능에 대한 욕심 (Feature Envy)

어떤 메소드가 자신이 속해 있는 클래스의 데이터가 아닌, 달느 클래스의 데이터가 필요한 경우

 

어떤 값을 계산하기 위해 다른 객체에 있는 여러 개의 get 메소드를 호출하는 경우 (Move Method, Extract Method)

더보기
// # 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. 코드가 독립적으로 분리되어 오류가 발생할 가능성이 적다.

 

 데이터 덩어리 (Data Clumps)

클래스의 변수 선언 필드나 함수 프로토 타입 장소에 항상 같이 몰려다니는 데이터들은 객체로 만들어야한다. 데이터들을 Extract Class 했을대 는 Field들의 묶음으로 보일 수 있지만 걱정할 필요 없다.

 

▪ 덩어리를 객체로 바꾼다 (Extract Class)

더보기
// # Extract Class (한 학급이 두 학급의 일을 한다면, 관련 기능들을 가각의 필드와 메소드로 구분한다.)

class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}

// # ...

class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}

// 1. 단일  책임 원칙을 준수하는데 도움이 된다. 즉 코드는 명확하고 이해하기 쉬워진다.
// 2. 단일 책임 클래스라 보다 신뢰성 있고 변경이 허용되지 않는다. 

// a. 과도하게 사용할 경우 'Inline Class'를 사용해야 한다.
한 학급이 두 학급의 일을 하지말고, 각각의 필드와 메소드를 구분하자.

 

▪ 파라미터 리스트를 단순하게 수정한다 (Introduce Parameter Object, Preserve Whole Object)

더보기
// # 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 Parameter Object (그룹은 하나의 객체로)

function amountInvoiced(startDate, endDate) {...}
function amountReceived(startDate, endDate) {...}

// # ...
 
function amountInvoiced(aDateRange) {...}
function amountReceived(aDateRange) {...}

// 1. 어려운 매개 변수 대신 이해할 수 있는 단일 오브젝트로 표시되어 읽기 쉽다.
// 2. 흩어져있는 동일한 매개 변수 그룹은 코드 복제를 만든다. 
반복되는 매게변수가 있다면, 객체로 교체한다.
// # 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. 다만 유연성이 떨어진다.

 

 기본 타입에 대한 강박관념 (Primitive Obsession)

객체의 유용한 점은 기본 타입과 레코드 타입의 경계를 흐리게 하거나 없애버린 것이다. 언어의 기본 타입과 구별할 수 없는 작은 클래스를 쉽게 작성할 수 있는 객체를 사용하자. 

 

▪ 데이터 값이 코드이고 값이 동작에 영향이 없다면? (Replace Type Code With Class)

더보기
프로그램 동작에 영향을 미치지 않는 타입 코드가 필드에 있으면, 새 클래스를 만들고 해당 개체를 사용한다.

 

▪ 타입 코드에 의존하는 조건문이 있을땐? (Replace Type Code With Subclasses)

더보기
프로그램 동장에 영향을 주는 타입이 있으면, 각 값에 대한 서브 클래스를 작성한다.

 

▪ 항상 몰려다녀야할 필드 그룹이 있다면? (Extrace Class)

더보기
// # Extract Class (한 학급이 두 학급의 일을 한다면, 관련 기능들을 가각의 필드와 메소드로 구분한다.)

class Person {
  get officeAreaCode() {return this._officeAreaCode;}
  get officeNumber()   {return this._officeNumber;}

// # ...

class Person {
  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  get officeNumber()   {return this._telephoneNumber.number;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  get number()   {return this._number;}
}

// 1. 단일 책임 원칙을 준수하는데 도움이 된다. 즉 코드는 명확하고 이해하기 쉬워진다.
// 2. 단일 책임 클래스라 보다 신뢰성 있고 변경이 허용되지 않는다. 

// a. 과도하게 사용할 경우 'Inline Class'를 사용해야 한다.
한 학급이 두 학급의 일을 하지말고, 각각의 필드와 메소드를 구분하자.

 

▪ 배열을 쪼개서 쓰고 잇는 자신을 발견한다면? (Replace Array With Object)

더보기
// # Replace Array with Object (다양한 타입의 데이터가 포함된 배열은 객체로 바꾼다.)

string[] row = new string[2];
row[0] = "Liverpool";
row[1] = "15";

// # ...
Performance row = new Performance();
row.SetName("Liverpool");
row.SetWins("15");

// 1. 주 클래스나 다른 곳에 저장된 모든 관련 동작을 배치할 수 있다.
// 2. 클래스의 필드는 배열의 요소보다 문서화 하기 쉽다.

 

 스위치문 (Switch Statements)

객체 지향 코드의 가장 명확한 특징은 switch 문을 적게 쓰는 것이다. switch문을 사용시 코드 수정을 한다면, 모든 switch문을 찾아 바꿔야한다.

 

▪ 대부분의 경우 다형성을 한다.(polymorphism)

더보기
// # 다형성 (자바 기준)

public class CrappySwitch {
  public string DoSomething(int foo) {
    switch (foo) {
      case 0: return "foo";
      case 1: return "bar";
      case 2: return "baz";
    }
  
    return null;
  }
}

// # 팩토리 패턴 사용

public interface IFooable {
  string DoSomething();
}

public class ZeroFooable : IFooable {
  public string DoSomething() {
    return "foo";
  }
}

public class OneFooable : IFooable {
  public string DoSomething() {
    return "bar";
  }
}

public class TwoFooable : IFooable {
  public string DoSomething() {
    return "baz";
  }
}

public class DefaultFooable : IFooable {
  public string DoSomething() {
    return null;
  }
}

public class FooableFactory {
  public IFooable GetFooable(int foo) {
    switch (foo) {
      case 0: return new ZeroFooable();
      case 1: return new OneFooable();
      case 2: return new TwoFooable();
    }

    return new DefaultFooable();
  }
}

public class CrappySwitch {
  private readonly FooableFactory factory;

  public CrappySwitch(FooableFactory factory) {
    this.factory = factory;
  }

  public string DoSomething(int foo) {
    return factory.GetFooable(foo).DoSomething();
  }
}

// 이 경우 코드는 줄어들지않고 늘어날 가능성이 높다. 다만 디자인을 개선하는 것이다.

▪switch-case 문을 Extract Method 한 뒤, polymorphism이 필요한 class에 Move Method 한다. 그리고 나서 Replace TypeCode With Subclasses 를 사용할 것인지 Replace Type Code With State /Strategy를 사용할 것인지를 결정한다. 상속구조를 정의할 수 있을때에는 Replace Conditional With PolyMorphism 한다. 

더보기
프로그램 동장에 영향을 주는 타입이 있으면, 각 값에 대한 서브 클래스를 작성한다.

 

프로그램 동장에 영향을 주는 타입이 있으면, 각 값에 대한 서브 클래스를 작성한다.
// # 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. 새롭게 변형하는 경우 기존 코드를 건드리지 않고 새 하위 클래스를 추가해 만들 수 있다.

▪ 조건 case에 null이 있을 경우 (Introduce Null Object )

더보기
// # Introduce Null Object(null 체크가 과도하게 많을 경우!)

if (customer == null) 
{
  plan = BillingPlan.Basic();
}

else 
{
  plan = customer.GetPlan();
}

// # ...

public sealed class NullCustomer: Customer 
{
  public override bool IsNull 
  {
    get { return true; }
  }
  
  public override Plan GetPlan() 
  {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
customer = order.customer ?? new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.GetPlan();

// 1. Null 객체를 도입함으로 Null 체크를 줄일 수 있다.

// a. 새로운 클래스를 창출한다.

 

 게으른 클래스 (Lazy Class)

클래스를 생성할 때 마다 유지하고 이해하는 비용이 있다. 그래서 필요없을 경우 과감히 지워야 한다.

 

▪ 별로 쓰지 않는 subclass들 (Collapse Hierachy)

더보기
서브 클래스와 슈퍼 클래스가 동일한 계층이 있으면, 그냥 합쳐라

거의 쓸모 없는 클래스들 (Inline Class) 

 

 추측성 일반화 (Speculative Generality)

일어날 가능이 없는 일 까지 대비할 필요는 없다.

 

▪ 추상 클래스들이 할 일이 없을 경우 (Collapse Hierarchy )

▪ 불필요한 Delegation (Inline Class)

▪ 사용하지 않는 파라미터들을 가진 메소드(Remove Parameter)

▪ 추상적이고 두리뭉실한 메소드의 이름들(Rename Method)

 

 임시 필드 (Temporary Field)

때로는 객체 안의 인스턴스 변수가 특성 상황에서만 세팅되는 경우가 있다. 이는 객체의 모든 변수가 값을 가지고 있을거라는 선입관 때문에 이해하기 어렵다.

 

▪ 특정 상황에서만 값을 가지는 변수를 가진 객체 (Extract Class, Introduce Null Object)

 

 메세지 체인 (Message Chains)

클라이언트가 어떤 객체를 얻기 위해 다른 객체(B)에 물어보고, B는 C에게 물어보고, C는 D에게 물어보고...의 메시지 체인을 지운다. 즉 클라이언트가 클래스 구조와 커풀링 되어있다. 

 

▪ 커풀링으로 인해 코드 변경시 여러군데를 고쳐야한다. (Hide Delegate)

더보기
클래스 A에서 객체 B의 호출은 위임한다. 다만 미들맨이 발생할 수 있음.

 

 

 미들맨 (Middle Man)

클래스의 인터페이스를 보니 메소드 태반이 위임자 역활(delegation 남용)을 하고 있다.

 

▪ delegateion 남용 (Remove Middle Man)

더보기
위임자만 있으면? 그냥 직접 호출하도록 만든다. 위(Hide Delegate)랑 반대의 경우다.

 

▪몇몇 메소드가 일을 하지 않으면? (Inline Method)

추가되는 동작이 있다면, 미들맨을 실제 객체의 서브클래스로 바꾼다. (Replace Delegation With Inheritance )

더보기
클래스를 델리게이트 상속자로 만들어 위임 메소드가 필요없도록 만든다. 다만 위임만 있지 않고, 클래스의 부모가 없을때만 사용가능하다.

 

 

 다른 인터페이스를 가진 대체 클래스 (Alternative Classes with Different Interfaces)

▪ 같은 일을 하지만 다른 시그니처를 가진 메소드들 (Rename Method, Move Method)

 

 불완전한 라이브러리 클래스 (Incomplete Library Class)

▪ 라이브러리에서 제공하는 메소드들이 불충분 할 때 (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. 외래 호출이 복제보단 낫다.
메소드를 포함하는 새 클래스를 만들고, 이를 유틸리티 클래스의 하위나 랩퍼 형태로 제작한다.

 

 데이터 클래스 (Data Class)

각 필드는 getter/setter를 가지거나 안가지고 있을 수 잇다. 즉 외부의 개입이 가능해 멍청하게 관리하는 것이다.

 

▪ public 필드를 가질 경우 (Encapsulate Field)

더보기
// # Encapsulate Field (프로퍼티를 사용한다.)

class Person 
{
  public string name;
}

// # ...

class Person 
{
  private string name;

  public string Name
  {
    get { return name; }
    set { name = value; }
  }
}	

// 1. 액세스와 관련된 복잡한 작업을 쉽게 수행한다.

▪ Collection 필드를 가질 경우 (Encapsulate Collection)

더보기
게터는 읽기 전용으로 설정하고, 요소는 추가/삭제 형태로 제작한다.

▪ 값이 변경 안되는 필드들 (Encapsulate Collection)

 

 거부된 유산 (Refused Bequest)

서브 클래스는 메소드와 데이터를 부모한테 상속 받는다. 허나 원치 않을경우에는 어떻게 될까?

 

▪ 상속 구조가 잘못 되었다.(Push Down Method, Push Down Field) : 메소드와 필드를 서브 클래스로 보낸다.

▪ 인터페이스를 거부할 때 (Replace Inheritance With Delegation)

더보기
슈퍼 클랫으ㅢ 메소드중 일부만 사용하는 서브 클래스가 있을 때, 필드를 만들고 그안에 그안에 슈퍼 클래스의 객체를 넣는다. 슈퍼 클래스에 위임하고 상속을 제거한다.

 

 주석 (Comments)

주석이 많은 함수는 리펙토링을 해야한다. 왜냐? 이해가 안가는 코딩이기 때문이다. 주석은 무엇을 해야할지 모를 때 쓰는 것이다. 어떤 행동을 할지 서술하고,  이것을 왜 해야하는가를 추가한다.

 

▪ 코드 블록이 무슨 작업을 하는지 설명하기 위해 주석이 필요한 경우? (Extract Method)

▪ 메소드가 이미 추출 되어있는데도 여전히 코드가 하는 일에 대한 주석이 필요할 때? (Rename Method)

▪ 시스템으 필요한 상태에 대한 규칙을 설명할 필요 있을 떄? (Introduce Assertion)

 

리펙토링 2부 : https://noooong1231.tistory.com/11

 

리펙토링 2부

리펙토링 방법 메소드 정리 (Composing Methods) ▪ 대부분의 문제는 긴 메소드에서 나온다. 긴 메소드는 많은 정보를 가지고 있기 때문에 복잡하다. 그래서 이해하기어렵다. 가장 중요한 리펙토링은 Extract Meth..

noooong1231.tistory.com

사용 스킨 : dracula

 

참고 사이트 

https://refactoring.guru/

 

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

리펙토링 2부  (0) 2019.12.04

댓글