1 of 19

2. 함수�

2 of 19

FitNesses(오픈소스 테스트 도구)�

public static String testableHtml(

PageData pageData,

boolean includeSuiteSetup

) throws Exception {

WikiPage wikiPage=pageData.getWikiPage();

StringBuffer buffer = new StringBuffer();

if (pageData.hasAttribute("Test")) {

if (includeSuiteSetup) {

WikiPage suiteSetup =

PageCrawlerImpl.getInheritedPage(

SuiteResponder.SUITE_SETUP_NAME, wikiPage

);

if (suiteSetup != null) {

WikiPagePath pagePath =

suiteSetup.getPageCrawler().getFullPath(suiteSetup);

String pagePathName=PathParser.render(pagePath); buffer.append("! include -setup .")

.append(pagePathName)

.append("\n");

}

}

WikiPage setup =

PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);

if (setup != null) {

WikipagePath setupPath=

wikipage.getPageCrawler().getFullPath(setup);

String setupPathlame=PathParser.render(setupPath); buffer.append("!include -setup .")

.append(setupPathName)

.append("\n");

}

}

buffer.append(pageData.getContent());

if (pageData.hasAttribute("Test")) {

WikiPage teardown =

PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);

if (teardown != null) {

}

WikiPagePath tearDownPath =

wikiPage.getPageCrawler().getFullPath(teardown);

String tearDown PathName = PathParser.render(tearDownPath); buffer.append("\n")

.append("! include -teardown .")

.append(tearDownPathName)

.append("\n");

if (includeSuiteSetup) {

WikiPage suiteTeardown =

PageCrawlerImpl.getInheritedPage(

SuiteResponder.SUITE_TEARDOWN_NAME,

wikiPage

);

if (suiteTeardown != null) {

WikiPagePath pagePath =

suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);

String pagePathName = PathParser.render(pagePath);

buffer.append("! include -teardown.")

.append(pagePathName)

.append("\n");

}

}

}

pageData.setContent (buffer.toString());

return pageData.getHtml();

}

3 of 19

리팩터링

  • 함수가 설정 setup 페이지와 해제(teardown) 페이지를 테스트 페이지에 넣은 후 해당 테스트 페이지를 HTML로 렌더링

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();

}

4 of 19

작게 만들어라!

  • 함수를 만드는 첫 번째 규칙은 "작게!"
  • 함수를 만드는 두 번째 규칙은 "더 작게!"
  • 작은 함수가 좋다는 근거는 제시하기 어렵지만, 저자는 40년 동안 다양한 크기의 함수를 구현한 경험을 토대로 작은 함수가 좋다고 확신한다.
  • 과거에는 함수가 한 화면을 넘어가면 안 된다는 규칙이 있었으나, 현재는 가로 150자를 넘어서는 안 되며, 함수는 100줄을 넘어서는 안 된다.

5 of 19

블록과 들여쓰기

  • if문/else 문/while 문 등에 들어가는 블록은 한 줄이어야 한다.
  • 함수에서 들여쓰기 수준은 1단이나 2단을 넘어서면 안 된다.

6 of 19

한 가지만 해라!

  • 함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.
  • 한가지의 기준은 하나의 추상화 수준에서 동작하는지 봐야 한다.
  • 우리가 함수를 만드는 이유는 큰 개념을 다시 말해, 함수 이름을 다음 추상화 수준에서 여러 단계로 나눠 수행하기 위해서 이다.

7 of 19

함수 당 추상화 수준은 하나로!

  • 1번 코드의 getHtml(), String pagePathName = PathParser.render(pagepath);, .append("\n")의 코드 추상화 수준은 모두 각각 다르다.
  • 한 함수 내에 추상화 수준을 섞으면 코드를 읽는 사람이 헷갈린다.
  • 코드는 위에서 아래로 이야기처럼 읽히도록 작성한다.
    • 함수 추상화 수준이 아래로 내려가면서 낮아지도록 구현한다. 

8 of 19

Switch 문

Payroll.java

