Programming Convention

문서 규격화의 중요성

보통 이력서, 보고서, 논문 등 문서를 작성할 때 사람들은 각각의 규격에 맞추어 쓰려고 합니다. 비록 몇몇 사람들은 규격에 벗어난 글을 쓰기도 하지만, 그 역시도 대부분 규격에서 크게 벗어나지 않습니다. 이렇게 규격이란 것을 정하고 지켜가며 글을 쓰는 이유는 가독성 때문입니다. 필요한 정보를 찾기 위해 (같은 스타일의) 많은 문서를 대강 훑어볼 때, 문서들이 규격에 맞추어져 있으면 매우 효율적으로 필요한 정보를 찾을 수 있게 됩니다. 예를 들어 우리가 하루에도 어마어마하게 쏟아지는 뉴스기사들을 모두 확인하고 필요한 뉴스를 찾을 수 있는 이유는, 핵심이 되는 키워드(와 자극적인 멘트)가 들어있는 제목과 내용을 짧게 요약하고 있는 소제목이라는 규격을 지켜 뉴스들이 작성된 덕분입니다.

alt text

뉴스의 제목과 소제목의 예1

비슷한 예를 네트워크 패킷에서도 찾을 수 있습니다. 만약 IP header가 표준화되지 않고 사용하는 사람마다 다른 포맷으로 보낸다면, 출처 IP 주소를 찾기 위해서 일단 header(조차 크기가 제각각 이겠지만)에 해당하는 부분을 찾고 header를 통째로 읽어야합니다. 하지만 아래와 같이 표준화가 된 덕분에 header의 다른 부분은 신경쓰지도 않고 96번째 bit에서 127번째 bit까지의 위치에 바로 접근해서 출처 IP 주소를 읽을 수 있게 되었습니다.

alt text

IP Header의 구조2

이렇게 거대한 크기의 데이터안에서 필요한 정보를 빠르게 찾기 위해서 데이터의 규격화는 중요합니다. 프로그램 역시 유지 보수하는데 있어서 방대한 문자열들 사이에서 필요한 정보를 쉽고 빠르게 찾는게 중요한 만큼, 팀 내부의 CODING CONVENTION을 정하고 지키는 것은 선택이 아닌 필수입니다.

Python

Python syntax는 작성된 코드의 가독성을 높이기 위해 구성되어있습니다. 하지만 syntax만으로는 모든 코드의 규격을 정할 수는 없기 때문에 각 개발팀들은 기본적으로 PEP8 이라는 문서를 참조하고 더욱 발전시켜서 coding convention을 제시하고 있습니다. 피플펀드에서도 PEP8에서 발전된 구글 스타일 가이드3를 이용하고 있지만 아직 지켜지지 않은 몇가지 convention 및 추가적인 convention에 대해 이야기하려 합니다.

Variable Naming

기존에는 function의 local variable들과 function 밖에서 들어온 (값이 변하지 않는게 좋은) parameter를 구분할 수 있도록 local variable의 이름을 지정할 때 앞에 밑줄(_)을 사용하였지만, 가독성을 위해 밑줄을 넣지 않습니다.

# 기존
_repayment = Repayment.objects.get(
    LOAN_id=_current_loan_id
    NUM = _current_number
)
_principal = _repayment.PRINCIPAL
_interest = _repayment.INTEREST
_overdue_interest = _repayment.OVERDUE_INTEREST
_repayment_amount = _principal + _interest + _overdue_interest

# 변경 후
repayment = Repayment.objects.get(
    LOAN_id=current_loan_id
    NUM = current_number
)
principal = repayment.PRINCIPAL
interest = repayment.INTEREST
overdue_interest = repayment.OVERDUE_INTEREST
repayment_amount = principal + interest + overdue_interest

단 local variable과 paramter를 쉽게 구분할 수 있게 syntax highlight를 사용하도록 합니다.

  • Pycharm을 예를 들면 ‘Preference > Editor > Color Scheme > Python > Parameter에서’ ‘Use inherited attributes’ 체크 해제하고 foreground를 체크한 후 원하는 색깔을 할당합니다.

  • 기존에 만들어진 color scheme이나 theme을 사용하면 보통 이미 색깔이 할당되어 있습니다.

  • 개인적으로 추천드리는 (그리고 거의 유일하게 존재하고 유지되는) Pycharm Theme은 Material Theme입니다.

List vs Tuple

