[개발 깨알 상식_Tips] / 실행 전에 문법 오류 먼저 걸러내기.md

실행 전에 문법 오류 먼저 걸러내기

조회

2026년 6월 13일 | 개발 깨알 상식_Tips


실행 전에 문법 오류 먼저 걸러내기 흐름 도식
모듈을 실제로 import하기 전에 문법 오류를 먼저 걸러내는 사전 점검 흐름.

Python 스크립트 묶음을 한 번에 고칠 때 제일 피곤한 실패는, 한참 뒤 실행 경로에 들어가서야 문법 오류가 터지는 경우다. 테스트가 잘 짜여 있으면 결국 잡히지만, 작은 CLI나 배치 스크립트는 모든 파일을 import하지 않을 때가 많다. 나는 이런 폴더에서는 무거운 테스트를 돌리기 전에 python -m compileall -q를 한 번 먼저 넣는다. 코드를 실행하지는 않지만, 파서가 읽을 수 없는 파일을 빠르게 걷어내는 얇은 체에 가깝다.

실행 검증이 아니라 파싱 검증으로 보기

compileall은 디렉터리 아래의 .py 파일을 바이트코드로 컴파일한다. 여기서 중요한 점은 모듈을 import해서 top-level 코드를 실행하는 검사가 아니라는 것이다. 그래서 네트워크 호출, 환경변수 접근, 데이터베이스 연결 같은 부작용 없이 문법 오류를 먼저 잡을 수 있다.

python -m compileall -q scripts src tests

-q를 붙이면 정상 파일 목록은 줄이고 에러만 남긴다. 실패한 파일이 있으면 종료 코드도 실패로 올라온다. CI 앞단이나 로컬 점검 스크립트에 넣기 좋은 이유가 여기에 있다. 출력이 길어지지 않으면서도, 빠진 괄호나 잘못된 들여쓰기처럼 실행 전부터 틀린 부분을 바로 알려 준다.

테스트보다 앞에 두면 좋은 순간

나는 이 명령을 테스트 대체재로 보지는 않는다. 대신 테스트가 시작되기 전에 깨질 파일을 먼저 분리하는 단계로 둔다. 예를 들어 자동 생성된 스크립트, 임시 마이그레이션 파일, 실험용 유틸이 섞인 폴더에서는 테스트가 특정 경로만 밟고 지나갈 수 있다. 그 상태에서 배포나 배치 실행 때 처음 열리는 파일이 있으면, 그때서야 SyntaxError가 튀어나온다.

python -m compileall -q tools jobs migrations
python -m pytest -q

이 순서로 두면 실패 해석이 단순해진다. 첫 줄에서 깨지면 아직 실행 의미를 볼 단계가 아니다. 파일이 Python 문법으로 읽히지 않는다는 뜻이다. 첫 줄은 통과했는데 테스트가 깨지면 그때부터 import 경로, fixture, 입출력, assertion을 본다. 파싱 실패와 실행 실패를 같은 테스트 실패로 섞지 않는 것만으로도 로그를 다시 여는 시간이 줄어든다.

__pycache__가 생기는 점은 의도적으로 다루기

