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

2022-09-19 02:52:24 +08:00
 AntiGameZ

问题前置描述

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

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

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

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

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

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

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

--

总结一下

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

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

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

--

解决办法

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

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

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

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

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

--

我的问题

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

--

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

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

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

不是解决问题的办法

还是要反复思考去抽象业务逻辑,提高提高编码质量

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/881126

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX