Pre-commit이란 무엇인가?

Pre-commit과 Ruff에 대해서 소개합니다.
avatar
2025.03.16
·
7 min read

이 글은 Open source contribution with python 시리즈의 첫 게시글 입니다. 다음으로 작성할 내용은 GitHub Actions, pytest에 해당합니다.

Why should we use pre-commit?

Pre-commit에 대해서 이해하려면, 우선 "이걸 왜 사용해야 하는가?"를 이야기할 필요가 있다. 가령 대규모 프로젝트를 한다고 생각해보자. (e.g. Poetry, Docker)

4061

그런데 만약 "일관된 규칙"이 없다면, 개발자(contributor)들은 각자 다른 방식으로 코드를 작성할 것이며, 따라서 줄 간격이 불규칙해지므로 가독성이 저하된다는 등의 문제가 발생할 수 있다.

이를 해결하기 위해 등장한 개념이 일관된 규칙을 적용하는, Pre-commit이다. Rust, C, Ruby, Swift처럼 다양한 언어에 대해 그들만의 규칙이 있지만, 오늘은 Python에서 가장 많이 사용되는 Ruff에 대해서 알아보고자 한다.


What is Ruff?

Ruff의 가장 큰 특징은 Rust로 빌드되었다는 점이다. 그 덕분에, Rust 고유의 memory-efficient & Fast 장점을 가진다. (c.f. Black, flake8 은 python으로 빌드된 pre-commit이다.)

심지어 Black, flake8의 기능을 그대로 사용하기 때문에, "더 가벼우면서 기능은 다양한" 도구라고 볼 수 있다. 이러한 장점 덕분에, Ruff는 python 생태계에서 표준 pre-commit으로 자리를 잡아가는 추세이다.

Features in Ruff

Formatting

Formatting은 "줄 간격을 정리함으로써 가독성을 향상시킨다"고 볼 수 있다. 간단한 예제를 살펴보자.

# Input
def _make_ssl_transport(
    rawsock, protocol, sslcontext, waiter=None,
    *, server_side=False, server_hostname=None):
    '''Make an SSL transport.''' # Markdown is wrtten with '
    if waiter is None:
      waiter = Future(loop=loop)
 

    if extra is None:
      extra = {}

    ...

# After Ruff
def _make_ssl_transport(
    rawsock,
    protocol,
    sslcontext,
    waiter=None,
    *,
    server_side=False,
    server_hostname=None,
):
    """Make an SSL transport."""
    if waiter is None:
        waiter = Future(loop=loop)

    if extra is None:
        extra = {}

위의 예제에서 변화는 다음과 같이 정리해볼 수 있다. :

  • Function 에서 augments (인자)의 줄 간격을 조절함으로써 가독성 향상

  • Markdown 부분을 '''에서 """으로 변경 (주석에 '가 들어가는 문제 방지)

  • 2줄 이상 불필요하게 줄 간격이 된 부분을 조절

Linting

Linting은 "사용되지 않는 인자를 감지해 안전하게 제거한다."고 볼 수 있다. 간단한 예제를 살펴보자.

# Input
import json  # 사용되지 않음

def add(x, y):
    z = 10
    return x + y

# After Ruff 
def add(x, y):
    return x + y

위의 예제에서 변화는 다음과 같이 정리해볼 수 있다. :

  • Unused package (json)이 삭제되어 코드가 간결해졌다.

  • add(x, y)에서 사용되지 않는 assignment (z)를 삭제해 코드가 간결해졌다.

How to use Ruff?

가장 간단한 방법은 ruff check --fix이지만, 대규모 오픈소스는 고유의 규칙을 .pre-commit-config.yaml 파일에 지정함으로써 관리한다. (e.g. Airflow, Poetry) 따라서 만약 당신이 오픈소스에 기여하고 싶다면, 이 규칙을 준수하는 것이 요구된다.

이를 지키는 방법은 pre-commit install을 통해서, 규칙을 로컬에 설치하는 방법이다. 이렇게 하면 commit을 하기 전에, 자동으로 pre-commit을 통과했는지 여부를 검사한다.

4068

그 뒤에는 pre-commit run --all-files를 통해서, pre-commit을 적용시킨다. (이 과정에서 다양한 오류가 발견될 것이고, 직접 해결해야 한다.)

만약 모든 내용을 Pass했다는 알림을 얻었다면, 이제 contribution을 할 시간이다. git commit -m "~"이 실행 가능해지며, 드디어 commit할 준비가 되었다고 볼 수 있다!!


Conclusion

지금까지 내용을 정리해보면, Ruff는 코드를 더 간결하고 체계적으로 검사함으로써 높은 품질의 코드를 위해 쓰이는 도구라고 정리해볼 수 있다. 가장 중요한 점은, 모든 contributor가 일관된 규칙 아래에서 개발하는 규칙을 준수함으로써 보다 완성도 높은 프로젝트를 경험할 수 있다는 점이다.

만약 여러분이 어떤 open source project에 기여를 해보고 싶지만, pre-commit을 통과하지 않은 상태로 PR (Pull Request)를 작성한다면, 심한 잔소리 (또는 차단당할 수도 있다..)를 들을 수 있다. 따라서 이를 방지하기 위해서는, pre-commit에 대해서 익숙해져야할 필요가 있다고 볼 수 있다.

See also

[1] Code Review with Ruff : Ruff 수정사항에 대한 자세한 설명이 있습니다.

[2] Sample pre-commit-config.yaml : 연습을 위한 간단한 파일입니다.

sexclude: |
    (?x)(
        # Documents
        ^.*\.md$|
        ^docs/|

        # Dataset & Checkpoints
        ^data/|
        ^datasets/|
        ^checkpoints/|
        ^weights/|
        ^.*\.pth$|
        ^.*\.ckpt$|

        # Config files
        ^\.env|
        ^\.venv/|
        ^venv/|
        ^\.idea/|
        ^\.vscode/
    )

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-yaml
    -   id: check-toml
    -   id: debug-statements
    -   id: check-added-large-files
        args: ['--maxkb=1024']


-   repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.9
    hooks:
    -   id: ruff
        args: [--fix, --exit-non-zero-on-fix]







- 컬렉션 아티클