public Money calculatePay (Employee e)

throws InvalidEmployeeType {

switch(e.type){

case COMMISSIONED:

return calculateCommissioned Pay(e);

case HOURLY:

return calculateHourlyPay(e);

case SALARIED:

return calculateSalaried Pay(e);

default:

throw new InvalidEmployeeType(e.type);

}

}

9 of 19

Switch 문

  • 함수가 길다. 새 직원 유형을 추가할 때마다 길어진다. 
  • '한 가지' 작업만 수행하지 않는다. 
  • SRP(Single Responsibility Principle)를 위반한다. 코드를 변경할 이유가 여럿이다. 
  • OCP(Open Closed Principle)를 위반한다.

10 of 19

서술적인 이름을 사용하라! 

  • 함수 이름을 서술적으로 정하는 것이 좋다.
  • 좋은 이름을 고르면 코드가 더욱 깨끗해지고, 개선하기 쉬워진다.
  • 함수가 작고 단순할수록 서술적인 이름을 고르기 쉬워진다.
  • 이름을 정할 때는 여러 단어를 사용해 함수 기능을 잘 표현하는 것이 좋다.
  • 일관성 있는 이름을 사용해야 한다.
  • 코드를 읽어보면서 여러 개의 이름을 시도하고, 최대한 서술적인 이름을 골라야 한다.
  • 이름을 붙일 때는 문체가 비슷하면 이야기를 순차적으로 풀어가기 쉬워진다.

11 of 19

함수 인수

  • 함수의 이상적인 인수 개수는 0개(무항)이다.
  • 인수는 개념을 이해하기 어렵게 만든다.
  • 인수 개수가 많을수록 코드를 읽는 사람과 테스트하는 것이 어렵다.
  • 출력 인수는 입력 인수보다 이해하기 어렵다.
  • 함수에서 입력 인수가 없는 경우가 가장 이상적이며, 1개일 때도 괜찮다.
  • 함수 이름과 인수 사이에 추상화 수준이 다를 때는 인스턴스 변수 대신 함수 인수를 사용하는 것이 코드를 이해하기 쉽게 만들어준다.

12 of 19

함수 인수

  • "많이 쓰는 단항 형식" : 
  • 함수가 하나의 인수를 넘기는 경우이며 가장 많이 사용된다. 입력 인수를 변환하는 함수라면 변환 결과는 반환값으로 돌려준다. 인수를 입력과 출력으로 사용하지 않는다.
  • "플래그 인수" : 
  • 함수가 여러 가지 동작 모드를 가질 수 있을 때 사용되므로 사용을 하지 말아야 한다. 
  • "이항 함수" : 
  • 두 개의 인수를 받아서 처리하기 때문에 단항 형식보다 이해하기 어렵다. 인수의 순서를 헤깔려 하는 개발자도 있다. 불가피한 경우가 아니면 가급적이면 단항 형식으로 바꾸는 것이 좋다.

13 of 19

함수 인수

  • "삼항 함수" : 
  • 이항 함수보다도 더 어려우므로 신중히 고려하여 결정해야한다. 
  • "인수객체" : 
  • 함수에 인수가 많아지면 개념이 포함된 신규 객체를 생성해서 인수객체를 넘기는 방법이 이해하기 더 쉽다. 
  • "동사와 키워드": 
  • 함수와 인수가 동사/명사 쌍을 이루도록 작성한다.
  • write(name)-> write 동사 / name 명사 =더 좋은 이름=> writeField(name) -> name은 field라고 알 수 있음
  • 함수 이름에 인수 이름 추가: assertEquels ->assertExpectedEqualsActual(expected, actual) 로 지으면 인수 순서를 기억하지 않아도 함수 이름에 나타난다.
  • "출력 인수": 
  • 함수의 인수는 일반적으로 입력으로 사용되지만, 출력으로 사용되는 경우도 있다. 출력 인수를 사용하는 함수는 코드를 읽기 어렵게 만들기 때문에 객체 지향 프로그래밍에서는 this 변수를 사용하여 출력 인수를 대체하는 것이 좋다. 함수에서 상태를 변경해야 하는 경우에는 함수가 속한 객체의 상태를 변경하는 방식을 사용하는 것이 좋다.

