이 글에서는 로버트 C. 마틴의 저서인 클린 코드 중 2장 의미있는 이름에 대해서 설명합니다.
- 2장 의미있는 이름
- 2장 의미있는 이름 발표자료
2장 의미있는 이름
들어가면서
소프트웨어에서 이름은 어디나 쓰인다. 우리는 변수에도 이름을 붙이고, 함수에도 이름을 붙이고, 인수와 클래스와 패키지에도 이름을 붙인다.
이름은 가장 많이 사용하므로 이름을 잘 지으면 여러모로 편하다.
이 장에서는 이름을 잘 짓는 간단한 규칙을 몇 가지 소개한다.
의도를 분명히 밝혀라
- 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
- 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다
- 이름을 지을 때 아래 질문에 답할 수 있는 이름을 지어라
- 변수(혹은 함수나 클래스)의 존재 이유는? 수행 기능은? 사용 방법은?
예제 1
int d; // 경과 시간(단위: 날짜)
d로 이름을 짓고 주석을 기록하는 것은 의도가 드러나지 않는다. 아래와 같이 짓는 것이 좋다.
int elapsedTimeInDays; int daysSinceCreation; int daysSinceModification; int fileageInDays;
예제 2
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을 어떻게 사용하는가?
1. theList에 무엇이 들었는가?
2. theList에서 0번째 값이 어째서 중요한가?
3. 값 4는 무슨 의미인가?
4. 함수가 반환하는 리스트 list1을 어떻게 사용하는가?
public List<int[]> getFlaggedCells() { List<int[]> flaggedCells = new ArrayList<int[]>(); for (int[] cell: gameBoard) if (cell[STATUS_VALUE] = FLAGGED) flaggedCells.add(cell); return flaggedCells; }
위의 코드는 코드의 단순성은 그대로지만 코드는 더욱 명확해졌다.
int 배열을 사용하는 대신, 칸을 간단한 클래스로 만들어서 개선한 결과는 다음과 같다.
public List<Cell> getFlaggedCells(){ List<Cell> flaggedCells = new ArrayList<Cell>(); for (Cell cell : gameBoard) if (cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; }
단순히 이름만 고쳤는데도 함수가 하는 일을 이해하기 쉬워졌다. 바로 이것이 좋은 이름이 주는 위력이다.
그릇된 정보를 피하라
- 프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다.
- 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안 된다.
- 유닉스 플랫폼 이나 유닉스 변종을 가리키는 이름은 변수 이름으로 적합하지 않다.
- 서로 흡사한 이름을 사용하지 않도록 주의한다.
- 유사한 개념은 유사한 표기법을 사용한다.
- 최신 자바 환경은 코드 자동 완성 기능을 제공한다.
- 이름으로 그릇된 정보를 제공하는 변수 이름은 사용하지 않는다.
hp와 list는 개발자에게는 이미 알려진 다른 의미이므로 아래의 예제를 참고하라.
- 직각삼각형의 빗변(hypotenuse)을 구현할 때 hp라는 변수 이름은 독자에게 그릇된 정보를 제공한다.
- 여러 계정을 그룹으로 묶을 때, List가 아니라면 accountList라 명명하지 않는다. 단순히, accountGroup 또는 accounts가 낫다.
의미 있게 구분하라
- 코드를 구현할 때, 컴파일러나 인터프리터만 통과하려는 생각으로 이름을 지어서는 안 된다.
- 이름이 달라야 한다면 의미도 달라져야 한다.
- 연속된 숫자를 덧붙인 이름이나 불용어는 적절하지 못하다.
- al, a2, ..., aN
- 이름만 다르고 개념이 같은 경우에는 불용어(Info, Data, a, an, the)를 사용하지 말아야 한다.
- 변수 이름에 variable이라는 단어는 단연코 금물이다.
- 표 이름에 table이라는 단어도 마찬가지다.
- NameString --> Name, CustomerObject --> Customer
- 클래스나 변수의 이름을 지을 때 읽는 사람이 차이를 알도록 지어야 한다.
- customerInfo는 customer, accountData account, theMessage message와 구분이 안 된다.
함수 인수 이름으로 source와 destination을 사용하면 코드 읽기가 쉬워진다.
public static void copyChars(char a1[], char a2[]) { for (int i = 0; i < al.length; i++) { a2[i] = al[i]; } }
발음하기 쉬운 이름을 사용하라
- 발음하기 쉬운 이름을 사용하면 코드 이해가 쉬워지며, 지적인 대화가 가능하다
- 불편한 발음 - genymdhms(generate date, year, month, day, hour, minute, second)
- 정상적으로 읽는 법: "젠 와이엠디 에이취 엠에스"
- 발음나는대로 읽는 법: "젠 야 무다 힘즈"
새로운 개발자가 들어오면 변수를 설명해준 다음 (올바른 영어 단어가 아니라) 우리가 만들어낸 발음을 알려줬다. 다음 두 예제를 비교해보자.
class DtaRcrd102 { private Date genymdhms; private Date modymdhms; private final String pszqint="102"; /* ... */ };
VS
class Customer { private Date generationTimestamp; private Date modificationTimestamp; private final String recordId="102"; /* ... */ };
지적인 대화: "마이키, 이 레코드 좀 보세요. 'Generation Timestamp' 값이 내일 날짜입니다! 어떻게 이렇죠?"
검색하기 쉬운 이름을 사용하라
- 문자 하나를 사용하는 이름과 상수는 검색이 어려울 수 있다.
- 숫자를 사용하는 상수도 검색이 어렵고, 다른 의도로 사용되는 경우가 있을 수 있다.
- 긴 이름이 짧은 이름보다 검색하기 쉽다.
- 간단한 메서드에서 로컬 변수는 한 문자를 사용할 수 있지만, 변수나 상수를 여러 곳에서 사용할 경우 검색하기 쉬운 이름이 좋다.
- WORK_DAYS_PER_WEEK VS 5
for (int j=0; j<34; j++) { s += (t[j]*4)/5; }
VS
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; }
인코딩을 피하라
- 인코딩한 이름은 거의가 발음하기 어려우며 오타가 생기기도 쉽다.
헝가리식 표기법
- 과거에는 변수 이름의 길이가 제한되어 있어 헝가리식 표기법 등의 명명 규칙이 필요했다.
- 현재는 프로그래밍 언어가 다양한 타입을 지원하고 컴파일러가 타입을 강제하므로 변수 이름에 타입을 인코딩할 필요가 없어졌다.
- 변수, 함수, 클래스 이름이나 타입을 바꾸기가 어려워지며, 인코딩 방식이 읽기도 어려워지고 독자를 오도할 가능성이 커진다.
- 헝가리식 표기법은 변수의 목적과 의미를 더 쉽게 이해할 수 있도록 하기 위해 만들어졌으나, 현대 프로그래밍 언어에서는 덜 일반적으로 사용된다.
* 헝가리식 표기법이란?
헝가리어 표기법은 소프트웨어 개발에서 데이터 유형 또는 변수의 의도된 용도를 나타내기 위해 사용되는 명명 규칙입니다. 헝가리 컴퓨터 과학자인 Charles Simonyi에 의해 대중화되었으며 처음에는 Microsoft에서 사용되었습니다. 헝가리어 표기법에서 변수 이름은 변수의 데이터 유형을 나타내는 소문자 접두사로 시작하고 그 뒤에 변수 이름이 옵니다. 예를 들어 정수를 저장하는 변수의 이름은 "iCount"로 지정할 수 있습니다. 여기서 "i" 접두사는 정수임을 나타냅니다. 헝가리어 표기법의 기본 아이디어는 변수 이름을 보고 변수의 목적과 의미를 더 쉽게 이해할 수 있도록 하는 것입니다. 헝가리어 표기법은 과거에 인기가 있었지만 현재는 강력한 타이핑 및 기타 언어 기능으로 인해 필요하지 않은 현대 프로그래밍 언어에서는 덜 일반적으로 사용됩니다. 그러나 일부 개발자는 여전히 코드를 자체 문서화하는 방법으로 사용합니다.
헝가리어 표기법은 소프트웨어 개발에서 데이터 유형 또는 변수의 의도된 용도를 나타내기 위해 사용되는 명명 규칙입니다. 헝가리 컴퓨터 과학자인 Charles Simonyi에 의해 대중화되었으며 처음에는 Microsoft에서 사용되었습니다. 헝가리어 표기법에서 변수 이름은 변수의 데이터 유형을 나타내는 소문자 접두사로 시작하고 그 뒤에 변수 이름이 옵니다. 예를 들어 정수를 저장하는 변수의 이름은 "iCount"로 지정할 수 있습니다. 여기서 "i" 접두사는 정수임을 나타냅니다. 헝가리어 표기법의 기본 아이디어는 변수 이름을 보고 변수의 목적과 의미를 더 쉽게 이해할 수 있도록 하는 것입니다. 헝가리어 표기법은 과거에 인기가 있었지만 현재는 강력한 타이핑 및 기타 언어 기능으로 인해 필요하지 않은 현대 프로그래밍 언어에서는 덜 일반적으로 사용됩니다. 그러나 일부 개발자는 여전히 코드를 자체 문서화하는 방법으로 사용합니다.
멤버 변수 접두어
- 멤버 변수에 m_이라는 접두어를 붙일 필요는 없다.
- 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다.
- 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해야 마땅하다.
public class Part { private String m_dsc; // 설명 문자열 void setName(String name) { m_dsc = name; } } public class Part { String description; void setDescription(String description) { this.description = description; } }
사람들은 접두어(또는 접미어)를 무시하고 이름을 해독하는 방식을 재빨리 익힌다. 코드를 읽을수록 접두어는 관심 밖으로 밀려난다.
인터페이스 클래스와 구현 클래스
- ABSTRACT FACTORY 패턴을 구현할 때, 인터페이스 클래스와 구현 클래스의 이름을 어떻게 지어야 할지 고민해야 한다.
- 개인적으로는 인터페이스 이름에 접두어를 붙이지 않는 편이 좋다고 생각한다.
- 인터페이스 이름에 접두어 I는 주의를 흐트리고 과도한 정보를 제공할 수 있다.
- IShapeFactory와 ShapeFactory 중 ShapeFactory로 인터페이스 클래스를 생성
- 내가 다루는 클래스가 인터페이스라는 사실을 남에게 알리고 싶지 않다. 클래스 사용자는 그냥 ShapeFactory라고만 생각하면 좋겠다.
- 인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면 구현 클래스 이름을 선택하는 것이 좋다.
자신의 기억력을 자랑하지 마라
- 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다.
- 자신만 아는 이름으로 작명하지 말라
- 기존에 a,b 를 사용한다고 c를 사용하면 안된다.
- 루프에만 i,j,k를 사용하라
클래스 이름과 메서드 이름
- 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
- 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)할 때는 정적 팩토리 메서드를 사용한다.
- 메서드는 인수를 설명하는 이름을 사용한다.
- 생성자 사용을 제한하려면 해당 생성자를 private로 선언한다.
Complex fulcrumPoint = Complex.FromRealNumber(23.0); 위 코드가 아래 코드보다 좋다. Complex fulcrumPoint = new Complex(23.0);
기발한 이름은 피하라
- 재미난 이름보다 명료한 이름을 선택하라.
- kill() 대신에 whack()이라 부르거나 Abort() 대신 eatMyShort()라 부르면 안된다.
- 특정 문화에서만 사용하는 농담은 피하는 편이 좋다.
의도를 분명하고 솔직하게 표현하라
- 한 개념에 한 단어를 사용하라
- 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
- 예>
- 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.
- 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽다.
말장난을 하지마라
- 한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.
- 예>
- 지금까지 구현한 add 메서드는 모두가 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다
- 새로 작성하는 메서드는 집합에 값 하나를 추가한다.
- 새 메서드는 기존 add 메서드와 맥락이 다르다. 그러므로 insert나 append라는 이름이 적당하다.
해법 영역에서 가져온 이름을 사용하라
- 코드를 읽을 사람도 프로그래머라는 사실을 명심한다. 그러므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
- 모든 이름을 문제 영역(domain)에서 가져오는 정책은 현명하지 못하다.
- 같은 개념을 다른 이름으로 이해하던 동료들이 매번 고객에게 의미를 물어야하기 때문이다.
문제 영역에서 가져온 이름을 사용하라
- 적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져온다.
- 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.
- 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 구분할 줄 알아야 한다.
- 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.
의미 있는 맥락을 추가하라
스스로 의미가 분명한 이름이 없지 않다. 하지만 대다수 이름은 그렇지 못하다.
그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
예를 들어, firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다. 변수를 훑어보면 주소라는 사실을 금방 알아챈다. 하지만 어느 메서드가 state라는 변수 하나만 사용한다면? 변수 state가 주소 일부라는 사실을 금방 알아챌까? addr라는 접두어를 추가해 addrFirstName, addrLastName, addrState라 쓰면 맥락이 좀 더 분명해진다. 변수가 좀 더 큰 구조에 속한다는 사실이 적어도 독자에게는 분명해진다. 물론 Address라는 클래스를 생성하면 더 좋다.
예를 들어, firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다. 변수를 훑어보면 주소라는 사실을 금방 알아챈다. 하지만 어느 메서드가 state라는 변수 하나만 사용한다면? 변수 state가 주소 일부라는 사실을 금방 알아챌까? addr라는 접두어를 추가해 addrFirstName, addrLastName, addrState라 쓰면 맥락이 좀 더 분명해진다. 변수가 좀 더 큰 구조에 속한다는 사실이 적어도 독자에게는 분명해진다. 물론 Address라는 클래스를 생성하면 더 좋다.
목록 2-1 맥락이 불분명한 변수 private void printGuessStatistics (char candidate, int count) { String number; String verb; String pluralModifier; if (count = 0) { number = "no"; verb = "are"; pluralModifier= ""; } else if (count = 1) { number = "1"; verb = "is"; pluralModifier = ""; } else { } number = Integer.toString(count); verb = "are"; plura Modifier = "s"; String guessMessage = String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier ); print(guessMessage); }
목록 2-2 맥락이 분명한 변수 public class GuessStatisticsMessage { private String number; private String verb; private String pluralModifier; public String make (char candidate, int count) { createPluralDependentMessageParts (count); return String.format( "There %s %s %s%s", verb, number, candidate, plura Modifier ); } private void createPluralDependentMessageParts (int count) { if (count = 0) { thereAreNoLetters(); } else if (count = 1) { thereIsOneLetter(); } else { thereAreManyLetters(count); } } private void thereAreManyLetters (int count) { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } private void thereIsOneLetter() { number = "1"; verb = "is"; pluralModifier = ""; } private void thereAreNoLetters() { number = "no"; verb = "are"; pluralModifier = ""; } }
불필요한 맥락을 없애라
'고급 휘발유 충전소Gas Station Deluxe'라는 애플리케이션을 짠다고 가정하자.
모든 클래스 이름을 GSD로 시작하겠다는 생각은 전혀 바람직하지 못하다.
솔직히 전봇대로 이 쑤시는 격이다. IDE에서 G를 입력하고 자동 완성 키를 누르면 IDE는 모든 클래스를 열거한다. 현명하지 못하다. IDE는 개발자를 지원하는 도구다. IDE를 방해할 이유는 없다.
비슷한 예로, GSD 회계 모듈에 MailingAddress 클래스를 추가하면서 GSDAccountAddress로 이름을 바꿨다. 나중에 다른 고객 관리 프로그램에서 고객 주소가 필요하다. GSDAccountAddress 클래스를 사용할까? 이름이 적절한가? 이름 17자 중 10자는 중복이거나 부적절하다.
일반적으로는 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해 서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다. accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 못하다. Address는 클래스 이름으로 적합하다. 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI라는 이름도 괜찮겠다. 그러면 의미가 좀 더 분명해진다.
솔직히 전봇대로 이 쑤시는 격이다. IDE에서 G를 입력하고 자동 완성 키를 누르면 IDE는 모든 클래스를 열거한다. 현명하지 못하다. IDE는 개발자를 지원하는 도구다. IDE를 방해할 이유는 없다.
비슷한 예로, GSD 회계 모듈에 MailingAddress 클래스를 추가하면서 GSDAccountAddress로 이름을 바꿨다. 나중에 다른 고객 관리 프로그램에서 고객 주소가 필요하다. GSDAccountAddress 클래스를 사용할까? 이름이 적절한가? 이름 17자 중 10자는 중복이거나 부적절하다.
일반적으로는 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해 서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다. accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 못하다. Address는 클래스 이름으로 적합하다. 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI라는 이름도 괜찮겠다. 그러면 의미가 좀 더 분명해진다.
마치면서
- 사람들이 이름을 바꾸지 않으려는 이유 하나는 다른 개발자가 반대할까 두려워서다.
- 우리들 대다수는 자신이 짠 클래스 이름과 메서드 이름을 모두 암기하지 못한다.
- 암기는 요즘 나오는 도구에게 맡기고, 우리는 문장이나 문단처럼 읽히는 코드 아니면 (정보를 표시하는 최선의 방법이 항상 문장만은 아니므로) 적어도 표나 자료 구조처럼 읽히는 코드를 짜는 데만 집중해야 마땅하다.
2장 발표자료
ppt 파워 포인트 버전을 아래에 슬라이드로 첨부하였습니다.
아래 ppt 파일은 다음의 버튼 링크에서 제공합니다.
클린코드 - 2장 의미있는 이름 발표자료 다운로드 | 파일 > 다운로드필요하신 분들은 편하게 사용하셔도 좋습니다.