编写好函数是编写好代码的基础,一个系统容易腐化的部分正是函数。
不解决函数的复杂性,就很难解决系统的复杂性。
虽然函数不像面向对象技术那么复杂,但要写好函数也不是一键容易的事情。
好的函数能大大降低阅读代码的困难度,提升代码的可读性。
一、封装判断
如果没有上下文,if和while语句中的布尔逻辑和代码块就难以理解,如果把解释条件意图作为函数抽离出来,用函数名把判断条件的语义线性化的表达出来,就能立即提升代码的可读性和可理解性。例如下面的简单代码,通过抽取checkJob和fillDefaultValue函数使代码的可读性大大提高,将来增加新的逻辑越多,这种好处越明显:
@Override
public void createJob(JobDto job) {
//参数合法性校验
checkJob(job);
//填充默认值
fillDefaultValue(job);
//持久化
edfOperationDbGateway.createJob(job);
}
private void checkJob(JobDto job) {
List<SysDataSourceDto> shardingDbInfo = edfSystemDbGateway.listShardingDbInfo();
boolean fromMatch = shardingDbInfo.stream().anyMatch((ds) -> {
return job.getFromDatabaseDbkey().equalsIgnoreCase(ds.getDbkey());
});
if (!fromMatch) {
throw new RuntimeException("源库dbkey"+job.getFromDatabaseDbkey()+"不存在");
}
boolean toMatch = shardingDbInfo.stream().anyMatch((ds) -> {
return job.getToDatabaseDbkey().equalsIgnoreCase(ds.getDbkey());
});
if (!toMatch) {
throw new RuntimeException("目标dbkey"+job.getToDatabaseDbkey()+"不存在");
}
}
private void fillDefaultValue(JobDto job) {
job.setJobId(UUIDUtil.getUUID());
job.setJobStatus(ConstantUtil.status_ready_not);
job.setCreateTime(LocalDateTime.now().toString());
}
二、函数参数
最理想的参数数量使零,其次是一元函数,再次是二元函数,尽量避免三元函数。当然这不是绝对的,有足够理由的时候也可以使用三元函数,避免教条主义。
总体来说,参数越少,越容易理解,函数越容易使用和测试。
如果函数需要3个以上参数,一般情况下就可以考虑将参数封装成类了。
三、短小的函数
显然短小的函数更易于理解和维护。
大部分情况下,你只需要把长方法改成多个短方法,代码的可读性就能大大提高,前面也说过了,分解后的短方法就显性化的解释了这段代码的含义,一目了然。
函数的代码行数多长才算合适呢?
没有绝对值,但最好不超过20行。
如果你的团队坚持这个标准,代码可读性将大大提高。
四、职责单一
一个方法只做一件事情,这是单一职责原则SRP,大家都知道。
五、精简辅助代码
优化判空代码、优化缓存代码、通过注解优雅降级、异常处理
六、组合函数模式
组合函数模式是一个成本低、容易上手、实用,对代码可读性和可维护性起到立竿见影效果的编程原则。
组合函数要求所有公有函数(public)读起来像一本书的目录,而这些目录的真正实现细节全部位于私有函数中。
最典型的例子可以是Spring的 AbstractApplicationContext的refresh函数,通过10多个子函数,将复杂的过程清晰的展示了出来。
七、SLAP 抽象层次一致性原则
SLAP是和组合函数模式密切相关的一个原则。组合函数要求将大函数拆分成多个小函数,而SLAP就是用来指导如何拆分更合理更优雅的,它要求函数体内的内容必须要在同一个抽象层次上。这就相当于一本书的一级目录二级目录三级目录 再同一个抽象层次上,如果一级目录和三级目录编排在了同一个函数体内,就会显得逻辑凌乱,难以理解。
当你的代码满足组合函数模式和SLAP时,你的代码就构筑了一个完美的金字塔,自上而下富有层次感,易读好理解。
八、函数式编程
函数式编程和面向对象编程并没有本质上的区别。在函数式编程中,函数不仅可以调用函数,而且还可以作为参数被其它函数调用。它和传递对象的唯一区别就是只有方法(函数)没有属性(数据),更深层次理解是传递的只有指令没有数据(老吕说的)。
函数式的风格可以让代码更加整洁(见解)优雅。
例子:
实现 String 转换 Integer的功能。
1)经典类实现
Function<String,Integer> strToIntClass = new StrToIntClass();
public static class StrToIntClass implements Function <String,Integer> {
@Override
public Integer apply(String s){
return Integer.parseInt(s);
}
}
2)匿名类实现
Function<String,Integer> strToIntClass = new Function <String,Integer> {
@Override
public Integer apply(String s){
return Integer.parseInt(s);
}
}
3)Lamda 实现
Function<String,Integer> strToIntClass = s-> Integer.parseInt(s);
4)方法引用实现
Function<String,Integer> strToIntClass = Integer::parseInt;