내가 생각하는 협업하기 좋은 코드 [2편]

늘보·2023년 1월 13일
0

코딩스타일

목록 보기
2/2
post-thumbnail

내가 생각하는 협업하기 좋은 코드 [1편]

지난 편에 이어서 내가 생각하는 '협업하기 좋은 코드'에 대해 이야기하고자 한다.

저번에는 코드적인 관점이었다면 이번엔 더 넓은 관점에서 설명한다.
다시 말하지만 나는 누군가가 내 코드를 읽고, 수정하기 쉬워야 그 코드는 비로소 하나의 의미있는 자산이 된다고 생각한다.

첫 편에 이은 두 번째 이야기, 시작해보자.

📍 '좋은 코드'의 기준은 사람마다 다를 수 있음을 유의하자.


3. 수정이 용이한 코드

3-1. 시작이 반, 프로젝트 구조

수정이 쉬운 코드는 좋은 프로젝트 구조로부터 나올 수 있다고 생각한다.
프로젝트 구조를 큰 범위에서 통일하고, 충분히 설계하자.

  • 모든 소스는 제3자가 예상 가능한 위치에 있어야 한다.

깃허브에서 오픈소스를 열었을 때 README.md나 LICENSE가 어느 위치에 있을 것이라고 예상할 수 있듯이, 당신이 속한 조직의 이해 관계 속에서 소스 코드와 설정 파일은 예상 가능한 위치에 있어야 한다고 생각한다.

# 우리 조직에선 파이썬 코드 자산에 대해 이러한 이해 관계를 갖고 있어요.
build/
   Dockerfile
   .env
src/
  application/
    __init__.py
    main.py
tests/
  __init__.py
README.md
requirments.txt
setup.py
setup.cfg
Makefile
LICESNSE
  • 폴더 구조 설계 역시 중요하다.

폴더 구조를 기준에 따라 나눠두면, 소스를 클릭하기 전부터 해당 소스의 역할을 이해하고 시작할 수 있다.
예를 들면 DDD(Domain-Driven Design)를 기반으로 개발해야겠다고 기획하였으면,
Presentation, Application, Domain, Infrastructure은 폴더 구조부터 명확히 분리가 되어 있어야 한다.

# 출력만 수정해 달라고? presentation 폴더 보면 되겠네.
src/
  application/
    presentation/
    application/
    domain/
    infrastructure/

🔔 만약 프로젝트 구조를 신경쓰지 않고, 개발에만 집중하면 제3자는 당신의 코드를 보기도 전에 혼란을 겪게 될 수 있다.

3-2. 디자인 패턴 활용

인지에서 그치지 말고 디자인 패턴을 적극 활용하자.

만약 본인이 요구사항을 받을 때마다, 수정이 필요한 영역이 늘어가고 있다면 디자인 패턴 적용을 검토해 보아야 한다.

디자인 패턴은 공통적인 문제들에 대한 표준적인 해법이다.
이미 결합도를 낮추고, 응집도를 높이기 위한 고민이 포함된 설계도인 것이다.

이제는 설계 단계부터 디자인 패턴을 머리속에 그려두고 접근하자.
이후 제3자, 혹은 본인을 그 패턴 안에서만 놀게 하는거다.

# MacOS 지원이요? 금방하죠.

class Windows(System):
    def action(self) -> str:
        return "Windows action!"

class Linux(System):
    def action(self) -> str:
        return "Linux action!"

"""
class Mac(System):
    def action(self) -> str:
        return "Linux action!"
"""

class Facade:
    def __init__(self, systems: List[System]) -> None:
        self._systems = systems

    def action(self) -> None:
        for system in self._systems:
            print(system.action())

def client(facade: Facade) -> None:
    facade.action()

🔔 디자인 패턴 활용도가 높아질 수록 점점 수정 영역이 줄어드는 모습을 볼 수 있을 것이다.

4. 확장/재사용이 쉬운 코드

4-1. 의존성 최소화

