본문 바로가기

SW LAB/Algorithm

Clean Code : (2) 의미 있는 이름

 프롬스의 SWDEVLAB 

의미 있는 이름

소프트웨어에서 이름은 어디나 쓰입니다. 변수, 함수, 인수, 클래스, 패키지 그리고 소스파일과 디렉토리에도 이름을 붙입니다. 이렇듯 많이 사용하므로 이름을 잘 지으면 여러모로 편합니다. 이번에는 이름을 잘 짓는 간단한 규칙을 몇가지 살펴봅니다.

 

의도를 분명히 밝혀라

좋은 이름으로 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많습니다.

변수(혹은 함수나 클래스)의 존재 이유는? 수행 기능은? 사용 방법은? 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이 됩니다.

 

int d; // 경과 시간 (단위: 날짜)

여기서 이름 d는 아무 의미도 드러내지 않습니다. 경과 시간이나 날짜라는 느낌이 안듭니다. 측정하려는 값과 단위를 표현하는 이름이 필요합니다.

 

int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워집니다.

 

다음 코드는 무엇을 하는 것일까요?

public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
      list1.add(x);
  return list1;
}

 

복잡한 문장도 없고, 공백과 들여쓰기도 적당하고, 변수는 세 개, 상수는 두 개 뿐이지만, 코드가 하는 일을 짐작하기 ㅇ렵습니다. 문제는 코드의 단순성이 아니라 코드의 함축성입니다. 코드 맥락이 코드 자체에 명시적으로 드러나지 않았습니다.

  1. theList에 무엇이 들어갔는가?
  2. theList에서 0번쨰 값이 어째서 중요한가?
  3. 값 4는 무슨 의미인가?
  4. 함수가 반환하는 리스트 list1을 어떻게 사용하는가?

지뢰찾기 게임을 만든다고 가정했을 때, theList가 게임판이라는 사실을 알 수 있습니다. 각 칸은 단순 배열로 표현하고, 배열에서 0번째 값은 칸 상태를 뜻합니다. 값 4는 깃발이 꽂힌 상태를 가리킵니다. 코드를 수정해봅니다.

public List<Cell> getFlaggedCells() {
  List<Cell> flaggedCells = new ArrayList<Cell>();
  for(Cell cell : gameBoard)
    if(cell.isFlagged())
      flaggedCells.add(cell);
  return flaggedCells;
}

 

단순히 이름만 고쳤는데도 함수가 하는 일을 이해하기 쉬워졌습니다.

 

그릇된 정보를 피하라

그릇된 단서는 코드 의미를 흐립니다. 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안됩니다.

 

여러 계정을 그룹으로 묶을 때, 실제 List가 아니라면, accountList라 명명하지 않습니다. 프로그래머에서 List는 특수한 의미이기 때문에 실제 List가 아니라면 그릇된 정보를 제공하는 셈입니다. 그러므로  accountGroup, bunchOfAccounts, Accounts 등으로 명명해야 합니다.

 

유사한 개념은 유사한 표기법을 사용합니다. 이것도 정보입니다. 일관성이 떨어지는 표기법은 그릇된 정보입니다.

최신 자바 환경에서는 몇 글자 입력 후 핫키 조합을 누르면 후보 목록이 뜹니다. 후보 목록에 유사한 개념이 알파벳 순으로 나온다면 그리고 각 개념 차이가 명백히 드러난다면 코드 자동 완성 기능은 굉장히 유용해집니다.

 

의미있게 구분해라

Noise Word(불용어)를 사용하는 것은 적절치 못합니다.

 

Noise Word를 추가한 이름은 아무런 정보도 제공하지 못합니다. Product라는 클래스가 있고, ProductInfo, ProductData라는 다륻 클래스가 있는 것이 예입니다. Info나 Data는 a, an, the와 마찬가지로 의미가 불분명한 Noise Word입니다.

 

