읽기 쉽고 유지보수하기 쉬운 코드를 쓰기 위한 모범 사례 | 의미있는 이름 고르기

Jung Wook ParkJung Wook Park
8 min read

원문: Thiago Pacheco, "Best practices to write a readable and maintainable code | Choosing meaningful names"

많은 사람들이 소프트웨어 개발자로서 가장 어려운 것 중 하나가 이름 짓기라고 합니다. 동시에 의미있는 이름을 짓는 것은 가장 중요한 일 중 하나라고 할 수 있죠. 이를테면 변수나 함수, 클래스, 그 밖의 모든 것들을 정의하는 것 말입니다. 좋은 사례들과 명명 규칙을 따르면 코드를 더 읽기 쉽고 유지보수하기 좋게 만들 수 있으며 다른 사람들이 코드를 더 쉽게 이해하고 작업할 수 있도록 해줍니다.

하지만 어떻게 그렇게 할 수 있을까요? 소프트웨어 개발에서 이름을 지을 때 좋은 사례와 나쁜 사례로는 어떤 것들이 있을까요?

저는 코딩을 거의 책을 쓰는 것처럼 생각하길 좋아합니다. 자신들이 찾는 것으로 이어지는 올바른 길을 독자들이 따라가도록 돕기 위해서, 의미 있는 이름이 함께 하는 명확한 구조를 따르도록 안내하려 노력하거든요.

제가 코딩할 때 따르려 하는 한 가지 제안이 있는데요. 일단 잠시 멈추고 "내가 이걸 만든 동기가 뭐였지?"하고 생각하는 것입니다. 데이비드 토마스와 앤드류 헌트의 "실용주의 프로그래머 (참고로 매우 좋은 책입니다)"라는 책에 따르면, 이렇게 하면 당장의 문제를 해결하기 위한 사고방식에서 벗어나 더 큰 그림에 집중할 수 있다고 합니다.

위에서 언급한 참고자료를 한 번 더 인용하자면, 이름을 올바르게 짓는 것이 중요한 이유를 보여주는 훌륭한 예시가 있습니다.

이를 실제로 확인하기 위해 간단한 연습을 해보겠습니다.

아래의 이미지를 본 다음 빠르게 지시에 따라주시기 바랍니다. 1분도 채 안 걸릴 것을 약속드리죠!

  1. 단어들을 있는 그대로 읽어 보세요.

  2. 이제 단어들을 색깔에 따라 읽어 보세요.

두번째 단계에 성공했어도 아마 더 많은 노력을 기울이셨을 겁니다. 안 그런가요?

이는 비록 주변 맥락이 다른 것을 의미하더라도 글로 쓰여진 것이 우리 뇌에서 우선된다는 것을 보여주는 간단한 예시일 뿐입니다.

이제 이름 짓기가 얼마나 중요한 지에 대해 감을 잡았으니 몇몇 전략들을 살펴봅시다.

👀 변수 이름 짓기

변수는 가장 작은 조각이지만 전체 응용 프로그램의 기본 구성 요소이기 때문에 보통 제일 중요합니다. 이들의 이름을 잘못 지으면 이후 등장하는 모든 변수가 이전 변수의 잘못된 의미에 의존하게 되는 도미노 효과처럼 느껴질 수 있습니다.

이를 방지하기 위한 몇 가지 방법을 살펴봅시다.

  • 두문자어와 축약어를 피하자

  • 단순하고 서술적으로 작성하고, 일반적인 이름을 피하자

  • 오해의 소지가 있는 이름에 주의하자

  • 짧지만 의미있게 유지하자

🔍 축약어 피하기

축약어는 코드를 작게 만드는 데 도움이 될 수 있지만 변수의 의미를 이해하기 더욱 복잡하게 만듭니다. 임베디드 시스템과 같이 매우 제한적인 환경에서 작업하지 않는 한 축약어를 피하는 것을 고려해야 합니다.

축약어는 코드를 작성하는 사람 또는 제품에 대해 명확하게 이해하고 있는 사람들에게는 직관적으로 보일 수 있지만 처음 (가끔은 심지어 두 번째, 세 번째…) 코드를 읽는 사람들에게는 큰 방해물이 될 수 있습니다.

📜 일반적인 이름 피하기

많은 프로그램이 변수에 일반적인 이름을 사용하곤 합니다. 때로는 이런 경우가 for i of items와 같은 패턴을 경력 초기에 배운 후 계속 사용하기 때문에 벌어지고는 하죠.

