Python 클린 코드 (1)

노하람·2022년 2월 7일
0

오늘부터(22.02.07) 파이썬 클린 코드(마리아노 아나야, 2019.02.18)를 읽으며 파이썬 클린 코드에 대해 공부하고 정리해보려고 합니다.

앞으로 지속적으로 퇴근 후에 회사에 남아서 공부하면서 연재하려고 하니 많은 분께 참고가 되었으면 좋겠습니다 🥳

사전 준비

예제 코드 다운로드

링크 : https://www.packtpub.com/books/content/support

  • 회원가입

  • 상단의 support탭으로 이동

  • Code Downloads & Errata 메뉴 클릭

  • Search 상자에 책 이름 입력

  • 코드 예제를 받으려는 책 이름 선택

  • 혹은 깃허브 레포를 pull해서 씁니다. git pull https://github.com/PacktPublishing/Clean-Code-in-Python

가상환경 구축

  • anaconda를 활용해서 파이썬 3.7 환경을 구축했습니다. conda create -n pcc python=3.7
  • 어떤 venv 도구를 써도 상관없고 파이썬 3.7 버전을 사용하면 됩니다.

소개, 코드 포매팅과 도구

  • 코드 품질 관리 실패는 결과적으로 기술 부채의 누적을 유발함
  • 포매팅과 문서화는 오버 스펙의 작업처럼 들리지만, 코드 관리의 기본이다.
  • 코드를 문서와 일치시키려면 왜 지속적인 작업이 필요한지 알고, 자동화를 위한 도구를 살펴본다.
  • 빌드의 일부로서 주요 도구를 설정해서 자동 실행되도록 한다.
  • 클린코드는 포매팅 이상의 훨씬 중요한 것이다. 때문에 표준 포매팅을 유지하는 것이 유지보수성의 핵심 유의사항이다.
  • 파이썬이 제공하는 기능을 사용하여 자체 문서화된 코드를 작성하는 방법
  • 코드의 레이아웃을 일정하게 유지하여 문제의 본질을 해결하는데 초점을 맞출 수 있도록 도구 설정

클린 코드의 의미

유일하고 엄격한 정의는 없다. 측정할 방법도 없다.
checker를 사용해 문법을 체크하거나, 린터(linter)를 사용해 취약 부분을 찾거너, 정적 분석기를 실행해 코드를 분석할 수는 있다. 그러나 충분하지 않다.

프로그래밍 언어의 진정한 의미는 아이디어를 다른 개발자에게 전달하는 것이다.

다른 엔지니어가 코드를 읽고 유지 관리할 수 있는지 여부가 클린 코드의 본질이다.
기존 코드를 수정하거나 새로운 기능을 추가할 때마다 수정하거나 확장할 코드의 환경을 먼저 읽어야만 한다.
언어 자체는 의사소통의 도구일 뿐이다.

클린 코드의 중요성

유지보수성 향상, 기술 부채 감소, 애자일 개발을 통한 효율적인 진행, 성공적인 프로젝트로 이어진다.

  • 예측 가능한 속도로 지속적으로 배포하려고 한다면 유지보수가 가능한 좋은 코드를 갖는 것이 필수다.(그렇지 않으면 새 기능을 요구할 때마다 리팩토링을 하고 앉았겠지?😂😂)
  • 기술 부채 : 나쁜 결정/적당한 타협의 결과, 현재 직면한 문제가 과거의 잘못된 코드로 인한 결과인가? 혹은 현재의 조합한 코드로 인해 미래에 문제가 생기는 것은 아닐까?
    - 지금 해결하는 것보다 미래 해결하는 것이 훨씬 어렵고 비싸다(비용은 측정이 가능하다). 갈수록 비싸진다.
    • 개발팀이 코드를 수정하고 리팩토링하기 위해 멈춘다는 것은 기술 부채에 대한 비용을 지불한다는 것(언젠간 프로젝트의 돌발 변수가 된다)

클린 코드에서 코드 포매팅의 역할

클린 코드가 PEP-8 또는 프로젝트 가이드라인 같은 표준 지침에 따라 코드를 포매팅하고 구조화하는 것을 말할까? -> 아니다!

