[C++] 누구나 쉽게, 리팩토링(클린코드)-⑤ NULL체크를 위한 널 객체

리팩토링

 

리팩토링은 내부의 코드를 개선하는 하나의 방법론이자 솔루션을 뜻합니다. 

 

리팩토링을 한마디로 말하자면, 나쁜 냄새가 나는 코드를 최적화하는 것입니다. 여기서 주의할 점은 런타임 시의 최적화가 아니라 프로그래밍을 할 때 버그 없는 프로그램을 만드는 것입니다외부 인터페이스는 그대로 두고 내부의 코드만을 개선하여 재사용과 가독성을 높이는 데 목적이 있습니다.

 

 

[ 리팩토링 시리즈 ]

[C++] 누구나 쉽게, 리팩토링(클린코드)-① 순수가상함수/추상클래스

[C++] 누구나 쉽게, 리팩토링(클린코드)-② 인터페이스

[C++] 누구나 쉽게, 리팩토링(클린코드)-③ 캐스팅

 [C++] 누구나 쉽게, 리팩토링(클린코드)-④ 상속 (개념과 특징)

 

 

이번시간에는 리팩토링을 진행하면서 Null 객체를 도입에 대해 살펴보겠습니다.

 


5. 널 객체

파일과 콘솔에서 로그를 기록하는 FileLog와 ConsoleLog 클래스를 구현합니다.  각각의 객체가 존재할 때 Write를 하는 LogService 클래스도 구현합니다.

 

- FileLog : 파일에서 로그를 기록하는 클래스

- ConsoleLog : 콘솔에서 로그를 찍는 클래스

- LogService : 각 객체에서 write하는 클래스

 

- run() 함수

일하자! LogService 클래스에서는 run 함수 안에서 FileLog가 null이 아니면 FileLog.write()를, ConsoleLog가 null이 아니면 console.write()를 하게 됩니다.

 

 

그런데! 같은 일(write())을 하는데 객체마다 한번씩 여러 번을 처리하고 있습니다.

????????????!!!!!!!!!!!!!!!!!!!! 배운거에요!!!!!!!!!!!!!

 

 

과거의 문제 해결방법 도입

위와 같은 경우 강한 결합Tightly coupling 으로 OCP를 위배합니다. 다형 객체를 하나로 처리하기 위해서 부모 클래스가 필요합니다.  파일 로그와 콘솔 로그의 다형적 객체를  하나의 타입으로 처리하기 위해 부모 클래스 Log를 도입합니다. 리스코프의 치환 원칙 "공통 속성 값은 부모가 제공해야 한다."를 생각해보니 순수가상함수가 생각나시죠? 바로 강제화!

 

그리고 이는 서로 다른 클래스를 연결하는 인터페이스의 성향이 강합니다. 접두어 I를 붙여 ILog로 표현해봅시다.

class ILog 
{
  public :
      virtual void write() = 0;
};

class FileLog : public ILog
{
  public :
	void write(){ cout << "File::write()" << endl; }
};

class ConsoleLog : public ILog
{
  public :
	void write(){ cout << "Console::write()" << endl; }
};

ILog 클래스(인터페이스)를 만들어주고 write()를 순수가상함수로 만들어 자식 클래스에서 기능을 구현하도록 강제할 것이에요. FileLog, ConsoleLog는 모두 ILog를 상속받도록 하구요. 이렇게 하면 25 라인의 LogService에서 28, 29라인 대신 30 라인으로, 34 라인 대신 35 라인처럼 구현할 수 있게 됩니다.

 

새로운 문제 발생

!!!!띠용

30-34 라인에서 if(pLog != 0) pLog가 널 여부를 체크를 하게 됩니다. 

지금이야 2번이지만 100번의 write를 한다고 하면 할 때마다 100번을 모두 널 체크를 해주어야되겠죠?

 

그러나 Null check를 자주 사용하는 것 역시 벧 스멜~

Null check가 계속되면 Null object를 만들어서 null check를 없애야 합니다.

 

 

문제 해결

 

♥ 널 객체를 도입해라(Introduce Null Object)

 

ILog 클래스에 null인지 체크하는 is_null()을 만들고 기본적으로는 false를 반환하게 하여 null이 아님을 리턴해줍니다.

class ILog 
{
  public :
      virtual void write() = 0;
      virtual bool is_null() { return false; };	// null인지 체크(default : not null)
};

 

그리고 ILog를 상속받는 NullLog를 만듭니다. 그리고 (부모클래스인 ILog에서 virtual로 구현을 했으니 무조건 구현을 해주어야하지만 하는 일은 없으니) write()를 물려받아 빈 몸체로 만들고, is_null()은 true를 반환하게 합니다.

 

class NullLog : public ILog
{
  public :
      void write() {};
      virtual bool is_null() { return true; };
};

 

 

38 라인처럼 ILog* p = new NullLog를 사용하면 null인 객체는 NullLog에서 true를, null이 아닌 객체는 ILog에서 false를 반환하게 됩니다. 이렇게  if(pLog != 0) 없이도 널 체크가 가능합니다.

 

 

 

정리

널인지 여부를 확인하는 Null Check를 자주 사용하는 것은 Bed Smell! 

>> Null Obejct를 사용하라.

 

 

 

참고

우선 ILog 인터페이스를 만든 후에 → Null Check를 진행한 것처럼

♥ 리팩토링을 할 때에는 문제를 쪼갠 후, 하나씩 리팩토링해 나가는 것이 중요합니다.

♥ 또한 테스트 케이스를 작성해서 리팩토링 전의 결과와 후의 결과가 같은지 검증하는 것이 필요합니다. 

 

반응형
그리드형

댓글

❤️김세인트가 사랑으로 키웁니다❤️