短小 ,再短小,更短小
20行最佳
只做一件事 准确说来每个方法应该是只做抽象概念上的的一件事
只做一件事的方法是无法把逻辑分段的
**Notice
how each function introduces the next, and each function remains at a consistent level
of abstraction.**
To include the setups and teardowns, we include setups, then we include the test page con tent, and then we include the teardowns.
To include the setups, we include the suite setup if this is a suite, then we include the regular setup.
To include the suite setup, we search the parent hierarchy for the “SuiteSetUp” page
and add an include statement with the path of that page.
To search the parent. . .
就是说,一个逻辑应该这样:
方法1:( 为了做A 我需要先做 B) 调用 方法2
方法2:(为了做B,我要做C,D)调用方法3,方法4
方法3:做C
方法4:做D
欣赏一下这段代码:
package fitnesse.html;
import fitnesse.responders.run.SuiteResponder;
import fitnesse.wiki.*;
public class SetupTeardownIncluder {
private PageData pageData;
private boolean isSuite;
private WikiPage testPage;
private StringBuffer newPageContent;
private PageCrawler pageCrawler;
private SetupTeardownIncluder(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 SetupTeardownIncluder(pageData).render(isSuite);
}
private String render(boolean isSuite) throws Exception {
this.isSuite = isSuite;
if (isTestPage()) {
includeSetupAndTeardownPages();
}
return pageData.getHtml();
}
private boolean isTestPage() throws Exception {
return pageData.hasAttribute("Test");
}
//这个方法貌似做了四件事,但是是并列的四件事,这四件事都是 SetupAndTeardownPages 这件事的next level(关键之处),也可以理解为这件事的四个步骤。
// each function remains at a consistent level
// of abstraction
private void includeSetupAndTeardownPages() throws Exception {
includeSetupPages();
includePageContent();
includeTeardownPages();
updatePageContent();
}
private void includeSetupPages() throws Exception {
if (isSuite) {
includeSuiteSetupPage();
}
includeSetupPage();
}
private void includeSuiteSetupPage() throws Exception {
include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
private void includeSetupPage() throws Exception {
include("SetUp", "-setup");
}
private void includePageContent() throws Exception {
newPageContent.append(pageData.getContent());
}
private void includeTeardownPages() throws Exception {
includeTeardownPage();
if ( ) {
includeSuiteTeardownPage();
}
}
private void includeTeardownPage() throws Exception {
include("TearDown", "-teardown");
}
private void includeSuiteTeardownPage() throws Exception {
include(SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
}
private void updatePageContent() throws Exception {
pageData.setContent(newPageContent.toString());
}
private void include(String pageName, String arg) throws Exception {
WikiPage inheritedPage = findInheritedPage(pageName);
if (inheritedPage != null) {
String pagePathName = getPathNameForPage(inheritedPage);
buildIncludeDirective(pagePathName, arg);
}
}
private WikiPage findInheritedPage(String pageName)
throws Exception {
return PageCrawlerImpl.getInheritedPage(pageName, testPage);
}
private String getPathNameForPage(WikiPage page) throws Exception {
WikiPagePath pagePath = pageCrawler.getFullPath(page);
return PathParser.render(pagePath);
}
private void buildIncludeDirective(String pagePathName, String arg) {
newPageContent.append("\n!include ").append(arg).append(" .")
.append(pagePathName).append("\n");
}
}
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
想想这个方法有什么问题?
违法SRP
该方法不止做了一件事(作者这么说对么?难道这个不是next level abstraction?更上边的说法矛盾?那么怎么才算一件事呢?)
之所以说违法SRP,是因为这个方法可以说做了多件事,calculateCommissionedPay(e);calculateHourlyPay(e);calculateSalariedPay(e);
因为他们直接没有“then”的关系,他们是完全独立的同类型的事情。
也就是说,所谓一件事,你可以是a then b,then c.但是不能 if(..) do A; if(..) do B……..
违法OCP
新类型需要添加,则就必须修改代码。 可以扩展,但不能修改就是OCP
不够短,其实最严重的问题是 calculateCommissionedPay(e);calculateHourlyPay(e);calculateSalariedPay(e);里可能有相同结构的代码(如isPayday(Employee e, Date date),等等相同代码),那为什么不抽象出来呢
那么怎么重构?
作者认为通过 工厂去消化掉switch,动态的得到某个类型,利用多态的统一接口,在这个具体类型里去实现相应方法.
欣赏下:
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
-----------------
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
/*为什么又有switch,作者觉得在用于多态的创建对象 是tolerant. 什么意思?其实我的理解就是,你要用可以,应该用的抽象点,不能在具体方法里,具体逻辑里做. 对吗?自己思考吧 */
switch (r.type) {
case COMMISSIONED:
return new CommissionedEmployee(r);
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmploye(r);
default:
throw new InvalidEmployeeType(r.type);
}
}
}
那最后,原来的switch 变为:
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
e.calculatePay()
}
includeTeardownPages , includeSuiteTeardownPage , and includeTeardownPage
标识参数:
方法参数 为boolean 的这中标识参数,是不是可以说明你的方法做了两件事,1.true……..2.false….. 所以一般不要用标识参数
单参数(monadic):
通常两种意思,
1.Command——你要把这个参数转化或加工 valueOf(String s);
2.Query——你要提出关于这个参数的问题 isExists(T t)
3.另外还有一种不太常用,但是重要. 单参数的void方法,你要用这个参数去设置系统状态或其他什么——
The overall
program is meant to interpret the function call as an event and use the argument to alter the
state of the system, for example, void passwordAttemptFailedNtimes(int attempts) .
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
String.format(“%s worked %.2f hours.”, name, hours);
public String format(String format, Object… args)
writeField(name)
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
作者又该作何解释呢?
这是我在stackoverflow的提问后,一个较为满意的答案:
public enum Error {
OK,
INVALID,
NO_SUCH,
LOCKED,
OUT_OF_RESOURCES,
WAITING_FOR_EVENT;
}
因为如果你要增加一个Error类型,使用到Error的代码都要重新编译、部署,不如使用exception(当然是衍生的).
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {
logger.log("page deleted");
} else {
logger.log("configKey not deleted");
}
} else {
logger.log("deleteReference from registry failed");
}
} else {
logger.log("delete failed");
return E_ERROR;
}
很明显,deletePage(page) 是Command(违反CQS)那就不要用来返回boolean并去做判断.
用 exception 代替 if esle 中对返回码的判断
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logger.log(e.getMessage());
}
try catch 里又有业务逻辑,作者建议try catch 里只有这个层面的抽象:
提取try catch finally 代码块
public void delete(Page page) {
try {
deletePageAndAllReferences(page);
} catch (Exception e) {
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throws Exception {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e) {
logger.log(e.getMessage());
}
很明显,更具有条理!
总结:记住写代码就是讲一个逻辑故事,如何像写散文一样,写诗歌一样写出代码?