티스토리 뷰

반응형

파이썬에서는 인터페이스를 통한 다형성의 성질을 잘 사용하지 않으며, 의존관계 주입에 대한 활용성이 많이 떨어진다.( Django Framework에서는 더욱이 그러한 것을 볼 수 있다.)

하지만 파이썬에서도 인터페이스를 통해 역할과 구현을 나누면 많은 이점을 챙길 수 있다. 이번에 실제로 회사 코드를 리팩토링을 하게 되면서 사용한 방식에 대해서 예를 들며 설명해보겠습니다.

 

Before ( 리팩토링 이전의 모습)

 

    def do(self):
        number = self.line.number
        if number == 'One':
            result = self.do_One()
        elif number == 'Two':
            result = self.do_Two()
        elif number == 'Three':
            result = self.do_Three()
        elif number == 'Four':
            result = self.do_Four()
        elif number == 'Five':
            result = self.do_Five()
        elif number == 'Six':
            result = self.do_Six()
        elif number == 'Seven':
            result = self.do_Seven()
        elif number == 'Eight':
            result = self.do_Eight()
        elif number == 'Nine':
            result = self.do_Nine()
    
    def do_One(self):
    	pass
        
    def do_Two(self):
    	pass
    
    def do_Three(self):
    	pass
        
    def do_Four(self):
    	pass
    
    def do_Five(self):
    	pass

 

실제로 대충 이러한 형식의 형태로 되어 있었다.

 

이러한 코드의 단점은 다음과 같다.

 

  • 코드를 분석할 때 찾기가 힘들다.
  • 숫자 번호에 따른 처리를 나열식으로 하면서 다른 번호가 추가될 때 elif 추가 및 do_xx() 함수를 추가하는 작업을 하면서 개방 폐쇄 원칙에 어긋나게 된다.( SOLID 법칙 중 하나)
  • 새로 들어온 사람이 코드를 분석할 때 적응하기가 힘들다.(필자도 처음 들어올 때 거대한 척추뼈 보고 많이 놀랐었다.)
  • 하나의 구현체를 클래스 내부 함수로 처리하게 되면서 내부적으로 분리하기 힘들어진다.( 예를 들면 1이라는 숫자에 대한 처리를 하기 위해 do_1()이 실행되고 이에 대한 처리를 하기 위해 내부적으로 로직이 설계되어 있는데 내부 로직에서 각 기능 별 역할로 더 세분화 할  수 있지만 하나의 클래스에서 워낙 방대하기 때문에 쪼개기 주저하게 되면서 점점 악화되는 현상이 있었다.)

예시

def do_1(self):
    travel = self.travel
    if self.money < 0:
    	raise ValueError('돈이 없어~')
    elif self.visa is None:
    	raise ValueError('비자 챙기세요~')
    elif self.tiket is None:
    	raise ValueError('tiket 챙기세요~')
    if travel == '유럽':
		유럽 관련
    elif travel == 'Asia':
    	Asia 관련
    
 
 
 ---------------------------------
 
 
 def do_1(self):
    self.check_customer_status()
    if travel == '유럽':
		유럽 관련
    elif travel == 'Asia':
    	Asia 관련

def check_customer_status(self):
    if self.money < 0:
    	raise ValueError('돈이 없어~')
    elif self.visa is None:
    	raise ValueError('비자 챙기세요~')
    elif self.tiket is None:
    	raise ValueError('tiket 챙기세요~')

 

 

After ( 리팩토링 1단계)

1. 역할 만들기 (인터페이스 만들기)

class Role(ABC):

    def __init__(self, action):
        self.action = action

    @abstractmethod
    def do(self):
        ...

우선 어떤 역할을 줄 것이고 이러한 행동을 하게끔 인터페이스를 만듭니다.

 

2.  구현체 만들기(인터페이스를 구현하여...)

class One(Role):
    def do(self):
        pass

 

 

3. 기존 존재하는 do의 리팩토링 후 모습

위에서 보신 것처럼 구현했다고 합시다. 이제 여기서 궁금한 점은 그럼 어떻게 해당 구현체를 동적으로 가져올 수 있을까? 에 대한 질문이 있을 수 있습니다. 스프링을 해보셨다면 ComponentScan이라는 것을 아실텐데요. 스프링에서는 Component 어노테이션으로 등록된 클래스를 가져오는 작업이 있습니다. 저는 이러한 방식을 파이썬 스럽게 만들어 봤습니다. 비록 약간의 제약은 있지만 당장 바로 적용했을 때에는 큰 이점이 있기에 소개해드립니다.

 

    def do(self):
        number = self.line.number
        try:
            module = __import__('path.to.directory.%s' % number, fromlist=['path.to.directory'])
            func = getattr(module, number)
            result = func(self.line).do()
        except ModuleNotFoundError:
            return None
        except Exception as e:
            raise e
        return result

 

작동원리에 대해서 설명해드리겠습니다.

 

1.   모듈 가져오기

module = __import__('path.to.directory.%s' % number, fromlist=['path.to.directory'])

의미하는 바는 path > to > directory 안에 있는 number로 되어있는 .py 파일에 대한 내용을 임포트하겠다!

그랬을 때 위에 있는 One.class는 path>to>directory>One.py 로 되어 있어야 합니다.

 

2.  특정 클래스 가져오기

            func = getattr(module, number)

getattr을 이용하여 One.class를 가져옵니다.

 

그리고 해당 함수 또는 클래스를 이용하시면 됩니다.

 

3. 한계점

1. 이름에 대한 제한

파이썬 pep8 특징상 파스칼 형태가 아닌 스네이크 형식을 권고하고 있으며, 회사에 따라서는 특정 파일의 네이밍 규칙이 있을 수 있습니다. 

그러면 위의 방식은 사용할 수 없는 걸까요?

 

우선 결론부터 말하자면 그렇지 않습니다. 핸들러를 추가하여 이름을 넣으면 현재 사용하고 있는 네이밍에 맞게 바꿀 수 있는 함수로 이용하여 바꿔주면 됩니다.

 

def handler(self, name):
	return name.lower()

예를 들어 현재 회사 내부에서 무조건 소문자로 된 이름만 가능하다면 해당 함수로 이름을 바꿔주는 작업을 한 후에 그대로 따라가면 됩니다.

 

2. 디렉토리에 대한 제한

inspect을 이용하여 반복문을 돌면서 이름을 찾아가며 진행할 수는 있습니다. 하지만 이 방법 또한 config 관련 파일에 어느 디렉토리를 탐색할 것인지에 대해서 리스트화해야 하는 수고로움은 존재합니다.

개방폐쇄원칙을 지키면서 디렉토리에 대해서 자율성을 높이고 싶으면 config 파일에 어느 디렉토리를 탐색할 것인지에 대해서 리스트를 적어주시고, inspect문과 importlib 또는 __import__ 을 이용하여 탐색하시면 디렉토리에 대해서 자율성을 가질 수 있습니다.

 

 

1차 리팩토링 후기

우선 거대한 척추뼈를 걷어내면서 각 조건문 마다 구현체를 만들고 나니 확실히 가시성은 많이 높아졌습니다. 하지만 아직 내부 코드도 리팩토링에 대한 여지는 많이 있지만, 천천히 걷어낼려고 합니다. 한 번에 모든 걸 바꿀려고 하면 뭐가 문젠지 바로 캐치할 수 없기에 천천히 바꿔가며 더 나은 모습으로 되는게 클린코드가 아닐까 싶습니다.

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함