이외의 경우는 확장하기 쉽도록 하기 위해 어떻게든 일반적인 코드를 유지하려고 노력하기 때문입니다. 이유야 어찌 됐든 선택의 여지가 없는 한 굉장히 일반적인 이름보다는 명확하고 구체적인 이름을 사용하는 것을 고려하세요.

일반적인 이름은 보통 모호함을 가지기 때문에 현재 코드 조각이 무엇을 해야 하는지를 나타내지 못한다는 문제가 있습니다.

간단한 예시를 살펴봅시다.

# User-type employee
user = User("Jon", "Snow", type="employee")

...

do_something_with_user(user)

위 예제에서 코드베이스가 여러 타입의 사용자를 관리하는 경우 모든 함수가 따르거나 확인해야하는 모든 경로를 알기 까다로워질 수 있습니다. 이러면 각 타입의 사용자에 대해 어떤 일이 일어날지 확인하기 위해 함수 전체를 강제로 읽어야 할 것입니다. 시그니처를 읽는 것만으로 함수가 무엇을 하는지 짐작할 수 있다면 더 좋지 않을까요?

이 문제를 겪지 않기 위해서는 우선 변수명이 무엇을 위해 사용되고 있는지에 대한 의도를 반영해야 합니다. 그 다음 변수명이 나타내는 것에 대해 명확하고 고유한 의미를 가지는지 다시 확인해야 하죠.

아래와 같이 바꾸세요.

employee = User("Jon", "Snow", type="employee")

🚽 오해의 소지가 있는 이름들

코드베이스의 유지보수, 리팩토링 또는 변경 중 발생할 수 있는 매우 무서운 일로, 변수가 나타내는 것을 변경한 후 변수의 이름을 바꾸는 것을 실수로 잊어버릴 수 있습니다.

이는 다음 독자들에게 코드를 잘못 해석하도록 직접적으로 유도하기 때문에 매우 해롭습니다. 때문에 코드를 이해하고 관리하는 데에 많은 노력이 들어가게 되죠. 이상하고 복잡한 주석과 문서가 존재하는 이유이기도 합니다.

오해하지 마세요. 문서화는 필수적이고 정말 중요한 단계입니다! 하지만 잘못 이름지어진 코드나 오해를 사기 쉬운 이름을 설명하기 위해서만 존재하는 매우 장황한 문서를 작성하는 것은 쓰는 사람에게나 나중에 읽을 사람들에게나 모두 시간 낭비입니다.

이 문제를 피하기 위해서는 코드를 변경할 때마다 맥락에 따라 변수의 이름을 바꿔주어야 합니다. 코드에 변경을 적용할 때도 여기 설명된 첫 번째 단계를 수행하세요. 오해하기 쉬운 이름을 피할 수 있을뿐만 아니라 잘못된 이름을 가진 기존 변수를 수정할 수도 있을 테니까요.

💊 짧지만 의미있는 이름 유지하기

매우 짧은 변수명은 항상 좋으며 변수가 의미하는 바를 나타내는 한 이를 목표로 해야 합니다. 하지만 의미있는 이름이 짧은 이름보다는 항상 우선이라는 사실을 명심하시고 의미에 먼저 집중하세요.

🔫 함수 이름 짓기

변수에 대한 모든 제안은 함수 이름을 지을 때도 적용됩니다. 이를 따르면 함수가 나타내는 바에 대한 올바른 맥락을 표현하도록 도와줄 것입니다.

그러나 명확한 맥락을 제공하는 것뿐만 아니라 이 함수가 수행해야 할 동작이 무엇인지 명시적으로 보여줄 필요가 있습니다.

아래 몇 가지 제안을 드리겠습니다.

  • 함수는 동사를 사용해야 하며 수행하는 동작이 무엇인지 명확해야 한다

  • 한 번에 하나의 동작만을 처리하자

  • 명확한 매개변수명을 사용하자

💉 접두사 추천

다음은 함수의 의도를 명확하게 표현하기 위해 사용할 수 있는 접두사에 대한 간단한 레퍼런스입니다.

