常用知识 - 参数用一个数组还是分开几个

  • 作者:KK

  • 发表日期:2016.8.23


要点速读

  1. 就算定义成一个$params参数,函数增加了新key的处理,那调用的地方传的参数没新key也是要编写新key的赋值代码

  2. 不定义成一个$params的话除了调整函数的处理,还要调整传参的代码,也是要改两处

  3. 该不该将参数定义成数组,要看这些参数是不是同一个类型的集合,是的话就从概念上将它定义为一个整体,就用数组,不能参与到这个整体中的就单独定义


问题的起源

做了一两年的程序员通常都会遇到这样情况:原来有个获取某时间段新用户列表的函数function getNewUserList($startTime, $endTime),后来因为需求说要支持获取的人数,导致要加多个参数,变成了function getNewUserList($startTime, $endTime, $nums),不止定义函数的地方改了,而且到处的调用代码都要找出来修改,导致有改漏的情况,调用出错了


于是出现了数组参数的设计

有一部分人开始学聪明了,他直接这样设计函数:

function getNewUserList($where){}

这个$where是一个含有startTimeendTimenums下标的数组,可是我个人觉得这还是有问题的

  • 调用的地方还是要改的

    虽然函数的调用代码不用改,依然是getNewUserList($where)这样调用,可是调用的地方的$where也不一定具备新元素的nums,于是还是有一部分调用的代码附近要做$where['nums'] = $limitNums这样的赋值处理,不然函数收到的$where根本不会有新数据嘛对不对

    所以要变需求时,除了变动函数对key值的处理逻辑,还有一部分调用代码附近的逻辑要改,补充赋值才行

    要么改函数调用参数表,要么改附近的赋值,其实这成本还是一样

  • 产生了对命名和阅读的纠结,代码好像不像从前那么美了

    不仅如此,这时候导致函数也不是很限阅读的样子,无论是定义的地方还是调用的地方都不好阅读。

    你看嘛参数命名为$where,没错如果说在sql里where start_time > xx and end_time < yy这逻辑肯定说得过去,是where部分,可是限制数量nums却不属于条件,而是对筛选结果的进一步挑选,在sql里就是limit呢

    不说限制数量吧,以后需求再说要排序控制,分组控制,这全部扔在$where里那你就会开始觉得这个参数不叫where而统一叫params了对吧?

    一旦这样发展下来,你就会慢慢嗅到一股怪味道:咋发现人家外面的程序不带这样设计的,你就偏偏往这条路子钻下去呢?你又有没有观察过成员们在调用函数方面的编程苦恼?

至少,外界没有一个流行开来的开源程序和框架用一个$params的设计,我也从没听说哪个大牛认可这个设计,除非在特定应用场合,比如WebService

放心讲这么多话不是说你犯了什么十恶不赦的大罪,只是在分析后果,其实我曾经也苦恼这个参数设计问题,差点陷入了这个深渊,但喜欢探索的我在各大论坛不断发帖求知+请大神吃饭交流,最终解除了这个疑惑


同类的参数集合设计成数组,不同类的单独一个

这是我在网上找到的答案,也是一些有10多年经验的老程序员指点的经验

  • 关于阅读的问题

    以上面说的function getNewUserList($startTime, $endTime, $nums)为例子,其实startTime和endTime是同类参数来的,它们都表达了一个时间,并且作用是在查询条件上,所以我们可以将它定义为一个整体,这个整体所扮演的角色就是条件,于是我们可以说条件是会存在很多种的,此时条件既然是一个整体,我们可以用数组表达:

    function getNewUserList($where, $nums)
    

    这样阅读也挺顺的

  • 关于调整的问题

    将同类参数定义成一个整体后,可以解决一部分调整的问题,当需求说“除了时间以外要增加一个条件,就是性别”,那没事,函数里增加对$where['sex']的处理,并且调用函数的地方给$where赋值sex

    可是按照我这样说其实也是会遇到调整问题,比如需求又要分组了咋办。。。

    但再往深一层想:一个项目假设有20个类,每个类平均有10个方法,难道这20*10=200个方法都要重构?

    按照经验,3年时间内要调整参数位的函数/方法超不过50个左右,有的甚至就20个以内,除非设计这个函数+代码审核人员的水平都比较差,否则函数的返修率通常都是很低的,不然你看PHP的函数从PHP4到PHP7期间,有多少函数说要增加参数位,更换参数位的,设计这些参数的人都有丰富的编程经验,绝大部分函数都已经尽可能将返修率降到最低了,我们应该关注的是降低返修率这个能力,这样才能让我们更加进步,设计函数时更加小心

    好了最终还是有一部分要返修的,咋办?修就修,你想哦,要是把全部参数揉合成一个$params数组,光是它带来的代码阅读和维护成本,都比你分开一个个参数重构麻烦,至少它根本不能做到只修改函数处理,不用修改外部调用代码的境界,因为还是要为新的key赋值的嘛

  • 阅读问题

    其实无论是只用一个$params参数还是说分开参数(分开后,个别参数也可能是数组),都会有阅读问题,初次接触这个函数的新成员会疑惑地思考:”我怎么知道这个数组参数还能不能添加更多key做更多控制,或者减少某个key也能正常工作?

    这其实要靠文档,比如先在定义函数的地方编写phpdoc注释(参考下面例子):

    /**
     * 获取新注册的用户列表
     * @param array $where 条件
     * 必须包含`startTime`和`endTime`,其它可选,根据数据表的实际字段增加就可以
     * @param int $nums 要获取的用户个数
     */
    function getNewUserList($where, $nums){}
    

最近的案例

最近我们项目有个计算两个用户间距离的方法设计,负责这个事的是一位1年经验的程序员,他的设计是这样的:

/**
 * 获取两个用户的距离
 * @param float $user1X 用户1的经度
 * @param float $user1Y 用户1的纬度
 * @param float $user2X 用户2的经度
 * @param float $user2Y 用户2的纬度
 * @return int 距离(米)
 */
function getDistance($user1X, $user1Y, $user2X, $user2Y){}

以上4个参数分别代表用户1的经纬度(X和Y)和用户2的经纬度

我指导他调整这样的:

/**
 * 获取两个坐标之间的距离
 * @param array $position1 第一个坐标
 * 必须包含`x`和`y`
 * @param array $position2 第二个坐标
 * 必须包含`x`和`y`
 * @return int 距离(米)
 */
function getDistance($position1, $position2){}