어떤 코드 또는 소프트웨어 컴포넌트가 표준을 100% 준수한다해도 클린 코드의 요건을 충족하지 못할 수 있다.(품질, 견고하고 유지보수가 쉽게, 기술 부채 회피)

클린코드는 PEP-8이나 코딩 스타일과 관련이 없다고도 할 수 있고, 그 이상의 것을 의미한다. 그러나 코드를 올바르게 포매팅 하는 것은 작업을 효율화하기 위해 중요하다.

프로젝트 코딩 스타일 가이드 준수

코딩 가이드라인은 품질 표준을 지키기 위한 최소한의 요구사항이다.

  • 좋은 코드 레이아웃에서 가장 필요한 특성 : 일관성

구조화된 패턴을 가진 코드는 훨씬 빠르게 이해하고, 오류를 감지하기 쉽다.

특히 파이썬이 따라야 하는 코딩 스타일은 PEP-8이다. 작업하는 프로젝트의 특수성(예. 한 줄의 길이, 들여쓰기 간격 등)을 확장하거나 일부만 채택할 수 있다.
그러나 다른 표준을 고안하기 보다는 순수 PEP-8을 사용하든, PEP-8을 확장해서 사용할 것이 권장된다.

그 이유는 PEP-8은 이미 파이썬 구문의 많은 특수성(다른 언어와 다른)을 고려하여 작성되었으며, PEP-8 구문은 실제로 파이썬 개발자가 만들었으므로 이만큼 좋은 표준이 없기 때문이다.

PEP-8은 다음과 같은 특징을 가지고 있다.
1. 검색 효율성(Grepability) : 코드에서 토큰을 grep(텍스트 검색 명령어) 할 수 있는 기능.
- 이 표준은 변수에 값을 할당하는 방법과 함수의 키워드 인자에 할당하는 방법의 차이를 소개
- 다음 예제를 통해 grep 명령을 실행하면 어떤 파일의 몇 번쨰 줄에서 할당하는지 알려준다.
- grep -nr "location=" .

- PEP-8은 키워드 인자에 값을 할당할 때는 띄어쓰기를 사용하지 않지만, 변수에 값을 할당할 때는 띄어쓰기를 사용하도록 권고
- 이를 활용해 검색 조건을 변경할 수 있음.(검색 시 공백 차이) -> 표준을 따를 때 얻을 수 있는 장점 중 하나

  1. 일관성 : 코드가 일정한 포맷을 가지면 훨씬 쉽게 읽을 수 있음.
    • 코드 레이아웃, 문서화, 이름 작명 규칙 등이 모든 저장소에서 동일하다면 새로운 팀원이 교육받을 때 매우 빠르게 습득 가능
  2. 코드 품질 : 코드를 구조화하여 살펴보면 한 눈에 코드를 이해하고 버그와 실수를 찾기 쉬움
    • 코드 품질 도구를 사용하면 잠재적인 버그 찾기 가능, 정적 분석 도구를 사용하면 한 줄당 버그의 개수를 줄이는데 도움

Docstring과 어노테이션

파이썬 코드 안에 직접 문서화를 하는 방법
훌륭한 코드는 그 자체도 자명하지만 문서화도 잘 되어 있다.

코드를 문서화하는 것은 주석을 추가하는 것과는 다르다.
주석은 가급적 피해야만 한다.
문서화는 데이터 타입이 무엇인지 설명하고 예제를 제공할 수 있다.

파이썬은 동적으로 타입을 결정하기 때문에 함수나 메서드를 거치면 변수나 객체의 값이 무엇인지 알기 어려운 경우가 많으므로(너무 공감된다. 디버그의 향연..)
어노테이션을 통해 이러한 정보를 명시하면 향후 다른 개발자가 이해하는데 도움이 많이 된다.

어노테이션은 Mypy 같은 도구를 사용해 타입 힌트 등의 자동화된 검증을 실행할 수 있다.

Docstring

