💡백명석 님의 클린 코더스 강의를 듣고 요약한 자료입니다.
목차
1, 2강 OOP
3, 4강 Function
5강 Function Structure
6강 Form
Function
함수는 한 가지 일만 해야 한다.
한 가지 일만 하는 함수를 만들기 위해서는 indentation, while, mested, if등이 없어야 한다.
잘 지어진 서술적인 긴 이름을 갖는 많은/작은 함수들로 유지해야 한다.
The First Rule of Functions
- 함수는 더 이상 작을 수 없을 만큼 작아야 한다.
따라서 큰 함수를 보면 클래스로 추출할 생각을 해야 한다.
- 블록이 적어야 한다.--> 이 말은 블록 안의 또다른 블록은 계속 함수로 추출해야 한다는 뜻.
- if-else, while등 내부 블록은 한 줄이어야 한다.
- Indenting은 최대한 적게. 함수는 중첩 구조를 가질 만큼 크면 안 된다. 길어야 한 두 단계여야 한다.
함수를 작게 만들어야 하는 이유
- 읽기 쉬워진다.
- 이해하기 쉬워진다.
- 함수가 의도를 잘 드러낸다.
- 1이 가능하면 2-3은 따라오는 것이니 1이 가장 중요하다.
예제로 Refactoring 해보기
리팩토링 전 FitnessExample
public class FitnessExample {
public 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 = wikiPage.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 setupPathName = 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 tearDownPathName = PathParser.render(tearDownPath);
buffer.append("!include -teardown .").append(tearDownPathName).append("\n");
}
if (includeSuiteSetup) {
WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);
if (suiteTeardown != null) {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteTeardown);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -teardown .").append(pagePathName).append("\n");
}
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
}
리팩토링 후 FitnessExample
매우 길었던 testableHtml()
메서드를 클래스로 추출했다. 그 다음 하나의 메서드에서 모두 처리하던 것을 여러 메서드로 분산했다. 그럼으로써 코드는 길어졌지만 훨씬 읽기 쉽고 복잡하지 않은 코드가 되었다.
public class FitnessExample {
public String testableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception {
return new TestableHtmlBuilder(pageData, includeSuiteSetup).invoke();
}
private class TestableHtmlBuilder {
private PageData pageData;
private boolean includeSuiteSetup;
private WikiPage wikiPage;
private StringBuffer buffer;
public TestableHtmlBuilder(PageData pageData, boolean includeSuiteSetup) {
this.pageData = pageData;
this.includeSuiteSetup = includeSuiteSetup;
buffer = new StringBuffer();
wikiPage = pageData.getWikiPage();
}
public String surround() throws Exception {
if (isTestPage()) {
includeSetUps();
buffer.append(pageData.getContent());
includeTearDowns();
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
private boolean isTestPage() throws Exception {
return pageData.hasAttribute("Test");
}
private void includeTearDowns() throws Exception {
includeInherited("teardown", "TearDown");
if (includeSuiteSetup)
includeInherited("teardown", SuiteResponder.SUITE_TEARDOWN_NAME);
}
private void includeSetUps() throws Exception {
if (includeSuiteSetup)
includeInherited("setup", SuiteResponder.SUITE_SETUP_NAME);
includeInherited("setup", "SetUp");
}
private void includeInherited(String setup1, String suiteSetupName) throws Exception {
WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(suiteSetupName, wikiPage);
if (suiteSetup != null) {
includePage(setup1, suiteSetup);
}
}
private void includePage(String teardown1, WikiPage suiteTeardown) throws Exception {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteTeardown);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -" + teardown1 + " .").append(pagePathName).append("\n");
}
}
🧐 tip 메서드 여러 곳에서 사용하는 변수를 Field Variable로 선언하면 메서드에서 또 다시 메서드를 추출할 때 파라미터를 달고 다니는 일을 하지 않아도 된다.
하지만 리팩토링 후에도 testableHtml()
는 하나 이상의 일을 하는 것처럼 보인다.
- 페이지가 테스트 페이지인지 결정하고
- 테스트 페이지라면 setups, teardowns를 include하고
- HTML로 페이지를 렌더링한다.
이 세 가지 일을 하는 메서드의 이름은 surround다. surround는 이 세가지 동작의 추상화된 이름이다. 이 세 가지 일을 surround라는 하나의 일로 추상화해버렸기 때문에 세 가지 일을 한다고 볼 수 없다.
이 부분이 잘 이해가 가지 않는다면 로그인을 생각해보면 좋다.
- 아이디와 비밀번호를 입력 받는다.
- DB에 있는 정보를 대조한다.
하는 일은 두 가지지만 로그인이라는 하나의 일로 추상화할 수 있다.
Function Part2 주요 내용
One thing?
함수는 한 가지 일만 해야 하고 그 한 가지를 잘 해야한다.
도대체 그 한 가지는 무엇인가요?
호출하는 입장에서는 그것이 한 가지다.
하지만 그것을 읽는 사람 입장에서는 한 가지 일이 아니다.
코드에 대해 말할 때 가장 존중받아야 하는 사람은 그것을 읽을 혹을 읽게 될 사람이다.
읽기 쉬운 코드를 작성하는 일은 어려운 일이다. 하지만 나만 조금 어려우면 그걸 읽게 될 수많은 사람들이 편해진다. 남을 배려해서 코드를 쓸 줄 알아야 한다. 남 좋은 일 하는 것 같지만 결국 나에게 도움이 되는 일이다.
함수를 어떻게 작게 만들 것인가?
- 주요 섹션을 함수로 추출
- 서로 다른 추상화 레벨로 분리
- 함수가 하나 이상의 추상화 레벨을 다루면 이 함수는 한 가지 이상의 일을 하는 것
하지만 추상화 레벨이란 것은 불분명하다.
Extract Till you drop
- 더 이상 extract할 수 없을 때까지 extract하라.
- extract할 코드를 가진 함수는 한 가지 이상의 일을 하는 것이다.
- 4줄 이내의 함수로만 구성된 클래스
- if, while 문 등에서 {}가 보이면 extract 대상
- {}는 extract할 기회
'Study > 클린코더스 강의' 카테고리의 다른 글
클린코더스 6강 - Form (0) | 2021.03.30 |
---|---|
클린코더스 5강 Function Structure #1 (0) | 2021.03.10 |
클린 코더스 1, 2강 - OOP (0) | 2021.02.27 |