플랫폼 리팩터링 경험 공유. Code Complexity 줄이기 with Python

David K. Kim
네이버 플레이스 개발 블로그
11 min readAug 24, 2022

“For every minute spent on organizing, an hour is earned” — Benjamin Franklin

Photo by JESHOOTS.COM on Unsplash

리팩터링 : 결과의 변경 없이 코드의 구조를 재조정함. 주로 가독성을 높이고 유지보수를 편하게 하기 위해 사용되는 것이고, 버그를 따로 없애거나 새로운 기능을 추가하는 행위는 아니다. 사용자가 보는 외부 화면은 그대로 두면서 내부 논리나 구조를 바꾸고 개선하는 유지보수 행위 - Wikipedia

“코드 리팩터링 진행하겠습니다”

이 말은 회사에서 자주 접해봤지만 어떤 의미인지 정확히는 이해하기 힘들었던 것 같습니다. Wikipedia에 따르면 코드를 개선하는 건데, 코드를 어떤 식으로 개선해야 하는 건지 헷갈렸던 것 같습니다. 운 좋게 이번 기회에 플랫폼 코드의 일부분을 리팩터링을 할 수 있는 기회가 생겼는데, 다양한 동료들의 피드백을 받아 가면서 리팩터링이 어떤 건지 조금이나마 이해할 수 있었던 것 같습니다. 혹시나 다른 사람에게도 도움이 될까 하는 마음에 이 경험을 공유해보려고 합니다.

리팩터링하면서 코드를 개선할 수 있는 방법은 굉장히 다양하게 존재합니다. 저는 이번 과정에 아래 2가지에 집중해봤습니다.

1. Code complexity 줄이기

2. 코드 확장성 개선

Code complexity이란 코드의 복잡성을 뜻하는데, 다른 개발자가 봤을 때 헷갈리기 쉽고 실수 유발 가능성이 높은 코드가 code complexity가 높다고 할 수 있습니다. 코드를 간단하고 명확하게, 그리고 변경 시 실수를 최소화할 수 있도록 개선해봤습니다. Code complexity 측정 방법에 대한 추가적인 설명은 글 끝에서 더 확인하실 수 있습니다.

코드 확장성도 개선해보려고 했습니다. 제가 살펴본 이번 글로벌 플랫폼 코드는 다양한 국가들이 추가가 될 수 있는 케이스로 계속 확장하고 커질 수 있는 코드였습니다. 그래서 코드를 더 쉽게 추가하고 관리할 수 있도록 개선해봤습니다.

이번 글에서는 리팩터링 과정에 집중할 수 있도록 간단한 예시 코드를 세팅해봤습니다. 동료들의 피드백을 받아 가면서 수정한 코드들을 변화를 Version 별로 세팅해봤습니다. 즉 Version1이 최초로 작성한 코드이고 Version2는 피드백 받은 이후 수정한 코드라고 생각해주시면 됩니다. 사용한 언어는 python이고 사용한 editor는 pycharm입니다.

Pexels에서 Christina Morillo님의 사진: https://www.pexels.com/ko-kr/photo/1181359/

Version 1 — 최초 코드

제가 이번 예시 코드로 구현하려고 한 것을 간단하게 살펴보면 국가별, 그리고 환경별로 다르게 변수를 세팅하는 코드입니다. 그 이후 마지막 하나의 변수에 대해서는 변화를 주게 됩니다.

main.py 코드 설명

  • 지역, 환경별로 특정 변수들을 정의한 이후, threshold에는 마지막에 10을 더하는 코드입니다. 해당 코드에는 한국(kr), 일본(jp), 그리고 미국(usa)이 있는데 이 지역은 계속 늘어날 수 있다는 가정입니다.
  • 환경은 테스트 환경(qa)과 운영 환경(prod)이고 이 환경도 늘어날 수 있다는 가정입니다.

test.py 코드 설명

  • Python Mock Test를 이용한 테스트 코드로 actual 와 expected 값이 서로 같은지를 비교하고 서로 동일하면 통과하는 테스트입니다. 리팩터링이 올바르게 진행되었는지 확인하기 위해 TEST는 중요한 요소입니다. 리팩터링은 새로운 기능을 추가하는 게 아니라서 코드 변경한 이후에도 TEST는 계속 문제없이 통과되어야 합니다.

main.py 코드는 조건문에 all-in-one 코드로 가독성, 확장성이 모두 떨어지고 실수 위험성이 높은 코드입니다. 지역은 계속 추가가 될 수 있는데 그렇게 되면 조건문은 무한대로 늘어나게 될 수 있고, 환경이 추가되면 코드는 더더욱 복잡해질 수 있습니다.

“일본 운영 환경에 있는 threshold를 40으로 늘려주세요"

이와 같은 요구사항이 들어오면, 알맞은 변수를 조심하면서 찾아내서 값을 변경해야 하는데, 급하게 작업하다보면 잘못된 지역 또는 환경의 변수값을 변경할 위험이 있습니다. 또한 마지막에 threshold에 10을 더하는 별도 로직을 놓칠 위험성도 존재합니다.

Version 2 — function split

지역 따라 달라질 수 있는 변수를 정의한 코드를 main.py으로부터 분리하고 define_properties.py에 세팅하면서 main.py은 해당 변수들에 변화를 주는 threshold + 10 로직만 남겼습니다. 지역과 환경에 따라 변수 정의를 변경해야 하는 거면 define_properties.py을 살펴보면 되고, 변수값에 로직을 추가해야 하는 거면 main.py를 변경하면 됩니다. 이런 식으로 변경하여 코드 가독성을 우선 조금 올려봤습니다.

Version 3 — 지역, 환경별 조건문 제거

define_properties.py에 작성되었던 if elif 조건문들을 모두 없애고 dictionary로 대신해서 세팅하여 가독성을 더 올려봤습니다. dictionary 안에 있는 key를 알맞게 정의해서 변수들을 정의할 수 있습니다. 지역과 환경 추가가 더 쉬워져서 확장성도 조금 늘어났다고는 할 수 있지만, dictionary도 수정이 조금 번거로울 수 있는 한계점은 여전히 존재했습니다.

Version 4 — yaml로 변수 세팅

  • 마지막 단계에서는 dictionary들도 모두 없애고, 각 지역과 환경에 맞는 별도 packages와 yaml 파일을 세팅했습니다. 각 yaml 파일 안에 위와 같이 변수값들을 분리해서 따로 정의했습니다.
  • 그 이후 config.py로 원하는 지역과 환경에 맞는 변수들을 가져올 수 있도록 세팅하고, define_properties.py에는 원하는 값이 무엇인지 정의해서 알맞게 변수에 세팅했습니다.
  • main은 이전 Version3와는 동일하고 Test의 경우는 Version4의 코드를 바라보고 돌려봤을 때 문제없이 통과가 된걸 확인할 수 있었습니다.

Code Complexity 측정

코드를 개선하다 보니 code complexity의 변화를 어떻게 하면 조금 더 객관적으로 측정하고 공유할 수 있을까 고민하게 되었습니다. 그러다보니 Wily와 Radon이라는 library를 발견하게 되었습니다.

Wily는 commit 별로 code complexity가 얼마나 증가 또는 감소했는지 보여주는 툴로 다양한 지표들을 한 번에 아래와 같이 정리해서 보여줍니다. “Add check_complexity” commit이 Version1의 코드로, 각 commit마다 Version이 업데이트되었다고 생각해 주시면 됩니다.

Wily 결과 캡쳐

Wily의 경우는 pre-commit plugin으로도 세팅이 가능한데, 아래와 같이 세팅하면 됩니다. 이렇게 세팅하면 commit할 때마다 code complexity가 어떻게 변했는지 자동으로 계산해서 보여주게 됩니다.

