Ruff, Black, Mypy를 이용해 동료과 효율적으로 협업하기

Liam
dotslashdash
Published in
12 min readJun 12, 2023

사람별로 성향이 다르듯이, 사람마다 코딩 스타일이 다릅니다. 여러 사람이 모인 회사에서 하나의 프로젝트를 향해 달려갈 때, 모두 각자가 원하는 데로 코드를 작성하면 프로그램의 코드 스타일은 제각각이 되겠죠. 주석은 이렇게 작성하고, 모듈 추가는 저렇게, 변수 선언은 요렇게 하고. 정작 프로그램은 잘 실행되겠지만 나중에 유지 보수와 리팩토링을 하려면 정말 어려운 상황이 연출될 겁니다.

혼자서 작업하든 여러 사람이 하나의 코드를 작업하든 결국 정해진 규칙이 없다면 나중에 어려운 상황이 오는 것은 모두들 알고 있습니다. 그래서 이를 해결하기 위해 저희는 코딩 컨벤션(Coding Convention)을 세울 수 있습니다.

이미 많은 프로젝트와 기업은 코딩 컨벤션을 정해놓고 사용합니다. 하지만 코딩 컨벤션을 외워가며 일일이 확인하면서 개발을 진행하는 것은 상당히 비효율 적이기 때문에 저희는 특정한 개발 도구들을 사용하여 코딩 컨벤션을 정할 수 있습니다. 린터(Linter), 코드 포멧터(Code Formatter) 등이 바로 그것이죠.

그래서 저는 오늘 닷슬래시대시 개발팀이 RuffMypy를 도입하게 된 계기와 Ruff와 Mypy에 대해서 소개하고 저희 팀이 어떠한 컨벤션 규칙을 가지고 프로젝트를 진행하는지에 대해 작성해 보려고 합니다.

닷슬래시대시 개발팀이 Ruff와 Mypy를 도입한 이유

본래 저희 팀은 파이썬에서 import 하는 라이브러리들을 자동으로 정렬해 주는 Isort와 엄격한 코드 포멧터인 Black만을 사용했습니다. 때문에 아래와 같이 여러 가지 불편한 사항들이 있었습니다.

  1. typing모듈의 타입을 불러오지 않아도 list로 타입 힌트를 줄 수 있거나, Optinal|로 입력할 수 있는 등의 최신의 파이썬 언어는 새롭게 작성한 코드부터 적용되기 때문에 작성 방식이 뒤죽박죽 섞여 있는 경우가 존재함
  2. 잘못된 타입 선언으로 인한 타입 에러를 야기
  3. 사용하지 않는 변수와 모듈은 어쩌다 발견해서 제거하지 않는 이상 레거시와 같이 남아있음

그러다 우연히 슬랙 #공유-개발지식채널에서 동료가 소개한 Ruff에 대해 알게되었습니다. Ruff를 통해 그동안 여러모로 파이썬을 사용하면서 불편했던 사항을 해소해 볼 수 있겠다는 생각이 들어, Ruff와 Mypy를 조사해 보는 걸 시작으로 결국 도입까지 할 수 있게 되었습니다.

파이썬의 Type Annotation

파이썬은 자바와 같이 정적 프로그래밍 언어가 아닌 동적 프로그래밍 언어입니다. 파이썬의 인터프리터가 코드를 실행하면서 타입을 추론하여 체크하기 때문에 타입이 고정되어 있지 않습니다.

그래서 안전성에 대한 위험 요소를 줄일 수 있도록 python 3.5 이후부터 자료형 힌트 기능이 추가되었습니다.

Python type hint/annotation example

하지만 이렇게 해도 타입 힌트만 줄 뿐이지, 파이썬을 동적 타입 언어에서 정적 타입 언어로 변환 시켜주지는 않습니다. 예를 들어 타입 힌트를 string으로 주고 int값을 준다고 해서 동작에 이상이 없다면 문제없이 실행됩니다.

이러한 이유 때문에 파이썬은 Mypy와 같은 정적 타입 검사기라는 것을 통해 코드를 검사하여 코드 구조의 연속성을 확보할 수 있습니다.

정적 타입 검사 도구 Mypy

Mypy는 타입 어노테이션과 런타임 에러를 유발할 수 있는 에러를 잡을 수 있는 현재 파이썬에서 가장 많이 사용되고 있는 정적 타입 검사 도구입니다.

