클린 코드 책을 읽으면서 해당 단원이 가장 쉬운 부분 같지만 잘 지키지 않고, 그리고 지키면 가장 효과가 가장 크다 생각한다.
아래 함수를 보면 이해가 잘 되는가? 해당 함수를 깨끗하게 만들어보자.
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();
}
함수를 작게 만들어라! & if 블록에는 한줄만!
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
훨씬 읽기가 좋아졌다. if 문안에서는 특정 행동을 할텐데 이 부분을 함수로 빼자. 그렇게 되면 코드를 읽을 때 훨씬 쉬워진다.
한 가지 일만 해라!
함수는 한 가지만의 일을 해야 한다. 그렇다면 그 한가지만의 일을 하는지 아닌지 판단은 어떻게 할 것인가?
- 맨 처음 예제 코드를 보면 추상화 수준이 둘이다. 밑에 코드처럼 축소할 수 있다면 한가지 일을 하고 있는 것이 아니다
함수당 추상화 수준은 하나로!
만약 한 함수내에 추상화 수준이 섞이게 되면 읽는 사람이 헷갈린다.
위에서 아래로 읽을 수 있도록 짜자
코드는 기사고 이야기이다. 그냥 읽는대로 읽혀야 깔끔한 코드다!
Switch 문
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);
}
}
해당 코드는 무슨 문제가 있을까?
- 함수가 길다! -> 직원 유형이 추가되면 더 길어진다.
- '한가지' 작업만 수행하지 않는다.
- SRP(single responsibility principle) 위반 -> 코드를 변경할 이유가 많다.
- OCP(open close principle) 위반 -> 직원 유형이 추가될 때마다 코드를 변경해야 한다.
- 가장 심각한 문제! 다른 종류의 함수 isPayDay(Employee e, Date date) 와 같은 것이 있으면 무한 반복이다...
해당 코드를 리팩토링하면 아래와 같이 짤 수 있다.
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
public 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);
}
}
}
switch 문은 다형적 객체를 생성하는 코드에서만 쓰고 웬만해서는 사용하지 말자!
서술적인 이름을 사용하라!
“코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다” - 워드
함수 인수 개수는 몇개가 적당한가?
이상적인 함수인수는 0개이고, 필요하다면 1개까지이다.
- 많이 쓰는 단항 형식
boolean fileExists(“MyFile”);
InputStream fileOpen(“MyFile”);
- 플래그 인수는 쓰지마라 - boolean값을 넘기는것 자체가 그 함수는 한번에 여러가지 일을 하는 것이다
- 이항 함수도 웬만하면 단항함수로 바꾸자!
Point p = new Point(x,y); // 이정도는 이해해줄 수 있다
writeField(outputStream, name)
outputStream.writeField(name) // 클래스 구성원 변수로 만들어버리자!
- 삼항 함수는 그냥 만들지 말자!
- 인수 객체가 2-3개 필요하다면 일부를 독자적인 클래스 변수로 선언하자
-> 단순히 눈속임이라고? 쓸데없는 짓이라고? 해당 리팩토링 덕분에 개발자는 개념을 이해하기 쉬워지고 실수가 줄어든다!
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
- 동사와 키워드
writeField(name); // 단항 함수는 인수가 동사,명사 쌍을 이뤄야 한다.
assertExpectedEqualsActual(expected, actual); // 함수 이름에 키워드를 넣으면 순서를 기억할 필요가 없다
부수 효과를 일으키지 말자!
검증을 하겠다는 함수에서 남몰래 Session.initialize()를 하고 있다! 이런 함수명과 맞지 않는 부수효과를 내서는 안된다!
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;
}
}
출력인수는 피하자!
함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 사용하자!
오류코드보다 예외를 사용하라!
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;
}
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());
}
'Clean code' 카테고리의 다른 글
Clean Code - 13장. 동시성 (0) | 2020.01.12 |
---|---|
Clean Code 4장 - 주석 (0) | 2020.01.07 |
Clean Code 5장 - 형식 맞추기 (0) | 2020.01.07 |
리팩토링 - 12장. 복합 리팩토링 ( 절차코드를 객체로 전환 ) (0) | 2020.01.05 |
Clean Code 9장 - 단위테스트 (0) | 2018.09.18 |