代码整洁之函数书写准则!
代码整洁之函数书写准则!
月伴飞鱼短小
短小函数的第一规则
第二条规则还是要更短小
- 每行都不应该有150个字符那么长,函数也不该有100行那么长
函数的缩进层级不该多于一层
单一职责 只做一件事
只做一件事,如果函数只是做了该函数名下同一抽象层上的步骤,则函数还是只做了一件事
判断函数是否不止做了一件事,还有就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现
- 只做一件事的函数无法被合理地切分为多个区段。
设计模式中有单一职责原则,我们可以把这条原则理解为代码整洁之道中的函数单一职责原则
函数参数尽可能的少
每个函数一个且仅有抽象层级:
- 我们想要让代码拥有自顶向下的阅读顺序
- 让代码读起来像是一系列自顶向下的 起头段落是保持抽象层级协调一致的有效技巧
- 把读代码想象成读报纸
- 总分结构,或者总分总结构
switch语句:
- 多态–将switch语句埋到抽象工厂底下,在系统的其他部分看不到,就还能容忍
- 可以使用抽象工厂来进行改进
函数名称使用:描述性名称
testableHtml 改为
SetupTeardownIncluder.render
,好的名称价值怎么好评都不为过如果每个例程都让你感动深合已意,那就是简洁代码
函数参数:
- 参数越少越好,参数越少越便于理解
一元函数的普遍形式:
- 函数名称应能区分出来
问关于参数的问题,如
boolean fileExist("myFile");
将参数转换为其他什么东西,在输出之:
- 如
InputStream fileOpen("myFile");
事件:
- 有输入参数而无输出参数,程序将函数看做一个事件,使用该参数修改系统状态
标识参数丑陋不堪,即如果标识为true将会这样做,标识为false将会这样做:
- 向函数传入布尔值简直是骇人听闻的做法
如果出现了参数是Boolean 值,就要思考一下,函数是否只做了一件事情
- 反复问自己 ,这个函数是不是做了一件事,或者承担了一项职责
二元函数:
- 可以把某个参数转换成当前类的成员变量,从而无需再传递它
- 当然有一些有两个参数更加合适
Point(x,y)
这种就比较合理三元函数:
- 排序,琢磨,忽略的问题都会加倍体现,写三元函数之前一定要想清楚
参数列表:
- 一个数量可变的参数等同于一个参数
函数命名应与参数形成动词/名词对,如
writeField(name)
无副作用:
- 函数承诺只做一件事,但还是会做其他被藏起来的事,例如时序性耦合
输出参数:
- 面向对象语言中对输出参数的大部分需求已经消失了
- 应避免使用输出参数,如果函数必须要修改某种状态,就修改所属对象的状态吧
看下这个例子:
1 | appendFooter(s) |
看到这个 首先就会 想把 s 添加什么东西,或者它把什么东西加到s 后面?
- s 是输入 还是输出参数呢?
1 | public void appendFooter(StringBuffer report); |
看了函数的签名,我们大概明白了。
所以 在面向对象语言中,最好的做法
1 | report.appendFooter() // 这样来调用。 |
分隔指令与询问
函数要么做什么事(
do
),要么回答什么事(boolean
),但二者不可兼得
1 | public boolean set(String attributer, String value); |
用户调用:
1 | if (set("username","frank")) {//... |
看到这个代码,首先想 是否能够成功设置username 为frank, 还是要问,username 是否已经之前被设置过为frank了呢?
对看代码的人会比较困惑
所以 解决方案 就是 把做什么和是否成功分开
- 防止发生混淆
1 | if (exists("username")){ |
使用异常替代返回错误码
使用异常替代错误码返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化
抽离
try/catch
代码块:
try/catch
代码块丑陋不堪最好把try和catch代码块的主体部分抽离出来,另外形成函数
错误处理就是一件事:
- 如果try在某个函数中存在,它就该是这个函数的第一个单词
- 而且在catch/finally代码块后面也不该有其他内容
Error.java
依赖磁铁:
返回错误码通常暗示某处有个类或是枚举定义了所有错误码
使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署
使用异常的好处,可以避免嵌套太深的层级
- 代码可以写起来更加简化
来看一个例子:
- 这里使用了很多的 嵌套的 if语句 这种很难让人理解,嵌套层级特别多,看起来很复杂
- 对于这种代码 最好直接抛出异常就可以,直接 使用
try
语句 然后捕获具体的异常就可以了而不是一层,一层的进行判断,嵌套起来,之后就很难维护这样的代码
对于try 代码块 丑陋不堪, 它们会搞乱代码结构,把错误处理和正常流程混为一谈
最好把 try 和 except 的主体部分抽离出来 单独形成函数
- 这样以后维护也会方便一点,看起来代码清晰,代码结构简单
1 | public void delete(Page page){ |
别重复自己
别重复自己:重复可能是软件中一邪恶的根源
- 其实可以这样说,重复可能是软件中一切邪恶的根源,许多原则与实践规则都是为控制与消除重复而创建的
仔细想一想,面向对象编程是如何将代码集中到基类,从而避免了冗余的
而面向方面编程(Aspect Oriented Programming)
面向组件编程(Component Oriented Programming)多少也是消除重复的一种策略
- 这样看来,自子程序发明以来,软件开发领域的所有创新都是在不断尝试从源代码中消灭重复
重复而啰嗦的代码,乃万恶之源,我们要尽力避免
结构化编程:
- 每个函数、函数中的每个代码块都应该有一个入口,一个出口,遵循这些规则
- 意味着在每个函数中只该有一个return语句,循环中不能有break或continue语句,而且永永远远不能有任何goto语句
- 对于小函数,这些规则助益不大,只有在大函数中,这些规则才会有明显的好处
如何写出这样的函数
先想什么就写什么,然后再打磨它
- 初稿也许粗陋无序,你就斟酌推敲,直到达到你心中的样子
最好的情况要配上一套单元测试,覆盖每一行丑陋的代码
然后开始打磨这些代码,分解函数,修改名称,消除重复。最后要保持测试可以顺利通过
我并不是一开始就按照规则写函数,我想没有人做得到