Introduction
function testTableHtml(PageData: PageData, includeSuiteSetup: boolean) {
let wikiPage: WikiPage = pageData.getWikiPage();
let stringBuffer: string = "";
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetUp) {
let suiteSetup: WikiPage = PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup !== null) {
let pagePath: WikiPagePath =
suiteSetup.getPageCrawler().getFullPath(suiteSetup);
let pagePathName: string = PathParser.render(pagePath);
stringBuffer = "!include -setup ." + pagePathName + "\\n";
}
}
let setUp: WikiPage = PageCrawlerImpl.getInheritedPage("setUp", wikiPage);
if (setUp !== null) {
let setUpPath: WikiPagePath = wikiPage.getPageCrawler().getFullPath(setUp);
let setUpPathName: string = PathParser.render(setUpPath);
stringBuffer += ("!include -setup ." + setUpPathName + "\\n";
}
}
stringBuffer += pageData.getContent();
if (pageData.hasAttribute("Test") {
let tearDown: WikiPage = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (tearDown !== null) {
let tearDownPath: WikiPageDown = wikiPage.getPageCrawler().getFullPath(tearDown);
let tearDownPathName: string = PathParser.render(tearDownPath);
stringBuffer += ("!include -setup ." + tearDownPathName + "\\n");
}
if (includeSuitSetup) {
let suiteTearDown: WikiPage = PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_TEARDOWN_NAME,
wikiPage
);
if (suiteTearDown !== null) {
let pagePath: WikiPagePath = suiteTearDown.getPageCrawler().getFullPath(suiteTearDown);
let pagePathName: string = PathParser.render(pagePath);
stringBuffer += ("!include -setup ." + pagePathName + "\\n");
}
}
}
pateData.setContent(buffer.toString());
return pageData.getHtml();
}
- Java의 오픈 소스 테스트 도구 라이브러리인 FitNesse에 포함된 함수
- 설정(setup) 페이지와 해제(teardown) 페이지를 테스트 페이지에 넣은 후, 해당 페이지를 HTML로 렌더링
refactoring
function renderPageWithSetUpsAndTearDowns(pageData: PageData, isSuite: boolean) {
let isTestPage: boolean = pageData.hasAttribute("Test");
if (isTestPage) {
let testPage: WikiPage = pageData.getWikiPage();
let newPageContent: string = "";
includeSetUpPages(testPage, newPageContent, isSuite);
newPageContent.concat(pageData.getContent());
includeTearDownPages(testPage, newPageContent, isSuite);
pageData.setContent(newPageContent);
}
return pageData.getHtml();
}
- 리팩토링한 코드가 읽기 쉽고 이해하기 쉬운 이유는?
- 직관적인 함수명, 변수명. isTestPage, includeSetUpPages, includeTearDownPages
- 적절한 추상화
- 실행 플로우를 파악하기 쉬움 - 케이스에 따른 실행될 코드와 실행되지 않을 코드
작게 만들어라
- 필자의 경험에 따른 결론: 어찌됐든 작은 함수가 좋다.
- 얼마나 작아야 좋을까?
- 한 함수는 오직 하나의 일만 할 만큼 작아야 한다.
- 2~4줄이 적당하다.
- 중첩 구조가 생기지 않아야 한다.
한 가지만 해라
- 한 가지만 하고, 그 한 가지를 잘 하도록 해라.
- 이 함수가 하나의 일만 하고 있는지 어떻게 아는가?
- 하나의 일만 하는 함수는 함수 내 코드의 추상화 수준이 하나다.
- 하나의 일만 하는 함수는 단순히 다른 이름이 아닌, 의미 있는 다른 이름으로 다른 함수를 추출해낼 수 없다.
부수 효과가 없도록 해라.
- 어떤 함수가 원래 의도와는 다른 일을 야기하는 경우다.
- ‘한 함수는 하나의 일만 하라’는 규칙을 지키지 않았을 때 생기는 일이다.
- 함수형 프로그래밍은 부수 효과를 일으키지 않는다.
switch 문을 깨끗하게 만들어보자
public Money calculatePay(Employee e) throws InvalidExployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidExployeeType(e.type);
}
}
리팩토링 전에는 직원 유형마다 함수를 하나씩 만들어 관리했다.
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
public interface EmployeeFactory {
public Employee makeEmployee(EmployeedRecord r) throws InvalidEmployeeType;
}
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployyee(r);
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmployee(r);
default:
thorw new InvalidEmployeeType(r.type);
}
}
}
리팩토링 과정에서 다음과 같은 변화가 있었다.
- 추상 Factory에 switch문을 숨겼다.
- 내부 switch문에서는 type 속성에 따라 Employee 추상 클래스를 상속한 자식 클래스를 생성하여 반환하도록 했다.
- 이제 다형성을 이용하여 코드의 중복을 피하면서 각 파생 클래스에서 재정의된 메서드가 실행될 수 있다.
Employee em = makeEmployee(record);
// em의 타입은 Employee 추상 클래스를 구현한
// CommissionedEmployee, HourlyEmployee, SalariedEmployee 중 하나
em.isPayday() // 타입에 따라 재정의된 함수 호출 가능
서술적인 이름을 사용해라
- 가독성을 높여 다른 사람이 코드를 이해하는 데 들이는 시간을 최소화할 수 있다.
- 주석 없이도 함수의 역할을 짐작할 수 있도록 지어야 한다.
- foo(), bar(), tmpFunc() - X / makeWordVectors(), cleanSession() - O
- 이름은 서술적이면서도 일관성이 있어야 한다.
- create_index_to_word_dict(), update_word_to_index_dict()
- 이름 안의 여러 단어가 쉽게 읽혀야 한다.
- Camel case, Snake case - 보통 언어에 따라 갈린다.
- 길어도 괜찮다. (진짜?)
함수의 인수는 적을수록 좋다
- 인수가 많으면 어떻게 되는가?
- 개념을 이해하기 어렵다.
- 인수가 들어가야 할 순서를 외워야 한다.
- 모든 경우를 테스트하기 어렵다.
- 인수는 아예 받지 않거나, 받아도 1개만 받는 것이 바람직하다.
- 궁금증: 인수가 없거나 적으면 함수를 재사용하기 어렵지 않을까?
- 애초에 한 가지 일만 잘 하는 함수들은 복잡하지 않아서 인수를 많이 받지 않아도 되겠다.
- 2개 이상의 인수를 받는 함수들은 클래스의 구성 메서드로 만들어 사용할 수 있는 가능성이 있다.