Search

Git Hook과 Pylint를 활용한 코드 품질 관리

Git Hook 이란?

Git Hook은 특정 Git 이벤트(커밋, 푸시, 머지 등)가 발생할 때 자동으로 실행되는 스크립트를 말한다. 레포지토리 루트의 .git/hooks/ 폴더에 훅 스크립트를 넣으면 해당 이벤트 발생 시 자동 호출된다.
대표적인 Git Hook의 예:
pre-commit: 커밋 수행 전에 실행
prepare-commit-msg: 커밋 메시지를 작성하기 전에 실행
post-commit: 커밋 완료 직후에 실행

Git Hook 장점

코드 품질검사 자동화
커밋 시 코드 포맷팅(파이썬의 Black, isort 등)과 린트(Flake8, Pylint)를 자동으로 수행해 코드 품질을 유지
린트 검사로 잠재적인 문제를 미리 발견하여 개발 프로세스 내에서 발생할 수 있는 이슈를 최소화
커밋메시지 표준화
브랜치명이나 Jira 이슈 번호를 커밋 메시지에 자동으로 삽입해 메시지의 일관성을 확보
개발자 생산성 향상
반복적인 코드 포맷 및 스타일 검사를 훅 단에서 처리함으로써 개발자가 별도 명령어를 실행할 필요를 제거
현재 데이터 팀에서는 pre-commitprepare-commit-msg를 적용해 코드 품질 관리를 하고 있다. 이어서 이 두 훅을 어떻게 설정하고 적용했는지 알아보겠다.

Pre-commit 훅 설정

pre-commit은 커밋 직전에 호출되며, pre-commit 프레임워크를 이용하면 YAML 파일로 다양한 작업을 한 번에 관리할 수 있다.
1.
준비사항
기존 Git 전역 커밋 템플릿 설정이 있으면 제거 (없다면 생략 가능)
git config --unset --global commit.template
Bash
복사
pre-commit 설치
pip install pre-commit
Bash
복사
2.
pre-commit-config.yaml 생성 및 작성
프로젝트 루트에서 기본 샘플을 복사
pre-commit sample-config > .pre-commit-config.yaml
Bash
복사
필요한 훅만 설정
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace # 불필요한 행 끝 공백 제거 - id: end-of-file-fixer # 파일 끝에 공백/줄바꿈을 자동 추가 - id: check-yaml # YAML 문법 검사 - id: check-added-large-files # 추가된 큰 파일 감지 - repo: https://github.com/psf/black rev: 23.12.0 hooks: - id: black # Python 코드 자동 포매팅 language_version: python3 - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort # import 구문 정리 args: - --profile=black - repo: https://github.com/PyCQA/autoflake rev: v2.2.1 hooks: - id: autoflake # 불필요한 import 및 변수 제거 args: - --in-place - --remove-unused-variables - --remove-all-unused-imports - --expand-star-imports - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: - id: flake8 # 코드 스타일 검사 args: - --ignore=W605,W503,E402,E203 - --max-line-length=200 - repo: https://github.com/pycqa/pylint rev: v3.3.0 hooks: - id: pylint # 정적 분석 및 코드 품질 검사 additional_dependencies: - pydantic - acryl-datahub - pyyaml
YAML
복사
3.
작성한 훅 적용
pre-commit install
Bash
복사
YAML 파일 변경 시 위 명령을 다시 실행해줘야 변경 사항이 적용됨
4. 테스트 예시
git commit 을 하면 다음과 같이 지정해둔 작업들이 하나씩 실행됨
$ git add . $ git commit -m "feat: 예시 커밋" Trim Trailing Whitespace.................................................Passed fix end of files.........................................................Passed check yaml...............................................................Passed check for added large files..............................................Passed black....................................................................Passed isort....................................................................Passed autoflake................................................................Passed flake8...................................................................Passed pylint...................................................................Passed [main 401a188] [main] Replace indent logic 3 files changed, 632 insertions(+), 10 deletions(-) create mode 100644 .pylintrc
YAML
복사
모든 훅이 통과되야 커밋이 완료된다
에러가 있을 경우, 해당 훅의 에러 메시지가 출력되고 커밋은 중단

Prepare-commit-msg 훅 설정