동작접두사설명예시
데이터 획득get, find, show, list데이터를 획득해야 할 때 사용get_users, find_employees, show_user_details, list_comments
데이터 삽입insert, add새로운 정보를 추가하려는 의도가 있을 때 사용add_user, insert_user
데이터 갱신update, change정보를 갱신하고 싶을 때 사용update_user, change_username
갱신 또는 삽입set갱신하거나 존재하지 않는 경우 새로운 레코드를 만들고 싶을 때 사용set_days_off
서드 파티로부터 데이터 획득fetch, retrieve예를 들어 HTTP 요청 같은 서드 파티 자원에서 데이터를 가져오고 싶을 때 사용fetch_users, retrieve_comments

위 표는 그저 일련의 제안일 뿐, 꼭 따라야 하는 규칙이 아님을 명심하세요. 저는 몇 년 간 이들이 함수의 이름을 짓기 위한 가장 일반적인 접두 규칙이라는 것을 알게 되었지만 각 회사가 각자의 모범 사례를 가지고 있으며 여러분은 이미 특정한 가이드라인을 따르고 있을 수도 있습니다.

📌 함수는 단일 작업을 처리해야 합니다

함수가 여러 동작을 수행하지 않도록 하세요.

함수 이름에 and나 여러 가지를 한 번에 하고 있음을 알려주는 무언가가 들어가 있다면 여러 개의 함수로 분할하는 것을 고려해야 합니다.

예를 들어 다음과 같습니다.

def update_user_and_send_notification():
    ...

위와 같은 형태는 너무 장황하며 대부분의 경우 가독성이 떨어집니다.

다음과 같이 나누어 보세요.

def update_user():
    ...

def notify_user_updated():
    ...

하지만 이들이 순차적으로 실행되어야 한다면요?

문제 없습니다. 종합하는 역할을 하는 함수를 만들면 되죠.

def update_user_handler():
    # 우선
    update_user()
    # 그 다음
    notify_user_updated()

위에서 제안된 handler를 포함한 정의는 보통 serviceAPI 레이어같이 외부 호출자들에게 노출된 레이어에서 주로 사용됩니다. 아래의 모듈·패키지 이름 짓기에서 더 자세히 알아봅시다.

📌 함수 매개변수

함수를 다룰 때 매우 일반적인 매개변수를 굉장히 흔하게 볼 수 있는데 이 또한 큰 문제가 될 수 있습니다. 잘못 지어진 함수 매개변수명이 있을 때마다 변수 이름 짓기 부분에서 이야기했던 것과 같은 문제가 발생합니다.

아래 예시를 보시죠.

update_user(id, data: dict):

매개변수 data는 사용자 인스턴스의 무엇이 갱신되고 있는지 명시하지 않습니다.

사용자명인가요? 나이? 아니면 모든 속성?

전체 함수를 읽고 코드가 무엇을 하는지 보기 전까지는 알 수 없습니다.

이를 피하기 위해서는 함수가 변경해야 하는 속성이 무엇인지 명시적이어야 합니다.

이를 위한 한 가지 방법으로는 모든 속성을 함수의 개별 매개변수로 나열하는 것입니다.

def update_user(id: int, name: str, age: int):

속성 목록이 너무 길다면, DTO(Data Transfer Object, 데이터 전송 객체) 같은 전략을 사용하여 함수의 가독성을 살리면서도 구체적으로 만드는 방안을 고려해 보세요.

def update_user(id: int, user_data: UpdateUserData):
    ...

class UpdateUserData:
    name: str
    age: int

간단한 팁: Python이나 TypeScript 같은 동적 타입 언어를 사용 중이라면 명시적으로 타입 힌트를 정의하는 것을 잊지 마세요. 함수 정의를 읽는 것만으로도 함수가 무엇을 하는지 이해하기 훨씬 쉽게 만들어줍니다.

✏️ 메서드 이름 짓기

메서드 이름 짓기는 함수 이름 짓기와 매우 유사하지만 메서드는 특정 클래스에서 호출하기 때문에 컨텍스트가 어느 정도 명시적이라는 차이점이 있습니다.

예를 들어 새로운 사용자를 추가하기 위한 독립적인 함수는 아래와 같이 정의될 수 있습니다.

def add_user(user: User):

같은 기능을 예를 들어 UserRepository 클래스의 메서드로 정의하는 경우에는 클래스명에 의해 암묵적으로 표시되기 때문에 메서드 이름에도 컨텍스트를 위한 부분을 넣을 필요는 없습니다.

class UserRepository:

    def add(user: User):
        ...

📁 클래스 이름 짓기

