3 函数

短小

  • 函数第一规则 要短小
  • 函数第一规则 还要更短小
    通常要小于 10 行
    if | else | while 语句,其中的代码块应该只有一行 这行大抵应该是个函数调用语句
  • 不但能保持短小
  • 函数名较具有说明性 增加了文档价值
    => 函数不应大到足以容纳嵌套结构 函数缩进层级不该多于一层或两层

只专心做一件事

函数应该只做好一件事

判断函数是否不止做了一件事 ?

  • 判断几步是否都在该函数名下同一抽象层
  • 能否再拆出一个函数 该函数不仅只是单纯地重新诠释其实现

每个函数一个抽象层级

函数中语句都要在同一抽象层次上 让每个函数后面都跟着位于下一抽象层级的函数【从上而下自大而小的递归顺序】

写出只停留于一个抽象层次上的函数 => 是保持函数短小 确保只做一件事的要诀

swith

确保每个 swich 都在较低的抽象层级 而且永不重复

public Money calculatePay(Employee e) throws InvalidEmployeeType {
	switch (e.type) {
		case COMMISSIONED:
			return caclculateCommissionedPay(e);
		case HOURLY:
			return caclculateHourlyPay(e);
		case SALARIED:
			return caclculateSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);
	}
}

问题

  • 太长 出现新类型时 还会更长
  • 不止做了一件事
  • 违反了单一权责原则 —— 有好几个修改它的理由
  • 违反了开放闭合原则 —— 每当添加新类型时 就必须修改之
  • 最麻烦的:可能到处都有类似结构的函数 calculatePay | isPayday | deliverPay
    • 解决:埋到抽象工厂下 不让人看到

单一职责原则: 就一个类而言,应该仅有一个引起它变化的原因。

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 {
	switch (e.type) {
		case COMMISSIONED:
			return caclculateCommissionedPay(e);
		case HOURLY:
			return caclculateHourlyPay(e);
		case SALARIED:
			return caclculateSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);
	}
}

使用描述性名称

函数越短小 功能越集中 就越便于取个好名字
长而具有描述性的名称 比描述性的长注释好
命名方式保持一致

函数参数

0 参 > 1 参 > 2 参 避免 3 参 有足够特殊的理由才能用三个以上参数
如果参数多余 2 个 测试覆盖所有可能值的组合简直让人生畏

一元函数
  • 转换:有入有出 出入相关
  • 事件:有入无出

void includeSetupPageInfo(StringBuffer pageText)
避免使用输出参数 如果函数要对输入参数进行转换 转换结果就该提现为返回值

void transform(StringBuffer out)
=>
StringBuffer transform(StringBuffer in)
标识函数

标识参数丑陋不堪 向函数传入布尔值简直就是骇人听闻的做法 方法签名立刻变得复杂起来 大声宣布函数不止做一件事 —— 一分为二!!!

render(true) 看不懂做了什么
render(Boolean isSuite) => renderForSuite() | renderForSingleTest()

二元函数

笛卡尔点
特点:自然组合 | 自然排序
问题:顺序搞不清 e.g. assetEquals(expected, actual)
二元转一元:方法写成其中一个参数的成员
e.g. writeField(outputStream, name) => outputStream.writeField(name)
也可以把 outputStream 写成当前类的成员变量,从而无需再传递它
还可以分离出 FieldWriter 新类 构造器包含 outputStream 包含一个 write 方法

三元函数

排序 琢磨 忽略 问题会加倍体现,使用前一定要想清楚

参数对象

如果函数需要 2 个、3 个,3+个参数,就说明其中一些参数应该封装为类了

Circle makeCircle(double x, double y, double radius);
=>
Circle makeCircle(Point center, double radius);
参数列表

String.format 其实是个二元函数

动词与关键字

一元函数:函数名参数应当形成一种非常良好的动词/名词对形式

write(name) => writeField(name) // name 是一个 Field
assetEqual => assetExpectedEqualsActual(expected, actual)

无副作用

时序性耦合 可能会在函数中悄悄执行其它的事情 这些事情应该在函数名称中说明 虽然还是违反了只做一件事的原则
checkPassword【函数中有执行方法 Sesson.initialize() 】 => checkPasswordAndInitializeSession

避免使用输出参数 如果函数必须要修改某种状态 就修改所属对象的状态吧

分隔查询与指令

函数要么做什么事(修改某对象的状态) 要么回答什么事(返回该对象的有关信息) 两者不可兼得

boolean set(A, B)
=> 
boolean setAndCheckIfExists()
=>
if (exists) {
	setXxx(A, B)
}

使用异常代替返回错误码

从指令式函数返回错误码违反了指令与询问分隔的规则 => 如 if 语句判断中把指令当做表达式使用
if (deletePage(page) == E_OK) 不要这样用

如果使用异常替代返回错误码 错误处理代码就能从住路径代码中分离出来 得到简化

抽离 try-catch 代码块

try-catch 把错误处理与正常流程混为一谈 —— 最好抽离

  • try-catch 函数
  • throws 函数
// 错误处理代码 => 负责 catch 和处理
public void delete(Page page) {
	try {
		deletePageAndAllReferences(page);
	} catch (Exception e) {
		logError(e);
	}
}

//  业务功能代码 => 完全删除一个 page,方法层面向上抛出错误
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());
}
错误处理就是一件事

如果关键词 try 在某个函数中存在 它就该是这个函数的第一个单词 而且在 catch / finally 后也不该有其他内容
如上例的 delete 函数

Error.java 依赖磁铁

