CLEAN CODE

스위치 문 리팩토링( Refactoring Switch statement )

아래는 많은 함수들에 쉽게 사용되고 있는 Switch structure (스위치 문)이다.

switch(typeCode)
	case type1:
    	return data specific to type1
    case type2:
    	return data specific to type2
    case type3:
    	return data specific to type3

일상 코드에서 흔히 볼 수 있는데,

무작정 스위치문을 사용하는 것은 확장하기 어려워지고,  Open-Closed Principle 규칙을 해한다.

 

Open-Closed Principle?

 Code should be open to extension, but closed to modification .

코드는 확장에는 열려있어야 하지만, 수정에는 닫혀있어야 한다.

 

자! 그럼 왜 switch문은 확장하는데 어려울까?

 

우선 새의 종류(Bird Type)로 예를 들어보자.

만약 위의 switch문의 type을 새들(bird)로 하고, 많은 함수에 switch문이 적용되었다고 생각하자.

이때, 새로운 새가 등장하게 된다면,

우리는 switch문이 있는 함수마다 새로운 새의 타입을 추가시켜줘야 한다.

  • 추가시켜주는 과정은 개발자를 굉장히 따분하게 만들어주고
  • 잠재적인 버그(bug)들도 많이 생기게 해 준다.
  • 그리고 어느 하나 메소드에 있는 것을 수정하는 것도 까먹을 수 있게 만들어준다. ( 많이 있는 경우 )

 

 

그럼, 어떻게 OCP를 해하는 이 switch문을 리팩토링 할 수 있을까?

 

해결방법

서브클래스(SubClass)를 사용해 타입 코드를 대체시키자!

  1. 타입 코드를 대표하는 각 타입을 위한 서브클래스를 추가
  2. 이 서브클래스들을 만들기 위해 팩토리 메소드 기법을 사용
  3. Push Down Method를 적용해 Switch문을 서브클래스들로 이동

Push Down Method란?

슈퍼클래스의 행위가 특정 서브클래스 하고만 관련 있다면 해당 메소드를 그 서브클래스로 옮기는 리팩토링 방법