List와 Tuple의 기술적인 관점에서의 차이점은 둘다 수열(sequence)을 저장하지만 전자는 수정이 가능하고(mutable) 후자는 수정이 불가능하다는(immutable) 점입니다. 하지만 많은사람들이 사용하는 방식을 보면, list는 그 안에 포함된 항목들이 모두 같은 타입을 (homogeneous) 갖고 있는 길이 미정의 수열에 사용되며, tuple은 그 안에 포함된 항목들이 다른 타입을 (heterogenenous) 갖고 있는 지정된 길이의 수열에 사용됩니다. 4 5

간단하게 표현하면, 코드를 작성 할 때 이미 알고 있는 상수의 개념이라면 tuple을, 아니라면 list를 사용한다라고 생각할 수 있습니다 (C를 알고 있다면 array와 struct의 차이와 비슷하다고 생각할 수 있습니다).

# List의 사용 예
odd = []
for i in range(10):
    if (i % 2 == 1):
        odd.append(i)
if cur_num in odd:
    do_something

# Tuple의 사용 예
# 유저의 타입이 기존에 1~10번까지 지정되어있다
if user_type in ('1', '3', '5'):
    do_something

Multi-line Expression

코드의 길이 제한 때문에 expression을 여러줄에 걸쳐서 작성할 경우가 있습니다. 일반적인 expression에서는 \(back-slash)를 사용하기 보다 괄호 ()를 이용합니다. 6

# ok
very_long_variable_name = "very_long_string" \
    if another_very_long_variable_name \
    else some_other_very_long_variable_name

# better
very_long_variable_name = ("very_long_string"
    if another_very_long_variable_name
    else some_other_very_long_variable_name)

Conditional Statement and Block

또한 if statement처럼 block에 indentation이 들어가야 하지만 condition 코드의 길이가 길 경우 block 코드들과의 햇갈리는 것을 방지하기 위해 indentation을 2번 사용합니다. 7

# ok
if ((very_long_variable_name == some_value)
    and (another_long_variable_name == another_value)):
    new_variable = 5

# better
if ((very_long_variable_name == some_value)
        and (another_long_variable_name == another_value)):
    new_variable = 5

Binary Operator

마지막으로 논리연산(예: and)이나 수학 기호(예: +)처럼 2항 연산기호를 쓰는 경우 줄바꿈을 연산기호 뒤에서 하기보다 기존의 수학자들(과 개발자들)이 사용하는 방식대로 연산기호 전에 줄을 바꿉니다. 8

# No: operators sit far away from their operands
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

# Yes: easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

Math Equation

수학 방정식을 쓸 때에는 일반적으로 사람이 생각하는 순서대로 작성합니다.

invested_ratio = investment * 100 / total_investment

# better
# 보통 백분율을 구할때는 항목값/총합 * 100으로 표현합니다
invested_ratio = investment / total_investment * 100

Conditional Statement

조건문을 작성할 때는 사람들이 보통 읽는 순서로 작성합니다

if 3 < x:
    do_something

# better
# 보통 `x가 3보다 클 경우`로 읽습니다.
if x > 3:
    do_something

Docstring

