마리아노 아나야의 파이썬 클린코드를 읽고 정리한 포스트입니다.
주제
- 클린 코드는 포매팅 이상의 훨씬 중요한 것을 의미한다.
- 때문에 표준 포매팅을 유지하는 것이 유지보수성의 핵심 유의사항이다.
- 파이썬이 제공하는 기능을 사용하여 자체 문서화된 코드를 작성하는 방법
- 코드의 레이아웃을 일정하게 유지하여 팀 멤버들이 문제의 본질을 해결하는데 초점을 맞출 수 있도록 도구를 설정하는 방법
클린 코드의 의미
클린 코드는 기계나 스크립트가 판단할 수 있는 것이 아니다. 클린 코드인지 아닌지는 다른 엔지니어가 코드를 읽고 유지 관리할 수 있는지 여부에 달려 있다. 우리는 코드를 작성하는 것보다 읽는데 훨씬 많은 시간을 소비한다.
클린 코드의 중요성
- 민첩한 개발과 지속적인 배포가 가능하다.
- 기획자가 새로운 기능을 요구할 때마다 리팩토링을 하고 기술 부채를 해결하기 위해 멈춰야 하는 상황이 발생하지 않는다.
- 기술부채(나쁜 결정이나 적당한 타협으로 인해 생긴 소프트웨어적 결함)를 감소시킨다.
- 개발팀이 제 시간에 어떤 것을 제공할 수 없어서 코드를 수정하고 리팩토링하기 위해 멈춘다는 것은 기술 부채에 대한 비용을 지불한다는 것이다.
- 기술 부채의 가장 안 좋은 점은 장기적이고 근본적인 문제를 내포하고 있다는 점이다.
- 잠재적인 문제들이 언젠가는 깨어나 돌발 변수가 될 것이다.
- 클린 코드는 수정이 가능한 코드를 만들기 위한 절대적인 요구사항이고, 클린 코드를 유지하는 것이 중요하다.
- 클린코드는 단순히 멋진 것이라거나 사치품이 아니라 필수적인 것이다.
예외 사항
아래와 같은 경우에는 상식적인 선에서 처리하는 것이 가능하다. 다만, 공통적으로 다시 사용하지 않을 코드이며 좋은 품질의 코드가 아니어도 큰 무리가 없는 상황이라는 점에 유의하자.
- 해커톤 참여
- 일회성 작업을 위한 간단한 스크립트 작성
- 프로그래밍 경진대회 참여
- 기존에 없던 개념을 검증하기 위해 개발하는 경우
- (나중에 버려질 것이 확실하다고 생각되는) 프로토타입을 개발할 때
- 앞으로 버려질 것이 확실한 레거시 프로젝트에 짧은 시간 동안만 작업하는 경우
클린 코드에서 코드 포매팅의 역할
파이썬에서는 PEP-8과 같은 표준을 가지고 있다.(https://www.python.org/dev/peps/pep-0008/). PEP-8은 가장 잘 알려진 표준이며 띄어쓰기, 네이밍 컨벤션, 줄 길이 제한 등의 가이드라인을 제공한다. 그러나 클린 코드는 PEP-8을 따르는 것 이상의 의미를 가진다. 클린 코드는 코딩 표준, 포매팅, 린팅 도구나 다른 검사 도구를 사용한 코드 레이아웃 설정과 같은 것 이상을 뜻한다. 클린 코드는 품질 좋은 소프트웨어를 개발하고, 견고하고 유지보수가 쉬운 시스템을 만들고, 기술 부채를 피하는 것을 의미한다. 이렇게 말하면 클린 코드는 PEP-8이나 코딩 스타일과 관련히 없다고 생각할 수도 있지만, 코드를 올바르게 포매팅하는 것은 작업을 효율화하기 위해 중요하다.
프로젝트 코딩 스타일 가이드 준수
좋은 코드 레이아웃에서 가장 필요한 특성은 일관성이다. 코드가 일관되게 구조화되어 있으면 가독성이 높아지고 이해하기 쉬워진다. 만약 팀원 모두가 코드의 일관성을 유지하지 않고 자신만의 방식으로 일을 하고 있다면 결국은 많은 시간과 노력을 투입해야만 할 것이다. 오류가 발생하기 쉽고 이해하기 어렵거나 애매한 부분들이 많이 생기게 되기 때문이다. 만약 개발 팀의 모든 멤버가 표준화된 구조를 사용한다면 훨씬 익숙한 코드를 작성하게 될 것이다. 그 결과 신속하게 패턴을 파악할 수 있으며 이러한 패턴을 염두에 두고 있다면 오류를 감지하는 것이 훨씬 쉽다.
코드 주석
가능한 한 적은 주석을 사용하는 것이 좋다. 좋은 코드는 코드 자체가 문서화되어 있기 때문이다. 의미 있는 함수나 객체를 통해 책임을 분리하고 올바를 추상화를 했고 명확하게 이름을 지정했다면 주석이 필요하지 않아야 한다. 주석을 추가한다는 것은 코드를 올바르게 작성하지 않았다는 징후이다. 그러나 어떤 경우에는 코드에 주석을 추가하는 것을 피할 수 없으며 그렇게 하지 않으면 위험할 수 있다. 주석을 추가해야 하는 경우는 다음과 같다.
- 외부 함수의 문제를 피하기 위해 특정한 파라미터를 전달해야 하는 경우: 이런 경우는 가능한 간결하게 문제가 무엇인지 설명하고 어떻게 해결해야 하는지 설명해야 한다. 어떤 식으로도 정당화하기 어려운 나쁜 주석도 있다. 바로 주석 처리된 코드이다. 이러한 코드는 무조건 삭제해야 한다. 주석 처리된 코드는 코드의 가독성을 떨어뜨리고, 코드를 이해하는데 혼란을 줄 수 있다. 특히, 요즘 버전 제어 시스템을 사용하면 주석 처리된 코드를 복구할 수 있기 때문에 주석 처리된 코드를 남겨두는 것은 더욱 불필요하다.
문서화
코드 문서화는 파이썬에서 중요한 부분이다. 왜냐하면 변수의 타입이 동적이어서 변수나 객체의 값이 무엇인지 잃어버리기 쉽기 때문이다. 파이썬 코드 안에 직접 문서화를 하는 방법인 docstring과 annotation에 대해 살펴보자.
Docstring
Docstring은 주석(comment)이 아니라 소스 코드에 포함된 문서
다. 모듈, 클래스, 메서드 또는 함수에 대해 문서를 제공하기 위한 것이다. 내가 작성한 컴포넌트를 다른 엔지니어가 사용하려고 할 때 docstring을 보고 동작 방식과 입출력 정보 등을 확인할 수 있어야 한다. 최대한 docstring을 추가하려고 노력하는 것은 좋은 습관이다.
Annotation
Annotation은 코드 사용자에게 함수 인자로 어떤 값이 와야 하는지 흰트를 주는 것이다. 아래와 같이 함수를 정의할 때 인자에 대한 타입을 지정하거나 리턴 타입을 지정할 수 있다.
@dataclass
class Point:
x: int
y: int
def location(p: Point) -> str:
"""맵에서 좌표에 해당하는 객체를 검색"""
return f'{p.x}, {p.y}'
타입 흰트는 인터프리터와 별개로 타입이 바르게 사용되었는지 확인하는 도구를 제공하고, 사용자에게는 호환되지 않는 데이터 타입을 사용한 경우 무엇이 잘못되었는지 흰트를 주기 위한 것이다. 데이터 타입과 관련하여 잠재적인 문제를 검사하는 도구는 mypy, pytype, pyre 등이 있다. @dataclass 데코레이터를 사용하면 init 메서드에 변수를 선언하고 할당하는 작업을 하지 않아도 바로 인스턴스 속성으로 인식한다.
[BEFORE]
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
[AFTER]
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
어노테이션과 docstring의 차이
데이터 타입을 어노테이션으로 명시하면 굳이 docstring에서 파라미터의 데이터 타입을 명시할 필요가 없다. 하지만 docstring은 함수의 동작 방식을 설명하는 데에는 여전히 유용하다. 어노테이션은 함수의 동작 방식을 설명하지 않기 때문이다. 둘은 서로 보완적인 관계이다.
도구 설정
도구를 사용한다는 것은 특정 검사를 ‘반복적으로’ 그리고 자동화하여 한다는 것을 의미한다. 이러한 도구는 CI(Continuous Integration) 빌드의 일부분으로 포함되어야 한다.
데이터 타입 일관성 검사
타입 흰트가 파이썬에 도입된 이후 데이터 타입의 일관성을 확인하기 위한 많은 도구가 개발되었는데, 그 중 mypy(https://github.com/python/mypy)를 살펴보자.
먼저, mypy 설치 방법은 다음과 같다.
$ pip install mypy
mypy 역시 완벽하지 않으므로 가끔 잘못된 탐지라고 생각되면 다음과 같이 해당 라인에 대한 검사를 무시하도록 할 수 있다.
def location(p: Point) -> str:
"""맵에서 좌표에 해당하는 객체를 검색"""
return f'{p.x}, {p.y}' # type: ignore
검사하는 방법은 다음과 같다.
$ mypy <파일명>
mypy나 기타 타입 검사 도구가 유용하게 쓰이려면 먼저 어노테이션을 정확하게 작성해햐 한다.
일반적인 코드 검증
앞서 소개한 데이터 타입을 검사하는 것 외에도 다른 도구를 사용하여 보다 일반적인 유형의 품질 검사를 하는 것도 가능하다. 대표적으로 pycodestyle(pep8), pylint, flake8 등이 있다. PEP-8 표준을 위반한 모든 라인과 에러 유형을 출력한다.
pylint의 설치 방법은 다음과 같다.
$ pip install pylint
pylint는 기본으로 모든 함수에 대해 docstring을 요구한다. 이 옵션을 원하지 않는다면 다음과 같이 설정할 수 있다.
[DESIGN]
disable=missing-docstring
자동 포매팅
PEP-8 준수 여부를 검사할 뿐만 아니라 자동으로 코드를 포매팅해주는 도구도 있다. 대표적으로 black(https://github.com/psf/black)이 있다. black을 사용하면 PEP-8보다는 제약이 많이 생기지만 대신에 문제의 핵심에 보다 집중할 수 있게 된다.
black의 설치 방법은 다음과 같다.
$ pip install black
black을 사용하면 코드를 자동으로 포매팅할 수 있다.
$ black <파일명>
black은 코드를 자동으로 포매팅할 뿐만 아니라 코드를 검사할 수도 있다.
$ black --check <파일명>
자동 검사 설정
리눅스 개발환경에서 빌드를 자동화하는 가장 일반적인 방법은 Makefile을 사용하는 것이다. Makefile은 빌드를 자동화하는 데에 사용되는 스크립트 파일이다. Makefile을 사용하면 빌드를 자동화하는 데에 필요한 모든 명령어를 한 곳에 모아둘 수 있다. 예를 들면 다음과 같이 할 수 있다. .PHONY는 make 명령어를 실행할 때 해당 명령어를 실행하도록 하는 것이다.
.PHONY: typehint
typehint:
mypy --ignore-missing-imports src/
.PHONY: test
test:
pytest tests/
.PHONY: lint
lint:
pylint src/
.PHONY: checklist
checklist: typehint test lint
.PHONY: black
black:
black -l 79 *.py
.PHONY: clean
clean:
find . -type f -name "*.pyc" | xargs rm -fr
find . -type d -name "__pycache__" | xargs rm -fr
이제 개발 머신이나 CI 빌드 머신에서 다음과 같이 명령어를 실행하면 된다.
$ make checklist
이것은 다음 단계를 수행한다.
- 먼저 PEP-8이나 black의 –check 파라미터를 사용해 코딩 가이드라인을 잘 지키고 있는지 확인
- 올바른 타입을 사용했는지 검사
- 최종적으로 테스트 실행
이 중 하나라도 실패하면 전체 프로세스가 실패하게 된다. 이러한 black, pylint, mypy와 같은 도구를 IDE에 연결하면 파일을 저장하거나 수정할 때마다 자동으로 검사가 실행되도록 설정할 수 있어서 보다 편리하게 사용할 수 있다. 이때 Makefile을 사용하면 몇 가지 장점이 있다.
- 가장 많이 사용하는 반복적인 작업을 간단하게 표준화할 수 있다.
- 팀원들이 빌드를 실행하는 방법을 알아야 하는데, Makefile을 사용하면 빌드를 실행하는 방법을 알려주지 않아도 된다. 새로운 구성원은 기존에 어떤 도구를 사용하는지에 상관없이 그저 make checklist 명령어를 실행하면 된다.
- 여러 작업을 한꺼번에 실행하는 표준화된 방법을 제공한다. 즉, CI 도구에서 Makefile의 명령어를 호출하도로고 하고, Makefile 안에서 실제 작업을 실행하도록 하면 CI 도구에서는 가능한 한 적은 설정 옵션을 갖게 된다.(나중에 세부 설정을 변경해야 할 때도 쉽게 변경할 수 있다.)
Next
‘2. 파이썬스러운(Pythonic) 코드’ -> Next