14 of 19

명령과 조회를 분리하라!

  • 함수는 뭔가를 수행하거나 뭔가에 대한 답변을 제공하거나, 둘 중 하나만 해야 한다.
  • 둘 다 하는 경우 혼란을 초래할 수 있다.
  • 예를 들어, set 함수는 속성을 설정하고 설정이 성공하면 true를, 실패하면 false를 반환
  • 이 함수는 이름이 attribute인 속성을 찾아 값을 value로 설정한다.
  • 그러나 이 함수를 호출하는 코드만 봐서는 의미가 모호하기 때문에, 명령과 조회를 분리하는 것이 좋다.
  • 이를 위해 attributeExists 함수를 사용하여 속성이 존재하는지 확인한 후 setAttribute 함수를 사용하여 속성 값을 설정할 수 있다.�

15 of 19

오류 코드보다 예외를 사용하라!

  • 오류 코드 대신 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다
  • Try/catch 블록은 원래 추하다. 코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섞는다. 그러므로 try/catch 블록을 별도 함수로 뽑아내는 편이 좋다.
  • 오류 처리도 한 가지 작업이므로 오류처리 함수는 오류만 처리해야한다.
  • 오류 코드를 사용하면 오류 코드의 변경시 모든 클래스를 재 컴파일 해야한다. 그러나 예외(Exception 클래스)를 사용하면 재컴파일/재배치 없이도 가능하다. 

16 of 19

반복하지 마라!

  • 중복 코드는 유지보수를 어렵게 하고 모든 악의 근원이다 중복을 제거해야한다.
  • 코드 가독성도 높여준다.

17 of 19

구조적 프로그래밍

  • 데이크스트라의 구조적 프로그래밍 원칙은 모든 함수와 함수 내 모든 블록에 입구(entry)와 출구(exit)가 하나만 존재해야 한다는 것이다.
  • 함수는 return 문이 하나여야 한다는 규칙이 있으며, 루프 안에서는 break나 continue를 사용해서는 안 된다. 또한 goto문은 결코 사용해서는 안 된다.
  • 함수가 작을 때는 위 규칙이 별 이익을 제공하지 않으며, 함수가 아주 클 때만 상당한 이익을 제공한다.
  • 함수를 작게 만들면 return, break, continue를 여러 차례 사용해도 괜찮다. 오히려 때로는 단일 입/출구 규칙(single entry-exit rule)보다 의도를 표현하기 쉬워진다.
  • 하지만 goto문은 큰 함수에서만 의미가 있으므로 작은 함수에서는 피해야 한다.

18 of 19

함수를 어떻게 짜죠?

  • 함수 작성은 글쓰기와 유사하다. 초안은 서툴고 어수선하며, 나중에 다듬어진다.
  • 코드를 테스트하는 단위 테스트 케이스를 만들고, 코드를 다듬고, 이름을 바꾸고, 중복을 제거한다.
  • 최종 함수는 이 장에서 설명한 규칙을 따른다.
  • 처음부터 완벽한 함수를 작성하는 것은 불가능하며, 함수를 다듬어가는 과정이 필요하다.

19 of 19

결론

  • 모든 시스템은 DSL로 만들어진다.
  • 함수는 DSL에서 동사, 클래스는 명사다.
  • 대가(master) 프로그래머는 시스템을 풀어갈 이야기로 여긴다.
  • 함수를 잘 만드는 기교를 소개하며, 여기서 소개한 규칙을 따른다면 짧고, 이름이 좋고, 체계가 잡힌 함수가 나오지만, 진짜 목표는 스템(이야기)을 풀어가는 것이다.
  • 작성하는 함수가 분명하고 정확한 언어로 깔끔하게 같이 맞아떨어져야 이야기를 풀어가기가 쉬워진다.