티스토리 뷰

python

(펌) Python Clean Code

게으른 the lazy 2025. 7. 9. 16:22

윤쌤 (지행사)

원문: https://testdriven.io/blog/clean-code-python/

[Clean Code in Python

This article looks at how to write clean code in Python.

testdriven.io](https://testdriven.io/blog/clean-code-python/)

내용은 일부 생략했습니다.


PEP 8

Naming

  • 클래스명은 CamelCase로 한다. (MyClass)
  • 변수명, 함수명, 모듈명은 snake_case로 한다. (first_name, quick_sort(), numpy)
  • 상수는 대문자 snake_case로 한다. (PI = 3.14159)
  • 문자열을 작은 따옴표로 할지 큰 따옴표로 할지는 일관성만 갖추면 된다.

Line formatting

  • 들여쓰기는 4칸이다. 탭보다 스페이스가 선호된다.
  • 한 줄은 79칸을 넘지 않는다.
  • 한 줄에 여러 statement를 적지 않는다.
  • Top-level 함수와 클래스 정의는 앞뒤로 두 줄의 공백을 둔다.
  • 클래스 내에서 메서드 정의는 앞뒤로 한 줄의 공백을 둔다.
  • 서로 다른 모듈의 import는 줄을 분리한다.

Whitespace

  • []{} 내에서는 불필요한 공백을 넣지 않는다.
  • 줄 끝에 공백을 넣지 않는다.
  • 이항 연산자 양쪽에 공백을 하나씩 넣는다.
  • 키워드 인자를 지정할 때에는 = 앞뒤로 공백을 넣지 않는다.

Comments

  • 주석은 코드와 모순되지 않아야 한다.
  • 주석은 완성된 문장이어야 한다.
  • 주석을 쓸 때 # 뒤에 공백이 하나 있어야 한다.
  • 독스트링은 첫 줄에 요약 문장을 쓰고, 그 다음에 추가 설명을 붙인다.

The Zen of Python

  • 아름다운 것이 추한 것보다 낫다. (Beautiful is better than ugly.)
  • 명시적인 것이 암묵적인 것보다 낫다. (Explicit is better than implicit.)
  • 간결한 것이 복잡한 것보다 낫다. (Simple is better than complex.)
  • 복잡한 것이 난해한 것보다 낫다. (Complex is better than complicated.)
  • 수평적인 것이 내포된 것보다 낫다. (Flat is better than nested.)
  • 여유로운(희소한) 것이 밀집한(조밀한) 것보다 낫다. (Sparse is better than dense.)
  • 가독성은 중요하다. (Readability counts.)
  • 규칙을 어겨야 할 만큼 특별한 경우라는 것은 없다. (Special cases aren't special enough to break the rules.)
  • 실용성이 순수성보다 중요하기는 하지만. (Although practicality beats purity.)
  • 오류는 절대로 묵시적으로 전달되어서는 안된다. (Errors should never pass silently.)
  • 묵시적으로 전달하도록 '명시'한 것이 아니라면. (Unless explicitly silenced.)
  • 모호함을 대할 때, 이를 추측하려는 유혹을 거부하라. (In the face of ambiguity, refuse the temptation to guess.)
  • (문제해결의) 명백한 하나의 방법이 존재해야 하며, 이왕이면 유일한 방법이어야 한다. (There should be one - and preferably only one - obvious way to do it.)
  • 비록 그 방법이 처음에는 명백해 보이지 않을지라도. (Although that way may not be obvious at first.)
  • 지금 행동에 옮기는 것이 아예 안하는 것보다는 낫다. (Now is better than never.)
  • 아예 안하는 것이 지금 당장 하는 것보다 나을 때도 많지만. (Although never is often better than right now.)
  • 구현방법을 설명하기 어렵다면, 아이디어가 나쁜 것이다. (If the implementation is hard to explain, it's a bad idea.)
  • 구현방법을 설명하기 쉽다면, 좋은 아이디어일지도 모른다. (If the implementation is easy to explain, it may be a good idea.)
  • 네임스페이스는 개훌륭한 아이디어이다. 더 많이 사용하자! (Namespaces are one honking great idea -- let's do more of those!)

Code Principles

DRY: Don't Repeat Yourself

  • 모든 정보는 하나의, 신뢰할 수 있는, 모호하지 않은 형태여야 한다.
  • 코드에 중복된 부분이 있다면 iteration으로 바꿔라.
  • 주의사항: DRY 원칙은 코드을 과하게 추상화시키고, 외부 의존성을 만들고, 코드가 복잡해질 수 있다. 초반부터 DRY 원칙을 적용하는 것은 좋지 않다. 잘못된 추상화보다는 조금 반복된 코드가 더 낫다.

KISS: Keep It Simple, Stupid

  • 대부분 시스템은 복잡할 때보다 간단할 때 잘 동작한다.
  • 시스템 설계 시 불필요한 복잡성을 발생시키지 말아야 한다.

SoC: Separation of Concerns

  • 프로그램의 각 부분은 서로 다른 관심사(concern)를 가져야 한다. 대표적인 예가 MVC이다.
  • 주의사항: 너무 많은 모듈을 만들지 마라. 모듈이 많아지면 문제도 많아진다. 새로운 모듈이 의미가 있을 때만 만들어라.

SOLID

  • The Single-responsibility principle: 클래스를 수정하는 이유는 단 한 가지여야 한다. 클래스는 한 가지 책임만 가져야 한다.
  • The Open-closed principle: 코드는 확장에는 열려 있고 수정에는 닫혀 있어야 한다. 기존 코드를 바꾸지 않고도 기능 확장이 가능해야 한다.
  • The Liskov substitution principle: 하위 클래스는 상위 클래스의 자리를 문제 없이 대신할 수 있어야 한다. 상위 클래스 자리에 하위 클래스를 넣어도 계획대로 잘 동작해야 한다.
  • The Interface segregation principle: 사용자가 사용하지 않는 기능을 강제로 구현하는 일은 없어야 한다. 인터페이스는 가능한 작게 나눠서, 클라이언트가 필요없는 기능에 끌려다니지 않게 해야 한다.
  • The Dependency inversion principle: 구체화의 의존하지 말고 추상화에 의존하라. 추상화된 인터페이스를 먼저 설계하고, 구체적인 구현은 그에 뒤따르게 하라.

Code Formatters

Python code formatters:

Python linters


Naming convention

  • 이름에는 의미와 의도가 담겨야 한다. 길고 설명적인 이름이 짧은 이름에 주석을 덧붙이는 것보다 낫다.
# This is bad
# represents the number of active users
au = 55

# This is good
active_user_amount = 55

변수

  • 변수명에는 명사를 쓴다.
  • 변수명에는 설명적이고 의도가 드러나는 이름을 쓴다.
# This is bad
c = 5
d = 12

# This is good
city_counter = 5
elapsed_time_in_days = 12
  • 변수명에는 발음 가능한 이름을 쓴다.
from datetime import datetime

# This is bad
genyyyymmddhhmmss = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')

# This is good
generation_datetime = datetime.strptime('04/27/95 07:14:22', '%m/%d/%y %H:%M:%S')
  • 변수명에 모호하거나 직접 개발한 축약어는 쓰지 않는다.
# This is bad
fna = 'Bob'
cre_tmstp = 1621535852

# This is good
first_name = 'Bob'
creation_timestamp = 1621535852
  • 변수명에 쓸 단어는 일관성을 가져야 하며 동의어는 쓰지 않는다.
# This is bad
client_first_name = 'Bob'
customer_last_name = 'Smith'

# This is good
client_first_name = 'Bob'
client_last_name = 'Smith'
  • 상수로 쓸 값은 변수로 의미를 부여한다.
import random

# This is bad
def roll():
    return random.randint(0, 36)  # what is 36 supposed to represent?

# This is good
ROULETTE_POCKET_COUNT = 36

def roll():
    return random.randint(0, ROULETTE_POCKET_COUNT)
  • 여러 자료형이 섞여있을 경우 접미사로 자료형을 붙여라.
# This is bad
names = ["Nick", "Mike", "John"]

# This is good
score_list = [12, 33, 14, 24]
word_dict = {
    'a': 'apple',
    'b': 'banana',
    'c': 'cherry',
}
  • 맥락을 중복하지 마라.
# This is bad
class Person:
    def __init__(self, person_first_name, person_last_name, person_age):
        self.person_first_name = person_first_name
        self.person_last_name = person_last_name
        self.person_age = person_age


# This is good
class Person:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

함수

  • 함수명에는 동사를 쓴다.
  • 같은 의미의 다른 단어를 쓰지 마라.
# This is bad
def get_name(): pass
def fetch_age(): pass

# This is good
def get_name(): pass
def get_age(): pass
  • 짧고 간단한 함수를 작성하라.

  • 하나의 함수는 하나의 작업만 한다. 해석에 몇 분이 걸리는 함수는 잘못 만든 함수이다.

# This is bad
def fetch_and_display_personnel():
    data = # ...

    for person in data:
        print(person)


# This is good
def fetch_personnel():
    return # ...

def display_personnel(data):
    for person in data:
        print(person)
  • 함수의 인자는 가능한 적게 만들어라.
# This is bad
def render_blog_post(title, author, created_timestamp, updated_timestamp, content):
    # ...

render_blog_post("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")


# This is good
class BlogPost:
    def __init__(self, title, author, created_timestamp, updated_timestamp, content):
        self.title = title
        self.author = author
        self.created_timestamp = created_timestamp
        self.updated_timestamp = updated_timestamp
        self.content = content

blog_post1 = BlogPost("Clean code", "Nik Tomazic", 1622148362, 1622148362, "...")

def render_blog_post(blog_post):
    # ...

render_blog_post(blog_post1)
  • 함수 내에서 flag를 사용하지 마라.
text = "This is a cool blog post."


# This is bad
def transform(text, uppercase):
    if uppercase:
        return text.upper()
    else:
        return text.lower()

uppercase_text = transform(text, True)
lowercase_text = transform(text, False)


# This is good
def uppercase(text):
    return text.upper()

def lowercase(text):
    return text.lower()

uppercase_text = uppercase(text)
lowercase_text = lowercase(text)
  • 부작용을 제거하라.

주석

  • 과한 주석은 코드를 더럽게 만든다.

  • Documentation은 사용자에게, (함수, 모듈 등을) 언제 어떻게 쓰는지 설명한다.

  • Comment는 개발자에게, 왜 코드를 이렇게 썼는지 설명한다.

  • Clean Code는 개발자에게, 무엇을 하는 코드인지 설명한다.

  • 나쁜 코드는 주석을 달지 말고 다시 짜라.

  • 가독성이 높은 코드는 주석이 필요 없다.

# This checks if the user with the given ID doesn't exist.
if not User.objects.filter(id=user_id).exists():
    return Response({
        'detail': 'The user with this ID does not exist.',
    })
  • 불필요한 주석은 쓰지 않는다.
numbers = [1, 2, 3, 4, 5]

# This variable stores the average of list of numbers.
average = sum(numbers) / len(numbers)
print(average)
  • 올바른 종류의 주석을 써라.
  • 디버그에 사용한 코드는 남겨두지 마라.

Decorators

  • 데코레이터를 이용하면 SoC(Separation of concern)을 지키면서 모듈화된 코드를 만들 수 있다.
def ask_for_passcode(func):
    def inner():
        print('What is the passcode?')
        passcode = input()

        if passcode != '1234':
            print('Wrong passcode.')
        else:
            print('Access granted.')
            func()

    return inner


@ask_for_passcode
def start():
    print("Server has been started.")


@ask_for_passcode
def end():
    print("Server has been stopped.")


start()  # decorator will ask for password
end()  # decorator will ask for password

Context manager

  • 외부 자원을 이용할 때는 context manager를 써라.

Iterator

  • Iterator로 loop을 간단하게 써라.
# This is bad
names = ["Mike", "John", "Steve"]
names_iterator = iter(names)

for i in range(len(names)):
    print(next(names_iterator))

# This is good
names = ["Mike", "John", "Steve"]

for name in names:
    print(name)
댓글