코드
https://github.com/rkwhr0010/clean_code/tree/main/src
변경 사항은 git history 참고
의도를 분명히 밝혀라
이름을 짓는데 시간을 투자해야 한다. 좋은 이름으로 절약하는 시간이 더 크기 때문이다.
이름 붙일 대상은 전부다. 패키지, 클래스, 변수 등
주석이 필요한 것은 의도가 명확하지 않은 것이다.
class Ex001 { void 의도를분명히밝혀라(){ /** * 주석이 필요하면 의도가 명확하지 않은 것 */ Date d; // 도착시간 /** * 의도는 명확히 */ Date arrivalTime; } } |
class Ex002 { List<int[]> theList = new ArrayList<>(); public List<int[]> getThem(){ List<int[]> list1 = new ArrayList<>(); // theList 내용물이 뭐지? theList.stream() .filter(intArr -> intArr[0] == 4) // intArr[0] == 4 는 뭐지? .forEach(intArr -> list1.add(intArr)); //반환되는 리스트는 어떻게 사용하지? return list1; } } |
코드 로직은 쉬우나, 무엇을 하는 코드인지 판단이 안된다.
class Ex002_2 { //지뢰찾기 게임이라는 것을 알아냈다. private static final int FLAGGED = 4; private static final int STATUS_VALUE = 0; List<int[]> gameBoard = new ArrayList<>(); public List<int[]> getFlaggedCells(){ List<int[]> flaggedCells = new ArrayList<>(); gameBoard.stream() .filter(cell -> cell[STATUS_VALUE] == FLAGGED) .forEach(flaggedCells::add); return flaggedCells; } } |
코드 로직은 변경된게 하나 없이, 좋은 이름만으로 의도가 명확해 졌다.
class Ex002_3 { List<Cell> gameBoard = new ArrayList<>(); public List<Cell> getFlaggedCells(){ List<Cell> flaggedCells = new ArrayList<>(); gameBoard.stream() .filter(Cell::isFlagged) .forEach(flaggedCells::add); return flaggedCells; } class Cell { private int[] status; private static final int FLAGGED = 4; private static final int STATUS_VALUE = 0; public boolean isFlagged() { return FLAGGED == status[STATUS_VALUE]; } } } |
더 나아가 int[] 배열 대신, 별로 클래스를 만들어 로직을 캡슐화 했다.
그릇된 정보를 피하라
그릇된 정보는 코드 의미를 흐린다.
축약어 같은 경우 보는 사람 관점에서 서로 다르게 해석될 여지가 있다.
hp 같은 경우 게임을 좋아하는 사람은 health point로 노트북을 좋아하는 사람은 hp 회사를 떠올린다.
자료구조와 연관된 단어는 신중히 사용한다.
Account 여러 개를 그룹화하는 컨테이너를 만든다고 가정하면 AccountList 같은 이름은 List를 구현한 것 처럼 보이기에 사용하면 안된다.
실제로 컨테이너가 List라도 List이름을 붙이는 것을 지양해야 한다.
class Ex003 { class Account {} class AccountList{} // 안 좋은 이름 마치 List를 구현한 것 처럼 느껴진다. //GOOD class AccountGroup{} class Accouts{} } |
되도록 비슷한 이름을 사용하는 것을 피해야 한다.
인지적으로 찾기 너무 힘들다.
클래스 이름이 만약 앞부분 만 제외하고, 같아면 크게 상관 없겠지만,
이름 길이도 비슷한데, 중간 부분 철저만 조금 다르면, 다른 점을 찾기가 힘들다.
class Ex003_2 { void exam(){ //아무 의미없는 임시 변수명 지양 int a = 0; int b = 1; if(a == b) { a = 10; } } void exam2(){ //아무 의미없는 임시 변수명에, 비슷한 철자까지 더 하면 더 끔찍하다. int i = 0; int l = 1; int I = 2; } void exam3(){ //보통 기업들 코딩 컨벤션을 보면, 임시 변수는 아래와 같이 //반복문에 같은 곳에만 허용한다. for(int i = 0; i < 10; i++){ } } } |
의미 있게 구분하라
class Ex004 { //나쁜 이름 public static void copyChars(char[] a1, char[] a2){ for(int i = 0; i < a1.length; i++){ a2[i] = a1[i]; } } } class Ex004_2 { //의미가 분명한 좋은 이름 public static void copyChars(char[] source, char[] destination){ for(int i = 0; i < source.length; i++){ destination[i] = source[i]; } } } |
a1, a2 같은 불용어를 쓰면, 어느 배열에서 어느 배열로 복사하는 지 로직을 봐야지 알 수 있다.
즉, 정보를 제공하지 않는 의미없는 불용어 때문에 메서드 내부 구조를 봐야한다.
불용어
문자 의미를 파악하는 데 기여하는 게 거의 없는 문자를 불용어라고 한다.
주로 접두나 접미에 많이 위치한다.
My, Info, Data, a, The 같은 것이 불용어다.
과거 좋은 IDE가 없던 시절엔 유의미한 경우가 있었지만, 지금은 의미가 없다.
예를 들어, strName 은 String 타입 name 이라는 뜻이지만,
현재는 IDE가 명확히 타입을 다 알려주기에 str은 의미가 없는 불용어다.
만약, 계좌에 대한 정보를 담은 클래스를 찾아야하는 데
Account 와 AccontInfo 두 클래스를 발견했다.
어떤 클래스를 봐야하는 지 알수가 없다.
남이 봤을 때 명확히 구분되는 이름을 사용해야 한다.
발음하기 쉬운 이름을 사용하라
인간의 두뇌는 단어라는 철자 묶음을 잘 처리한다.
대부분의 단어는 발음하기도 쉽다.
따라서 발음하기 쉬운 이름은 단어를 잘 사용하는 것이다.
이는 지나친 축약어에서 발견된다.
//발음하기 쉬운 이름을 사용하라 class Ex005 { //안 좋은 예 private Date modymdhms; //y년, m월, d일, h시, m분, s초; //좋은 예 private Date modificationTimestamp; } |
검색하기 쉬운 이름을 사용하라
문자 하나를 사용하는 상수는 찾기가 힘들다.
예시 ) 0, 1 , a, i
이런 이유로 긴 이름이 검색이 쉬운 것은 당연하다.
이름의 길이는 자신이 속한 스코프 범위에 비례해야 한다.
예를 들어, 반복문 속 i 같은 변수는 괜찮다. 그런데 저 멀리 외부 범위 여기 저기서 읽어오는 변수이름이 i 라면 정말 찾기 힘들다.
class Ex006 { void bad() { //예시 용 변수 int s = 0; int[] t = null; for (int j = 0; j < 34; j++ ) { s += (t[j] * 4 ) / 5; } } } |
s는 그나마 누산용 변수인 것을 알 수 있다.
왜 34까지 반복하는지 파악이 안된다.
t[] 배열은 무슨 배열이고, 계산식도 어떤 의미인지 파악이 안된다.
class Ex006_2 { void good() { //예시 용 변수 int[] taskEstimate = null; int realDaysPerIdealDay = 4; final int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j = 0; j < WORK_DAYS_PER_WEEK; j++ ) { int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; } } } |
의미 있는 이름을 지으면, 메서드가 길어진다.
그래도 무엇을 하는지 알 수 있으며, 검색하기 쉽다.
인코딩을 피하라
이름에 불필요한 정보를 더하지 말아야한다.
헝가리식 표기법
프로그래밍에서 변수 및 함수 인자 이름 앞에 데이터 타입을 명시하는 표기법
String strName;
헝가리식 표기법이 대표적
과거에는 자원 문제로 이름 길이가 제한되거나, 컴파일러 기능이 미약해 타입 감지를 못
해줬다.
현재는 상황이 다르다. 필요없다.
자신의 기억력을 자랑하지 마라
i, s ,r 과 같이 한글자로 지어놓고, 기억해놨으니 괜찮다는 식은 안좋다.
아무리 자신의 기억력이 좋아도, 한참 뒤 보면 무슨 역할 변수인지 알 수 없다.
클래스 이름
명사나 명사구가 적합하다. 동사는 사용하지 않는다.
JSONParser, Product
불용어가 안 들어가게 조심한다.
ProductInfo, ProductData
메서드 이름
동사나 동사구가 적합하다.
기발한 이름은 피하라
그 코드를 개발한 자신만 알 수 있다. 심지어 나중에 자신이 봐도 기억 못할 수 있다.
무조건 명확한 이름이 좋다.
한 개념에 한 단어를 사용하라
위 메서드들을 예시로 들면
찾는 것은 find, 저장은 save, 삭제는 delete 인 것을 볼 수 있다.
찾기라는 개념을 fetch, retrieve, search 등 혼용해서 사용하면 안된다.
말장난을 하지 마라
class Ex007 { //다른 곳도, add 어휘 메서드는 무언 가를 더한 값을 리턴한다. int add(int left, int right) { return left + right; } //무언가 컨테이너에 값을 더하는 메서드가 필요해서 //구현하고 이름을 add로 지으면, 기존 add 어휘가 가지는 의미가 두 개가 된다. List<Object> list; void add(Object obj){ list.add(obj); } //add 어휘에 일관성을 지키기 위해 insert라는 이름으로 바꾼다. void insert(Object obj){ list.add(obj); } } |
해법 영역에서 가져온 이름을 사용하라
디자인 패턴이나, 알고리즘 같은 용어를 사용하는 것이 좋다.
적절한 해법 영역에 용어가 없다면, 문제 영역에서 이름을 가져온다.
코드 성격이 문제 영역인지, 해법 영역인지 어느 쪽에 가까운 지 판단 후 이름을 지어야 한다.
의미 있는 맥락을 추가하라
변수 하나의 이름이 스스로 의미있는 맥락을 가지는 경우는 거의 없다.
class Ex008_2 { //addr 라는 접두를 추가해 맥략을 분명히 했다. void eaxm() { String addrFirstName, addrLastName, addrStreet , addRhouseNumber, addrCity, addrState, addrZipcode; /* * 무언가 하는 코드... */ } } class Ex008_3 { void eaxm() { Address address = new Address(); /* * 무언가 하는 코드... */ } //더 좋은 방법은 야에 새로운 클래스로 같은 맥락 변수를 묶는 것이다. class Address { String firstName, lastName, street, houseNumber, city, state, zipcode; } } |
불필요한 맥락을 없애라
미래 백화점 어플리케이션을 만든다고 가정하고,
어플리케이션에 클래스를 만드는데 굳이 불필요하게
Future Department Store 약어로 FDS를 접두로 클래스마다 붙이는 것은 의미가 없다.
마치면서
우리는 클래스, 메서드 등에 붙은 모든 이름을 외우지 못한다.
좋은 이름을 짓는 것은 매우 중요하다.