// Before
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
WikiPage testPage = pageData.getWikiPage();
StringBuffer newPageContent = new StringBuffer();
includeSetupPages(testPage, newPageContent, isSuite);
newPageContent.append(pageData.getContent());
includeTeardownPages(testPage, newPageContent, isSuite);
pageData.setContent(newPageContent.toString());
}
return pageData.getHtml();
}
// After
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData)) {
includeSetupAndTeardownPages(pageData, isSuite);
}
return pageData.getHtml();
}
함수는 한가지를 해야 한다. 그 한가지를 잘 해야 한다. 그 한가지만을 해야 한다.
지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 하는 것이다.
단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출 할 수 있다면 해당 함수는 여러작업을 하는것이다.
추상화 : 복잡한 자료, 모듈, 시스템등으로 부터 핵심적인 개념 또는 기능을 간추려 내는 것
// Bad
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
// Good
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
-----------------
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployee(r) ;
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmploye(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}
“코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다” - 워드 커닝햄(Ward Cunningham)
- 함수가 작고 단순할수록 서술적인 이름을 고르기 쉬워진다.
- 겁먹을 필요없다, 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
- 이름을 정하느라 시간을 들여도 괜찮다. 이런저런 이름을 넣어 코드를 읽어보면 더 좋다.
- 서술적인 이름을 사용하면 머릿속에서 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.
- 일관성 있게 이름을 붙여야 한다.
인수에 질문을 던지는 경우
인수를 뭔가로 변환해 결과를 변환하는 경우
이벤트 함수일 경우 (이 경우에는 이벤트라는 사실이 코드에 명확하게 드러나야 한다.)
위의 예가 아니라면 단항함수는 가급적 피한다.
예시 1, 입력 인수를 변환하는 함수라면 변환결과는 반환값으로 돌려준다. 출력인수 사용 X.
// Bad
void transform(StringBuffer out);
// Good
StringBuffer transform(StringBuffer in);
이항 함수보다 이해하기가 훨씬 어렵다.
위험도 2배 이상 늘어난다. 삼항 함수를 만들 때는 신중히 고려하라.
예시 1, 인수가 많아질 수록 안좋다.
assertEquals(expected, actual);
assertEquals(message, expected, actual)
인수가 많이 필요할 경우, 일부 인수를 독자적인 클래스 변수로 선언할 가능성을 살펴보자.
객체를 생성해 인수를 줄이는 방법이 눈속임이라 여결지 모르지만 그렇지 않다. 개념을 표현할 수 있게 해준다.
예시 1
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
때로는 인수 개수가 가변적인 함수도 필요하다. String.format 메서드가 좋은 예.
가변 인수 전보를 동등하게 취급하면 List 형 인수 하나로 취급할 수 있다.
예시 1, String.format 은 사실상 이항 함수다.
public String format(String format, Object... args);
public class UserValidator {
private Cryptographer cryptographer;
public boolean checkPassword(String userName, String password) {
User user = UserGateway.findByName(userName);
if (user != User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword();
String phrase = cryptographer.decrypt(codedPhrase, password);
if ("Valid Password".equals(phrase)) {
Session.initialize();
return true;
}
}
return false;
}
}
// Bad
appendFooter(report);
// Good
report.appendFooter();
✍️ 명령과 조회를 분리하라!
-------------
* 함수는 뭔가 객체 상태를 변경하거나, 객체 정보를 반환하거나 둘 중 하나다. 둘 다 하면 안된다.
* public boolean set(String attribute, String value); 같은 경우에는 속성 값 설정 성공 시 true 를 반환하므로 괴상한 코드다.
* 예시 1
// Bad
if (set("username", "unclebob"))...
// Good
if (attributeExists("username")) {
setAttribute("username", "unclebob");
}
// Bad
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
logger.log("page deleted");
} else {
logger.log("configKey not deleted");
}
} else {
logger.log("deleteReference from registry failed");
}
} else {
logger.log("delete failed"); return E_ERROR;
}
// Good
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logError(e);
}
// Better
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
} catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
public enum Error {
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES,
WAITING_FOR_EVENT;
}