案例:FitNess
FitNesse 实例
这段代码案例来自
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 suiteSetupPage = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_SETUP_NAME, wikiPage);
if (suiteSetupPage != null) {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteSetupPage);
String pagePathName = PathParser.render(pagePath);
buffer.append("\n!include -setup .")
.append(pagePathName)
.append("\n");
}
}
WikiPage setupPage = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
if (setupPage != null) {
WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setupPage);
String setupPathName = PathParser.render(setupPath);
buffer.append("\n!include -setup .")
.append(setupPathName)
.append("\n");
}
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
WikiPage teardownPage = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardownPage != null) {
WikiPagePath tearDownPath = wikiPage.getPageCrawler().getFullPath(teardownPage);
String tearDownPathName = PathParser.render(tearDownPath);
buffer.append("\n")
.append("!include -teardown .")
.append(tearDownPathName)
.append("\n");
}
if (includeSuiteSetup) {
WikiPage suiteTeardownPage = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);
if (suiteTeardownPage != null) {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteTeardownPage);
String pagePathName = PathParser.render(pagePath);
buffer.append("\n!include -teardown .")
.append(pagePathName)
.append("\n");
}
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
假定这一个函数已经通过了测试,按照简单设计的评判步骤,我们需要检查代码是否存在重复。显然,在上述代码的
- 获取
Page - 若
Page 不为null ,则获取路径 - 解析路径名称
- 添加到输出结果中
这套模板的差异部分可以通过参数差异化完成,故而可以提取方法:
private static void includePage(WikiPage wikiPage, StringBuffer buffer, String pageName, String sectionName) {
WikiPage suiteSetupPage = PageCrawlerImpl.getInheritedPage(pageName, wikiPage);
if (suiteSetupPage != null) {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(suiteSetupPage);
String pagePathName = PathParser.render(pagePath);
buildIncludeDirective(buffer, sectionName, pagePathName);
}
}
private static void buildIncludeDirective(StringBuffer buffer, String sectionName, String pagePathName) {
buffer.append("\n!include ")
.append(sectionName)
.append(" .")
.append(pagePathName)
.append("\n");
}
在提取了
public static String testableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception {
WikiPage wikiPage = pageData.getWikiPage();
StringBuffer buffer = new StringBuffer();
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetup) {
includePage(wikiPage, buffer, SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
includePage(wikiPage, buffer, "SetUp", "-setup");
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
includePage(wikiPage, buffer, "TearDown", "-teardown");
if (includeSuiteSetup) {
includePage(wikiPage, buffer, SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}
从重复性角度看,以上代码已经去掉了重复。当然,也可以将
private static boolean isTestPage(PageData pageData) {
return pageData.hasAttribute("Test");
}
若遵循信息专家模式,
public class PageData
private boolean isTestPage() {
return hasAttribute("Test");
}
}
重构后的
public static String renderPage(PageData pageData, boolean includeSuiteSetup) throws Exception {
if (pageData.isTestPage()) {
WikiPage testPage = pageData.getWikiPage();
StringBuffer newPageContent = new StringBuffer();
includeSuiteSetupPage(testPage, newPageContent, includeSuiteSetup);
includeSetupPage(testPage, newPageContent);
includePageContent(testPage, newPageContent);
includeTeardownPage(testPage, newPageContent);
includeSuiteTeardownPage(testPage, newPageContent, includeSuiteSetup);
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();
}
对比我的版本和
- 方法名称过长,暴露了实现细节
isTestPage 变量不如isTestPage() 方法的封装性好- 方法体缺少分段,不同的意图混淆在了一起
最关键的不足之处在于第
public static String renderPageWithSetupsAndTeardowns(PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
该版本的方法仍然定义在
有限封装
封装需要有度,引入太多的层次反而会干扰阅读。尤其是方法,
includeSuiteSetupPage(testPage, newPageContent, includeSuiteSetup);
includeSetupPage(testPage, newPageContent);
includePageContent(testPage, newPageContent);
includeTeardownPage(testPage, newPageContent);
includeSuiteTeardownPage(testPage, newPageContent, includeSuiteSetup);
这五行代码不正是直截了当地表达了包含的页面结构吗?因此,我觉得
我并不反对定义细粒度方法,相反我很欣赏合理的细粒度方法,如前提取的
若发现一个主方法过长,可通过提取方法使它变短。当提取方法的逻辑层次嵌套太多,彼此的职责又高内聚时,就需要考虑将这个主方法和提取出来的方法一起委派到一个专门的类。此外,通过
由此可知,
public class TestPageIncluder {
private PageData pageData;
private WikiPage testPage;
private StringBuffer newPageContent;
private PageCrawler pageCrawler;
private TestPageIncluder(PageData pageData) {
this.pageData = pageData;
testPage = pageData.getWikiPage();
pageCrawler = testPage.getPageCrawler();
newPageContent = new StringBuffer();
}
public static String render(PageData pageData) throws Exception {
return render(pageData, false);
}
public static String render(PageData pageData, boolean isSuite) throws Exception {
return new TestPageIncluder(pageData).renderPage(isSuite);
}
private String renderPage(boolean isSuite) throws Exception {
if (pageData.isTestPage()) {
includeSetupPages(isSuite);
includePageContent();
includeTeardownPages(isSuite);
updatePageContent();
}
return pageData.getHtml();
}
private void includeSetupPages(boolean isSuite) throws Exception {
if (isSuite) {
includeSuitesSetupPage();
}
includeSetupPage();
}
private void includeSuitesSetupPage() throws Exception {
includePage(SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
private void includeSetupPage() throws Exception {
includePage("SetUp", "-setup");
}
private void includeTeardownPages(boolean isSuite) throws Exception {
if (isSuite) {
includeSuitesTeardownPage();
}
includeTeardownPage();
}
private void includeSuitesTeardownPage() throws Exception {
includePage(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
}
private void includeTeardownPage() throws Exception {
includePage("TearDown", "-teardown");
}
private void updateContent() throws Exception {
pageData.setContent(newPageContent.toString());
}
private void includePage(String pageName, String sectionName) throws Exception {
WikiPage inheritedPage = PageCrawlerImpl.getInheritedPage(pageName, wikiPage);
if (inheritedPage != null) {
WikiPagePath pagePath = wikiPage.getPageCrawler().getFullPath(inheritedPage);
String pathName = PathParser.render(pagePath);
buildIncludeDirective(pathName, sectionName);
}
}
private void buildIncludeDirective(String pathName, String sectionName) {
buffer.append("\n!include ")
.append(sectionName)
.append(" .")
.append(pathName)
.append("\n");
}
}
引入
对比
我曾就
当然,如果开发人员在编写代码时就能遵循简单设计原则,实则也不会写出