compileall은 이름 그대로 컴파일을 하므로 __pycache__ 아래에 .pyc 파일을 만들 수 있다. 로컬 작업에서는 큰 문제가 아니지만, 깨끗한 작업트리를 중요하게 보는 저장소라면 이 점을 미리 알고 있어야 한다. 보통은 __pycache__/*.pyc가 무시 목록에 들어가 있으니 괜찮지만, 그렇지 않은 저장소에서는 점검 뒤 상태가 지저분해 보일 수 있다.

그래서 나는 이 명령을 넣기 전에 두 가지를 확인한다. 하나는 무시 규칙이다. 다른 하나는 대상 경로다. 프로젝트 루트 전체를 무심코 훑기보다, 실제로 내가 관리하는 코드 폴더만 넘긴다. 예를 들어 vendor 코드나 임시 다운로드 폴더까지 같이 넣으면 내가 고칠 수 없는 파일의 오류까지 로그에 섞인다. 검사 범위는 넓게가 아니라 고칠 수 있게 잡는 편이 낫다.

제외할 경로는 -x로 먼저 잘라내기

폴더 안에 생성물이나 외부 복사본이 섞여 있으면 -x로 제외 규칙을 줄 수 있다. -x는 전체 경로에 대해 정규식을 적용한다. 나는 보통 가상환경, 빌드 결과, 캐시 폴더를 검사 대상에서 뺀다.

python -m compileall -q \
  -x '(^|/)(.venv|build|dist|node_modules|__pycache__)(/|$)' \
  scripts src

이런 제외 규칙은 멋을 내기 위한 옵션이 아니다. 실패 로그를 내가 고칠 수 있는 파일로 유지하기 위한 장치다. 외부 패키지나 생성물이 섞이면, 실제 문제는 내 코드 한 줄인데 로그는 수십 줄로 커진다. 경량 점검일수록 노이즈를 먼저 줄여야 실패 신호가 선명해진다.

빠르게 훑을 때와 강하게 훑을 때를 나누기

매번 같은 강도로 돌릴 필요는 없다. 저장 직후 가볍게 볼 때는 수정한 폴더만 넘긴다. 릴리스 전이나 배치 배포 전에는 대상 코드를 조금 넓혀 본다. 파일 수가 많으면 -j로 worker를 늘릴 수도 있지만, 나는 먼저 범위를 정확히 좁히는 쪽을 선호한다. 병렬화로 빨라진 검사가 엉뚱한 폴더까지 훑고 있으면, 결국 사람이 읽을 로그가 무거워진다.

파일 목록이 이미 정해져 있을 때는 -i도 쓸 수 있다. 예를 들어 생성된 manifest에 점검 대상 파일을 모아 두고, 그 목록만 컴파일하게 만드는 식이다. 이 방식은 큰 저장소에서 특히 편하다. 디렉터리 전체를 다시 훑는 대신, 방금 만든 스크립트 묶음이나 배포에 포함될 파일만 확인할 수 있기 때문이다.

python -m compileall -q -i python-files.txt

다만 목록 파일을 쓸 때도 기준은 같다. 목록이 오래됐거나 자동 생성 과정이 불안하면, compileall은 그 불안정함까지 해결해 주지 않는다. 그래서 나는 목록 기반 검사를 쓸 때 파일 생성 단계와 컴파일 단계를 로그에서 붙여 둔다. 어떤 파일을 대상으로 문법 검사를 했는지 나중에 다시 열 수 있어야, 통과 결과도 의미가 생긴다. 빈 목록을 통과로 볼지, 준비 실패로 볼지도 따로 정해 두는 편이 안전하다.

python -m compileall -q -j 0 scripts src

-j 0은 사용 가능한 CPU 수에 맞춰 병렬 컴파일을 시도한다. 다만 작은 저장소에서는 체감 차이가 거의 없고, 실패 로그가 동시에 올라와 읽기 불편할 때도 있다. 그래서 기본값은 단순하게 두고, 파일이 많은 저장소에서만 병렬 옵션을 붙이는 편이 낫다.

잡아 주는 것과 못 잡는 것

compileall이 잡아 주는 것은 문법적으로 읽을 수 없는 Python 파일이다. 괄호 누락, 잘못된 들여쓰기, 깨진 문자열, 버전에 맞지 않는 문법 같은 것들이다. 반대로 import 대상 패키지가 설치됐는지, 함수가 기대한 값을 반환하는지, 설정 파일이 맞는지는 여기서 알 수 없다. 그래서 이 명령 하나를 통과했다고 “프로그램이 맞다”고 말하면 안 된다.

내 기준에서는 python -m compileall -q가 가장 잘 맞는 자리가 있다. 테스트보다 앞, 배포보다 앞, 긴 배치보다 앞이다. 실행 의미를 검증하기 전, 최소한 Python 파일이 해석 가능한 상태인지 확인하는 작은 문턱으로 둔다. 문법 오류는 깊은 로그 속에서 발견할수록 허무하다. 가능하면 제일 얕은 단계에서, 가장 짧은 명령으로 먼저 떨구는 편이 낫다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.