编写短小的函数/方法

栏目: 后端 · 发布时间: 5年前

内容简介:我们经常会遇到两个词,函数(Function)与方法(Method),简言之:他们的区别:方法底层实现本质还是函数,只是隐式传递了对象引用或指针,方法最终通过转化为函数的形式进行调用。为了简化后面的述诉,方法与函数统一称函数,不再区分。

函数与方法

我们经常会遇到两个词,函数(Function)与方法(Method),简言之:

  • 函数不属于任何对象
  • 方法是关联到对象内的函数

他们的区别:

  • 函数是面向过程编程中,为解决问题划分每个步骤的体现
  • 方法是面向对象编程中,对象能提供的能力或行为的体现

方法底层实现本质还是函数,只是隐式传递了对象引用或指针,方法最终通过转化为函数的形式进行调用。为了简化后面的述诉,方法与函数统一称函数,不再区分。

他们的必要性:

  • 让代码可以重复使用,他们是”积木“
  • 函数黑盒特性,有效封装,隐藏细节

短小的好处

Kent BeckSmalltalk Best Practice Patterns 中定义的 Composed Method 模式。指出函数应该只能做一个层次上的抽象。函数应该是短小的,对于Smalltalk函数大小应该在一至六行。

如果是现代语言像Java/Go/Scala/Kotlin等,每个函数应该是控制在有效行数在 20到25 行 。短小的函数会带来如下好处:

  • 维护成本:越长的函数越要花费更大的成本去了解它要做什么,以及在什么地方作修改。而对于小函数,你可以快速的查明应该在何处作修改(尤其是在应用测试驱动开发的时候)
  • 代码可读性:在初始的学习曲线之后,小函数使得你更容易理解一个类要做什么。而且你不必滑动窗口就可以理解代码
  • 拥有重用潜力:把函数分解成一些小的模块,你可以识别出代码中通用的抽象,通过使用这些通用的抽象使你的代码总量急剧下降
  • 拥有子类化潜力:函数越长,你就越难以创建一个子类使用这个函数
  • 命名:小的函数命名起来要容易一些,因为它做的事情少
  • 性能属性:遇到了性能问题,一个使用composed method的系统更容易定位性能瓶颈
  • 灵活性:小函数使得重构更加容易,并且容易找到设计缺陷,比如feature envy
  • 代码质量:把大函数分解成小函数,定位隐晦的错误更加容易
  • 最小化注释:虽然注释是很有价值的东西,大多数注释可以通过谨慎的命名和调整结构消除。代码已经能说清楚,则注释是没有必要的。

注:来自 Small Methods: Nine Benefits of Making Your Methods Shorter

影响因素

为了达成短小的函数,业界已有非常多的度量指标,指导我们写出可读性很好的函数。当然我们不能只是奔着降低指标而去,指标只是手段,不是目标。

圈复杂度

圈复杂度是一种衡量代码复杂程度的标准,由 Thomas J. McCabe, Sr. 提出。它用来衡量一个模块判定结构的复杂程度,数量上表现为独立的行路径条数。

圈复杂度高带来的害处有(建议平均值控制在15以内):

  • 过高说明逻辑复杂不容易理解
  • 逻辑复杂可能导致质量低下
  • 路径过多难以测试覆盖

圈复杂度高主要表现:

  • 分支语句多:if/else, switch/case, for, while
  • 表达式复杂:条件表达式复杂,3个及以上逻辑运算符

降低圈复杂度方法:

  • 抽取函数:独立业务代码封装为函数,通过函数名诠释代码作用,做到见名知意
  • 合并重复:不同条件的分支,有相似的处理,可以提炼出分支以外,或者封装为函数
  • 分解条件式:复杂的条件表达式,使用函数进行封装
  • 移除控制标记:有控制标签作为条件的,使用break/return取代
  • 设计模式:对于同一层的if/else, switch/case分支过多,可考虑采用状态机或策略模式、表驱动法(Map查找)
  • 单一职责:一个函数应该只实现一个功能点

嵌套层次

不恰当的if/else与try/catch语句,是最为常见的嵌套层次过深代码;对于异步api,像nodejs还没有promise与async/await语言特性时,也会导致callback嵌套层次过深。

嵌套层次过深带来害处显而易见(建议层次不要超过4层):

  • 圈复杂度变高,难以理解与测试
  • 有时像”横放着的金字塔“,代码太长,不方法阅读

降低if/else嵌套层次方法:

if + return
e = (a==b) ? c : d

try/catch可以再嵌套try/catch,但也是最容易出逻辑问题的地方,降低try/catch嵌套层次方法:

  • 抽取函数:try与catch的中各代码块超过10行以上,建议是分别抽取函数,try/catch块内只有函数调用
  • 合并try/catch:分析是否可以合并try/catch到同一层次
  • 折分try/catch:让try的范围更小,尽早catch,避免大try中不必要的嵌套

我们会经常对数据进行操作,Java8引入Stream对象。若能合理使用它,也可以减少迭代(for语句–> map方法调用)、条件判断(if/else语句 –> filter方法调用)组合带来的嵌套。面向过程变成面向函数式编程,也会让你的代码更为的清爽。

参数个数

函数的输入参数过多,会使函数易于受外部的变化影响,从而导致函数变得不稳定,代码维护困难。过多的控制标记参数也会导致参数的使用组合变多,代码的分支路径变多,也就增加了测试的工作量。

参数个数多少个才算多?建议是不超过5个。

是否要将参数封装成对象,不能只看参数的数量,还要看它的业务意义,有时封装成对象反倒增加了阅读成本。参数过多说明它依赖过多,我们需要考虑是否需要对函数进一步拆分。

对于构造方法参数过多,我们可以采用 链式调用 ,它是一种Builder模式,比一堆的参数列表调用更有意义,也不容易出现赋值顺序出错而导致问题。

变量个数

函数局部变量个数多,是函数的逻辑实现需要依赖较多的外数或内部数据,依赖多则会导致复杂度增加。

局部变量个数多少个才算多?建议是不超过5个。

解决局部变量变多的另一种思路是 Replace Method with Method Object (来自重构一书),以函数对象取代函数,做法是将函数放进一个单独的对象当中,使用这个单独对象的值域(filed)来替代原函数中的局部变量。若这个单独的对象使用的范围非常小,我们通过把它声明为内部静态类。

结语

千里之堤溃于蚁泬,短小精悍的函数,是构建健壮的软件基石。千里之行始于足下,从编写短小精悍的函数开始。


以上所述就是小编给大家介绍的《编写短小的函数/方法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入应用C++11

深入应用C++11

祁宇 / 机械工业出版社 / 2015-5 / 79

在StackOverflow的最近一次世界性调查中,C++11在所有的编程语言中排名第二, C++11受到程序员的追捧是毫不意外的,因为它就像C++之父Bjarne Stroustrup说的:它看起来就像一门新的语言。C++11新增加了相当多的现代编程语言的特性,相比C++98/03,它在生产力、安全性、性能和易用性上都有了大幅提高。比如auto和decltype让我们从书写冗长的类型和繁琐的类型......一起来看看 《深入应用C++11》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具