대표적인 예로 오류가 될만한 null값 이나 기타 특수한 상황에서 타입 체크의 핸들링이 빠지지 않도록 해주거나 리팩토링에 도움을 줄 타입 어노테이션을 작성하게 합니다.

Mypy creating type annotation example

Mypy는 pip 또는 poetry와 같은 파이썬 패키지 매니저로 설치할 수 있으며, toml 파일을 활용한 옵션 설정을 지원합니다. 많은 옵션을 가지고 있기 때문에 필요한 옵션을 Mypy Docs선택하여 poetry의 pyproject.toml 파일에서 관리할 수 있습니다.

파이썬의 가장 빠른 린터 Ruff

Ruff는 rust로 만들어진 따끈따끈한 파이썬 린터입니다.

해당 이미지는 Ruff Docs에서 제공해 주는 표인데, 보시다시피 기존의 다른 파이썬 린터들보다 10~100배 빠르다고 되어져 있습니다. 실제로 가장 많이 쓰는 flake8과 pylint를 ruff와 비교해서 직접 테스트를 해본 결과 flake8보다 10배, pylint보다 20배 정도 빨랐습니다.

현재 Ruff는 Flake8 (plus dozens of plugins), isort, pydocstyle, yesqa, eradicate, pyupgrade, autoflake 등의 대체품으로 사용될 수 있습니다. 단일 툴에서 isort, autoflake와 같은 여러 툴들을 한 번에 빠르게 실행시킬 수 있는 것이 정말 큰 장점이죠.

Ruff도 파이썬 패키지 매니저로 설치하며, pyproject.toml 파일에서 룰을 관리할 수 있습니다. pyproject.toml 파일에서 select에 사용하고 싶은 플러그인을 추가하거나, 사용하고 싶지 않은 플러그인을 ignore에 넣어서 제외할 수가 있습니다.

[tool.ruff]
select = ["B", "E", "F", "I", "UP", "W"]
ignore = ["B905", "B008", "B904", "F811", "E501"]
src = ["src"]
fixable = ["B", "F", "I", "UP"]
unfixable = []

현재 저희 팀은 기존에 사용하던 isort(I), 사용하지 않는 변수와 모듈 제거를 위한 autoflake(F), 최신 버전의 파이썬 언어로 코드를 유지시켜줄 pyupgrade(up), 그리고 pep8을 위한 flake8(B(임시)) 등을 선택하여 사용하고 있습니다.

파이썬 코드 포멧터 Black

Black은 파이썬의 가장 대중적인 코드 포맷터입니다. Black Github에서는 Black을 Uncompromising Code Formatter라고 소개하고 있습니다.

기존의 다른 코드 포멧터(autopep8, yapf 등)와는 다르게 Black은 설정의 여지가 거의 없어서 정해놓은 특정 포멧팅 규칙을 그대로 따라야 합니다.

이렇게 유연하지 않아도 큰 인기를 끌 수 있었던 이유는, 기존에 존재하던 오랜 커뮤니티의 가장 대중적인 여러 의견을 수렴하고 테스트를 거쳐서 정해진 스타일이기 때문에 다른 코드 포멧터 처럼 스타일을 표준화하는 과정에서 불필요한 시간을 절약할 수 있었기 때문입니다.

도구 자동화하기

앞서 Mypy, Ruff, Black을 소개했습니다. 이제 이 세 개의 도구를 어떻게 실행해야 할까요? 각각 실행하기에는 너무 불필요하다는 것을 저희 모두 느끼고 있습니다. 기존의 Isort와 Black은 단축키 두 개로 충분히(?) 커버가 가능했지만 이제는 세 개가 되어버린 시점에서 각각을 단축키로 실행하기에는 정말 귀찮은 과정이 되어버린 거죠.

이미 Isort와 Black 두 개만 사용하던 시절에도, 실행하는 것을 까먹고 커밋(Commit) 하여 커밋을 취소한 후 다시 Isort와 Black를 실행하고 커밋을 하는 문제가 있었는데 세 개를 직접 돌리는건 할말 다했죠..

그래서 저는 Mypy, Ruff, Black를 한 번에 실행할 수 있는 배쉬 스크립트를 작성하여 그동안 있었던 오류들을 전부 잡고, 그 이후로는 Git hook의 pre-commit을 이용하여 도구들을 자동화하기로 했습니다.