Noise Word는 중복입니다. 변수 이름에 variable이라는 단어는 단연코 금물입니다. 표 이름에 table이라는 단어도 마찬가지 입니다. NameString이 Name보다 나은 것이 무엇일까요? Name이 부동소수점이 될 일이 있나요? 즉, 구분이 명확하게 될 수 있도록 이름을 지어야 합니다.

 

발음하기 쉬운 이름을 사용하라

사람은 단어에 능숙한데, 발음하기 어려운 이름을 짓는 경우도 있습니다.

어떤 회사에서 genymdhms(generate date, year, month, day, hour, minute, secode)를 의미하는 단어를 사용했습니다. "젠 야 무다 힘즈" 라고 우스꽝스럽게 말하게 됩니다.

 

다음 두 예제를 비교해봅니다.

class DtaRcrd102 {
  private Date genymdhms;
  private Date modymdhms;
  private final String pszqint = "102";
  /* ... */
}

class Customer {
  private Date generationTimestamp;
  private Date modificationTimestamp;
  private final String recordId = "102";
  /* ... */
}

둘째 코드는 지적인 대화가 가능해집니다. "이 레코드 좀 보세요. 'Generation Timestamp' 값이 날짜입니다!" 라고 말이죠..

 

검색하기 쉬운 이름을 사용하라

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다는 문제점이 있습니다.

개인적으로 간단한 메서드에서 로컬 변수만 한 문자를 사용합니다. 이름 길이는 범위 크기에 비례해야 합니다. 변수나 상수를 코드 여러 곳에서 사용한다면 검색하기 쉬운 이름이 바람직합니다.

 

다음 두 예제를 비교해봅니다.

// 1)
for (int j=0; i<34; j++) {
  s += (t[j]*4)/5;
}

// 2)
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j=0; j < NUMBER_OF_TASKS; j++) {
  int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
  int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
  sum += realTaskWeeks;
}

 

위 코드에서 WORK_DAYS_PER_WEEK는 찾기가 참 쉽습니다. 그냥 5를 사용한다면, 5가 들어가는 모든 이름을 찾은 후 의미를 분석해 원하는 상수를 가려내야 할 것입니다.

 

인코딩을 피하라

개발자가 익힐 코드 양은 상당히 많습니다. 여기다 인코딩 '언어'까지 익히라는 요구는 비합리적입니다. 문제 해결에 집중하는 개발자에게 인코딩은 불필요한 정신적 부담입니다.

 

헝가리식 표기법

헝가리식 표기법은 기존 표기법을 완전히 새로운 단계로 끌어올렸습니다. 예전의 C API의 경우 컴파일이 타입을 점검하지 않았으므로 프로그래머에게 타입을 기억한 단서가 필요했습니다.

하지만 자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없습니다. 객체는 강한 타입(strongly-typed)이며, IDE는 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 발전했습니다. 따라서 이제는 헝가리식 표기법이나 기타 인코딩 방식이 오히려 방해가 될 뿐입니다.

PhoneNumber phoneString;
// 타입이 바뀌어도 이름은 바뀌지 않는다!

 

멤버 변수 접두어

이제는 멤버 변수에 m_이라는 접두어를 붙일 필요도 없습니다. IDE가 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여줍니다. 접두어를 작성하는 것은 예전 구닥다리 코드라는 징표가 되어버렸습니다.

 

인터페이스 클래스와 구현 클래스

때로는 인코딩이 필요한 경우도 있습니다. 인터페이스 클래스(interface class)와 구현 클래스(concrete class)가 있다고 가정합니다. IShapeFactory와 ShapeFactory가 좋을까요? 저자는 개인적으로 다른 방안을 제안합니다.

ShapeFactory와 ShapeFactoryImpl이 더 낫다고 생각합니다. 인터페이스임을 명시적으로 알릴 필요가 없다고 생각하기 때문입니다. 혹은 ShapeFactory, CShapeFactory가 IShapeFactory보다 괜찮다고 합니다.

 

