你会拆分这样的函数么? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
请不要在回答技术问题时复制粘贴 AI 生成的内容
spacewander

你会拆分这样的函数么?

  •  
  •   spacewander
    spacewander Dec 5, 2014 7248 views
    This topic created in 4163 days ago, the information mentioned may be changed or developed.
    有一个函数,是这样子的:
    (仅仅用作表示,真实函数肯定不叫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一个函数,现在则需要修改三个子函数。分拆函数就是为了把逻辑独立出去,但是这三个函数在逻辑上不一定能够独立开来,分拆它们会把事情变得更复杂。

    各位怎么看?
    Supplement 1    Dec 5, 2014
    先申明最后我没拆……(但是也许以后重构的时候会拆,不过这个说不定。如果有V友因此不爽,我感到抱歉)

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

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

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

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

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

    300行的代码老兄你真的能忍 我是写python的
    88250
        11
    88250  
       Dec 5, 2014
    等纠结完这些,别人同样特性的产品已经上线了....
    jiang42
        12
    jiang42  
       Dec 5, 2014
    肯定拆

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

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

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

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

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

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

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

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

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

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

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

    二是为了测试而调整, 我是这样想的, 比如一个流程是 A->B->C, 各有5种可能的输入会产生 bug, 为了让这些情况都被覆盖到, 你**可能**就要设计一大坨测试用例(取决于ABC之间的关系, A 也许永远不会产生会导致 B 出现 bug 的输出, 那就只需要照顾到 A, 但这是隐患, 如果你将来不记得了, 把 A 改成 A', 可能就会出问题), 但是拆成ABC, 那你每个测试都写很少的用例就可以覆盖到全部情况了
    luoweihua7sync
        36
    luoweihua7sync  
       Dec 5, 2014
    同意二楼,单一原则,应该拆分。
    我个人认为,代码有2个主要作用:
    1,运行,不能跑的代码什么都不是
    2,可读(可维护)。
    ryd994
        37
    ryd994  
       Dec 5, 2014
    如果确认不可能复用,那不拆也可,但是用多行空行隔开,还有注释,保持清晰。
    chmlai
        38
    chmlai  
       Dec 5, 2014
    分开来写成方法不一定非为了复用, 很多时候是分离以下复杂度.
    ariestiger
        39
    ariestiger  
       Dec 5, 2014
    当然得拆了。
    尽量减少方法行数,超过 50 行的方法, 如果不是多个循环嵌套或者 if 来 else 去, 那说明这方法里已经干了不少事了, 方法体短一点, 方法名参数名有意义一点,节省别人的思考时间, 也让将来自己回头看的时候脑子少转个弯。
    为什么设计,开发,面向用户的时候都说“不要让我思考”, 等到写代码需要和同事交流,需要和未来的自己交流的时候, 就不这么考虑呢?
    JamesRuan
        40
    JamesRuan  
       Dec 6, 2014 via Android
    拆!300行一个函数不能忍!
    函数不仅仅是为了复用而写的,更是为了合理划分功能,提高可读性和可测试性。
    一个几百行的函数,十多个只用一两次的局部变量好,还是几个几十行的函数,每个里个位数的局部变量好?
    可读性,可测试性,可维护性哪个都是后者高。
    损失呢?除了内存使用和调用时间都是O(1),增加的代价可以忽略不计。
    jedihy
        41
    jedihy  
       Dec 6, 2014
    可以试试 inline 或者宏定义
    barbery
        42
    barbery  
       Dec 6, 2014
    必须拆。。。
    cvrock
        43
    cvrock  
       Dec 6, 2014
    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  
       Dec 6, 2014 via iPad
    我会拆的,初始化的时候这种情况很多,拆开来会比较eye candy。
    About     Help     Advertise     Blog     API     FAQ     Solana     1210 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 99ms UTC 23:21 PVG 07:21 LAX 16:21 JFK 19:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86