以下是一個錯誤示範,你花三分鐘可能還看不懂他在寫什麽
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 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("\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(); }以下是經過重構過得程式,是不是看起來容易懂多了
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(); }
- Function should be small
- Function以20行為最佳
- 之前的例子再進一步重構
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception { if (isTestPage(pageData)) { includeSetupAndTeardownPages(pageData, isSuite); } return pageData.getHtml(); }
- 第一個範例會讓人看不懂是因為他做太多事情了
- Function should do one thing. They should do it well. They should do it only.
- 當寫完function後,要再檢查看看是否還能把其中內容再拆成另一個function
- 函式中的statments都要再同一個抽象層級上
- 第一個範例中的getHtml()就是屬於較高層級的概念,append則是低層級的概念
- Reading code from top to bottom
- 就像讀報紙一樣
- 我們很難讓switch精簡短小
- 使用abstract factory和多型來處理switch
- You know you are working on clean code when each routine you read turns out to be pretty much what you expected."
- 不要害怕使用long name,SetupTeardownIncluder.render會比testableHtml要來的好
- 最理想的function是沒有參數,其次是一個參數,再來是兩個參數
- 除非是特殊理由,否則不要使用三個以上的參數
- 盡量避免使用output arguments,因為大多數人預期function由參數輸入並且由return回傳,output arguments會讓人想比較久
- boolean fileExists("MyFile") 透過參數來問一個問題
- InputStream fileOpen("MyFile") 操作一個參數並轉換成InputStream回傳
- 還有一種形式是event,void passwordAttemptFailedNtimes(int attempts)
- 此形式沒有return value,通常用於設定系統參數
- 小心為他命名,務必讓讀者知道這是一個event
- Flag Arguments
- Flag arguments are ugly!
- 違反Do One Thing的原則,盡量拆成兩個functions
- 兩個參數的function
- 比一個參數來的難懂
- assertEquals(expected, actual),很容易搞混前後參數的次序,expected在前只是一種不成文的規定
- 盡量拆解成一個參數的function,ex: writeField(outputStream, name)可改成outputStream.writeField(name)
- 三個參數以上的function
- 三個以上的參數非常難懂
- 參數有三個以上,通常代表你需要把其中參數封裝成class,ex: x, y封裝成Point
- 盡量拆解成一個參數的function,ex: writeField(outputStream, name)可改成outputStream.writeField(name)
- 執行以下這個function會有隱藏的side effect
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; } }
- 一個函式應該專門回答問題或是專心做某件事,而不是兩者都做
- public boolean set(String attribute, String value),if (set("username", "unclebob"))...
- set完參數後return是否成功,容易讓讀者搞混
- 拆成兩個會比較好
if (attributeExist("username")) { setAttribute("username", "unclebob"); }
- if (deletePage(page) == E_OK) 使用Exceptions來處理這種returning error codes
- 不要在函式中複製貼上重複的code
- 在小函式中可以使用break和continue
- 避免使用goto
沒有留言:
張貼留言