여기서 다들 pre-commit을 바로 이용하지 않고 배쉬 스크립트(Bash Script)를 먼저 이용한 것에 대해 의아해하실 수 도 있는데, 이러한 결정을 하게 된 이유는 pre-commit은 git add한 파일에 대해서만 도구를 실행하기 때문에 그동안 쌓인 모든 오류(ruff와 mypy를 실행하고 생긴 오류)를 잡기 위해서 통합 스크립트를 작성하였습니다.

Git hook 이란? Git에서 어떤 이벤트가 생겼을 때 자동으로 특정 스크립트를 실행할 수 있게 해주는 기능입니다. 그중에서도 pre-commit은 YAML 파일을 활용해서 git commit을 수행하기 전에 자동으로 특정 작업을 수행하도록 해주는 도구입니다.

Bash 스크립트

아래와 같이 스크립트를 작성해 줍니다. 파일 이름은 run_lint.sh로 했습니다.

#!/usr/bin/env zsh

# 실행 중간에 Error시 진행 안함
set -euo pipefail

echo "Run ruff"
poetry run ruff check --fix .

echo "Run black"
poetry run black .

echo "Run mypy"
poetry run mypy .

echo "All done! 👍"

이 통합 스크립트는 Terminal에 ./run_lint.sh 명령어를 입력해 실행할 수 있습니다.

만약 zsh: permission denied: ./run_lint.sh라는 메시지와 함께 실행이 되지 않는다면, Terminal에 sudo chmod 777 ./run_lint.sh를 입력해 실행 권한을 줌으로써 실행할 수 있습니다.

실행 후 잡힌 에러의 개수

스크립트를 실행하면 많은 에러가 잡히는데, 전부 잡아주시면 됩니다.

기존의 파일은 무시하고 새로운 파일부터 mypy를 적용하고자 한다면 모듈 레벨에서 상세 설정이 가능하기 때문에 기존의 타입 에러가 있는 프로젝트에서 mypy를 사용하셔도 전혀 문제가 되지 않습니다.

pre-commit을 이용한 도구 자동화

통합 스크립트로 모든 에러를 다 잡았다면 poetry로 pre-commit을 추가해 줍니다. pre-commit은 .pre-commit-config.yaml 파일을 조회하기 때문에, .pre-commit-config.yaml 파일을 만들고 아래와 같이 코드를 작성해 줍니다.

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.272
hooks:
- id: ruff
args: ["--fix"]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
hooks:
- id: mypy
args: [--ignore-missing-imports]

그 후 Terminal에 pre-commit autoupdate 명령어를 입력하여 도구들의 버전을 올바르게 적었는지 확인합니다. 그리고 pre-commit install 명령어를 입력하여 pre-commit 단계에서 실행되도록 해줍니다. 이후부터는 git commit 을 실행할 때마다 Ruff, Black, Mypy가 돌면서 수정되거나 에러가 발생하면 commit이 취소되고 다시 git add의 단계로 이동됩니다.

💡참고: pre-commit은 복잡성으로 인해 TOML 파일을 지원하지 않습니다.

끝맺음

이렇게 pre-commit까지 모든 단계가 끝났습니다. 본문의 내용을 전체적으로 정리를 해보자면,

  1. 첫 번째로 Ruff를 이용해 isort와 autoflake, pyupgrade 등으로 팀 내 정해놓은 규칙으로 코드를 린팅하고
  2. 두 번째로 Black 으로 동일한 코드 스타일로 포맷하여 일관된 코드 스타일을 유지하여 가독성을 증가시켰으며
  3. 마지막으로 Mypy 를 통해 데이터 타입의 일관성을 유지시켜 줌으로써 원활한 리팩토링이 가능하도록 코드의 완성도를 높여주었습니다.

위의 3단계의 과정을 통해 팀 구성원과의 더 나은 협업과 효율적인 리팩토링이 가능해졌습니다🙂.

위와 같이 새롭게 코딩 컨벤션을 확립하기 위해서 다양한 도구들의 자료조사부터 기존에 존재하던 에러를 잡고 자동화의 도입까지.. 기존의 업무와 병행하며 진행하다 보니 쉽지는 않았지만 매우 의미 있는 일이었음에 정말 재미있게 진행했습니다.

필요한 내용만을 전달하기 위해 아주 세세하게 기능들을 적지는 못했지만, 더욱 효율적인 업무를 위해 새로운 규칙을 확립하고자 하는 분들에게 이 글이 약간의 도움이라도 되었으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다. :-)

[참고 자료]

--

--

Liam
dotslashdash

Hello! I'm a developer working at DotSlashDash.