자신의 기억력을 자랑하지 마라

 자신만이 아는 변수 이름을 선택한 것은 바람직하지 못합니다.

 변수 이름을 한글자로 짓는 것은.. 아주 작은 단위의 루프일 경우만 괜찮습니다. 예를 들어, i, j, k 같은 경우를 말합니다. 그러나 l은 절대 해서는 안됩니다. r이라는 변수가 호스트와 프로토콜을 제외한 소문자 URL이라는 사실을 언제나 기억한다면 똑똑한 사람입니다.

 똑똑한 프로그래머와 전문가 프로그래머 사이에서 나타나는 차이점 하나를 들자면, 전문가 프로그래머는 명료함이 최고라는 사실을 이해합니다. 전문가 프로그래머는 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓습니다.

 

클래스와 메서드 이름

 클래스 이름과 객체 이름은 명사나 명사구가 적합합니다. Customer, WikiPage, Account, AddressParser 등이 좋은 예입니다. Manager, Processor, Data, Info 등과 같은 단어는 피하고 동사는 사용하지 않습니다.

 메서드 이름은 동사나 동사구가 적합합니다. postPayment, deletePage, save 등이 좋은 예입니다. 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 값 앞에 get, set, is를 붙입니다.

string name = employee.getName();
customer.setName("Mike");
if (paycheck.isPosted()) ...

 

 생성자(Constructor)를 중복정의(Overload)할 때는 정적 팩터리 메서드를 사용합니다. 메서드는 인수를 설명하는 이름을 사용합니다. 다음 두 예제를 봅시다.

Complex flucrumPoint = Complex.FromRealNumber(23.0);

// 와

Complex flucrumPoint = new Complex(23.0);

생성자 사용을 제한하려면 해당 생성자를 private로 선언합니다.

 

한 개념에 한 단어를 사용하라

 추상적인 개념 하나에 단어 하나를 선택에 이를 고수합니다. 예를 들어, 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽습니다. 최신 IDE를 통해 그 객체가 제공하는 메서드 목록을 볼 때, 이는 혼란스럽고 무엇을 사용할 지 모를 것이고 코드를 한줄 한줄 분석하게 될 것입니다.

 마찬가지로, 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽습니다. DeviceController, DeviceManager는 근본적으로 어떻게 다른가요? 이름이 다르면 당연히 클래스도 다르고 타입오 다를 것이라고 생각할 것입니다. 일관성 있는 어휘는 코드를 사용할 프로그래머가 반갑에 겨실 선물일 것입니다.

 

말장난 하지 마라

 기존에 add라는 메서드를 두 개를 더하는 의미로 사용했다고 가정할 때, 새롭게 집합을 추가하는 메서드에 add를 사용하는 것은 바람직할까요? 새로운 메서드는 insert나 append라는 이름이 더 적당할 것입니다.

 

해법 영역과 문제 영역을 구분하라

 코드를 읽는 사람도 프로그래머이기 때문에, 전산용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮습니다. 모든 이름을 문제 영역(Domain)에서 가져오는 정책은 현명하지 못합니다. 같은 개념을 다른 이름으로 이해하던 동료들이 매번 고객에게 의미를 물어봐야하기 때문입니다. 기술 개념에는 기술 이름이 가장 적합한 선택입니다. 예를 들어, Visitor 패턴이 적용됬다면, AccountVisitor으로 할 수 있고, JobQueue 같이 바로 이해할 수 있는 단어가 될 수 있습니다.

 앞서 말한 기술용어가 적당한 것이 없다면, 문제 영역(Domain)에서 이름을 가져와도 됩니다. 우수한 프로그래머라면 해법 영역과 문제 영역을 구분할 줄 알아야 합니다.

 

의미있는 맥락을 추가하라

 스스로 의미가 분명한 이름이 없지 않습니다. 하지만 대다수가 그렇지 못합니다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여합니다.

 예를 들어, firstName, lastName, state이 의미하는 바는 알 수 있습니다. 하지만 state가 주소 일부라는 사실은 알 수 있을까요? addr라는 접두어를 추가한다면, addrFirstName, addrLastname, addrState가 되어 더 분명해집니다. 변수가 좀 더 큰 구조에 속한다는 사실을 이해할 수도 있습니다.

 