쉽게 말해 소스 코드에 포함된 문서이다. 코멘트가 아니라 문서이다.
기본적으로 리터럴 문자열이며, 로직의 일부분을 문서화 하기 위해 코드의 어딘가에 배치된다.(def 정의 아래에만 들어가는게 아니었구나?)

  1. 주석은 코드로 아이디어를 제대로 표현하지 못했음을 나타낸다.
  2. 오해의 소지가 있다. 복잡한 내용을 이해하기 위해 시간을 할애하는 것보다 최악은 코드가 어떻게 동작하는지 주석을 확인한 후 실제로 동작하는 것의 다른 점을 파악하는 것이다. 코드를 변경할 떄 주석 업데이트를 깜빡하는 경우가 많아 현재와 일치하지 않는 경우 또 다른 위험을 초래한다.
  3. 외부 라이브러리에 오류가 있다면 짧은 조석을 다는 것이 허용된다.

docstring의 경우는 코드의 특정 컴포넌트(모듈, 클래스, 메서드 또는 함수)에 대한 문서화이다. 사용이 권장된다. 가능한 많은 docstring을 추가하라.

이유 : 파이썬은 동적 타이핑을 한다.
- 예를 들어 파라미터의 값으로 무엇이든 사용할 수 있고 타입을 체크하거나 강요하지 않는다.
- 따라서 함수를 수정해야 하는데 함수의 이름과 파라미터의 이름이 충분히 설명되어 있고, 어떤 타입을 사용해야하는지 docstring을 통해 도움을 받을 수 있다.
- 예상되는 함수의 입출력을 문서화하면 사용자가 함수의 동작 원리를 이해하기 쉽다.

docstring은 코드의 일부가 되어 접근할 수 있어야 한다. 객체에 docstring이 정의되어 있으면 __doc__ 속성을 통해 접근이 가능하다. <함수명>.__doc__ : docstring 출력

즉 런타임 중에 접근하고 심지어 소스 코드에서 docstring 내용을 추가하거나 컴파일 하는 것이 가능하다. Sphinx(스핑크스)를 실행하면 프로젝트 문서화를 위한 기본 골격을 만들어 준다.

특히 autidoc 익스텐션(sphinx.ext.autodoc)을 사용하면 코드에서 docstring을 가져와 문서화된 페이지를 만들어준다.(Readme.md 같은걸 말하는건가?)

docstring이 유용하려면 모든 팀원이 문서화에 참여할 수 있어야하고, 지속적으로 수작업이 필요하며, 상세하게 작성되어야 한다.
따라서 코드를 변경한 경우 위키(wiki), 사용자 매뉴얼, README, docstring 등 관련된 모든 내용을 업데이트하는 것이 중요하다.

어노테이션

PEP-3107에서는 어노테이션을 소개하고 있다.
기본 아이디어는 코드 사용자에게 함수 인자로 어떤 값이 와야 하는지 힌트를 주자는 것이다.
어노테이션은 타입 힌팅(type hinting)을 활성화한다.

어노테이션을 사용해 변수의 예상 타입을 지정할 수 있다.
실제로 타입 뿐 아니라 변수를 이해하는데 도움이 되는 어떤 형태의 메타데이터라도 지정할 수 있다.

class Point:
	def __init__(self, lat, long):
    	self.lat = lat
        self.long = long
        
def locate(latitude: float, longitude: float) -> Point:
	"""맵에서 좌표에 해당하는 객체를 검색"""

여기서 latitude와 longitude는 float 타입의 변수다.
이것을 통해 함수 사용자는 예상되는 타입을 알 수 있다. 하지만 파이썬이 타입을 검사하거나 강제하지는 않는다.

또한 함수 반환 값에 대한 예상 타입을 지정할 수도 있다.
위 예제에서 Point는 사용자 정의 클래스이므로 반환되는 값이 Point의 인스턴스라는 것을 의미한다.

그러나 어노테이션으로 타입만 지정할 수 있는 것은 아니다. 파이썬 인터프리터에서 유효한 어떤 것도 사용할 수 있다.
예를 들어 변수의 의도를 설명하는 문자열, 콜백이나 유효성 검사 함수로 사용할 수 있는 callable 등이 있다.

어노테이션을 사용하면 __annotaion__이라는 속성이 생긴다.
이 속성은 어노테이션의 이름과 값을 매핑한 사전 타입의 값이다.
이 정보를 이용해서 문서 생성, 유효성 검증 또는 타입 체크를 할 수 있다.

