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();
}
를 더 줄인 코드가 아래 코드.
아래 코드는 더 이상 나누기 힘듦
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
책에서는 함수를 작게 만들어야 하는 이유에 대해 구체적인 근거는 없다고 한다.
경험적으로 봤을 때 하나의 함수에 여러 가지 추상화 수준, 여러가지 의미가 있는 것보다는
하나의 의미만 존재하는 것이 함수를 이해하기 쉽게 만든다고 생각.
내려가기 규칙
'코드는 위에서 아래로 이야기처럼 읽혀야 좋다'
...
public static String render(PageData pageData) throws Exception {
return render(pageData, false);
}
public static String render(PageData pageData, boolean isSuite) throws Exception {
return new SetupTeardownIncluder(pageData).render(isSuite);
}
...
switch 문은 본질적으로 (한 가지가 아닌) 여러 가지를 처리하고 작게 만들기도 어렵다.
func calulatePay(e: Employee) -> Money {
switch e.type {
case COMMISSIONED:
return calulateCommissionedPay(e)
case HOURLY:
return calculateHourlyPay(e)
case SALARIED:
return calculateSalariedPay(e)
}
}
이러한 switch 문을 완전히 피할 방법은 없다.
하지만, 이를 개선할 방법은 존재한다.
protocol Employee {
func isPayday() -> Bool
func calculatePay() -> Money
func deliverPay(pay: Money)
}
protocol Employee {
func makeEmployee(r: EmployeeRecord) -> Employee
}
class EmployeeFactory implements EmployeeFactory {
func makeEmployee(r: EmployeeRecord) {
switch r.type {
case COMMISSIONED:
return calulateCommissionedPay(r)
case HOURLY:
return calculateHourlyPay(r)
case SALARIED:
return calculateSalariedPay(r)
}
}
}
무언가에 s 를 바닥글로 첨부
vs s에 바닥글을 첨부
객체의 역할면에서 봤을 때도 s 에 Footer 를 추가하는건 s 의 책임이지, s 를 사용하는 상위 모듈이 맡을 책임이 아니라고 생각
재사용 측면에서도 s.appendFooter() 가 더 적절. (p.56)
func makeCircle(x: Double, y: Double, radius: Double)
func makeCircle(center: Point, radius: Double)
swift에서는 func write(field: name)
class UserValidator {
private let cryptographer: Cryptographer
func checkPassword(userName: String, password: String) -> Bool {
let user = UserGateway.findByName(userName)
if user != nil {
let codedPhrase = user.getPhraseEncodedByPassword()
let phrase = cryptographer.decrypt(codedPhrase, password)
if phrase == "Valid Password" {
Session.initialize()
return true
}
}
return false
}
}
func set(_ attribute: String, _ value: String) -> Bool
if set("username", "unclebob") { }
if attributeExists("username") {
setAttribute("username", "unclebob")
...
}
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
}
try {
deletePage(page)
registry.deleteReference(page.name)
configKeys.deleteKey(page.name.makeKey())
} catch {
// 예외 처리
}
func delete(page: Page) {
try {
deletePageAndAllReferences(page)
} catch {
// 예외 처리
}
}
'어쩌면 중복은 소프트웨어에서 모든 악의 근원이다'
중복을 없애기 위한 성급한 추상화는 주의해야 할 것
글짓기와 비슷하다.
- 처음에는 길고 복잡하다. 인수 목록도 길고 이름도 즉흥적이고 코드도 중복된다.
- 하지만 그 서투른 코드를 빠짐없이 테스트하는 단위 테스트 케이스를 만든다.
- 코드를 다듬고, 함수를 만들고, 중복을 제거하는 등의 리팩토링을 진행하는 중에도 코드는 항상 단위테스트를 통과한다.
- 처음부터 완벽한 함수를 짜는 건 불가능하다.
'프로그래밍 기술은 언제나 언어 설계의 기술'
대가 프로그래머는 시스템을 구현할 프로그램이 아니라 풀어갈 이야기로 여긴다.
이 장에서는 함수를 잘 만드는 기교를 소개했다. 길이가 짧고 이름이 좋고, 체계가 잡힌 함수를 위해.
하지만 진짜 목표는 시스템이라는 이야기를 풀어나가는 데 있다는 사실을 명심
함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아 떨어져야 이야기를 풀어나가기 쉬워질 것.
함수명만 보고서는 정확한 의도를 알 수 없는, 나만 아는 코드를 많이 짰던 것 같다.
이제부터 함수명과 파라미터만 봐도 의도가 좀 더 명확히 보이는 함수를 작성하기 위해 노력해야겠다.
기존에 함수를 나누는 이유에 대해 중복을 제거하기 위함이라고만 생각해왔는데,
추상화 수준을 일정케하여 의도를 명확하게 보여주고자 하는 것(가독성)도 있다는 사실을 깨달았다.