클래스의 이름을 지을 때는 만들고 있는 클래스의 주 목적을 염두에 두고 있어야 합니다. 클래스의 범위가 너무 넓어서 여러 컨텍스트를 관리하기 위해 사용할 수 있다고 판단되면 다시 생각해보고 분할하는 것을 고려해봐야 합니다. 너무 넓은 범위의 클래스와 너무 긴 클래스 이름을 정의하는 것은 일반적인 이름이 적용되는 이유 중 하나이기 때문에 피하려 노력해야 하죠.

가장 간단명료한 클래스 정의들은 User, Client, Article 등과 같이 도메인 모델을 나타내는 것들입니다. 하지만 우리는 프로그램들이 거기서 그치지 않음을 잘 알고 있고 언제나 넓은 범위의 클래스에 걸려 넘어지고는 하죠.

아래의 몇 가지 간단한 제안을 드리겠습니다.

  • 단순하고 서술적인 이름을 유지하자

  • 명사를 사용하자

  • CamelCase (또는 선호하는 언어의 기본 표기법)를 사용하자

  • 전체 단어를 사용하고 두문자어와 축약어를 피하자

📗 추상화·인터페이스 이름 짓기

추상화는 코드의 결합도를 낮추고 테스트하기 쉽게 만드는 멋진 방법이지만 이름을 잘못 짓는다면 일이 복잡해질 뿐입니다.

몇 가지 간단한 제안은 다음과 같습니다.

  • 클래스와 같이 CamelCase를 사용하자

  • 선호하는 언어에 따라 추상화·인터페이스임을 명시하자

    • 예시: AbstractDBSession
  • 명확한 컨텍스트 혹은 컨텍스트 그룹을 나타내야 한다

    • 예시: AbstractUser, AbstractClient, AbstractRepository

📙 열거형 이름 짓기

열거형을 정의할 때 가장 먼저 정말 필요한지 확실히 해야합니다. 열거형이 단일 값만 포함하는 경우 상수 또는 단순 불리언(Boolean) 속성이 되어야 합니다.

예를 들면 다음과 같습니다.

class UserStatus(enum.Enum):
    ACTIVE = 'active'

class User:
    ...
    status: UserStatus

# 위 코드는 사용자 객체의 단순 참·거짓 속성이 될 수 있습니다
# 예시

class User:
    ...
    is_active: bool = True

열거형이 필요하다는 사실을 확정지었다면 클래스와 변수 정의에 대해 이 글에서 제안한 바를 동일하게 적용할 수 있습니다.

  • 명사를 사용하자

  • 어떤 컨텍스트에서 사용되어야 하는지 명확히 하자

    • 예시: UserStatus, UserType, AccountType
  • 짧고 의미있게 만들자

📂 모듈 이름 짓기

모듈 이름은 동일한 일반 컨텍스트에 속하는 기능 목록을 표현해야 합니다. 여러분의 모듈은 회사의 가이드라인에 따라 크게 달라질 수 있지만 납득할 수 있는 패키지 이름이 맞는지 확인하기 위해 몇 가지 일반적인 규칙을 적용할 수 있습니다.

제가 따르고 싶은 한 가지 좋은 제안은 구글의 Python 스타일 가이드에서 찾을 수 있는데요. 이에 따르면 패키지의 전체 경로를 사용하여 해당 패키지가 외부에 노출한 코드를 호출할 수 있어야 합니다. 예를 들어 다음과 같습니다.

from my_app import domain

user = domain.User("Jon", "Snow")
from my_app.services import user_handlers

users = user_handlers.get_all()

위의 예시를 적용하고 코드가 무엇을 하는지, 또 어디서 오는지 따라가기 쉽다면 패키지 이름이 충분히 명확하다는 것을 확인할 수 있습니다.

모든 언어, 프레임워크, 심지어 회사들도 이름 짓기에 대한 일련의 가이드라인이 있으며 이러한 가이드라인들의 목표는 다른 사람들과 미래의 자신이 이해하기 쉬운 코드를 작성하고 있는지 확인하는 것입니다.

다음 번에 코드를 작성할 때는 여러분 자신을 작가로 생각하고 작업 과정을 안내할 수 있도록 이러한 사례들을 적용해 보세요. 미래의 여러분 자신과 코드를 읽는 모든 사람들이 감사할 테니까요!

끝까지 들어주셔서 감사합니다. 글이 도움이 되었다면 좋아요와 댓글도 남겨주세요!

3
Subscribe to my newsletter

Read articles from Jung Wook Park directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jung Wook Park
Jung Wook Park

Code Tinker. Interested in user interface software development. Trying to think functionally.