把简单的事情做到极致,功到自然成,最终“止于至善”
自从有了面向对象技术之后,很多工程师把精力放在了对象技术上,反而忽视了函数。实际上面向对象和写好函数并不冲突。
写好函数技艺,好的函数能够大大降低阅读代码的困难度,提升代码的可读性。
软件中的函数(方法)
函数是一组代码的集合,是程序中最小的功能模块,一次函数的调用包括接收参数输入,数据处理,返回结果。同一个函数可以被一个或多个函数调用任意次。
在面向对象语言中:方法则是面向对象语言中对函数的叫法。
封装判断
好的函数应该是清晰易懂的,我们从一个简单又实用的函数重构技法说起。如果没有上下文,if和while语句中的布尔逻辑就难以理解。如果把解释条件意图作为函数抽离出来,用函数命名把判断条件的语义显性的表达出来,就能立即提升代码的可读性和可理解性。
例子
判断一个地址是否是小程序地址条件
1.url.scheme 是cmbc
2.url.host 是miniapp
原来代码
NSString *menuUrl = [aMenuItem getUrl];
NSURL *url = [NSURL URLWithString:menuUrl];
if ([url.scheme isEqualToString:@"cmbc"] && [url.host isEqualToString:@"miniapp"]) {
[self p_openMiniappFramework:menuUrl nav:aNav];
}
重构之后代码
NSString *menuUrl = [aMenuItem getUrl];
if ([CMBCMiniappUtil isMiniappUrlValid:menuUrl]) {
[self p_openMiniappFramework:menuUrl nav:aNav];
}
// 判断url是否是小程序url
+ (BOOL)isMiniappUrlValid:(NSString *)miniappUrl {
BOOL result = NO;
NSURL *url = [NSURL URLWithString:miniappUrl];
if ([url.scheme isEqualToString:@"cmbc"] && [url.host isEqualToString:@"miniapp"]) {
result = YES;
}
return result;
}
不难发现重构后的代码更容易理解,因为通过封装判断,判断条件的业务语义被显性地表达出来了,代码的可读性自然也好了许多。
函数参数
最理想的参数数量是零,其次一元函数,再次是二元函数,应尽量避免三元函数,有足够特殊的理由才能用三个以上参数。当然也的看具体的场景,在程序中最大忌讳就是教条,有可能两个参数比一个好。
总体上来说参数越少越容易理解,函数也越容易使用和测试,如果函数需要三个以上,那就说明其中一些参数应该封装成类了。
例如要绘制一条直线
可以用如下声明
- (void)makeLine:(CGFloat)startX
startY:(CGFloat)startY
endX:(CGFloat)endX
endY:(CGFloat)endY
X和Y可以看做一组概念被共同传递,我们应该为这一组概念提供一个新的抽象,叫做point,这样将参数对象化之后,参数的个数减少了,表达也更加清晰了。
创建一个新的对象Point
class Point {
CGFloat x;
CGFloat y;
}
- (void)makeLine:(Point)start
end:(Point)end;
短小的函数
函数的第一规则是要短小,第二规则是要更短小。
维护过超长函数折磨的读者应该深有体会,相比于3000行代码的庞然大物,肯定更短小的函数更容易理解和维护。
有时候保持代码的逻辑不变,只是把长方法改成多个短方法,代码的可读性就提高很多。超长方法是典型的代码的“坏味道”,对超长方法的结构化分解是提升代码可读性可读性最有效方式之一。
那么函数的代码的行数多长才合适呢?
这个没有绝对的量化标准,各团队可以有自己的标准,不同开发语言可能会稍有不同,建议一个方法不要超过20行,当把这个规定作为团队代码审查的硬性指标后,发现代码质量得到了显著的改善。
职责单一
按照行数规定函数的长度是定量的做法,但是我更喜欢另一种定性的衡量方法,即一个方法只做一件事,也就数函数职别的单一原则。
遵循单一职责不仅可以提升代码的可读性,还能提升代码的可复用性,因为职责越单一,功能越内聚,就越可能被复用。
通常长方法意味着肯定需要拆分,多要用多个子函数的组合来进行更好的表达,然而短小的函数并不一定就意味着不需要拆分,只要不满足单一职责就需要进一步分解。哪怕分解后的子函数只有一行代码。只要有助于业务语义显性的表达。
精简辅助代码
所谓的辅助代码是程序中必不可少少的代码,但又不是处理业务逻辑的核心代码,比如判空,打印日志,降级和缓存检查等。这些代码往往会在多个函数中重复冗余,减少辅助代码可以让代码更加干净整洁,易于维护。
如果辅助代码太多,会极大地干扰代码的可读性。因此我们应该尽量减少辅助代码对业务代码的干扰,让函数中的代码能直观的体现业务逻辑,而不是让业务代码淹没在辅助代码中,
优化判空
空指针的发明人曾表示对发明空指针的忏悔,说这是一个数十亿的错误,
我们经常会看到这样的代码 if (dec == NULL) return ;的代码,其本身并没有什么问题,也是为了代码的健壮性。只是这样的判空代码多了,会干扰阅读代码的流畅性。
看下面的简单实例有时候我们为了代码的健壮性,在访问值时会对其进行明确的检查
if(user!=null){
Address address = user.getAddress();
if(address!=null){
String province = address.getProvince();
}
}
可以看到这样的代码多么复杂