PEP-484를 적용하면 어노테이션을 통해 코드를 확인할 수 있다.(어노테이션을 통해 함수의 타입을 체크)

타입 힌팅은 인터프리터와 독립된 추가 도구를 사용하여 코드 전체에 올바른 타입이 사용되었는지 확인하고 호환되지 않는 타입이 발견되었을 때 사용자에게 힌트를 주는 것

이러한 검사를 도와주는 Mypy는 나중에 프로젝트에서 사용해보자.

  • linter : 정적 코드 분석 도구

파이썬 3.6부터는 함수 파라미터와 리턴 타입뿐만 아니라 변수에 직접 주석을 달 수 있다.
다음과 같이 값을 지정하지 않는 채로 변수의 타입을 선언할 수 있다.

class Point:
	lat: float
    long: float

어노테이션은 docstring을 대체하는 것인가?

대답은 "예"이다. 어노테이션과 덕스트링은 서로 보완적인 개념이기 때문이다.
덕스트링에 포함된 정보의 일부는 어노테이션으로 이동시킬 수 있는 것이 사실이다.
그러나 덕스트링을 통해 보다 나은 문서화를 위한 여지를 남겨두어야 한다.
특히 동적 데이터 타입과 중첩 데이터 타입의 경우 예상 데이터의 예제를 제공하여 어떤 형태의 데이터를 다루는지 제공하는 것이 좋다.

docstring을 사용해 상세한 설명을 한 예제는 아래와 같다.

def data_from_response(response: dict) -> dict:
	"""
    response에 문제가 없다면 response의 payload를 반환
    
    - response 사전의 예제::
    {
    	"status": 200, # <int>
        "timestamp": "...". # 현재 시간의 ISO 포맷 문자열
        "payload": {...} # 반환하려는 사전 데이터
    }
    
    - 반환 사전 값의 예제::
    {"data": {..} }
    
    - 발생 가능한 에외:
    - HTTP status가 200이 아닌 경우 ValueError 발생
    """
    if response["status"] != 200:
    	raise ValueError
    return {"data": response["payload"]}

기본 품질 향상을 위항 도구 설정

반복적인 확인 작업을 줄이기 위해 코드 검사를 자동으로 실행하는 기본 도구를 설정해보자.

좋은 코드를 위해선 다음 질문이 필요하다.

  • 이 코드를 동료 개발자가 쉽게 이해하고 따라갈 수 있을까?
  • 업무 도메인에 대해서 말하고 있는가?
  • 팀에 새로 합류하는 사람도 쉽게 이해하고 효과적으로 작업할 수 있을까?

뛰어난 엔지니어는 레이아웃의 개념을 뛰어 넘는 그 이상의 것을 읽고 쓸 수 있어야 한다.
실제 어떤 패턴이 사용되었는지 살펴서 코드의 실제 의미와 가치를 이해하는데 시간을 투자하는 것이 효과적이다.

이 모든 검사는 자동화해야 한다.

테스트 또는 체크리스트의 일부가 되어 지속적인 통합 빌드(continuous intergration build)의 하나가 되어야 한다.
이러한 검사의 실패하면 빌드도 실패해야 한다.
이렇게 하는 것만이 코드 구조의 연속성을 확보할 수 있는 유일한 방법이다.

이것은 팀에서 참고할 수 있는 객관적인 지표 역할도 한다.
일부 엔지니어 또는 팀 리더가 코드 리뷰 시 PEP-8에 대한 동일한 의견을 항상 말하도록 하게 만드는 대신,
빌드 시 자동으로 실패하도록 해야 객관성을 얻을 수 있다.

Mypy를 사용한 타입 힌팅

mypy는 파이썬에서 가장 일반적으로 사용하는 정적 타입 검사(linter) 도구이다.
mypy를 설치하면 프로젝트의 모든 파일을 분석하여 타입 불일치를 검사해준다.
버그를 조기에 발견할 수 있기 때문에 유용하지만, 가끔 잘못 탐지하는 경우도 있다.

프로젝트 셋업 파일에 종속성을 추가하는 것이 좋다.
pip install mypy

