写简单的函数

在工作的项目中总是可以见到不少非常长的函数, 这种函数一看到就会觉得烦躁, 往往读起来也花费相当多的时间, 阅读和修改都是相当的痛苦. 导致产生的因素会有很多, 私下归结了一下大致会有两种原因: 程序员本身没有良好的设计函数的思想、工期太赶; 市面上大多数的语言入门教程是不会教授如何编写好的函数, 而是花费大量时间来阐述语言本身的用法. 同样的情况也发生在学校的教学中. 而要真正学会去设计好的函数是一个非常漫长的过程, 需要不断地打磨自己的代码, 研读开源的优秀代码. 当有一些优秀的书籍是有专门讲授如何写简单的函数的章节的. 推荐阅读的有 编程格调代码大全代码整洁之道 不管是用何种语言写的设计良好的函数都具有大量相同的特征: 良好的输入输出, 每一个函数都有明确的输入(参数)和输出(返回值), 函数具有单一的职责不会同时做多件事情, 不会太长, 模块化良好, 可读性强, 阅读起来不费脑可以轻易理解其中的含义, 函数名和局部变量名命名良好, 状态不依赖于外部变量.

这里讨论一下每个函数只做一件简单的事情. 有一位同事在项目中为了省去一些函数, 让一个函数同时做了若干件事情, 导致的结果就是每次看到函数名时就一定要查阅其中的代码, 即便以前看过也是如此, 因为每次看到逻辑路线都不尽相同, 十分容易漏掉其中的一些逻辑. 在此贴出这段代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void upHashData(String roomId,String param,String value) {
    if (TwoEightConstant.GAME_INNING.equals(param) || 
            TwoEightConstant.BANKINCOME.equals(param) ) {
        redisUtil.hincrBy(RedisTable.GAME_INFO.getValue()+roomId,
                                  param,
                                  Long.parseLong(value),
                                  RedisConstant.REDIS_TYPE_SHARD_DEL,
                                  RedisConstant.DATABASENUM_11);
    } else{
        if (TwoEightConstant.STARTBET_TIME.equals(param)) {
            TwoEightConstant.startBetTimeMap.put(roomId,value);
        } else {
            redisUtil.setHashData(RedisTable.GAME_INFO.getValue()+roomId,
                                              param,
                                              value,
                                  RedisConstant.REDIS_TYPE_SHARD_DEL,
                                              RedisConstant.DATABASENUM_11);
        }
    }
    if(TwoEightConstant.BANK_UID.equals(param)){
        redisUtil.setHashData(RedisTable.TEB_BANKER_USERID.getValue(),
                                      roomId,
                                      value,
                      RedisConstant.REDIS_TYPE_SHARD,
                                      RedisConstant.DATABASENUM_1);
    }
}

这段乌七八绕的代码依据 param 的不同干了四件事情, 导致逻辑上十分不清晰. 王垠在其编程的智慧一文中论述到这种一个函数做多件事情, 它们之间共同点少于不同点, 最好拆分成多个函数, 否则这个函数的逻辑就会不清晰, 容易出现错误. 如果发现多件事情的大部分内容相同而只有少数不同时, 多半可以将相同的部分提取出去, 做成一个辅助函数. 这样既可以共享代码又做到了每个函数只做一件简单的事情, 逻辑上也清晰了许多. 这和设计模式中的单一职责原则(SRP)非常类似, 职责就是状态改变的原因, 单一职责的意思是说一个类或者模块只能一个改变的原因. 正如编辑其中的内容和样式是两个不同的改变因素, 代表着两个不同的职责, 需要放在分离的模块中.

还有就是解耦很重要, 一个函数只应该依赖于其输入, 而千万不要依赖于全局的数据, 这样才能够产生模块化的代码, 因为我们不知道其它的代码会不会改变这个全局数据, 一旦改变了那么即便输入是一致的, 得到的输出也不相同. 对于测试和调试来说简直就是噩梦. 特别是当这个全局数据在多个地方充当不同的角色时更是如此.

论述如何编写优雅的函数的书有不少很经典, 此处的论述仅仅是蜻蜓点水式的. 若希望真正学习设计函数, 阅读开头那几本书会是一个不错的开始.