如何写一个清晰明了的bug
bug是不可避免。但如何让自己的bug写得清新脱俗,结构清楚则是需要我们不断努力的。
在开始今天的话题之前,先抛出一个问题,代码结构好是好事吗?
代码结构好事好事吗?
该图是我的票圈里一位兄弟转发的。代码结构好了,别人接手容易,反倒是写得烂了,却可以成为焦点。你咋一听觉得这是什么神逻辑,虽然听着有道理,但总感觉有点政治不正确。
这个是一个问题,很值得思考的问题。
写程序,就是写逻辑,逻辑最初的样子,就是用if else来表达,事实上这就是我们描述这个世界的基本方式,if else。这两个分支可以覆盖一切情况。你能告诉我们还有if else之外的场景吗?!
可以这么说,if else可以描述这个世界上所有的逻辑。
if else就是整个世界
你手握if else 两个单词,心想,产品你尽管提需求吧,这个世界上还有我if else解决不了的问题吗。而且久而久之,你会发现写代码就是if else,整天就在if else两个代码块里盘旋。
慢慢的,你开始彷徨,开始思考人生,甚至怀疑人生,难道程序这么无聊吗?if else 外加crud就可以解决一切?
编程届两大流派
这个还是要从理论说起,其实描述这个世界有两种方式。一种是函数算法派,一种面向对象派。
而函数算法派其实就是if else派,这一派是一个古老的门派,他们围绕着一个方法体(或者叫函数)就可以一直写下去并且能解决问题。
而面向对象派则主张通过结构和组合的方式来解决问题,而不是围绕着if else来搞事情。
可以毫不夸张的说,我们现如今绝大多数人也包括我本人在内,我们都还处于函数算法派,也就是if else派。至于面向对象这些东西,充其量注入的时候体会一下,或者在new的时候体会一下。其余时候我们都是在安静的写着if else。
然而if else最终让我们走向不归路。
前面我们说过if else可以覆盖整个世界。但覆盖范围广并不等于它明了。
开始的时候,你发现自己的if else异常明了和清晰。
直到后来,一步步,就变成下面这样了:
直到后来,你开始怀疑人生,于是你决定去看一些技术书籍,让自己的心灵重新洗涤一下,这样第二天才能稍微平静的面对昨日遗留的if else。
避免if else泛滥的四法则:一提二抽三组四模式
那么我们如何避免if else的过渡泛滥呢?我总结了一个法则:一提二抽三组四模式。
1、一提
以下的代码我是从真实的项目代码中摘取的。
bad case:
good case:
上面两段代码执行了相同的逻辑,但第一种层次更多,可读性也差,这还是刚开始的代码,随着需求的不断变化,这段代码的层次结构最后就会变成一堆乱麻。这里其实并没有用什么技术,就是简单的对代码逻辑路径进行重新的编排,从而实现了代码的整洁和更好的可读性。
在if else的优化中,一个核心的思路就是:更少的缩进、更少的else。
上面重构后的代码,你可以看到缩进线由三条变成了两条,同时通过把异常场景的逻辑前提的方式去掉了else块。异常场景逻辑一般指负向的条件,比如==null,notExist,XxxException等。理想的代码,总是应该把这些异常情况,提前排除掉,然后才安心的去写主业务逻辑。这样你的代码就会显得层次分明。
理想的代码应该有的样子:
二抽
在有限代码行数内通过第一个法则可以让结构更加的清晰。当if中的代码行数过多时,这会就需要把可以独立成为方法的逻辑抽取成一个private的方法(也可以是public等),代码过长时,我们总是需要这样去做,这样可以让你的主方法就像一篇文章一样具有可读性。
依然是上面的forYes方法。我们把exist的逻辑单独抽取了一个方法,同时又把主逻辑代码也抽了一个方法,你会发现forYes1方法的代码长度并没有增加多少,依然保持的良好的可读性。
三组
上面的第二法则是抽取一个private方法,还没有出类。当我们抽取到一定地步,会发现适合单独成类的时候,应该把之前的这些private方法移动到一个新的类中。这个就是第三法则:组合,通过组合的方式来构建你的逻辑。
四模式
在用完组合后,我们的代码其实已经基本上比较清楚了。但为了让我们的代码更加的优雅,更上一个层次。有的场景下你需要使用到设计模式,设计模式被总结成为最佳实践,不是仅仅用来写框架用的,写日常纷繁复杂的业务逻辑代码也是需要设计模式的。
接下来我就以自己正在开发的项目中的场景为例,来说说如何使用设计模式改善你的既有代码。
在项目中我们需要为审批工作流提供一个回调(callback)接口。审批流有不同的状态,不同的状态回调会执行不同的逻辑。在重构前的代码大体是这样的:
这样的处理方式还不错。但当状态变多时,你就会一直扩张这个callback方法,这显然不是一种好的方案。为此你需要使用一个设计模式来改善现在的实现。
如果你还记得设计模式,你一定还记得有一个状态模式(State Pattern)。
现在我们就尝试使用状态模式来重构我们的代码。
我们希望最终的样子是这样的:
首先新建一个State接口类:
然后新建三个实现状态,分别是Yes,No和Cancel:
然后新建一个Context类:
然后,新建一个State工厂类:
改造完毕。通过上面的重构,我们使用了状态模式和一点点工厂模式。最终callback方法就只需通过newInstance就可以找到具体状态的回调逻辑,而以后即使状态在不断的增加的,你也只需新建一个新的实现状态,然后注入工厂类中,做到了可插拔。值得注意的是,这里我们的state其实并没有很好的被传递和持有,这很不“状态”,这通过这样的方式我们实现了对callback的重构,结构也更加的清晰了。总之,当你遇到业务需求的不断变化,你需要找到一种合适的设计模式来hold住它,即使GOLF不能满足你的需求,你也可以自己创造一个设计模式来让你的代码清晰易懂。
总结
本文一开始介绍了if else泛滥后的问题。然后为你介绍了“一提二抽三组四模式”法则。我们的项目也是万事万物中的一部分,套用《规模》一书中的一段话:耗散力一直在持续且不可避免地做着功,这使得所有系统都将退化。设计最为精巧的机器、最具创新组织力的公司、进化得最完美的生物都无法逃脱这一最为严酷的死神。耗散力就是一个系统运转所产生的摩擦力,系统复杂性所带来的无序热量,你的代码也一样,也需要通过不断的改进和重构来尽量的延缓和降低熵的增长。
最终我们都将屈服于各种形式的磨损和衰竭,熵能杀人,你需要吃饭!