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

你会拆分这样的函数么?

  •  
  •   spacewander ·
    spacewander · 2014-12-05 16:16:51 +08:00 · 6053 次点击
    这是一个创建于 3401 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有一个函数,是这样子的:
    (仅仅用作表示,真实函数肯定不叫doSomething)

    doSomething(a, b, c)
    {
    // prepare
    ...
    // do A
    ...
    // do B
    ...
    // do C
    ...
    }

    前提:
    1. do A/B/C中的内容不会被其他函数复用,仅仅在doSomething中被用到
    2. 这是C++写的
    3. 该函数不是“厨房下水道”函数,其长度是300多行

    有人认为,应该把do A/B/C部分,分别拆开成单独的函数,然后让doSomething调用它们。
    理由:
    1. 这样做大大减少了doSomething函数的大小,使得整个函数更加一目了然
    2. 无需再添加一行注释表示该部分代码是做什么的,用函数名就能清晰表示它们的职责
    3. 将部分逻辑置于独立的函数之中,便于调试

    不过楼主不认同这个观点,反而认为维持现状即可。
    理由:
    1. 这些部分不会被复用,即使独立成单独的函数,意义不大,不应该过度设计。
    2. 独立成单独的函数后,需要将函数的输入/输出参数重复一遍,而且万一以后发生变动,不得不修改多个函数之间的传递的类型信息。
    3. 独立出更多的函数后,看上去程序更好理解了,但实际是把单个函数的维护成本分摊到多个子函数上了。如果这样做了,要想明白doSomething的实现细节,你就不得不去阅读4个函数:doSomething,doA,doB和doC。
    4. 一旦doSomething的逻辑发生较大变化,原本只需修改doSomething一个函数,现在则需要修改三个子函数。分拆函数就是为了把逻辑独立出去,但是这三个函数在逻辑上不一定能够独立开来,分拆它们会把事情变得更复杂。

    各位怎么看?
    第 1 条附言  ·  2014-12-05 18:23:36 +08:00
    先申明最后我没拆……(但是也许以后重构的时候会拆,不过这个说不定。如果有V友因此不爽,我感到抱歉)

    毕竟这个是“It depends”的问题,总得具体问题具体分析,然后按照自己心中的尺度去权衡。换个不同的人,答案肯定不一定。
    44 条回复    2014-12-06 20:41:50 +08:00
    jox
        1
    jox  
       2014-12-05 16:25:14 +08:00
    如果A,B,C只在dosomething里会用到,没必要拆分,在 不同的功能块前面使用注释区分也一样,性能上也有一点点优势,免去了函数调用的开销,如果dosomething很长,无所谓,现在的IDE都带代码折叠功能,不需要编辑的区块折叠起来就行

    不过不需要拆分的情况不多见,一般函数之间都是互相调来调去的,都需要把重复的代码弄成个函数
    akira
        2
    akira  
       2014-12-05 16:25:55 +08:00
    1. 一个函数尽量只做一个事
    2.一个函数的长度要适中。

    至于你的理由,
    1/2可以把函数做成局部函数,
    3 doA/doB/doC 可以视为大纲,是有助于代码阅读的。
    4 确实是有可能会有这种情况,要具体案例具体分析了
    binux
        3
    binux  
       2014-12-05 16:26:47 +08:00
    首先2不成立,函数输入输出的重新设计,正是梳理函数功能的过程,让函数只能看到它需要的变量。
    3也不成立,函数单独拆出的原则是函数是能独立工作的,而经过2,使得接口更清晰,会让它更容易理解。而且拆出去之后loc不变,实际阅读量不变。
    4函数拆分的原则就是功能是独立的,拆出去的函数如果功能不独立,请考虑你拆分方式是否有问题?!

    由2-4点得,独立成函数不是意义不大的,不是过度设计。
    yeqiu
        4
    yeqiu  
       2014-12-05 16:30:51 +08:00
    别的语言我不懂啊,对于oo的语言,例如c#,如果拆分的话,因为方法树的存在会增加内存消耗。
    chmlai
        5
    chmlai  
       2014-12-05 16:30:59 +08:00
    这样拆分一下谈不上什么"过度设计"
    chmlai
        6
    chmlai  
       2014-12-05 16:31:57 +08:00
    另外只是那点函数调用就别谈什么性能不性能了吧, 又不是递归
    hh3755
        7
    hh3755  
       2014-12-05 16:32:00 +08:00
    虽然我也是个菜鸟,我觉得还是应该拆出来,因为函数太大,实在不好维护的,别人理解起来也会有点费劲。
    理由:
    1. 对于参数的问题。参数可以全部封装到一个对象中去,或者直接丢到一个KEY,VALUE的结构中。改变参数也不会改变代码,关于参数本身的校验全部耦合到参数对象中去。
    2. 独立后的每个子函数职责就更清晰,可以在函数命名时就明确其作用,比如doCheck,doProcess,releaseResouce.
    3. 当然不太清楚你的场景啊,瞎猜,有的时候拆着拆着,就会发现,原来有些东西可以放在一起,哈哈。
    JonyOang
        8
    JonyOang  
       2014-12-05 16:37:21 +08:00
    逻辑分割,单一目的,长度适中,支持分割,哈
    staticor
        9
    staticor  
       2014-12-05 16:40:21 +08:00
    可拆,或者说如果能拆就方便拆。
    主要是方便以后的扩展设计,对一个函数的调用修改可在调用时再装饰
    repus911
        10
    repus911  
       2014-12-05 16:44:18 +08:00
    应该按照实际情况来考虑,一个爬虫和一个系统毕竟不一样,但是……

    1. 过度设计你妹啊 300行的函数完全不能忍啊
    2. 你改原函数也要做的事情,而且300行代码还可能带暗雷
    3. 你看一个函数的名字,参数,输出,就可以明明白这个函数的功能了
    2/4 万一,一旦 你这算不算过度设计
    4. 拆开主要是帮你理清逻辑的,你会发现自己改的更快了

    300行的代码老兄你真的能忍 我是写python的
    88250
        11
    88250  
       2014-12-05 16:44:27 +08:00
    等纠结完这些,别人同样特性的产品已经上线了....
    jiang42
        12
    jiang42  
       2014-12-05 16:51:08 +08:00
    肯定拆

    理由1:你不会知道以后会不会在其它地方用到A,B,C的。。。
    理由2:IDE自带重构-。-
    理由3:如果需要读A,B,C只能说你A,B,C有问题,A,B,C的名字就应该描述其作用
    理由4:不能拆开你提什么分拆函数?实际上一般逻辑都是能拆开的,拆不开说明水平不够。至于修改doSomething,考虑一下,如果只有A出现问题,则去A子函数修改就好了,不拆分的话你需要理清楚修改A是否会影响B,C


    @yeqiu 99%的性能问题都是程序员的意淫。与其死扣细节,不如学好算法和数据结构。
    jerry2014
        13
    jerry2014  
       2014-12-05 16:51:39 +08:00
    先订好一套规则,大家都根据这个规则来写。
    standin000
        14
    standin000  
       2014-12-05 16:56:27 +08:00
    无参数调用,可以试试inline。
    有参数调用首先要考虑参数本身的意义。如果参数固定,就建议用子函数,要改什么就在子函数里改就可以了。
    lincanbin
        15
    lincanbin  
       2014-12-05 17:01:27 +08:00
    我不会,我只封装会重复使用的代码。
    现在只用一次,以后用不用,那是以后的事,现在就封装就是过度设计了。
    matrix67
        16
    matrix67  
       2014-12-05 17:01:51 +08:00 via Android
    楼主头像和我我好像像
    bzmario
        17
    bzmario  
       2014-12-05 17:09:06 +08:00
    sampeng
        18
    sampeng  
       2014-12-05 17:09:28 +08:00
    我会,理由。。
    3个月后,300行的代码,以我的记忆而言,肯定忘得干干净净。。写注释,怎么也不会比小函数简单明了。
    sampeng
        19
    sampeng  
       2014-12-05 17:12:16 +08:00
    补充一句,超过200行代码,不管多简单明了,不管是不是我写的,我都会直接上手自己重构一下。。因为没耐心去看
    ipconfiger
        20
    ipconfiger  
       2014-12-05 17:13:21 +08:00
    如果第一条成立就没有必要拆分。拆分狂魔恨不得一个函数只有一行代码,BT得过分
    goool
        21
    goool  
       2014-12-05 17:16:01 +08:00
    程序里偶尔出现长函数是可以理解的,参数检查、资源申请、错误处理、对象转换等等,七七八八“不干正事”但又不能没有的周边代码加起来自然就长了。

    但是——书上说的总是对的:别写长函数;但是,但是——实际代码总是有妥协的。

    总的来说,别真的太长,长函数别太多,一两个没事,再多就有问题了。
    acros
        22
    acros  
       2014-12-05 17:19:40 +08:00
    我很可能会拆。
    问题在第一句: “do A/B/C中的内容不会被其他函数复用,仅仅在doSomething中被用到”
    这个情况我不太敢肯定。需求永远会变的,即使看起来只被一个地方调用的函数,实际都有很高的概率被复用,先做好这个API比较容易被别人引用。如果写在一个大函数里面,可能自己都想不起来了。
    Sunyanzi
        23
    Sunyanzi  
       2014-12-05 17:20:52 +08:00
    不支持拆 ... 具体参考我之前的回复 /t/150282#reply12 ...

    结构化的部分使用合理的缩进区分 ...
    Her0
        24
    Her0  
       2014-12-05 17:21:06 +08:00
    我不会拆。
    第一:楼主明确说了拆出来的ABC也没有其他函数会调用它们,所以拆了冗余
    第二:拆出来需要每个函数都要重新写一遍参数,万一以后要改,代价就大了
    ipconfiger
        25
    ipconfiger  
       2014-12-05 17:26:08 +08:00   ❤️ 2
    @acros 不太感肯定的时候就先拆了再说是典型的过度设计的表现,首先你怎么知道你的拆分就是完全正确的?第二万一拆分后以后要复用的点不一样,那么你要怎么改?
    借用楼上很多主张拆的同学说的观点,有重构工具了拆分函数很方便,yes,这就是不拆的理由,将来万一需要拆分,有重构工具也就是很简单就可以拆出去了。

    现代IDE有folding功能可以把大函数的几个功能块折叠了,看不是问题

    再次重申,提前优化是万恶之源
    anerevol
        26
    anerevol  
       2014-12-05 18:17:39 +08:00
    通常情况我是拆的,除非ABC相关非常密切。
    忽然忘记300行是啥概念了,翻了翻自己的代码通常一个类200~400行。
    dorentus
        27
    dorentus  
       2014-12-05 18:30:01 +08:00 via iPhone
    如果没有其他原因的话,我不会选择去动这个函数。
    deljuven
        28
    deljuven  
       2014-12-05 18:39:04 +08:00 via Android
    各个部分加注释加log,然后不动。。。
    spacewander
        29
    spacewander  
    OP
       2014-12-05 18:42:08 +08:00
    @binux
    你说得也对,拆分之后适合更加细化去考虑每个子函数的职责。
    有一点我没有提出来……doA和doB和doC间传递(依赖)的参数过多,有4个deque和3个map。这样的参数依赖使得复用是基本不可能的……而且万一要从deque改成list,或者map改成unordered_map,函数参数就要大改了。
    而且现在不拆分,除了相对难以理解,并不会带来什么问题。而即使拆分了,也不会带来什么好处,因为从业务逻辑或者代码间的依赖而言,复用是无需考虑的事。等到将来有需要,再进行拆分,也不算迟。
    williamx
        30
    williamx  
       2014-12-05 18:45:27 +08:00
    对 lz 来说,这其实是一个非常主观的问题,而且自己其实已经有答案了,别人再多说什么也没有意义。我能说的只剩下:
    如果是我来写的话,看着不爽就拆,而不是考虑是不是被复用!
    spacewander
        31
    spacewander  
    OP
       2014-12-05 18:58:08 +08:00
    @binux 你说得对,分拆成各个函数后可以专注于每个函数的职责。
    我忘了提到一点……doA/doB/doC之间依赖非常重,是4个deque和3个map。
    第一个获取数据,填充这几个容器
    第二个根据前面获取的数据,再进行粗加工
    最后一个根据前面两步的结果再进行细加工
    因为相关逻辑的关系,基本上复用不会有的……就算将来要复用,再分拆也不迟。

    总之,现在不拆,麻烦就是程序逻辑相对难懂,但是拆了,就变得不那么灵活了。(这三个之间的耦合太重了,拆开比较困难)

    另外,如果要把deque改成list,把map改成unordered_map,相关的函数参数上的修改会令人抓狂的,而如果不拆,工作量会小很多。
    spacewander
        32
    spacewander  
    OP
       2014-12-05 19:01:27 +08:00
    @repus911
    其实debug(无论是自己还是将来的那谁)的时候,还是得细看源码。
    另外既然你提到了Python了,我只想说“动态语言大法好”!
    spacewander
        33
    spacewander  
    OP
       2014-12-05 19:04:59 +08:00
    网络问题……我还以为第一次回复不成功了呢……
    layout
        34
    layout  
       2014-12-05 19:05:06 +08:00
    支持拆分:
    1.函数过长维护难度变大,有可能变量定义在函数开头,底部还在使用,出错几率增加,同时阅读难度增加;
    2.职责划分,这三块虽然只在一个函数中被调用,但是可以将职责相当的内容封装在一起,便于理解和改动;现在不调用不代表将来不调用;
    3.便于测试,针对一个300行的函数写单元测试....我会奔溃,但是针对3个100行的函数,测试要考虑的点会少一些;

    这个世界是有很多个简单组成的复杂体,而不是一个大而冗长的对象。任何东西大了就会分解成为小的,便于我们的理解和重用。这也是为什么出现了组件化、对象化、函数化等设计方法。
    icylogic
        35
    icylogic  
       2014-12-05 19:22:10 +08:00
    就算题主已经有预设立场也不妨碍我们继续讨论嘛

    我目前觉得拆分函数, 主要就两个目的

    一个是重构成正交函数, 从而方便复用和增强可读性. 如果你觉得这是一段线性的, 连续的逻辑, 而不存在分支或者更复杂结构的逻辑, 那这个理由就没有了

    二是为了测试而调整, 我是这样想的, 比如一个流程是 A->B->C, 各有5种可能的输入会产生 bug, 为了让这些情况都被覆盖到, 你**可能**就要设计一大坨测试用例(取决于ABC之间的关系, A 也许永远不会产生会导致 B 出现 bug 的输出, 那就只需要照顾到 A, 但这是隐患, 如果你将来不记得了, 把 A 改成 A', 可能就会出问题), 但是拆成ABC, 那你每个测试都写很少的用例就可以覆盖到全部情况了
    luoweihua7sync
        36
    luoweihua7sync  
       2014-12-05 20:44:22 +08:00
    同意二楼,单一原则,应该拆分。
    我个人认为,代码有2个主要作用:
    1,运行,不能跑的代码什么都不是
    2,可读(可维护)。
    ryd994
        37
    ryd994  
       2014-12-05 20:55:30 +08:00
    如果确认不可能复用,那不拆也可,但是用多行空行隔开,还有注释,保持清晰。
    chmlai
        38
    chmlai  
       2014-12-05 22:51:03 +08:00
    分开来写成方法不一定非为了复用, 很多时候是分离以下复杂度.
    ariestiger
        39
    ariestiger  
       2014-12-05 23:06:40 +08:00
    当然得拆了。
    尽量减少方法行数,超过 50 行的方法, 如果不是多个循环嵌套或者 if 来 else 去, 那说明这方法里已经干了不少事了, 方法体短一点, 方法名参数名有意义一点,节省别人的思考时间, 也让将来自己回头看的时候脑子少转个弯。
    为什么设计,开发,面向用户的时候都说“不要让我思考”, 等到写代码需要和同事交流,需要和未来的自己交流的时候, 就不这么考虑呢?
    JamesRuan
        40
    JamesRuan  
       2014-12-06 03:58:57 +08:00 via Android
    拆!300行一个函数不能忍!
    函数不仅仅是为了复用而写的,更是为了合理划分功能,提高可读性和可测试性。
    一个几百行的函数,十多个只用一两次的局部变量好,还是几个几十行的函数,每个里个位数的局部变量好?
    可读性,可测试性,可维护性哪个都是后者高。
    损失呢?除了内存使用和调用时间都是O(1),增加的代价可以忽略不计。
    jedihy
        41
    jedihy  
       2014-12-06 11:27:09 +08:00
    可以试试 inline 或者宏定义
    barbery
        42
    barbery  
       2014-12-06 14:27:31 +08:00
    必须拆。。。
    cvrock
        43
    cvrock  
       2014-12-06 16:11:44 +08:00
    unsigned DoAct(ACT_TYPE type)
    {
    unsigned ret = 0;
    switch(type)
    {
    case TYPE_A:
    ret = DoActA();
    break;
    case TYPE_B:
    ret = DoActB();
    break;
    default:
    ret = -1;
    }
    //log、assert什么的统一放这里
    LOG("Do act xxx returns xx.\n");
    return ret;
    }

    我喜欢这样。
    ryanking8215
        44
    ryanking8215  
       2014-12-06 20:41:50 +08:00 via iPad
    我会拆的,初始化的时候这种情况很多,拆开来会比较eye candy。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5795 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 01:54 · PVG 09:54 · LAX 18:54 · JFK 21:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.