포스트

첫 코드 리팩토링

이 포스트에 실제 코드는 전혀 등장하지 않습니다.

소프트웨어 엔지니어로 입사했지만 그동안 한 번도 유지보수가 필요한 코딩을 해본 적이 없었다. 그러다 작년 말부터 동료 직원과 함께 이런저런 기능을 하는 스크립트의 모음집을 만들었는데, 분명 하나의 코드베이스에 시작한 구현들이 언제부턴가 한눈에 알아보기 힘들어지기 시작했다. 각자 자기 나름대로의 생각을 코드로 옮기는 과정에서 자기만의 표현 방식을 사용하다보니 다른 사람이 그 결과물을 봤을 때 ‘이 사람은 왜 이런 식으로 구현을 한 거지?’ 하고 생각의 흐름을 쫓아가는데 시간이 상당히 걸렸다. 차라리 함수명, 변수명이라도 통용되는 방식으로 지었으면 덜 헷갈렸을 텐데, arranged_book 같은 변수명을 보고 있자니 아득했다.

회사 분위기가 뒤숭숭해 일이 너무 하기 싫었던 어느 날, 큰맘 먹고 동료 직원에게 말했다.

“우리 코드 리팩토링 좀 합시다.”


사실 리팩토링은 아니었다. 왜냐하면 리팩토링의 결과물은 겉에서 봤을 때 코드 사용법이 달라져서는 안 되는데 이 작업의 결과물은 상당히 많은 부분에서 입출력이 바뀌고 없던 클래스도 생기고 별의 별 일이 다 일어났기 때문이다. 그렇지만 야매 개발자는 아는 게 없어서 리팩토링이라는 단어를 사용하지 않고서는 이 작업을 적확1하게 설명할 수가 없었다.

갑자기 이런 소리를 왜 하냐면 앞으로 이 글에서 이 일련의 코드 수정 작업을 전부 리팩토링이라고 칭할 것이기 때문이다.

이번 리팩토링의 커다란 목표는 다음과 같았다.

  1. 클래스 구현을 최대한 쪼개서 하나의 클래스가 그 이름과는 사뭇 다른 여러 가지 일을 하지 않도록 하기
  2. 함수명, 변수명은 남들도 이해할 수 있는 표현으로 바꾸기
  3. Type hint 적용하기
  4. Docstring 쓰기

1. 클래스 분리하기

보통 남이 짠 코드를 분석할 때 특정 파일 이름이나 클래스 이름을 보면 ‘이 모듈/클래스는 이런 역할을 할 것이다’ 하는 기대가 있는데, 동료 직원의 Pull Request(PR) 리뷰를 하다 보면 ‘이건 왜 여기다 구현한 거지…?’ 싶은 경우가 종종 있었다. (아마 동료 직원이 내 PR을 보면서 같은 생각을 했을 수 있다.)

그래서 클래스마다 해당 클래스의 역할을 명확히 하고 그동안 구현했던 모든 함수를 리스트업 한 다음 해당 함수의 원래 의도를 작성했다. 작성자의 의도와 실제 구현 결과물이 다른 경우나 기대와 다른 위치에 구현되어 있는 경우 등등이 눈에 보였다. 그런 것들을 ‘헤쳐모여’ 하는 작업을 했다. 유사한 기능들을 새로 그룹핑했고, 그룹핑 결과에 따라 클래스를 정리해 해당 클래스의 역할까지 재정의했다. 그 결과 일부 메서드는 위치가 옮겨졌고 새 클래스가 몇 개 만들어졌다.

2. 파일명, 함수명, 변수명 바꾸기

제일 골치아픈 부분이었다. 그렇지만 나중에 arranged_book을 또 마주할 자신이 없었기 때문에 이번에 그냥 확 바꿔버렸다. 지금 바꾸고 바꾼 이름에 익숙해지는 게 나중 가서 또 이상한 이름을 눈앞에 두고 ‘이거 뭐지…?’ 하는 것보단 낫겠지 싶었다.

명명은 아래의 규칙에 따라 정리했다.

  • 파일명은 나중에 모듈 이름으로 사용할 수 있는 부분이니까 명사형으로 짓자.
  • 함수는 실제 동작이기 때문에 동사형으로 이름을 짓는다.
  • 함수명, 변수명의 경우 차라리 길어지는 게 낫기 때문에 의미를 최대한 명확히 하자. (하는 일, 활용하는 데이터가 뭔지, 어디에다 업데이트를 하는지 등)

3. Type hint 적용하기