prepare-commit-msg 훅은 커밋 메시지를 작성하기 전에 호출되며, 미리 정의한 템플릿을 커밋 메시지에 삽입하거나 브랜치명을 자동 반영할 수 있다.
1. 커밋 템플릿 작성
레포지토리 루트에 .gitmessage.txt를 만들고 기본 템플릿을 작성
예시 템플릿
############### # <타입> : <제목> 형태로 커밋 제목 작성 # 예시: feat: 로그인 기능 추가 [__TICKET_NAME__] # 줄바꿈용 ################ # 본문 (세부 내용) 작성: 한 줄 72자 이내 # 여러 줄 작성 시, 줄마다 "-" 사용 ################ JIRA __TICKET_NAME__ ################ # 타입 목록: # feat : 새로운 기능 추가 # fix : 버그 수정 # docs : 문서 수정 # test : 테스트 코드 추가 # refact : 코드 리팩토링 # style : 코드 의미에 영향 없는 변경 # chore : 빌드/패키지 매니저 수정 등 ################
Plain Text
복사
2.
prepare-commit-msg 스크립트 작성
prepare-commit-msg 생성
touch .git/hooks/prepare-commit-msg
Bash
복사
스트립트 내용 작성
COMMIT_MSG_FILE=$1 BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) # 자신의 .gitmessage.txt 경로로 수정 cp <자신의 경로>/.gitmessage.txt "$COMMIT_MSG_FILE" sed -i '' "s/__TICKET_NAME__/$BRANCH_NAME/g" "$COMMIT_MSG_FILE"
Bash
복사
$1은 Git이 넘겨주는 실제 커밋 메시지 파일 경로
git rev-parse --abbrev-ref HEAD 명령으로 현재 브랜치명 추출
sed -igitmessage.txt 파일 내 모든 __TICKET_NAME__ 을 현재 브랜치명으로 변경
3.
실행권한 부여
chmod +x .git/hooks/prepare-commit-msg
Bash
복사
4.
테스트
git commit
Bash
복사
에디터가 열리면서 .gitmessage.txt 내용이 로드되고, 상단에 현재 브랜치명이 자동 삽입
템플릿을 수정하여 저장하면 커밋이 완료
예시
브랜치명이 develop-1234 이고 커밋 메시지로 fix: 오류 수정을 입력한 경우 주석은 사라지고 최종적으로 아래와 같은 형태가 됨
[develop-1234] fix: 오류 수정 JIRA develop-1234
C#
복사

Pylint

Pylint란?

Pylint는 Python 코드 스타일과 버그 패턴을 정적 분석해주는 도구이다. .pylintrc 파일을 통해 팀 표준 규칙을 한곳에서 관리함으로써 코드 일관성 제고 및 잠재적 버그 사전 차단이 가능하다.
내부적으로 AST(Abstract Syntax Tree) 기반으로 코드 구조를 분석하여 다음 항목들을 검사한다 .
코드 포맷
네이밍 컨벤션
모듈 import 유효성 검사
잠재적 런타임 오류
코드 복잡도

pylintrc 기본 템플릿 생성

기본 템플릿 생성 명령어:
pylint --generate-rcfile > .pylintrc
Bash
복사
Pylint가 제공하는 모든 기본 설정 항목을 포함한 파일이 생성
필요에 따라 값을 수정해 각 프로젝트 규칙에 맞게 조정
(.pylintrc 은 필수 파일은 아니고 Git Hook 내의 정의로도 동작 가능)

주요 설정

생성된 .pylintrc 파일에는 각 섹션별로 기본값이 주석 처리되어 있다.
프로젝트 규모나 코드 복잡도에 따라 pylint 실행 시의 성능에 영향을 줄 수 있으므로, 검사할 디렉터리 범위(ignore 옵션)나 최대 검사 깊이(max-line-length) 등을 초기 단계에서 적절히 조정하는 것이 권장된다.
[MASTER]
init-hook을 통해 모듈 탐색 경로를 추가할 수 있음
[MASTER] init-hook=import sys; sys.path.append('app/src')
Plain Text
복사
[MESSAGES CONTROL]
린트 경고 또는 오류 코드를 끄려면 disable= 목록에 추가
[MESSAGES CONTROL] disable = missing-docstring, duplicate-code, broad-exception-raised
Plain Text
복사
missing-docstring: 모듈, 클래스, 함수에 docstring이 없을 때 발생하는 메시지 차단
duplicate-code: 중복 코드 블록 검사 차단
broad-exception-raised: except Exception:와 같이 광범위한 예외 처리 경고 차단
[FORMAT]
코드 스타일(최대 행 길이 등)을 정의
[FORMAT] max-line-length = 200 indent-string = ' '
Plain Text
복사
max-line-length: 한 줄 최대 글자 수 지정(PEP8 기본값 100)
indent-string: 들여쓰기에 사용할 공백 개수 지정
[TYPECHECK]
타입 검사 관련 설정을 정의
외부 라이브러리 임포트 시 발생하는 타입 체크 오류를 무시
[TYPECHECK] ignored-modules = boto3, botocore ignored-classes = SQLObject, optparse.Values
Plain Text
복사

함수별 pylint 주석 제어

Pylint 설정을 전체 .pylintrc 외에도, 함수나 특정 코드 블록 위에 # pylint: disable=... 주석을 달아 세부 제어할 수 있다.
함수 전체에 경고 비활성화 예시
# pylint: disable=too-many-arguments, unnecessary-else def complex_function(a, b, c, d, e, f): # ... 복잡한 로직 ... return result # pylint: enable=too-many-arguments, unnecessary-else
Python
복사
한 줄에만 적용
def foo(bar): # pylint: disable=unused-argument pass
Python
복사
파일 맨 위에서 모듈 전체 제어
# pylint: disable=missing-docstring, invalid-name
Python
복사
이렇게 하면 특정 함수나 모듈에 한정해서 필요한 규칙만 비활성화하거나, 블록이 끝난 뒤 다시 활성화할 수 있어 훨씬 유연한 관리가 가능하다.

