[CleanCode] 2장 의미 있는 이름
- 들어가면서
- 소프트웨어에서 이름은 어디나 쓰인다.
이름을 잘 지으면 여러모로 편하다.
이 장에서는 이름을 잘 짓는 간단한 규칙을 몇 가지 소개한다.
- 소프트웨어에서 이름은 어디나 쓰인다.
- 의도를 분명히 밝혀라
- 의도가 분명한 이름은 정말로 중요하다.
- 변수나 함수 그리고 클래스 이름은 존재 이유와 수행 기능, 사용 방법을 답해야 한다.
- 1.1 변수
나쁜 예시int p; // 사람 수 (단위: 명)
이름 d는 아무 의미도 드러나지 않는다. 경과 시간이나 날짜라는 느낌이 안든다.
측정하려는 값과 단위를 표현하는 이름이 필요하다.int patientsCount; int loginFailUserCount;
- 좋은예시
- 1.2 코드
나쁜 예시public List<int[]> getThem(){ List<int[]> list1 = new ArrayList<int[]>(); for (int[] x : theList) if (x[0] == 4) list1.add(x); return list1; }
위 코드는 암암리에 독자가 다음의 정보를 안다고 가정한다.- theList에 무엇이 들어있는가?
- theList의 0번째 값이 어째서 중요한가?
- 값 4는 무슨 의미인가?
- 함수가 반환하는 list1을 어떻게 사용할 것인가?
- 코드에 정보 제공을 충분히 하자
public List<Cell> getFlaggedCells(){ List<Cell> flaggedCells = new ArrayList<Cell>(); for (Cell cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; } // or public List<Cell> getFlaggedCells() { List<Cell> flaggedCells = new ArrayList<Cell>(); for(Cell cell: gameBoard) { if(cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; }
- 그릇된 정보를 피하라
- 나름대로 널리 쓰이고 있는 의미가 있는 단어를 다른 의미로 사용하면 안된다.
ex)hp
,aix
,sco
- 여러 계정을 그룹으로 묶을 때 실제 List가 아니라면 accountList와 같이 명명하지 않는다.
ex)accountList
→accountGroup
,accounts
- 서로 흡사한 이름을 사용하지 않도록 주의한다.
ex)XYZControllerForEfficientHandlingOfStrings
,XYZControllerForEfficientStorageOfStrings
- 유사한 개념은 유사한 표기법을 사용한다.
- 소문자 L이나 대문자 O는 숫자 1이나 0과 혼동하기 쉬우니 주의한다.
- 나름대로 널리 쓰이고 있는 의미가 있는 단어를 다른 의미로 사용하면 안된다.
- 의미 있게 구분하라
- 읽는 사람이 차이를 알도록 이름을 지어라
ex)copyChars(char a1[], char a2[])
→copyChars(char source[], char destination[])
나쁜예시 (연속된 숫자)public static void copyChars(char a1[], char a2[]) { for (int i = 0; i< a1.length; i++) { a2[i] = a1[i]; } }
좋은예시 (연속된 숫자 -> 의미 있는 이름) public static void copyChars(char source[], char destination[]) { for (int i = 0; i< source.length; i++) { destination[i] = source[i]; } }
- 동일 범위 안에서 다른 두 개념에 같은 이름을 사용하지 못한다고 철자를 틀리게 적지 말자
- 연속된 숫자를 덧붙이거나 불용어(noise word)를 추가하는 방식은 적절하지 못하다.
이름이 달라야 한다면 의미도 달라져야 한다.Customer
와customerInfo
,moneyAmount
와money
,account
와accountData
는 구분이 안 된다. - ex)
ProductInfo
나ProductData
는 개념과 의미가 불분명하다.
- 읽는 사람이 차이를 알도록 이름을 지어라
- 발음하기 쉬운 이름을 사용하라
ex)genymdhms
→generationTimestamp
,modymdhms
→modificationTimestamp
나쁜예시class DtaRcrd102 { private Date genymdhms; private Date modymdhms; private final String pszqint = "102"; /* ... */ };
좋은예시 class Customer { private Date generationTimeStamp; private Date modificationTimeStamp; private final String rdcordId = "102"; };
- 검색하기 쉬운 이름을 사용하라
문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다.7 -> MAX_CLASSES_PER_STUDENT
개선 후 코드이름을 의미있게 지으면 함수가 길어지나,WORK_DAYS_PER_WEEK
는 찾기가 쉽다.
그냥 5를 사용한다면 5가 들어가는 이름을 모두 찾은 후 의미를 분석해 원하는 상수를 가려내야 한다. int realDaysPerIdalDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for(int j=0; j < NUMBER_OF_TASKS; j++) { int realTaskDays = taskEstimate[j] * realDaysPerIdalDay; int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; }
- 인코딩을 피하라
- 헝가리식 표기법 (타입을 변수 이름에 쓰는 방식)
- 자바는 변수 이름에 타입을 인코딩할 필요가 없다.
나쁜 예시PhoneNumber phoneString; // 타입이 바뀌어도 이름은 바뀌지 않는다.
- 자바는 변수 이름에 타입을 인코딩할 필요가 없다.
- 멤버 변수 접두어
- 이제 멤버 변수에
m_
이라는 접두어를 붙일 필요도 없다.
나쁜 예시public class Part { private String m_dec; // 설명 문자열 void setName(PString name) { m_dsc = name; } }
좋은예시 public class Part { private String description; void setDescription(String description) { this.description = description; } }
- 이제 멤버 변수에
- 인터페이스 클래스와 구현 클래스
- 인터페이스 클래스와 구현 클래스의 관계라면 클래스 사용자가 쉽게 접근할 수 있는 이름으로 하자.
- 인터페이스 클래스를 구현한 클래스엔
Impl
접미어 인코딩은 가능하다.ShapeFactory implements IShapeFactory
보다ShapeFactoryImpl implements ShapeFactory
가 낫다.
- 인터페이스 클래스를 구현한 클래스엔
- 인터페이스 클래스와 구현 클래스의 관계라면 클래스 사용자가 쉽게 접근할 수 있는 이름으로 하자.
- 자신의 기억력을 자랑하지 마라
- 자신이 아는 이름으로 변환하는 것이 아니라 명료한 단어를 선택하자
- 문자 하나만 사용하는 변수 이름은 문제가 있다.
(루프에서 반복 횟수를 세는 변수 i,j,k는 괜찮다. l은 절대 No! )
인코딩한 이름은 발음하기 어려우며 오타가 생기기도 쉽다.
- 헝가리식 표기법 (타입을 변수 이름에 쓰는 방식)
- 클래스 이름
- 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
Customer, WikiPage, Account, AddressParser
등은 좋은 예다.Manager, Processor, Date, Info
와 같은 단어는 피하자- 동사는 사용하지 않는다.
- 메서드 이름
- 메서드 이름은 동사나 동사구가 적합하다.
- 메서드 이름은 독자적이고 일관적이어야 한다.
postPayment, deletePage, save
등이 좋은 예다- 접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.
string name = employ.getName(); customer.setName("mike"); if(paycheck.isPayed()) ...
- 생성자를 중복정의할 때는 정적 팩토리 메서드를 사용한다.
- 나쁜 예시)
Complex fulcrumPoint = new Complex(23.0);
- 좋은 예시)
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
- 나쁜 예시)
- 생성자 사용을 제한하려면 해당 생성자를 private으로 선언한다.
- 기발한 이름은 피하라
- 재미난 이름보다 명료하고 분명한 이름을 선택하자.
- 특정 문화에서만 사용하는 농담은 피하고 의도를 분명하고 솔직하게 표현하자
- 한 개념에 한 단어를 사용하라
- 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
- 예) 똑같은 메서드를 클래스마다
fetch, retrieve, get
으로 제각각 부르면 혼란스럽다. - 마찬가지로 동일 코드에
controller, manager, driver
를 섞어 쓰면 혼란스럽다.
- 예) 똑같은 메서드를 클래스마다
- 일관성 있게 단어를 사용하자.
- 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
- 한 단어는 한 가지 목적으로만 사용하라
- ‘한 개념에 한 단어를 사용하라’는 규칙을 따르려고 ‘일관성’을 고려해 같은 이름을 선택하지 마라.
- ex) 지금까지 구현한
add
메서드는 기존 값 두 개를 더하거나 이어서 새로운 값을 만드는 메서드일 때, 집합에 값 하나를 추가하는 새로운 메서드는 일관성을 고려한add
가 아닌append
나insert
가 적당하다.
- ex) 지금까지 구현한
- ‘한 개념에 한 단어를 사용하라’는 규칙을 따르려고 ‘일관성’을 고려해 같은 이름을 선택하지 마라.
- 해법 영역에서 가져온 이름을 사용하라
- 우선 프로그래머가 통용적으로 사용하는 기술 이름을 사용하라.
- 코드를 읽는 사람도 프로그래머이므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
- 문제영역에서 모든 이름을 가져오지 마라. 기술 개념에는 기술 이름이 가장 적합한 선택이다.
- ex) VISITOR 패턴을 구현한
AccountVisitor
,JobQueue
- 문제 영역에서 가져온 이름을 사용하라
- 적절한 ‘프로그래밍 용어’가 없다면 문제 영역(도메인 관련 전문 이름)에서 이름을 가져온다.
- 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.
- 의미 있는 맥락을 추가하라
- 알고리즘, 내용을 읽어야 겨우 유추할 수 있는 이름은 피하자.
- 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
맥락이 불분명한 변수private void printGuessStatistics(char candidate, int count) { String number; String verb; String pluralModifier; if (count == 0) { number = "no"; verb = "are"; pluralModifier = "s"; } else if (count == 1) { number = "1"; verb = "is"; pluralModifier = ""; } else { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } String guessMessage = String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier); print(guessMessage); }
맥락이 분명한 변수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, pluralModifier); } 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 thereAreNoLetter(){ number = "no"; verb = "are"; pluralModifier = "s"; } }
함수를 작은 조각으로 쪼개고자GuessStatisticsMessage
라는 클래스를 만든 후 세 변수를 클래스에 넣었다. 이렇게 맥락을 개선하면 함수를 쪼개기가 쉬워지므로 알고리즘도 좀 더 명확해진다. - ex)
firstName
,lastName
,street
,houseNumber
,city
,state
,zipcode
라는 변수들을 보면 주소라는 사실을 알아차릴 수 있다. 하지만 어느 메서드가state
라는 변수 하나만 사용한다면? 주소 일부라는 사실을 알아차리기 쉽지 않다.addr
접두어를 추가해addrFirstName
,addrLastName
, …addrState
라고 쓰면 맥락이 분명해진다. 물론Address
라는 클래스를 생성하면 더 좋다. 그러면 변수가 좀 더 큰 개념에 속한다는 사실이 컴파일러에게도 분명해진다.
- 불필요한 맥락을 없애라
- 일반적으로 짧은 이름이 긴 이름보다 좋다.
단, 의미가 분명한 경우에 한해서다.
이름에 불필요한 맥락을 추가하지 않도록 주의한다.accountAddress
와customerAddress
는 Address 클래스 인스턴스로는 좋은 이름이나- 클래스 이름*으로는 적합하지 못하다. Address는 클래스 이름으로 적합하다.
- 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면
PostalAddress
,MAC
,URI
라는 이름도 괜찮다.
- 일반적으로 짧은 이름이 긴 이름보다 좋다.
💡 의미가 분명한 긴 이름을 추구하되, 불필요한 정보를 중복 추가하지 마라.
- 마치면서
- 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다.
- 사람들이 이름을 바꾸지 않으려는 이유 하나는 다른 개발자가 반대할까 두려워서다.
우리들 생각은 다르다. 오히려 좋은 이름으로 바꿔주면 반갑고 고맙다. - 이장에서 소개한 규칙 몇 개를 적용해 코드 가독성이 높아지는지 살펴보자
- 참고 링크
- https://velog.io/@lychee/Clean-Code-2장-의미-있는-이름
- https://hirlawldo.tistory.com/131?category=959634