파이썬에서 Type hint는 버전 3.5 때 도입된 기능이다. 변수의 데이터형이 무엇인지 힌트를 주는 기능인데 사실 파이썬 좋은 이유 중 하나가 변수 선언할 때 타입 지정 안 하고 맘대로 쓰는 것이었기 때문에 그동안 귀찮아서도 잘 안 썼던 기능이다. 근데 막상 타입 힌트 없는 남의 코드를 보자니 인자로 넘어온 변수가 dict형인지 list형인지조차 헷갈리는 것이었다. 그래서 이번에 아예 그냥 함수의 입출력마다 타입 힌트를 지정했다.

1
2
3
4
5
6
7
8
9
10
11
12
def show_me_the_sample_function(
    input_dict: dict,
    is_true: Optional[bool] = False
) -> list:
    return_list = list()
    for k, v in input_dict:
        if is_true:
            return_list.append(v)
        else:
            print(f"Invalid option: {k}, {v}")
    
    return return_list

이 때 힌트는 generic type(e.g. int, str, ...)으로 작성했다. 원래는 from typing import List 와 같은 방식으로 사용하고자 하는 형마다 import를 했어야 했는데 파이썬 버전 3.9부터는 generic type을 지원해주기 시작했다고 해서 과감하게 사용해줬다. (근데 팀원 중에 파이썬 3.8을 쓰는 사람이 있었다…)

4. Docstring 쓰기

변수명을 예쁘게 짓고 type hint까지 잘 줘도 해당 함수가 무슨 기능을 하는지 한눈에 파악하기 어려울 때가 있다. 그래서 하나하나 뜯어보기에는 너무 바빠서 대충 보고 넘어가고 싶을 때도 있고 심지어는 먼 훗날의 내가 과거의 내가 짠 함수를 보면서 ‘이거 뭔데 이래놨지…?’ 하며 긴가민가 할 때도 있다. 그래서 아주 완벽하지는 않더라도 명세서를 좀 써줘야겠단 생각을 했다.

readthedocs 같은 걸 쓸 건 아니라서 간단하게 구글 방식을 적용했다. 기왕이면 곳곳마다 다 적용하고 싶었지만 이번엔 리팩토링에 들일 수 있는 시간이 너무 짧아서 구현된 클래스와 해당 클래스에 있는 public method에 대해서만 docstring을 작성했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def show_me_the_sample_function(
    input_dict: dict,
    is_true: Optional[bool] = False
) -> list:
    """
    Create list of values if true. Print message if false.

    Args:
        input_dict: Dictionary contains some keys and values.
        is_true: Option to append list or not.

    Returns:
        List of values extracted from input dictionary.
    """
    return_list = list()
    for k, v in input_dict:
        if is_true:
            return_list.append(v)
        else:
            print(f"Invalid option: {k}, {v}")
    
    return return_list

그 외

  • bool형의 조건문 비교는 if something is True 말고 if something 과 같은 식으로 변경했다. (몰랐는데 PEP 8에서 앞선 구현방식은 Worse 케이스라고 소개함… ㅋㅋㅋ)
  • isort와 black 사용을 강제했다.
    • GitHub Actions를 설정해 PR 시 커밋마다 포맷팅을 맞추지 않으면 체크를 통과하지 못 하도록 했다. (나중에 실제 YAML을 업데이트 할 예정)
    • 다만 기본 설정인 line length 88은 너무 빡빡한 거 같아서 100으로 설정했다.

아쉬웠던 점 (생각이 오락가락해서 지속적으로 업데이트될 예정)

  • 클래스로 구현해야 사용하기 편한 것과 그렇지 않은 것이 구분되지 않고 죄다 클래스화 시킨 게 아쉬웠다. 나중에 main.py를 따로 빼서 CLI 구현을 흉내내볼까 한다.
  • Docstring을 파일 레벨로도 작성하고 클래스 내 private method까지도 다 명시해두고 싶다.
  • isort를 전혀 손을 안 대고 쓰고 있는데 known_third_party, known_first_party 등을 제대로 이해한 다음 추후 적용하고 싶다.
  • 로깅용으로 커스텀 Logger 하나 구현해두고 여기저기서 사용 가능하게끔 만들고 싶었는데 모듈 import 순서 문제라던지 이런저런 이유로 못 만든 게 천추의 한이다.
  • README를 좀 예쁘게 쓰고 싶었는데 여전히 개판이다. Awesome README 리스트를 좀 보긴 했는데 우리 실정에 맞는 걸 고르기엔 시간이 촉박했다….

  1. [네이버 국어사전] 적확하다 的確하다 - (형용사) 정확하게 맞아 조금도 틀리지 아니하다. 

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

Comments powered by Disqus.