불필요한 맥락을 없애라

 고급 휘발유 충전소(Gas Station Deluxe)라는 프로그램을 작성한다고 모든 클래스 이름에 GSD로 시작하도록 하는 것은 바람직하지 못합니다. 일반적으로 짦은 이름이 긴 이름보다 좋습니다. 단, 의미가 분명한 경우에 말이죠. 이름에 불필요한 맥락을 추가하지 않도록 주의해야합니다. accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 않습니다. Address는 클래스 이름으로 적합합니다. 포트 주소, MAC 주소, 웹 주소가 명확해야 한다면, PostalAddress, MAC, URI라는 이름이 괜찮을 것입니다. 그러면 의미가 분명해집니다.

마치면서...

 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화저인 배경이 같아야 합니다. 이것이 제일 어렵습니다. 기술, 비즈니스, 관리 문제가 아니라 교육의 문제입니다. 우리 분야 사람들이 이름 짓는 방법을 제대로 익히지 못하는 이유가 바로 여기에 있습니다.

 사람들이 이름을 바꾸지 않으려는 이유 중 하나는 다른 개발자가 반대할까 두려워서입니다. 여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 질책할지도 모릅니다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안됩니다.

 

마무리

소제목이 많아서, 간단히 요약해보도록 합니다.

 

  • 의도를 분명히 밝혀라
    이름을 지을 때 의도가 반영되어야 합니다.
  • 그릇된 정보를 피하라
    이미 널리 사용되고 있는 단어를 다른 경우에 사용하지 말아야 합니다.
  • 의미있게 구분하라
    Noise Word를 피해야합니다. 유사한 이름으로 지어서 구분이 안되는 경우를 피해야 합니다.
  • 발음하기 쉬운 이름을 사용하라
    코드에 대해서, 혹은 서비스 동작에 대한 대화가 원할하도록 발음하기 쉬운 이름을 지어야 합니다.
  • 검색하기 쉬운 이름을 사용하라
    숫자를 하드코딩하지 말고, 상수 변수를 선언하라. 작은 함수에서 지역변수는 한 단어로 써도 괜찮다.
  • 인코딩을 피하라
    헝가리식 표기법이나 멤버 변수 접두어는 옛방식이다. 인터페이스와 구현클래스처럼 인코딩이 필요한 경우도 있다.
  • 자신의 기억력을 자랑하지 마라
    자신만이 기억하고 이해할 수 있는 단어는 바람직하지 못합니다.
  • 클래스와 메서드 이름
    클래스에는 명사나 명사구, 메서드에는 동사나 동사구가 좋습니다.
  • 한 개념에 한 단어를 사용하라
    동일한 개념을 같은 단어를 여러 곳에 각각 사용하면 안됩니다.
  • 말장난 하지 마라
    동일한 개념을 일관성 있게 사용해야 합니다. 다른 개념에는 기존과 다른 단어를 사용하여 구분하세요.
  • 해법 영역과 문제 영역을 구분하라
    기술적인 의미가 있는 용어와 도메인 분야에서 사용하는 용어를 상황에 맞춰 선택하세요. 기술적인 의미가 있는 단어를 선택하는 것이 더 좋습니다.
  • 의미있는 맥락을 추가하라
    맥락을 구분하기 어려울 때는, 이름에 의미있는 맥락을 좀 더 추가하세요.
  • 불필요한 맥락을 없애라
    긴 이름보다는 짧은 이름이 더 낫습니다. 서로 다른 의미를 줄 때는 불필요한 맥락을 제거하고 정확히 구분하세요.

관련글

내용이 도움이 되셨으면 공감 버튼 꼬옥 눌러주세요
본문을 퍼가실 경우 댓글을 달아주세요