如何写出整洁的函数
Talk is cheap show me the code!
我想这句话充分表达了代码的重要性,而我们大部分的代码就是函数,整洁的函数优雅、高效,让人赏心悦目!它能够很容易的被修改、应该讲述事实,不引人猜测。为了写出整洁的函数,码农们一直在努力着、探索着、实践着,在这篇文章中,笔者结合自己多年的工作经验和其他大牛的一些文章,总结出了一些原则、模式,供大家参考与实践!
简洁函数的重要性
越简单的东西越容易理解,函数也是一样
写出简洁的函数一定是非常困难的,凡是写出那种一大堆让人读不懂的复杂函数的一定不是一个好的coder.
我们每次写代码要调用自己或别人写的函数的时候,是要看看具体的实现的,而如果该部分实现写的非常的晦涩难懂,那么在一定程度上也会影响我们的开发效率。
那么如何让写出的函数简洁呢?下面分别从以下几个方面介绍以下
取名字
命名无处不在,包名、类名、方法名、参数名、变量名等等。
好名字胜过千言万语!
名字要名副其实
要可以理解,不能模糊,要能表达具体的含义,如:
int i ; // 工作日
变量i什么也没说明,如果换成workDays是不是更好些?
避免误导
避免使用可能会引起误会的命名,如:
int pi
大家看到第一眼都以为是圆周率,但是实际上它是partIndex的缩写。
误导的另一种形式是相近的拼写。如:小写字母I和1,大写字母O和0。应当避免使用。
要有一定的区分度
避免使用a1,a2,……,aN这个样的命名,没有任何区分度。
user,userInfo,userData这几个命名同样是废话,也没有任何的区分度。
能读得懂的命名
一定是人类能够识别的语言,鸟语是看不懂的!
如:要表达一个交易时间的变量,可以命名为tradeymdhms,也可以命名为tradeDate。显然,人们更容易记住后者。(这也说明学好英语也是蛮重要的)
尽量用动词
方法名应是动词或动词短语,如:
void driveCar()
每个概念一个单词
给每个行为概念一个词,并一以贯之!这样当再次看到这个词的时候,就能大致明白是什么意思了。
如插入数据库记录,又是insert,又是save,又是put,这样很让人迷惑,所以用一个。
避免双关语
同一个单词,不同的概念,就是双关了!如:
- 插入数据库记录 insert
- list中插入数据 insert
这个地方insert双关了,应该换一个,如:list中用append
优先技术性命名,其次为业务性命名
技术性命名是指技术领域内的名称,如:queue、pool等
因为只有程序员才会读你写的代码(机器除外)!
因此,在命名的时候,应该首先以技术性相关的词语来命名,如usersQueue等,其次才是业务性的命名
语境
变量或命名应该有语境,这样才能自我说明。
如:name,单独放在这里没有什么意义,如果是userName,就有了意义,因为有user这个前缀作为语境。
不过,通常来说,不要添加没用的语境。比如user.userName,这里就显的很多余。
一些原则
我们写的函数比写的类还要多,写好函数非常的重要,如何写好函数呢?
最重要原则:
短小
其实我也不知道为什么要短小,只是写过看过了那么多的代码,函数就应该短小!
那么到底多短小算短小呢?也没有一个完美的答案,但有一个标准:
函数中代码块的缩进层级不该多于一层或两层。
只做一件事情
每个函数只做一件事情。
一件事情不等于就是一个函数调用,处在同一个抽象层级上的就可以。
多了的,应该分解为更小的函数。
同一抽象层级
一个函数的函数体应该只包含同一抽象层级的代码。
理解起来比较难,下面举个例子啊
public void transfer(Account from,Account to,Money m){
withdraw(from,m);
deposit(to,m);
}
public void withdraw(Account account , Money m){
account.withdraw(m);
}
public void deposit(Account account , Money m){
account.deposit(m);
}
其中withdraw和deposit处于同一抽象层,因为它们都只是定义了一个概念:支取、存入。
而下面这样写,就不是一个抽象层级。
public void transfer(Account from,Account to,Money m){
withdraw(from,m);
account.deposit(m);
}
public void withdraw(Account account , Money m){
account.withdraw(m);
}
望大家自行体会!
switch语句
应该避免使用之,因为它首先违反了单一权责原则(SRP),因为有好几个要修改他的理由。
也违反了开放闭合原则(OCP),每当添加新类型时,就必须修改之!
那怎么避免呢?可以使用抽象工厂来 尽量避免 它的发生。当然还有一些其他的方法,如:反射等。
取个好名字
函数越短小、功能越集中、就越便于取个好名字。
另外,别害怕长名字,要比短而令人费解的名称好很多。 别害怕花时间取名字,你会发现你不是在取名字,而是在整理思路!
名称要与抽象层级相符合!
要注意参数的顺序,最好参数也是名字的一部分,例如:
transfer(from,to,withMoney)
封装条件、避免使用否定性条件
尽量把if,while等逻辑判断的条件封装到一个函数中。
且这个函数尽量是肯定式的,否定式的要不肯定式的难明白一些。
魔术数字
单独出现的数字、字母等,让人无法理解,如:
if(status==1)
这个“1”是什么意思?
可以使用常量或枚举值的方式改造它,如:
if(status==ACTIVE)
尽量不要返回NULL
Null是很麻烦的,每次都要进行判断,导致了大量的恶心的代码。
尽量返回空对象,或抛出异常来解决。
函数参数
参数越少越好,最多不应该超过三个,不过还得具体问题具体分析。
参数越多,参数与参数之间的逻辑关系就越多,单元测试的组合就越多,越难以测试。
参数多的时候,可以将之封装为类。当然要把相同概念或问题域的封装在一起。
使用多个函数,要比使用一个函数多个参数要好一些!不过,这也通常说明,该函数不只做了一件事。
flag参数
flag是指一些标记,通常表示函数不只做一件事情,这个应该避免使用。
如:
transfer(Boolean useWX)
这个函数其实做了两件事,一个是通过微信转账,另一个是不通过微信转账。应该把其一分为二,即:transfer(),transferUseWX() 。
尽量没有副作用
应该是可以被重复调用的,而且尽量没有副作用。
尽量是幂等的。如转账,不会因为调用第二次而转了两次。
如果有副作用,在方法名中应该表达出来。如:createOrRetrunUser
查询与命令相分离
一个函数要么是查询的,即:不修改任何数据!,要么就是命令的,即:做业务,修改数据!
二者不可兼得
错误处理
错误处理就是一件事,应该是一个整体,而不是分散到各处的地方。
比如:在java等语言中,catch中的代码库就应该是一个函数!
别重复自己(DRY)
重复很危险,他让你的思路变得混乱,修改起来麻烦,代码中散发着坏味道。
总结
编写程序就是在讲故事,就是在把各个函数干净利落的拼装在一起,为此,应该:
取个好名字
短小的函数
抽象
DRY
DRY
DRY
本文文字及图片出自 微信公众号
你也许感兴趣的:
- 【外评】电脑从哪里获取时间?
- 【外评】为什么 Stack Overflow 正在消失?
- Android 全力押注 Rust,Linux 却在原地踏步?谷歌:用 Rust 重写固件太简单了!
- 【外评】哪些开源项目被广泛使用,但仅由少数人维护?
- 【外评】好的重构与不好的重构
- C 语言老将从中作梗,Rust for Linux 项目内讧升级!核心维护者愤然离职:不受尊重、热情被消耗光
- 【外评】代码审查反模式
- 我受够了维护 AI 生成的代码
- 【外评】Linux 桌面市场份额升至 4.45
- 【外评】作为全栈开发人员如何跟上 AI/ML 的发展?
你对本文的反应是: