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

提问对于低质量高复杂度业务代码逻辑的一根筋治理

  •  
  •   AntiGameZ · 2022-09-19 02:52:24 +08:00 · 4105 次点击
    这是一个创建于 825 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题前置描述

    为了便于表述,函数名字用日常生活举例子。问题都是真实的问题

    class 每日生活 {
      public Object 吃早饭 {
      	检查有没有起床();
        if (爸妈在家() && 早饭已经做好()) {
          真正的吃早饭(); // 封装了一堆选叉子还是筷子,端碗还是拿杯子的细节
        } else {
          做饭结果 结果 = 自己做早饭();
          if (结果.能吃()) {
            真正的吃早饭();
          }
        }
      }
    }
    

    今儿个轮到我做上帝,去操纵某个小人,于是实例化了上面这个 class ,上帝我准备发功指挥小人吃早饭。

    然而一开始就炸了,昨晚上小人喝多,睡了沙发。虽然爸妈在家饭也做好了,因为检查起床的逻辑是依赖于床的,没有考虑沙发,吃饭失败。程序在入口就报错了。

    折腾一通踩完坑,也修复了一些逻辑以后。这次我学乖了,想先看看代码的逻辑和依赖关系,尝试去理解那些本不应该需要我去理解的问题。

    光看“爸妈在家()” 并不知道是需要爸妈同时在家,还是只要有一个人在家即可,作为上帝,我也不大明白“爸妈在家”和“早饭做好”的必然联系(为什么?可能因为我家都是爷爷帮忙叫外卖的...)

    之后,花了一大堆时间去理清中间的逻辑,又加入了爷爷奶奶,外公外婆在家,做饭或者叫外卖的各种检查以及实现。

    --

    总结一下

    函数名字并不能如实的反应内在逻辑,即便增加注释也没啥用,而且注释和代码的同步也是个老大难问题,代码审查的人也不是什么时候都认真看代码。

    每个家庭的人口构成,生活习惯的不一样,会导致简单吃早饭这一件事儿的流程和实现也不一样。

    过两天这小人结婚了,还得把老婆 /老公一大家子接进来,又是麻烦事儿。

    --

    解决办法

    理论上书里都有,但是收到人力,财力,时间的限制,我自己不打算完全的去解决问题,我只想做到能够

    • 当问题发生的时候,快速识别问题在哪里
    • 不用完整看完代码,能够知道依赖是什么

    在考虑做一套 annotation ,并且抽象出来一套标签,比如

    @家庭情况(只支持当事人性别为男,父母同住,父亲不会做饭,母亲会做饭,家里从来不允许叫外卖)
    

    当然标签可以不止一个,标签也不用考虑到所有情况。我可以增加一些标签间的依赖关系,再加一个小 plugin 去编译这些 annotation 生成文档。甚至于到我这里的特殊情况,我可以安排人肉去检查标签和实际业务逻辑之间是否一致,是否反映了当下最新的业务需求。

    估计会有人问为啥不用 DDD ,那我就还得再引用上面的“但是收到人力,财力,时间的限制”一遍

    --

    我的问题

    其实想看看有没有什么现成好用的轮子。。。

    28 条回复    2024-10-09 13:09:17 +08:00
    lopssh
        1
    lopssh  
       2022-09-19 05:29:24 +08:00 via Android
    规则引擎?
    akira
        2
    akira  
       2022-09-19 06:30:56 +08:00
    只要早饭做好了,就能真正吃早饭。为啥要把其他逻辑扔进来呢。。
    golangLover
        3
    golangLover  
       2022-09-19 07:16:22 +08:00 via Android
    没有。这种复杂关系只有提早返回以及禁止 nested if 是有用的。其他的都是瞎整。
    vance123
        4
    vance123  
       2022-09-19 07:38:14 +08:00 via Android
    听起来像专家系统
    xuanbg
        5
    xuanbg  
       2022-09-19 08:11:48 +08:00
    OP 你这个问题和抽象有关。换我写的话,应该是这样:
    class 每日生活 {
    public Object 吃早饭 {
    if (不想吃早饭()) {
    return;
    }

    if (没做早饭()) {
    做早饭();
    return;
    }

    吃早饭();
    }
    }
    xuanbg
        6
    xuanbg  
       2022-09-19 08:13:41 +08:00   ❤️ 2
    上面写错了。。。OP 你这个问题和抽象有关。换我写的话,应该是这样:
    class 每日生活 {
    public void 吃早饭 {
    if (不想吃早饭()) {
    return;
    }

    if (没做早饭()) {
    做早饭();
    }

    吃早饭();
    }
    }
    xuanbg
        7
    xuanbg  
       2022-09-19 08:28:13 +08:00   ❤️ 1
    OP 可以说是代表了程序员中的大多数。。。真的,我很多同事都是这样的,把一件事想得特别复杂,很想面面俱到,但总有特殊情况。。。

    这就是典型的只看表面不抓本质。从思维的角度来说,就是不懂得归纳和抽象。任何复杂的事物,只要抽象到一定的层次,就会变得异常简单。
    拿吃早饭这事来举例吧。在抛开吃什么和谁做的这些无关细节后,剩下的无非就是想不想吃和有没有得吃的问题。想吃,也有得吃,那就吃呗。什么?没得吃?那也好办,自己做一份啊。至于不同的食物有不同的吃法,那也简单,封装一个吃饭方法,在这个方法里面处理就行。

    理清事物之间的联系、找到事物的本质,代码就会变得简单,而且基本没有 bug 。
    lazyfighter
        8
    lazyfighter  
       2022-09-19 09:00:20 +08:00
    爸妈不在家,早饭做好了,你不吃? 还要自己做,咋招嫌你爸妈做的早饭难吃?
    AntiGameZ
        9
    AntiGameZ  
    OP
       2022-09-19 09:02:45 +08:00
    @akira
    @golangLover
    @xuanbg
    @xuanbg

    列出来的例子是对现状的转述,我不赞同这么写,但是我也没啥能力纠正它,但是也不想什么都不做。所以一次解决一点问题先(就是我列出来的那俩)


    @xuanbg 其实对于我自己,问题的本质就是“不想花太多时间 debug ,问题出来了就能快速定位而不是一行行去抠代码(有意无意的,我这抽象的代码里一行 log 都没有,狗头)”

    就像你提到的,想吃,吃不吃得着,就不是我要关心的问题。如果我强行要去关心,就超越了自己的职权,老板们要闹的。

    小小一个码农,对于成型系统来说,不问本质只看表面是不是成熟的表现呢?
    leegradyllljjjj
        10
    leegradyllljjjj  
       2022-09-19 10:41:03 +08:00
    楼主可以考虑一下状态机吧,看看这篇文章
    https://www.cnblogs.com/CKExp/p/11767985.html
    wolfie
        11
    wolfie  
       2022-09-19 10:46:44 +08:00
    模板(能不能吃饭、有没有饭的判断) + 责任链(当前状态)
    DreamStar
        12
    DreamStar  
       2022-09-19 11:20:59 +08:00   ❤️ 2
    首先是代码写的不要职责太多, 一棵树从不同角度学科看能刨析出众多属性来, 抽象要合理, 限制在你的实际问题内.
    其次是复杂度是恒定的, 没有任何一种方式能规避复杂度, 他只会从你看的到的地方转移到你看不到的地方.
    Actrace
        13
    Actrace  
       2022-09-19 11:27:05 +08:00   ❤️ 1
    楼主,一根筋这种解法是不存在的,没有永动轮。
    反倒是,只用轮子,不了解轮子,最终会导致技术水平原地踏步。
    解决问题的核心是熟练运用编程语言的各种特性,了解优劣,才能选择最佳方案。
    想清楚这点,就明白为什么会有“低质量的代码”,以及为何需要重构了。

    首先优化的前提是基于现有的业务和现有的瓶颈,但是业务是不断变化的,你不可能优化正在变化的业务,很多时候你觉得现在合理的事情,也就现在合理,日后并不合理。后来人根据后来的情况再看你现在“已经基于现状优化好的代码”,情况也会有所不同。

    所以,一般来说,业务优化都是当业务沉淀了一段时间后,再回过头来看,哪些地方不合理,哪些地方需要变得合理。在大多数情况下(请注意,是大多数情况下),现在看起来低质量业务的代码,不是前人的技术问题,而是当时的最优解。因为对于软件工程来说,项目完成时间永远是第一考量,其它指标靠后。

    V2 的老帖子有一句名言:“这是一个创建于 xxx 天前的主题,其中的信息可能已经有所发展或是发生改变。”
    BingoXuan
        14
    BingoXuan  
       2022-09-19 11:51:20 +08:00
    不还是工厂模式吗?构建实例时候做约束就好了
    xiangyuecn
        15
    xiangyuecn  
       2022-09-19 12:22:17 +08:00
    try{
    万物皆可调用()
    }catch(e){
    e.print 啥玩意来着()
    //这后面必须完全没有代码🐶
    }
    AntiGameZ
        16
    AntiGameZ  
    OP
       2022-09-19 13:58:55 +08:00
    @leegradyllljjjj
    @DreamStar
    @Actrace
    @BingoXuan

    各位大佬,道理都懂,正如标题所说,对稀烂的现状我是有承认的。但是,之所以又说是一根筋,是因为不大可能在短期会有机会去从代码结构层面进行调整。退一步说,即便我真有一个“把大部分都做对的”设计,但是应用这个设计需要资源,资源对应到实际来说就是
    * 需要人
    * 需要时间
    * 需要有足够的收益去说服老板们拿到这些人和时间
    * 风险还不能太高

    然而,对于一个使用中的,性能和吞吐都没啥问题的系统来说,重构和甚至大规模一点的重构带来的收益都是不足以换来需要的资源的。

    对于老板来说,本来两个月你可以交付一个营收增长 XX% 的系统,现在你花了两个月去“做了一个设计更合理的系统”,且不谈能不能做成功,即便做成功了,XX% 增长的机会成本是损失了没跑,另外“更优秀设计”带来的生产力提升可能要持续 1 年才能看到。

    怎么权衡这种事儿,不是我拿这个工资的人需要考虑的。

    --

    我梳理梳理自己能做的事儿,能够阻止屎山扩张,能够减少问题出现寻找问题根源的耗时似乎是唯二能在 2-3 个月里能做的事儿,这才为啥提到楼顶的想法。

    希望没再进一步误导大家。
    jones2000
        17
    jones2000  
       2022-09-19 14:55:30 +08:00
    每个人任何一个动作, 都需要有前置条件检测, 和执行完以后结果检测, 至于检测规则可以让业务人员加。
    alexsunxl
        18
    alexsunxl  
       2022-09-19 15:32:01 +08:00
    是不是想太多,看书有点少。 可以考虑看看 《 Code Clean 》之类的
    aguesuka
        19
    aguesuka  
       2022-09-19 15:40:52 +08:00
    用纯函数, 不要搞什么获得一个对象保存在当前对象的属性里, 然后在下一个方法中使用, 而是应该当成参数传进去
    zhazi
        20
    zhazi  
       2022-09-19 16:11:51 +08:00
    @家庭情况(只支持当事人性别为男,父母同住,父亲不会做饭,母亲会做饭,家里从来不允许叫外卖)
    @家庭情况(只支持当事人性别为男,父母同住,父亲会做饭,母亲不会做饭,家里从来不允许叫外卖)
    @家庭情况(只支持当事人性别为男,父母同住,父亲不会做饭,母亲会做饭,家里允许叫外卖)
    @家庭情况(只支持当事人性别为男,父母同住,父亲会做饭,母亲不会做饭,家里允许叫外卖)
    @家庭情况(只支持当事人性别为女,父母同住,父亲不会做饭,母亲会做饭,家里从来不允许叫外卖)
    @家庭情况(只支持当事人性别为女,父母同住,父亲会做饭,母亲不会做饭,家里从来不允许叫外卖)
    @家庭情况(只支持当事人性别为女,父母同住,父亲不会做饭,母亲会做饭,家里允许叫外卖)
    @家庭情况(只支持当事人性别为女,父母同住,父亲会做饭,母亲不会做饭,家里允许叫外卖)

    这种方式没有解决程序的复杂性 还引进了新的复杂度 维护到后期发展到在注解里写逻辑

    不是解决问题的办法

    还是要反复思考去抽象业务逻辑,提高提高编码质量
    Actrace
        21
    Actrace  
       2022-09-19 18:04:55 +08:00
    @AntiGameZ 楼主有点像我刚出来工作那会儿。。。

    唉,多说无益,想做就去做吧,多交点学费才能毕业。
    tikazyq
        22
    tikazyq  
       2022-09-19 18:10:12 +08:00
    如果 `结果.不能吃()` 呢?
    zddwj
        23
    zddwj  
       2022-09-19 19:16:22 +08:00
    看了下楼主的需求就是想优雅的在屎山上跳舞,这种事我有经验。(从长远看不推荐这么干,还是得多看系统设计相关的书多思考)
    首先就是要熟悉调试工具,断点调试,单步调试是必须的,这些可以帮你快速定位关键代码。
    其次定位问题的思路:首先看能不能通过各种 log 分析直接定位出来,不行的话就用 diff 加二分查找的方式一步一步缩小问题范围。

    对于你上面的问题需要增加或者修改功能,如果你不想重构,直接定义一个 class 每日生活 2 ,把代码复制过来,再在里面加 if 写业务逻辑,完事出 bug 再用我上面的方法定位关键代码修复。这种具体业务实现代码是没有复用性可言的,强行复用只会增加不必要的耦合,导致原本无关的复杂度叠加
    akira
        24
    akira  
       2022-09-19 22:54:37 +08:00
    没有银弹
    AntiGameZ
        25
    AntiGameZ  
    OP
       2022-09-20 01:29:06 +08:00
    @alexsunxl
    @Actrace
    书看的多了 + 学费交的多了才会无奈
    尤其在代码历史复杂,同时贡献代码的人水平参差不齐的情况下
    企业,包括咱自己都是逐利的,2 个月内搞不定的事儿就不如不搞。所以在屎山,最佳实践,KPI 面前,我时不时得提醒自己克制


    @jones2000
    是这个道理,但是这么做代码侵入性较高。大概评估了下不是我一个人在 2-3 个月里能搞定的。所以我就想跳出修改现有代码逻辑的范畴,起码先把实际情况梳理梳理,也是为什么想去打 annontation


    @zhazi
    是这么个道理,这也是为什么在考虑引入这套玩意儿的时候我还举棋不定。这条路走到底可能就是搞一个领域语言,但是这也是个大坑我在可见的未来 1 年内填不了
    zand
        26
    zand  
       2023-04-24 17:43:15 +08:00
    @xuanbg 你好,看了你的回复有些疑惑,你给出的样例代码是简单了,但似乎没有解决 op 的问题,因为楼主提到的那些“小人在不在床上”,“父母有没有做饭”,“爸妈在家”总是要解决的 case ,你的意思似乎是不要在“吃早饭”这个函数中解决这些细节?但总是要有其他函数来干这些活吧
    (非挑战,只是求解惑)
    xuanbg
        27
    xuanbg  
       2023-04-25 07:39:37 +08:00
    @zand 起不起床和吃饭有必然联系吗?在床上不也能吃饭吗?就吃饭这件事而言,你说的那些事都不存在必然的联系。既然都是独立事件,那就应该独立处理。然后,有个最底层的事件循环叫“生活”。
    charlie21
        28
    charlie21  
       74 天前
    @xuanbg #6 #7 你写得非常对
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2790 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 14:37 · PVG 22:37 · LAX 06:37 · JFK 09:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.