V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Lxxyx
V2EX  ›  程序员

函数应该只做一件事,那么该怎么去定义和划分好“一件事”?

  •  
  •   Lxxyx ·
    Lxxyx · 2017-01-18 18:03:49 +08:00 · 3874 次点击
    这是一个创建于 2654 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看代码整洁之道,有提到:

    函数应该做一件事,做好这件事,只做这一件事。

    书中也说了:

    判断函数是否只做了一件事,有如下 2 种判断方法:

    1. 函数是否只是做了该函数名下同一抽象层上的步骤;
    2. 看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。

    看的时候感觉有些疑惑,自己知道函数应该只做一件事,但是该怎么去合理的划分出“这件事”? 同一抽象层又该怎么去定义?

    再比如说有个初始化系统的函数,他肯定会调用多个其他函数,比如初始化内存 /硬件等,那么做什么算是做“一件事”,而做了什么算是超过了“一件事”的范围呢?

    在这儿先谢谢各位啦!

    第 1 条附言  ·  2017-01-21 14:30:26 +08:00
    感谢各位的回复,让我对这个问题有了更深理解。感谢已发送,这两天一直在重构……
    13 条回复    2017-01-19 21:05:47 +08:00
    padeoe
        1
    padeoe  
       2017-01-18 18:12:34 +08:00
    没明确标准,可以回归出发点来判断。做好一件事只做一件事情的目的是啥?提高复用,降低耦合,控制复杂度,便于测试维护等。因此可以看看现有代码是否阻碍了这一目标。
    beginor
        2
    beginor  
       2017-01-18 18:14:41 +08:00 via Android
    这个还真不好界定,具体问题具体分析
    ihuotui
        3
    ihuotui  
       2017-01-18 21:09:29 +08:00   ❤️ 3
    业务函数和单个功能函数区别,当是单个功能函数就尽量单一,方便测试,假如功能函数测试完成,业务函数的测试就简单了。业务函数当然是各个功能函数的组合。
    要思考,不能死读书。
    hasbug
        4
    hasbug  
       2017-01-18 21:19:40 +08:00
    加入收藏,坐等收获。
    JamesRuan
        5
    JamesRuan  
       2017-01-18 21:21:22 +08:00
    Cyclomatic complexity

    我个人一般会让它保持在 10 以下,超过 10 就认为多件事情一起做了,就重构逻辑。
    JamesRuan
        6
    JamesRuan  
       2017-01-18 21:22:55 +08:00
    然而通常情况下由于习惯好,复杂一点的函数也最多 7-9 ,基本不会等到写完发现超过 10 才重构。
    ihuotui
        7
    ihuotui  
       2017-01-18 21:28:35 +08:00   ❤️ 1
    函数只做一件事情的核心思想是优雅代码和可测试。
    让人家一看就明白的代码。
    能写单元测试的代码。
    例如
    saveUserOrder(..){
    orderNo=getOrder();
    saveOrder(orderNo,...);
    saveDetail(orderNo,...);
    }
    lizon
        8
    lizon  
       2017-01-18 22:07:57 +08:00   ❤️ 1
    这么说吧,现在存在两段逻辑十分相似,那么进行脑内假设:如果,其中一段需要修改,另一段也必须做出相应的修改,那么可以判断这段逻辑应该在同一个函数,并且分别由两个地方调用。如果不是,那就不应该提炼出来,保持冗余。
    enenaaa
        9
    enenaaa  
       2017-01-19 09:28:57 +08:00   ❤️ 1
    1. 如果一段代码有两处以上地方用到,那么应该独立出去。
    2. 如果某个变量、常量来自其他模块,而且处理这个变量时没有涉及到其他数据, 那应该把处理代码放到对应的模块去。
    3. 函数内生成的新变量,需要进一步处理时, 例如:需要用到不方便引用的数据,渲染打印,文件 IO ,数据库存储等,考虑将代码独立出去。
    Symo
        10
    Symo  
       2017-01-19 10:09:25 +08:00   ❤️ 1
    @ihuotui
    我也同意这个, 之前没写过测试的时候对于这个问题也很费解.
    其实 TDD 一定程度上解决了这个事情, 如果你发现业务代码根本无法写出测试来, 那么就重构到能测试为止.
    测试方法不应该向一个迷之黑盒中扔几个参数然后期待奇迹发生.
    ihuotui
        11
    ihuotui  
       2017-01-19 10:15:51 +08:00
    @Symo 可能他们没有接触过 TDD 。
    libook
        12
    libook  
       2017-01-19 11:21:10 +08:00   ❤️ 1
    个人观点:

    要相信代码是给人看的,只是顺便让机器来执行,所以可读性第一。

    基本方法是分层和解耦,每一层能一目了然就好,例如:
    把大象装进冰箱里的程序。
    主函数层是这样几步:
    1. 把冰箱门打开;
    2. 把大象装进去;
    3. 把冰箱门关上。
    其中引用了三个函数,拿函数 1.“把冰箱门打开”来看又细分为如下步骤:
    1. 找到门把手;
    2. 判断把手的触发类型;
    3. 将门把手调整为开门状态;
    4. 判断门是朝哪个方向开;
    5. 打开门(返回)。

    这些没有硬性的标准,是纯粹主观感知的,自己写完可以看一遍,通过篇幅、分段(块)等自己判断一下是否足够“一目了然”。

    判断是否是“一件事”本身是无法精确实现的,全靠对函数的主观定义,比如如果我把函数定义成“打开冰箱门”,那这个函数就只能做打开冰箱门的事情,但如果定义为“开关冰箱门”就可以用来开或关冰箱门,这确实也可以理解为是“一件”事情。
    换个角度来说,对于函数的规划是根据需求来不断变化的,所以做项目的时候要关心这个功能以后的发展方向是什么样的,然后根据对未来迭代对这个功能的修改进行适当的扩展性和维护性的设计,以后需求变成这样的话这个函数是否好改,改完后是否还能保持较好的扩展性和维护性。这需要经验。
    greatbody
        13
    greatbody  
       2017-01-19 21:05:47 +08:00   ❤️ 1
    其实我觉得正确的姿势是先写代码,完成功能最大。然后开始逐步优化重构。

    这种事情做多了,就可以跳过写功能代码的步骤,直接上模块化的代码。能做到这个的,不是大神也是大拿了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1274 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 18:02 · PVG 02:02 · LAX 11:02 · JFK 14:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.