pylint 실행 예시

처음 pylint를 실행하면 방대한 정도의 에러 및 경고 목록이 출력될 수 있다.
커밋 훅이 활성화된 상태에서는 모든 에러를 해결하지 않으면 커밋이 차단된다. (그래서 이번 작업에서 처음에 에러가 너무 많아 일단 끄고 github action 작업을 완료한 후에 다시 켜서 점수를 확인했다.)
초반에는 필수적인 에러 위주로 우선 순위를 둬서 해결하고, 점차적으로 리팩토링하거나 예외처리를 통해 기준을 맞추도록 관리하는 것이 좋다.
데이터허브 프로젝트의 Pylint 결과
코드 수가 적음에도 불구하고 다수의 에러가 발생
각 에러 항목에는 고유한 에러 코드가 부여되어 있어 어떤 유형의 문제인지 파악 가능
************* Module src.application.test app/src/application/test.py:5:0: E0401: Unable to import 'datahub.sdk' (import-error) app/src/application/test.py:8:0: E0401: Unable to import 'app.src.services.test' (import-error) app/src/application/test.py:9:0: E0401: Unable to import 'app.src.utils.set_logger' (import-error) ************* Module src.services.test app/src/services/test.py:4:0: E0401: Unable to import 'yaml' (import-error) app/src/services/test.py:5:0: E0401: Unable to import 'datahub.sdk' (import-error) app/src/services/test.py:8:0: E0401: Unable to import 'app.src.configs.yaml_config' (import-error) app/src/services/test.py:34:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation) app/src/services/test.py:37:16: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation) app/src/services/test.py:43:8: R1705: Unnecessary "else" after "return", remove the "else" and de-indent the code inside it (no-else-return) app/src/services/test.py:61:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation) app/src/services/test.py:63:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation) app/src/services/test.py:67:12: W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation) ----------------------------------- Your code has been rated at 3.86/10
Plain Text
복사
Pylint 최종 점수는 전체 코드 품질을 10점 만점 기준으로 산정한 값

메시지 코드 분류

E(Error): 코드 실행에 지장을 주는 심각 오류(모듈을 못 찾음, 구문 에러 등)
W(Warning): 코드가 정상 동작해도 성능 저하나 유지보수성 저해 가능성을 알리는 경고
R(Refactor): 코드 구조 개선이나 리팩토링 권고
C(Convention): 네이밍 컨벤션이나 줄 길이 초과 등 스타일 위반
I(Information): 정보성 메시지 또는 제안

해결 기록

E0401: Unable to import 'app.src.utils….
파이썬에서 모듈 탐색시 패키지로 인식하려면 __init__.py 필요한데 app 경로에 누락됨
app 경로에 __init__.py 추가해서 해결
E0401: Unable to import 'datahub.sdk'
pylint가 검사할 때 해당 라이브러리가 설치되어 있어야 실제로 import 가능
pylint는 기본적으로 시스템 경로와 프로젝트 경로만 참조하므로, 가상환경이 활성화되지 않은 상태여서 라이브러리 찾지 못함
.pre-commit-config.yaml 파일의 pylint 부분에 additional_dependencies 항목으로 datahub-sdk-python을 명시하여, Pylint 실행 시 해당 라이브러리를 자동 설치하도록 설정하여 해결
W1203: Use lazy % formatting in logging functions
로깅 메시지에서 f-string을 사용하면 lazy loading 이 되지 않는 다는 경고
예를 들어,
logger.info(f"Updated metadata for {urn}")
Python
복사
로그 레벨이 INFO보다 높아 메시지를 출력하지 않을 때도, f-string 내부 표현(예시에서는 urn)이 미리 문자열을 만들어
만약 로깅 레벨이 WARNING 일 경우 최종 로그 메시지는 출력되지 않지만 f string이 내부 표현식을 계산하는 비용이 계속 발생한다는 이야기
logger.info("Updated metadata for %s", urn)
Python
복사
pylint에서는 위와 같이 % 포맷을 사용하는 것을 권고
R1705: Unnecessary "else" after "return" (no-else-return)
if 조건: return 구문 다음에 불필요하게 else: 블록을 사용하는 경우 뎁스가 증가하여 가독성이 저하
예시
def func(x): if x < 0: return True else: return False
Python
복사
위 코드는 다음과 같이 개선
def func(x): if x < 0: return True return False
Python
복사
Git Hook을 도입하면서 이전 직장에서는 Black을 적용했던 경우와 적용하지 않았던 경우가 커밋마다 뒤섞여 관리되지 않던 혼란에서 벗어났다. 코드 품질 규칙이 중앙화 + 자동화되고 스타일 문제가 사라졌다. 또한 Pylint로 자주 발생하는 모듈 import error나 미처 알지못한 잠재적 에러를 미리 확인할 수 있어 안정감이 생겼다. 이런식으로 하나씩 체계가 잡혀가는 과정을 경험하면서, 개발 프로세스가 정돈될 수 있다는 것을 깨달았고 덕분에 많은 것을 배울 수 있었다.