mypy <파일명> 을 입력하면 타입 검사 결과를 제공한다. 보고된 내용은 가능한 준수하는 것이 좋다.
실제 프로덕션 시 문제가 재현될 수 있기 때문이다.
완벽한 도구가 아니여서 가끔 잘못된 탐지를 할 수 있는데, 이런 경우 다음과 같이 문장 끝에 주석을 추가하여 mypy가 무시하도록 할 수 있다.
type_to_ignore = "something" # type: ignore

Pylint를 사용한 코드 검사

코드의 구조를 검사하는 많은 도구가 있다.
기본적으로 PEP-8을 준수했는지 여부를 검사하는 것으로 pycodestyle(pep8에서 pycodestyle로 이름이 변경됨)이나 Flake8 등의 많은 도구가 있다.
Pylint가 저자 의견으론 가장 완결성 높고 엄격하고 설정 가능한 옵션이 많다고 한다.

pip install pylint

그리고 커맨트창에서 pylint를 입력하기만 하면 된다.
Pylint는 .pylintrc 파일을 통해 설정 값을 바꿀 수 있다.
이 파일을 통해 규칙을 활성화 또는 비활성화할 수 있고 한 줄의 최대 글자수 같은 값을 설정할 수 있다.

자동 검사 설정

리눅스 개발환경에서 빌드를 자동화하는 가장 일반적인 방법은 makefile을 사용하는 것이다.
makefile은 프로젝트를 컴파일하고 실행하기 위한 설정을 도와주는 도구이다.
빌드 외에도 포매팅 검사나 코딩 컨벤션 검사를 자동화하기 위해 사용할 수도 있다.

이렇게 하기 위한 가장 좋은 방법은 테스트를 위한 각각의 target을 만들고,
이것들을 모두 실행하는 또 다른 targer을 만드는 것이다. 예를 들면 아래와 같다.

typehint:
mypy src/ tests/

test:
pytest tests/

lint:
pylint src/ tests/

checklist: lint typehint test

.PHONY: typehint test lint checklist

이제 개발 머신과 CI 빌드 머신에서 실행할 커맨드는 다음과 같다.
make checklist

이것은 다음 단계를 실행한다.
1. 코딩 가이드라인 검사(PEP-8 등)
2. 올바른 타입을 사용했는지 검사
3. 최종적으로 테스트 실행

만약 이 중에 어떤 단계라도 실패하면 전체 프로세스가 실패한 것으로 간주한다.
팀에서 사용하는 컨벤션이나 코드 구조화를 위한 자동화를 해보는 것도 좋은 생각이다.
Black이라는 도구는 자동으로 코드를 포매팅한다.
Black은 고유한 방식으로 포매팅한다. 이것은 단호하고 결정론적이어서 항상 같은 결과를 출력한다.

예를 들어 Black은 문자열에 항상 쌍따옴표를 사용하고, 파라미터의 형태는 항상 같은 구조를 사용한다.
코드가 항상 동일한 구조를 따르는 경우 실제 코드의 변경사항이 있는 경우에만 pull request에서 보이게 된다.
사일은 PEP-8보다 더 엄격한 기준이지만 도구를 사용함으로써 포매팅 걱정할 필요 없이 문제에만 집중할 수 있다.

black 사용 후기

43개의 파일이 reformatting을 완료했다.
대충 훑어봤을 때 따음표, 들여쓰기, argument 줄 바꿈 등이 수정되었다.
사실 난 바뀐게 보기 좀 더 불편하긴 한데, 다들 이렇게 약속하고 쓰는 거니까 이게 맞겠지!

==========

지금까지 클린 코드가 코드의 구조나 레이아웃보다 훨씬 중요함을 이해했다.
또한 최소한적으로 코딩 스타일이나 가이드라인을 준수하는 것 또한 여러모로 중요하다.
이러한 부분은 위에서 소개했듯 도구를 사용하고, 이를 자동화 하는 것이 편리하다.

다음 장에서는 좀더 파이썬 코드에 초점을 맞추어 분석을 해보고, 파이썬이 다른 언어와는 다른 철학과 방식을 가지고 있음을 이해해보자!

아, 재밌다. 알면 알수록 파이썬이 좋은 언어라는 걸 느낀다.☺️

profile
MLOps, MLE 직무로 일하고 있습니다😍

0개의 댓글