返回错误码 => 类 / 枚举 定义了所有错误码 => 依赖磁铁 许多类都得导入和使用它
使用异常替代错误码 新异常就可以从异常类派生出来 无需重新编译或重新部署

别重复自己

重复使代码臃肿 => 重复消除 => 整个模块可读性提升

消除重复

  • 数据库范式
  • 基类——子类
  • 面向方面编程
  • 面向组件编程

结构化编程

适用大函数:每个代码块都应该有一个入口 一个出口 每个函数只该有一个 return 语句 循环中不能有 break 或 continue 语句
对于小函数:只要保持短小 偶尔出现的 break return continue 没有坏处

如何写出这样的函数

打磨

  • 分解函数 —— 大=>小
  • 修改名称 —— 使函数名能精确表达所做之事
  • 消除重复 —— 抽出共有方法
  • 缩短和重新安置方法 —— 重构
  • 拆散类
  • 保证测试通过
    组装

不一开始就按照规则写函数 因为没人做得到

经典范例

作者这种分发我感觉有点极端了 复用性低 功能相近的函数我觉得可以整合在一个函数内 大概能省略将近一半的函数
最终的函数大小大概在 5~10 行

package fitness.html;

import fitness.responders.run.SuiteResponder;
import fitness.wiki.*;

public class SetupTearDownIncluder {
	private PageData pageData;
	private boolean isSuite;
	private WikiPage testPage;
	private StringBuffer newPageContent;
	private PageCrawler pageCrawler;

	// 抽象层级[0] 渲染页面数据
	public static String render(PageData pageData) throws Exception {
		return render(pageData, false);
	}

	// 抽象层级[1]
	private static String render(PageData pageData, boolean isSuite) {
		return new SetupTearDownIncluder(pageData).render(isSuite);
	}

	// 抽象层级[2]
	public SetupTearDownIncluder(PageData pageData) {
		this.pageData = pageData;
		testPage = pageData.getWikiPage();
		pageCrawler = testPage.getPageCrawler();
		newPageContent = new StringBuffer();
	}

	// 抽象层级[2]
	private String render(boolean isSuite) {
		this.isSuite = isSuite;
		if (isTestPage()) {
			includeSetupAndTeardownPages();
		}
		return pageData.getHtml();
	}

	// 抽象层级[3] 将if语句中的判断改为一个方法名描述 这样代码的可读性会上升 尤其是对于复杂的条件判断逻辑
	private boolean isTestPage() {
		return pageData.hasAttribute("Test");
	}

	// 抽象层级[3] 一个函数中的所有方法都是平级的 在下一级进行展开
	private void includeSetupAndTeardownPages() {
		includeSetupPages();
		includePageContent();
		includeTeardownPages();
		updatePageContent();
	}

	// 抽象层级[4]
	private void includeSetupPages() {
		if (isSuite)
			includeSuiteSetupPages();
		includeSetupPage();
	}

	// 抽象层级[5]
	private void includeSuiteSetupPages() {
		include(SuiteResponder.SUITE_SETUP_NAME, "-setup");
	}

	// 抽象层级[5]
	private void includeSetupPage() {
		include("Setup", "-setup");
	}

	// 抽象层级[4]
	private void includePageContent() {
		newPageContent.append(pageData.getContent());
	}

	// 抽象层级[4]
	private void includeTeardownPages() {
		includeTeardownPage();
		if (isSuite)
			includeSuiteTeardownPage();
	}

	// 抽象层级[5]
	private void includeTeardownPage() {
		include("TearDown", "-teardown");
	}

	// 抽象层级[5]
	private void includeSuiteTeardownPage() {
		include(SuiteResponder.SUITE_SETUP_NAME, "-teardown");
	}

	// 抽象层级[4]
	private void updatePageContent() {
		pageData.setContent(newPageContent.toString());
	}

	// 抽象层级[6]
	private void include(String pageName, String arg) {
		WikiPage inheritedPage = findInheritedPage(pageName);
		if (inheritedPage != null) {
			String pagePathName = getPathNameForPage(inheritedPage);
			buildIncludeDirective(pagePathName, arg);
		}
	}

	// 抽象层级[7]
	private WikiPage findInheritedPage(String pageName) {
		return PageCrawlerImpl.getInheritedPage(pageName, testPage);
	}

	// 抽象层级[7]
	private String getPathNameForPage(WikiPage page) {
		WikiPagePath pagePath = pageCrawler.getFullPath(page);
		return PathParser.render(pagePath);
	}

	// 抽象层级[7]
	private void buildIncludeDirective(String pagePathName, String arg) {
		newPageContent
				.append("\n!include ")
				.append(arg)
				.append(" .")
				.append(pagePathName)
				.append("\n");
	}
}
// 其它辅助类 lombok get set 注解已省略
public class SuiteResponder {
	public static final String SUITE_SETUP_NAME = "";
}

public interface PageCrawler {
	WikiPagePath getFullPath(WikiPage page);
}
public class PageCrawlerImpl implements PageCrawler {
	public static WikiPage getInheritedPage(String pageName, WikiPage testPage) {
		return null;
	}

	public WikiPagePath getFullPath(WikiPage page) {
		return null;
	}
}

public class PageData {
	private WikiPage wikiPage;

	public boolean hasAttribute(String test) {
		return false;
	}

	public String getHtml() {
		return null;
	}

	public char[] getContent() {
		return new char[0];
	}

	public void setContent(String str) {
	}
}

public class PathParser {
	public static String render(WikiPagePath pagePath) {
		return null;
	}
}

public class WikiPage {
	private PageCrawler pageCrawler;
}

public class WikiPagePath {
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅气呢杰哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值