(참고 : https://arisu1000.tistory.com/27629)

 

이렇게 말하면 무슨 말인지 알기 힘드니 순차적으로 리팩토링 작업을 진행해보겠다.

 

리팩토링 전 코드

public class Bird
{
	private readonly BirdType birdType;

	public Bird(BirdType type)
	{
		birdType = type;
	}
	public List<BirdColor> GetColors()
	{
		switch (birdType)
		{
			case BirdType.Cardinal:
				return new List<BirdColor>() { BirdColor.Black, BirdColor.Red };
			case BirdType.Goldfinch:
				return new List<BirdColor>() { BirdColor.Black, BirdColor.Yellow, BirdColor.White };
			case BirdType.Chickadee:
				return new List<BirdColor>() { BirdColor.Black, BirdColor.White, BirdColor.Tan };
		}
		throw new InvalidBirdTypeException();
	}
	public List<BirdFood> GetFoods()
	{
		switch (birdType)
		{
			case BirdType.Cardinal:
				return new List<BirdFood>() { BirdFood.Insects, BirdFood.Seeds, BirdFood.Fruit};
			case BirdType.Goldfinch:
				return new List<BirdFood>() { BirdFood.Insects, BirdFood.Seeds };
			case BirdType.Chickadee:
				return new List<BirdFood>() { BirdFood.Insects, BirdFood.Fruit, BirdFood.Seeds };
		}
		throw new InvalidBirdTypeException();
	}
	public BirdSizeRange GetSizeRange()
	{
		switch (birdType)
		{
			case BirdType.Cardinal:
				return new BirdSizeRange() { Lower=8, Upper=9 };
			case BirdType.Goldfinch:
				return new BirdSizeRange() { Lower=4.5, Upper=5.5 };
			case BirdType.Chickadee:
				return new BirdSizeRange() { Lower=4.75, Upper=5.75 };
		}
		throw new InvalidBirdTypeException();
	}
}

 

 

1. 팩토리 메소드 기법으로 생성자를 대체하자!

  • Bird() 생성자를 제거한다.
  • static Create 메소드를 추가한다.
private  BirdType birdType;

public static Bird Create(BirdType birdType)
{
	return new Bird()
	{
		birdType = birdType
	};
}

 

 

2. BirdType에 의해 구체화된 새의 타입 별로 서브클래스 생성

public class Cardinal : Bird
{
}

public class Chickadee : Bird
{
}

public class Goldfinch : Bird
{
}

이렇게 되면 클래스의 구조는 아래와 같아진다.

 

3.  2번에서 만든 서브클래스들을 사용하기 위해 팩토리 메소드를 업데이트시키자!

  • switch문에서 birdType에 기반한 Bird 서브클래스들을 만들어주는 것을 추가
public static Bird Create(BirdType birdType)
{
	Bird bird;
	switch (birdType)
	{
		case BirdType.Cardinal:
			bird = new Cardinal();
			break;
		case BirdType.Chickadee:
			bird = new Chickadee();
			break;
		case BirdType.Goldfinch:
			bird = new Goldfinch();
			break;
		default:
			throw new InvalidBirdTypeException();
	}

	bird.birdType = birdType;
	return bird;
}

자 이렇게 되면 일단 create 메소드에 switch문이 들어오게 되었다. 

 

이제부터는 PUSH DOWN METHOD 리팩토링 기법을 사용하자

 

깨끗한 코드를 위해 메소드 내에 있는 switch문들을 다형성을 통해 대체하길 원한다.

그래서 push down method를 통해 Bird에 있는 메소드들을 각 서브클래스들로 옮길 것이다.

 

근데, 그냥 옮기게 된다면, 만약 새로운 Bird type을 추가할 때 서브클래스에 메소드들을 만드는 것을 잊어 RunTimeException 발생할 수 있다.

 

그래서 이러한 문제가 발생하지 않게 하기 위해 Bird class에 있는 메소드들을 abstract 메소드로 만들 것이다.

그럼, 각 서브클래스들을 이 추상 클래스를 무조건 override(다형성) 할 수밖에 없게 된다. 

 

4. Bird 클래스를 abtract로 바꿔주고, 메소드들도 abstract로 바꿔준다.

 public abstract class Bird
 public abstract List<BirdColor> GetColors()
 public abstract List<BirdColor> GetFoods()
 public abstract List<BirdColor> GetSizeRange()

 

5. 그 후 각 서브클래스들에 Override를 하여 메소드들을 만들어준다.

public class Cardinal : Bird
{
	public override List<BirdColor> GetColors()
	{
		return new List<BirdColor>() { BirdColor.Black, BirdColor.Red };
	}
}
==========
public class Chickadee : Bird
{
	public override List<BirdColor> GetColors()
	{
		return new List<BirdColor>() { BirdColor.Black, BirdColor.White, BirdColor.Tan };
	}
}
===========
public class Goldfinch : Bird
{
	public override List<BirdColor> GetColors()
	{
		return new List<BirdColor>() { BirdColor.Black, BirdColor.Yellow, BirdColor.White };
	}
}

이렇게 되면 Bird 클래스에서 Create() 팩토리 메소드 외에는 switch문이 없어지게 된다.

 

이로써 다형성 (polymorphism)을 통해 switch문의 OCP를 해하는 문제점을 해결할 수 있다!!

참고

반응형

'CLEAN CODE' 카테고리의 다른 글

CLEAN 함수 (3)  (0) 2021.06.01
CLEAN 함수 (2)  (0) 2021.06.01
CLEAN 함수 (1)  (0) 2021.05.25
CLEAN 코드 네이밍 (2)  (0) 2021.05.24
CLEAN 코드 네이밍 ( 1 )  (0) 2021.05.24