Wily pre-commit 세팅 및 예시

Radon의 경우는 commit 별이 아닌 현재 코드의 complexity가 어느 정도인지 간단하게 보여주고 각 파일별로 code complexity의 정도를 수치화하여 점수를 통해 나타냅니다. 기본 default는 A-B-C 등급이지만 점수를 볼 수 있는 옵션도 존재합니다.

Radon 결과 캡쳐

위 예시에서 확인할 수 있듯이 Wily는 종합적으로 표를 통해 결과를 나타내고, Radon은 특정 지표에 대한 정보를 하나씩 제공하게 됩니다. Commit 별로 본인 코드의 complexity가 어떻게 달라졌는지 확인하고 싶으시면 Wily를 사용하면 되고, 간단하게 현재 코드가 어느정도의 complexity를 가지고 있는지 확인하고 싶으면 Radon을 사용하면 될 것 같습니다.

지표 설명

Cyclomatic Complexity: 코드 플로우에서 분기가 되는 부분의 개수. 예를 들어 if절이 있으면 +1이 되는 걸로 숫자가 클수록 complexity는 높습니다.

Unique Operands: 코드에 존재하는 변수(variable)와 값(value)들의 합. 높을수록 complexity는 높다고 볼 수 있지만 참고 지표 정도로만 생각해 주시면 됩니다.

Lines of Code (LoC): 순수한 코드 줄의 개수. 숫자가 크면 complexity도 높은 경향이 있지만, 꼭 그러지는 않습니다. 코드 줄은 더 많아져도 complexity는 떨어지는 경우들이 존재할 수 있습니다.

Maintainability Index: 코드가 얼마나 유지 보수하기 용이한지 보여주는 심화된 지표. Cyclomatic Complexity, LoC, 그리고 Halstead Volume이라는 지표를 모두 참고하는데, 숫자가 높을수록 complexity는 낮습니다. Wily에 사용된 공식은 아래와 같습니다.

Screenshot from https://www.youtube.com/watch?v=dqdsNoApJ80&t=38s

제가 세팅한 예시 코드의 complexity 분석 결과를 살펴보면 Cyclomatic Complexity는 지속적으로 줄어들고, Maintainabilty Index는 증가한 것으로 확인할 수 있습니다. 또한 Lines of Code의 경우는 증가할 때도 있고 줄어드는 경우들도 있다는 것을 발견할 수 있습니다. 즉, Lines of Code는 complexity와 큰 상관관계는 없다는 것을 확인할 수 있었습니다. 특이하게 Version4로 업그레이드했을 때 지표에는 변화가 없는 것을 확인할 수 있는데, 그건 yaml 파일의 경우는 wily가 측정할 수 없기 때문인 것으로 추측이 됩니다.

Wily와 지표들에 대해서 조금 더 알고 싶으신 분들을 위해 Wily를 직접 만드신 분의 설명 강의를 공유드립니다.

Version4와 Version1을 비교했을 때, code complexity는 줄어들고 코드 확장성은 늘어난 걸 확인할 수 있습니다. Wily와 Radon에서 분석한 지표들을 살펴봤을 때 complexity는 줄어들었고, yaml을 이용해 지역과 환경을 추가하는 게 더 간단해진 것을 확인할 수 있습니다. 마지막으로 Test를 통해 리팩터링 이후에도 코드가 뱉어내는 값은 변화가 없다는 것을 확인할 수 있었습니다.

이번 글은 리팩터링을 진행한 하나의 예시에 불과하고, 리팩터링할 수 있는 방법은 다양하게 존재합니다. 그래도 이 예시를 참고하여 다음에 코드를 직접 작성하시거나 수정하실 때 조금이나마 도움이 되었으면 마음입니다.

자세한 코드는 아래 github에서 확인하실 수 있습니다. 긴 글 읽어주셔서 감사합니다 😊

--

--