이번 장에서는 저자가 프로그램을 수정할 때마다 "왜"라고 자문한 다음 그 답을 기록한 거에요. 함께 저가 요약한 요약본을 읽어봐요.
부적절한 정보
코드 관리 시스템등에 저장될 정보를 주석으로 처리하는 것은 적절하지 못해요. 예를들면 코드 변경 이력을 주석으로 적으면 내용만 장황해져요. 굳이 넣는 다면 작성자, 최종 수정일, 소프트웨어 이슈 번호 등과 같은 메타정보 정도만 주석으로 넣으세요.
쓸모 없는 주석
오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없어요. 쓸모 없어질 주석은 아에 달지 않는 편이 좋아요. 쓸모 없는 주석은 코드와 무관하게 혼자 따로 놀며 코드를 그릇된 방향으로 이끌어요.
중복된 주석
코드만으로 충분한데 구구절절하게 설명하는 주석이 대표적이에요. 아래 예시에요.
i++; // i 증가/** * @param sellRequest * @return * @throws ManagedComponentException */public SellResponse beginSellItem(SellRequest sellrequest) throws ManagedComponentException성의 없는 주석
단어를 신중히 고르고 당연한 소리를 반복하지마세요. 간결 명료하게 작성하세요.
주석 처리된 코드
소스 코드 관리 시스템(Git)이 기억하는데 굳이 남겨놓지 마세요. 어처피 안쓸거 경험 많이 해보면 알자나요!!
여러 단계로 빌드
빌드는 간단히 한 단계로 끝나게하세요. 이해하기 힘든 명령이나 스크립트를 잇달아 실행해 각 요소를 따로 빌드하게 만들지 마세요. 온갖 JAR파일, XML 파일, 기타 시스템에 필요한 파일 찾느라 뒤질일도 없어야 해요.
svn get mySystem #svn 버전관리cd mySystemant all # 한방에 빌드여러 단계로 테스트
모든 단위 테스트도 한 명령으로 돌려지게 하세요. 버튼 딸각으로 테스트 돌리면 이상적이에요. 말했쬬? 테스트는 빠르고 쉽게 명백하게.
너무 많은 인수
함수의 인수는 작을수록 좋아요. 4개가 넘어간다면 아주 의심스러우니 최대한 피하세요.
출력 인수
출력 인수는 직관을 정면으로 위배해요. 보통 인수는 입력을 의미하니까요. 만약 상태를 변경해야한다면 함수가 속한 객체의 상태를 변경하세요.
플래그 인수
boolean 인수는 함수가 여러 기능을 수행한다는 증거에요. 혼란을 초래하니 피하세요.
죽은 함수
아무도 호출하지 않는 함수는 그냥 지우세요
한 소스 파일에 여러 언어를 사용한다
JSP를 예를 들면 HTML, 자바가 같이 들어갈 수 있어요. 그리고 어떤 자바 소스 파일은 XML, HTML, YAML 등이 함꼐 들어가 있기도 해요. 현실적으로 힘들 수 있지만 최대한 1개의 파일에는 1개의 언어가 들어가도록 해요.
당연한 동작을 구현하지 않는다.
요일 문자열을 요일을 나타내는 enum으로 변환하는 함수를 예를 들어봐요. 그러면 대소문자 구분없이 enum으로 바꾸길 기대해요. 하지만 이런 당연한 생각대로 동작하지 않으면 최소 놀람의 원칙에 위배해요.
Day day = DayDate.StringToDay(String dayName); //dayName에 대소문자 구분 안해도 되게!경계를 올바로 처리하지 않는다.
코드는 올바르게 동작해야해요. 그런데 올바른 코드 또한 동작이 아주 복잡한 경우가 많아요. 그리고 개발자들은 머릿속에서 코드를 돌려보고 끝내는 경우도 많아요. 그래서 모든 경계와 구석진 곳을 코드로 증명하지 않아 문제가 발생하는 경우가 많아요. 모든 경계 조건을 테스트하는 테스트 케이스를 작성해요.
안전 절차 무시
안전 절차를 무시하면 위험해요. 직접 제어하면 실수할 수 있는 경우에는 직접 제어하지 않는 것이 좋아요. 예를들면 컴파일러 경고를 꺼버리면 빌드가 쉬워질 수 있지만 끝없는 디버깅에 시달릴 수도 있어요.
중복
가장 중요한 규칙 중 하나에요. DRY 원칙이라고 아시나요? Don't Repeate Yourself의 약자에요. XP 핵심 규칙중 하나로 선언한 후 "한 번, 단 한 번만"이라 명명했어요. "모든 테스트를 통과한다"는 규칙 다음으로 중요하게 꼽기도 해요. 코드에서 중복을 발견할 때마다 추상화할 기회로 보세요. 중복된 코드를 하위 루틴이나 다른 클래스로 분리하세요. 그러면 추상화 수준이 높아져서 구현이 빨라지고 오류가 적어져요. 다른 프로그래머들이 어휘가 사용해지기 쉬워지기 때문이에요.
복붙한 코드들을 간단한 함수로 교체하고, 무수한 switch/case나 if/else문은 다형성을 활용하여 해결하고, 알고리즘이 유사하지만 코드가 다른 경우엔 템플릿 메서드 패턴이나 전략 패턴을 이용해봐요. 꼭 코딩이 아니라도 DB 스키마 중복 제거 등을 하기도 해요.
추상화 수준이 올바르지 못하다.
추상화는 처차원 상세 개념에서 고차원 일반 개념을 분리해요. 우리는 추상 클래스(고차원)과 파생 클래스(저차원)을 생성해 추상화하기도 해요. 추상화로 개념을 분리할 때는 철저하게 하세요.
예를 들면 세부 구현과 관련한 상수, 변수, 유틸 함수는 기초 클래스에 넣으면 안되요. 기초 클래스는 구현 정보에 무지해야해요. 우수한 소프트웨어 설계자는 개념을 다양한 차원으로 분리해 다른 컨테이너에 넣어야해요. 고차원 개념과 저차원 개념을 섞지 마세요.
public interface Stack { Object pop() throws EmptyException; void push(Object o) throws FullException; double percentFull(); // 추상화 수준 안맞음. 파생 Stack Class(Bounded Stack)에서 넣으면 더 맞을지도.}기초 클래스가 파생 클래스에 의존한다.
개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리하는 거에요. 그래서 기초 클래스가 파생 클래스를 사용하면 문제가 있다는 증거에요.
그런데 예외가 있어요. 예를 들면 간혹 파생 클래스 개수가 확실히 고정되어 있으면 기초 클래스에 파생 클래스를 선택하는 코드가 들어가요. FSM(유한 상태 머신) 구현에서 많이 보이는 사례에요. 이때는 JAR 파일도 같은 파일로 배포해요. 왜냐하면 기초 클래스와 파생 클래스가 굉장히 밀접하기 때문이에요.
과도한 정보
잘 정의된 모듈은 인터페이스가 작아요. 하지만 작은 인터페이스로도 많은 동작이 가능해요. 잘 정의된 인터페이스는 많은 함수를 제공하지 않아 결합도가 낮고, 그 반대는 높아요. 우수한 소프트웨어 개발자는 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 해요. 제공하는 메서드 수가 작고, 함수가 아는 변수도 적고, 클래스의 인스턴스 변수 수도 작을 수록 좋아요. 자료, 유틸리티 함수, 상수와 임시변수를 숨기세요. 그리고 정보를 제한해 결합도도 낮추세요.
죽은 코드
죽은 코드는 실행되지 않는 코드죠? 죽은 코드를 발견하면 적절한 장례식을 치뤄주세요.
수직 분리
변수와 함수는 사용되는 위치에 가깝게 정의하세요. 비공개 함수는 전체 클래스 범위에 속하지만 그래도 정의하는 위치와 호출하는 위치를 가깝게 유지하세요.
일관성 부족
어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현하세요. 앞서 말했던 최소 놀람 원칙과 부합해요. 표기법도 신중하게 일관되게 작성하세요.
잡동사니
비어 있는 기본 생성자는 쓸데없이 코드만 복잡하게 만들어요. 아무도 사용하지 않는 변수, 함수, 정보를 제공하지 않는 주석 등은 코드만 복잡하게 만드는 제거하세요.
인위적 결합
서로 무관한 개념은 인위적으로 결합하지 마세요. 예를들어 일반적인 enum이 특정 클래스에 속할 이유가 없어요. enum을 사용해야하는 코드가 있을때 특정 클래스를 통해 불러야만해요. 범용 static 함수도 마찬가지로 특정 클래스에 속할 이유가 없어요. 함수, 상수, 변수 선언에는 올바른 위치를 고민하세요. 당장 편한 곳에 두고 내버려두지 마세요.
기능 욕심
클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가지면 안돼요. 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이에요. 아래가 나쁜 예시와 좋은 예시에요.
public class HourlyPayCalculator { public Money calculateWeeklyPay(HourlyEmployee e) { int tenthRate = e.getTenthRate().getPennies(); // HourlyEmployee 에 대해 너무 알아야해!! HourlyEmployee에 속하고 싶니? int tenthsWorked = e.getTenthsWorked(); int straightTime = Math.min(400, tenthsWorked); int overTime = Math.max(0, tenthsWorked - straightTime); int straightPay = straightTime * tenthRate; int overtimePay = (int)Math.round(overTime*tenthRate*1.5); return new Money(straightPay + overtimePay); }}public class HourlyEmployeeReport { private HourlyEmployee employee ; public HourlyEmployeeReport(HourlyEmployee e) { this.employee = e; } String reportHours() { return String.format( "Name: %s\tHours:%d.%1d\n", employee.getName(), // employee가 보고서형식 알고싶어해. employee.getTenthsWorked()/10, employee.getTenthsWorked()%10); }}선택자 인수
함수 호출 끝에 달리는 false 인수는 밉살스러워요. true를 인수로 넣는것과 false로 넣는것의 결과가 어떻게 달라지는지 뜻을 알기 어려워요. 선택자 인수는 목적을 기억하기 어렵게하고, 각 선택자 인수는 여러 함수를 하나로 조합하게 만들어요. (true/false로 행동 달라지게 하려는 괘씸한 짓인데 그냥 함수를 나눠라는 뜻.)
이 외에도 enum, int 등 함수 동작을 제어하려하는 것은 바람직 하지 않다고 해요. 그런데 개인적으로 프론트에서 간단한 상태로 UI분기하는 것은 괜찮았던것 같아요..?
모호한 의도
코드를 짤 때는 의도를 최대한 밝혀요. 행을 바꾸지 않는 수식, 헝가리식 표기법, 매직 번호 등이 모두 저자의 의도를 흐려요.
잘못 지운 책임
소프트웨어 개발자가 내리는 가장 중요한 결정 중 하나가 코드를 배치하는 위치에요. 예를들면 PI 상수를 Math 클래스에 넣을지, Trigonometry 에 넣을지 Circle에 넣을지 결정해요. 자연스럽게 기대할 위치로 PI함수는 삼각함수를 선언한 클래스에 넣어야 맞다고 저자가 말하네요.
때로는 개발자가 영리하게 기능을 배치해요. 독자에게 직관적인 위치보다 개발자에게 편함 함수를 배치하죠. 이때는 함수 이름을 통해 의도가 잘 나타내게 해주세요.
부적절한 static 함수
Math.max(double a, double b)는 좋은 static 메서드에요. 클래스 내부에서 소유한 객체에서 가져오는 정보를 사용하지도 않고, 두 인수가 사용되는 정보의 전부에요. 하지만 간혹 static으로 정의하면 안되는 함수를 static으로 정의해요.
HourlyPayCalculator.calculatePay(employee, overtimeRate);
이라는 함수가 있다고 해봐요. 언뜻 보면 static 함수로 여겨도 적당해요. 특정 객체와 관련도 없고, 모든 정보를 인수에서 가져와요. 하지만 함수를 재정의할 가능성이 존재해요. 수당 계산 알고리즘이 여러개일 수 있어요. Calculator와 StraightTimeHourlyPayCalculator를 분리하고 싶을지도 몰라요. 그러므로 위 함수는 static 함수로 정의하면 안되고 Employee 클래스에 속하는 인스턴스 함수여야 해요. (???)
일반적으로 static보다 인스턴스 함수가 더 좋아요. 조금이라도 의심스럽다면 인스턴스 함수로 정의하세요.
서술적 변수
프로그램 가독성을 높이는 가장 효과적인 방법중 하나가 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 방법이에요. 아래는 그 예시에요. 첫 번째로 일치하는 그룹이 키, 두번째가 값이라는 것을 드러내요.
Matcher match = headerPattern.matcher(line);if(match.find()){ String key = match.group(1); String value = match.group(2); headers.put(key.toLowerCase(), value);}이름과 기능이 일치하는 함수
Date newDate = date.add(5);
위와 같은 함수는 5일을 더하는 함수인가요? 5주? 5시간? 그리고 새로운 date 인스턴스를 반환하나요? 코드만 봐서는 몰라요. date 인스턴스에 5일을 더해 변경하는 함수라면 addDaysTo() 또는 increaseDaysTo()라는 이름이 좋아요. 반면 인스턴스를 변경하지 않고 5일 뒤인 새 날짜를 반환하면 daysLater()이나 daysSince()라는 이름이 좋아요.
알고리즘 이해하라
괴상한 코드는 사람들이 알고리즘을 이해하지 않은 채 코드를 구현한 탓이에요. if나 플래그를 넣으며 코드를 돌렸겠죠. 프로그래밍은 흔히 탐험이에요. 실제 코드가 돌아갈때까지 이리저리 굴려보고 테스트 케이스가 통과하는 것을 보고 돌아간다는 사실을 깨달아요. 물론 이 방식이 틀리진 않아요. 사실상 대다수 상황에서는 원하는 대로 함수를 돌리는 유일한 방법이기도해요. 하지만 구현이 끝났다고 선언하기 전에 작성자가 알고리즘이 올바르다는 사실을 알고 기능이 뻔히 보일정도로 이해해서 깔끔하고 명확하게 재구성하는게 최고의 방법이에요.
논리적 의존성은 물리적으로 드러내라
한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 있어야 해요.논리적인 의존성만으로는 부족해요. 이는 외존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면 안 된다는 거에요. 의존하는 모든 정보를 명시적으로 요청하는편이 좋아요. 예를 들면 HourlyReporter와 HourlyReporterFormatter가 있다고 해봐요. 페이지 크기(PAGE_SIZE)라는 상수가 있고 55의 값을 가진다고 할 때, HourlyReporter에서 페이지를 가지고 그냥 출력한다면 HourlyReporterFormatter가 55 페이지를 처리할 수 있는지 모른채 출력해요. 그래서 Formatter.getMaxPageSize()와 같은 함수를 호출하여 비교해서 호출해야 해요. (예시가 뭔가 이상한데 이미 HourlyReporter가 Formatter가 55의 값을 처리할 수 있음을 알고 있어서 Reporter에 그 상수가 선언 되었다는 뜻)
If/Else나 Switch/Case 문보다 다형성을 사용해라
이건 하두 많이 이야기 해서 알거에요. 대부분의 개발자는 올바르기 보다는 당장 손쉬운 선택으로 switch 문을 쓰고, 실제로 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물어요. 그래서 저자는 선택 유형 하나에는 switch 문을 한번만 사용한다는 원칙이 있데요. 그 한번이 어느 코드에 쓰일지 머리에 당연히 그려지시겠죠!?
표준 표기법을 따르라
팀이 정한 표준은 팀원들 모두가 따라야해요. 실제 괄호를 넣는 위치가 중요하기 보다는, 모두가 동의한 위치에 넣는다는 사실이 중요해요.
매직 숫자는 명명된 상수로 교체하라
아까 숫자 55에 대해 LINES_PER_PAGE라고 하면 쪽 당 55줄을 인쇄한다는 의미가 되어 이해하기 쉬워져요. 하지만 코드 자체가 자명하다면, 상수 뒤로 숨길 필요가 없어요. 예를 들면 하루 일하는 시간이 8시간이라 WORK_HOURS_PER_DAY는 상수를 이용하면 법이나 관례가 바뀔지도 모르니까 좋을 수도 있어요. 하지만 한편으로 8이 들어간 공식이 깔끔해서 굳이 18자의 글자를 넣기가 꺼려져요. 아니면 5280.0 이라는 숫자는 FEER_PER_MILE로 누구나 아는 숫자(미국에서)라서 숫자만으로도 독자가 알아봐요. 또 3.141592...같은 숫자도 파이임을 금방 알아봐요. 하지만 그냥 숫자로 두기에는 오류 발생 가능성도 있고 근사값 등으로 오차가 발생해요.
'매직 숫자'라는 용어는 단지 숫자만 의미하지 않아요. 의미가 분명하지 않은 토큰 모두 가리켜요. 예를 들면 assertEquals(7777, Employee.find("John").employeeNumber()); 같은 함수에서 7777과 "John"은 매직 숫자에요. 의미가 분명하지 않고요. 그런데 위 코드는 팀원 모두가 항상 사용하는 데이터베이스의 어떤 정보라면 그거에 맞게 해석이 돼요.
정확하라
검색 결과 중 첫 번째 결과만 유일한 결과로 간주하는 행동은 순진해요. 부동소수점으로 통화를 표현하는 행동은 범죄에요. 비슷하게 갱신 가능성이 희박하다고 잠금과 트랜잭션 관리를 건너뛰는 행동은 게으름이에요. List로 선언할 변수를 ArrayList로 선언하는 행동은 지낯니 제약이에요. 모든 변수를 protected로 선언한 코드는 무절제해요.
코드에서 뭔가를 결정할 때는 정확히 결정하세요. 결정을 내리는 이유와 예외를 처리할 방법을 분명히 알아야해요.
관례보다 구조를 사용하라
설계 결정을 강제할 때는 규칙보다 관례를 사용해요. 명명 관례도 좋지만 구조 자체로 강제하면 더 좋아요. 예를 들면 switch/case보다는 추상 메서드가 있는 기초 클래스가 더 좋아요. switch/case 문을 매번 똑같이 구현하게 강제하기는 어렵지만, 파생 클래스는 추상 메서드를 모두 구현하지 않으면 안돼요.
조건을 캡슐화하라
조건의 의도를 분명히 밝히는 함수로 표현하세요
if (shouldBeDeleted(timer)) // 잘 추상화 됨if (timer.hasExpired() && !timer.isRecurrent())부정 조건은 피하라
부정 조건은 긍정보다 어려우니 피하세요. if (!isTrue) 피하기
함수는 한 가지만 해야한다.
함수를 짜다보면 한 함수안에 여러 단락을 이어, 일련의 작업을 수행하고 싶을 때가 있어요. 이런 함수는 한 가지만 수행하는 함수가 아니니 좀 더 작은 함수로 쪼개세요.
public void pay() { for (Employee e : employees) { if (e.isPayday()) { Money pay = e.calculatePay(); e.deliverPay(pay); } } }// 개선 public void pay() { for (Employee e : employees) payIfNecessary(e); } private void payIfNecessary(Employee e) { if (e.isPayday()) calculateAndDeliverPay(e); } private void calculateAndDeliverPay(Employee e) { Money pay = e.calculatePay(); e.deliverPay(pay); }숨겨진 시간적인 결합
때로는 시간적인 결합이 필요해요. 그리고 이 결합을 숨기면 안되요. 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러내세요. 아래 예시를 보면 각 함수는 순서대로 호출해야해요. 왜냐하면 각 함수의 결과를 다음 함수에서 사용하기 때문이에요. 그래서 순서를 못바꿔요. 그래서 아래와 같이 개선을 할 수 있어요. 더 복잡해졌다고 할 수 있어요. 하지만 추가된 복잡성이 원래 있던 시간적인 복잡성을 드러내고 있어요. 인스턴스 변수를 그대로 두었는데, 해당 클래스의 private 메서드에 필요한 변수일지도 몰라서 그래요. 아무튼 제자리를 찾은 변수들이 시간적인 결합을 더 명백히 드러내요.
public class MoogDiver { Gradient gradient; List<Spline> splines; public void dive(String reason) { saturateGradient(); reticulateSplines(); diveForMoog(reason); } ...}// 개선 후public class MoogDiver { Gradient gradient; List<Spline> splines; public void dive(String reason) { Gradient gradient = saturateGradient(); List<Spline> splines = reticulateSplines(gradient); diveForMoog(splines, reason); } ...}일관성을 유지하라
코드 구조를 잡을 때는 이유를 고민하고 코드 구조로 명백히 표현하세요. 구조에 일관성이 없으면 남들이 맘대로 바꿔도 괜찮다고 생각하지만 시스템 전반에 걸쳐 구조가 일관성이 있으면 남들도 일관성을 따르고 보존해요.
경계 조건을 캡슐화하라
경계 조건은 빼먹거나 놓치기 쉬워요. 경계 조건은 한 곳에서 별도로 처리하세요. 여기저기에서 처리하지 마세요. 다음은 경계 조건을 변수로 캡슐화하는 거에요.
if(level + 1 < tags.length){ parts = new Parse(body, tags, level + 1, offset + endTag); body = null;}// 개선int nextLevel = level + 1; // 캡슐화if(nextLevel < tags.length){ parts = new Parse(body, tags, nextLevel, offset + endTag); body = null;}함수는 추상화 수준을 한 단계만 내려가야 한다.
함수 내 모든 문장은 추상화 수준이 동일해야 해요. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 해요. 이 장에서 설명하는 휴리스틱 중 가장 이해하기 어렵고 따르기도 어려운 항목이라 짐작해요. 개념은 아주 간단하지만 인간은 추상화 수준을 뒤섞는 능력이 너무 뛰어나요.
설정 정보는 최상위 단계에 둬라
추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨기지 마세요. 만약 고차원 함수에서 저차원 함수를 호출하면 그 때 인수로 넘기세요. 그래야 저수준을 뒤질 필요없이 찾기도 쉽고 변경하기도 쉬워요.
추이적 탐색을 피하라
보통 한 모듈은 주변 모듈을 모를수록 좋아요. A가 B를 사용하고 B가 C를 사용해도 A가 C를 알아야 할 필요는 없어요. 이를 디미터 법칙이라고 불러요.
긴 import 목록을 피하고 와일드 카드를 사용하라
패키지에서 클래스를 둘 이상 사용하면 와일드카드를 사용해 패키지 전체를 가져오세요. import package.*; (흠.. 논란있을듯? 명시적으로 어떤 패키지 가져오는지 아는게 더 중요하지 않을까..? 너무 많다면 또 모르겠지만)
상수는 상속하지 않는다.
남의 코드가 상수를 인터페이스에 넣어 상속해서 그 상수를 사용한다고 생각해보세요. 어디서 왔는지 찾으러 가야하는데 벌써 짜증이 나나요?
상수 대 Enum
자바 5부터 enum을 제공해요. public static final int라는 기교를 더이상 사용할 필요가 없어요. int는 코드에서 의미를 잃어버리기도 해요. 하지만 enum은 이름이 부여된 열거체에 속해서 그렇지 않아요. enum은 메서드와 필드도 사용할 수 있어 int보다 더 유연하고 서술적인 강력한 도구에요.
public class HourlyEmployee extends Employee { private int tenthsWorked;HourlyPayGrade grade; public Money calculatePay() { int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK); int overTime = tenthsWorked - straightTime; return new Money(grade.rate() * (tenthsWorked + OVERTIME_RATE * overTime) ); } ...}public enum HourlyPayGrade { APPRENTICE { public double rate() { return 1.0; } }, LEUTENANT_JOURNEYMAN { public double rate() { return 1.2; } }, JOURNEYMAN { public double rate() { return 1.5; } }, MASTER { public double rate() { return 2.0; } }; public abstract double rate();}서술적인 이름을 사용하라
소프트웨어 가독성의 90%는 이름이 결정해요. 그래서 시간을 들여 현명한 이름을 선택하고 유효한 상태로 유지하세요.
적절한 추상화 수준에서 이름을 선택하라
구현을 드러내는 이름은 피하세요. 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영해 이름을 선택하세요.
가능하다면 표준 명명법을 사용해라
기존 명명법을 사용하는 이름은 이해하기 더 쉬워요. 예를 들면 Decorator 패턴을 사용하면 장식하는 클래스명에 Decorator라는 단어를 사용하거나, 자바에서 객체를 문자열로 변환하는 함수는 toString이라는 이름을 사용해요. 이런 경우에는 관례를 따르는 것도 좋아요. 그리고 팀마자 특정 프로젝트에 적용할 표준명으로 유비쿼터스 언어를 고안하기도 해요.
명확한 이름
함수나 변수의 목적을 명확히 밝히는 이름을 선택하세요.
긴 범위는 긴 이름을 사용하라
이름 길이는 범위 길이에 비례해야해요. 범위가 작으면 짧고, 길면 긴 이름을 사용하세요. 아래 예시에요. 아무래도 범위가 길면 짧은 이름은 금방 머리에서 잊혀지겠죠? 하지만 짧으면 짧은 이름이라도 금방 알아채고요.
private void rollMany(int n, int pins){ for (int i=0; i<n; i++) g.roll(pins);}인코딩을 피하라
이름에 유형 정보나 범위 정보를 넣지 마세요. 예를 들면 m_, f_ 같은 접두어나 vis_같은 접두어를 붙여서 유형같은 거를 나타내느 거에요. 요즘은 IDE에서 타입 같은걸 잘 보여줘서 굳이 필요없어요.
이름으로 부수 효과를 설명하라
함수 변수 클래스가 하는 일을 모두 기술하는 이름을 사용하세요. 이름에 부수 효과를 숨기지 마세요. 여러 작업을 수행하는 함수에 동사 하나만 달랑 넣으면 곤란해요!!
불충분한 테스트
테스트 케이스는 몇 개나 만들어야 충분할까요? 대부분 "이 정도면 충분하지 않을까"라는 척도를 사용해요. 테스트 케이스는 잠재적으로 깨질만한 부분을 모두 테스트하세요.
커버리지 도구를 사용하라
커버리지 도구는 테스트가 빠뜨리는 공백을 알려줘요. 커버리지 도구를 활용하여 테스트 커버리지를 시각적으로 빠르게 확인해보세요.
사소한 테스트를 건너뛰지 마라
사소한 테스트는 짜기 쉬워요. 사소한 테스트가 제공하는 문서적 가치는 구현에드는 비용을 넘어요.
무시한 테스트는 모호함을 뜻한다
요구사항이 불분명하여 프로그램이 돌아가는 방식을 확신하기 어렵기도 해요. 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 @ignore로 표현해요. 모호함이 존재하는 테스트 케이스는 컴파일이 가능한지 불가능한지에 달려있어요.
경계 조건을 테스트하라
경계 조건에서 실수가 많으니 각별히 신경쓰세요.
버그 주변은 철저히 테스트하라
버그는 모이는 경향이 있어요. 한 함수에 버그가 발견되면 그 함수는 철저히 테스트하세요.
실패 패턴을 살펴라
때로는 테스트 케이스가 실패하는 패턴으로 문제 진단이 가능해요. 꼼꼼히 테스트 케이스를 짜라는 이유이기도해요. 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러내요.
테스트 커버리지 패턴을 살펴라
통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러나요.
테스트는 빨라야 한다.
느린 테스트 케이스는 실행하지 않게 되요. 그래서 촉박한 일정에서 제일 먼저 건너띄어요. 테스트 케이스가 빨리 돌아가게 최대한 노력하세요.
저자는 위 냄새 목록이 완전하다고 말하기는 어렵다고 해요. 네 실제로 프레임워크나 플랫폼마다 저마다 다른 고충이 있기도 하고, 세상이 변하면서 냄새의 종류가 달라지기도 하는 것 같아요. 그래서 이런 목록을 달달 외운다고 아무 쓰잘데기 없어요. 중요한 것은 본인이 엔지니어로서 생각하고 행동하고 가치를 만들어 내는 것이 아닐까요? 분리!