코드를 더럽히지 않는 선이라면 최대한 의존성을 제거하자.

어떤 코드에서 새로운 모듈을 import 할 때마다 이 코드는 점점 재사용하려는 입장에선 부담스러워 진다.
의존되는 패키지를 설치하기 위해 도커 이미지를 업데이트해야 하거나, 빌드에 필요한 시간도 증가시킬 수 있다.
혹은 해당 모듈에서 취약점이 발견되었다고 가정해보자. 휴가 중에 회사로 뛰어와야 할 수 있다.

의존성은 전이된다.
모듈 없이 코드를 더럽히지 않고 구현이 가능하다면 구현 방향으로 가자.

# PE 파일 구별만 필요한 간단한 요구사항

# pefile 의존성 추가
import pefile
def is_pe(file_path):
	try:
    	self.pe = pefile.PE(self.file_path)
        return True
	except pefile.PEFormatError:
    # ...

# 의존하는 모듈없이 직접 시그니쳐, 구조 등을 확인
def is_pe(file_path):
    with open(file_path, "rb") as f:
        if f.read(2) == "MZ":
    # ...

🔔 중복된 코드 작성이 많아진다고? 팀 내부적으로 사용하는 공통 모듈을 만드는 것을 추천한다.

4-2. 주석 달기

적절한 주석은 사랑이다.

주석은 해당 함수를 재사용하는 입장에서 매우 반갑다.
우리의 모국어는 한국어이기 때문에 해당 함수에 대한 이해가 훨씬 빨라지기 때문이다.

그렇다고 모든 함수에 주석을 다는 것은 다른 것을 떠나 작성하는 사람 역시 힘들다.
그러므로 현실적으로 협의하여 '적절한' 주석은 필요하다고 한 것이다.

위에서 말한 '적절한'이란 함수의 수준을 의미한다.
실제 기능을 수행하는 낮은 수준의 함수는 명확한 함수/변수명으로 충분히 설명이 가능하다.
사실 추후 들여다볼 일도 많지 않다.
그러나 높은 수준의 함수(예로 인터페이스)는 주석은 필요하다고 생각한다.
가장 사용할 일이 많을 뿐더러 요구 사항, 히스토리와 함께하는 가장 큰 뿌리이기 때문이다.

앞으로 모두 적절한 주석을 달고자 노력해보자.

def get_md5(file_path):
    with open(file_name, 'rb') as f:
        data = f.read()    
        md5 = hashlib.md5(data).hexdigest()
    return md5

class FileAnalyzer:
    """파일 분석을 수행하는 클래스"""

    def get_info(file_path):
        """입력된 파일의 해시값, 평판 정보를 반환
        
        Args:
            file_path (str): 정보를 가져올 파일의 경로
        
        Returns:
            dict: 파일의 해시값과 평판 정보 반환
        """
        
        fileinfo = {}
        fileinfo["md5"] = get_md5(file_path)
        fileinfo["reputation"] = self._get_reputation(file_path)
        return fileinfo

🔔 개인적으로 주석은 코드 사이에 들어가는 순간 지저분해 보인다고 생각한다.
꼭 필요한 것이 아니면 가장 상단의 멀티라인 주석안에서 간결히 풀어내보자.


협업하기 좋은 코드, 좋은 코드에 대한 기준이 각자 다를 것이다.
중요한건 모두의 생각이 존중받아야 한다는 것이다.
왜냐면 언젠가 우리는 서로의 협업자가 될 수 있기 때문이다.

그러므로 나는 오늘도 제3자인 당신의 생각을 듣고 싶다.

"좋은 코드가 뭐라고 생각하시나요?"


해당 포스팅은 로버트 C. 마틴의 'Clean Code'에서 영향을 받아 작성되었습니다.

긴 글을 읽어주셔서 감사합니다. 곧이어 다른 이야기로 돌아오겠습니다 😃

profile
늘보의 개발생활

0개의 댓글