docstring을 작성할 때 첫번째 줄은 (가능하다면 한줄로) 이 function이 무엇을 하는지 요약하고 (더욱 설명이 필요하면) 한줄 내린 후 자세한 설명을 추가합니다. 요약은 """ 바로 옆에 씁니다. 3

def process_investment(self, user_id, loan_id, amount):
    """user_id 유저가 loan_id 채권에 amount만큼 투자하는 것을 처리한다.

    투자 처리는 아래와 같은 순서로 이루어진다.
    1) 투자시도라는 상태의 내역서를 만든다.
    2) user_id 유저의 계좌정보에서 amount만큼 출금한다.
    3) loan_id 채권에 amount만큼 투자한다.
    4) 내역서를 투자완료 상태로 변경한다.

    Args:
        user_id (int): USER_ID
        loan_id (int): LOAN_ID
        amount (int): loan_id에 투자할 금액

    Returns:
        bool: 투자처리가 완료되면 True
    """
    do_something

Python의 built-in type이 아닐 경우 해당하는 type을 import 해주면 Pycharm의 자동완성 기능 등을 사용할 수 있습니다.

  • 예를 들어, Django의 Queryset의 경우 (model.objects.filter를 사용했을 때) from django.db.models.query import QuerySet 한 후에 docstring에 타입을 Queryset[name_of_model]로 작성하면 Pycharm의 자동완성 기능을 사용할 수 있습니다.
  • Pycharm의 type hinting에 대한 정보는 Pycharm 공식 홈페이지에서 볼 수 있습니다. 9

Block Comments

docstring을 제외하고 코드 중간에 여러줄에 걸친 주석을 달 때에는 ''' '''보다는 #을 이용합니다. 10

__init__.py

__init__.py는 다른 곳에서 이 패키지를 from package import *를 이용해 불러왔을 시에 같이 import 시킬 하위 패키지/모듈들을 정의할 수 있습니다. 단 from package import *를 이용해야만 이러한 편의성을 제공하고 import *는 최대한 쓰지 말아야 하는만큼, __init__.py는 최대한 비워둡니다. 만약 test나 model 패키지들처럼 하위 모듈들을 import 해야한다면 __all__을 사용합니다.

__all__ = [
    'module1',
    'module2',
]

TestCase

테스트 케이스를 작성할 때 한 테스트 케이스 당 하나의 function을 사용하며 function이름 만으로도 어떤 테스트를 했는지 알아볼 수 있도록 작성합니다. 11

def test_상환_불균등_다수_투자자(self):
    """
    세명의 투자자가 불균등하게 투자한 채권을 상환할 때 투자자들에게 알맞은 금액이 상환되는지 확인한다.
    """
    test_something

Argument Validation

Django의 view와 같이 외부에서 들어오는 요청의 argument들은 제대로된 타입들을 갖고 있는지 validation하는 작업이 필요하지만, 내부에서 요청되는 function들은 input의 타입 validation을 하지 않습니다 (docstring의 type hinting을 최대한 이용). 이는 불필요한 코드를 줄이는 역할도 하지만 Python이 duck typing을 지원하는 방향성을 따르기도 합니다. 12

  • duck typing:

오리처럼 생겼고 오리 같이 울고 오리처럼 걷고…하면 그것은 오리다

라는 의미로, input이 원하는 타입이 아니여도 특정 기능을 지원하면 function이 문제없이 동작해야하는 방향성 입니다. 예를 들어 function body에 param1 + param2가 있을 때, param1이 int이기를 예측했지만 param1이 다른 타입을 갖고 있더라도 __add__란 method가 정의만 되어 있으면 이 function은 아무 문제없이 진행되어야 합니다. 만약 validation이 필요하다면 duck typing을 지원하기 위해 Python에서는 isinstance(param1, int)보다는 try except를 이용해서 __add__가 없으면 exception을 띄우도록 권장합니다.

Git

Git 역시 매우 많은 데이터가 모이는 장소인 만큼, 규격이 존재해야 나중에 필요한 정보를 찾을 때 더욱 효율적으로 찾을 수 있습니다.

Commit Message

커밋 메시지들을 다시 읽을 때 도움을 주기 위해 아래의 규칙에 따라 커밋 메시지를 작성합니다. 13

  • 첫 문장은 72자 내로 커밋 내용의 요약을 씁니다 (‘How to Write a Git Commit Message’13에서는 50자를 권장하지만 github에서 72자까지는 글을 줄이지 않고 보여주기 때문에 72자까지는 허용합니다).
  • 요약을 쓸 때에는 (영어로 쓸 때) 첫자를 대문자로 쓰며, 마침표를 찍지 않습니다.
  • 요약은 명령어 (imperative mood)로 작성합니다.
  • 요약이 끝나고 한줄을 내립니다.
  • 본문에는 커밋에 대해 자세히 풀어씁니다. 단 ‘무엇’을 ‘왜’ 했는지를 적으며 ‘어떻게’는 적지 않습니다.
  • 본문은 마침표를 찍습니다.
  • 간단한 내용으로 80자이내에 요약으로만 설명이 가능한 경우가 아니면 -m 옵션을 사용하지 않도록 합니다.
Fix rain disaster

집중호우로 인해 집안에 물이새어 창고가 난리가 났음. 처리하지 않으면 다음 집중호우가 매우 두려울 것으로 예상되기
때문에 창고 정리 및 수리를 진행했음.
  • 이러한 규칙을 지키기 위해서 Shell을 사용한다면 vimrc 등을 통해 라인 길이를 제한하거나, GitKraken과 같이 기본적으로 지원하는 툴을 사용하길 권장합니다.

References