如何写出"简单"代码

1,323 阅读12分钟

如何写出"简单"代码?

之前因为公司事情一直比较多,所以没更新内容,朋友在我账号发过一篇,最近不忙了,继续更新文章。

当你做实际开发中,你一定会发现有人跟你说,或者你在网上看:要写简单代码要写简单代码。

这句话其实就跟大家熟悉的设计模式中的简单(kiss)原则是挂钩的,如果学习过设计模式的小伙伴,在学习到kiss原则的时候,或多或少会遇到下面几个问题:

  • 为什么身边的程序员都告诉你保持"简单"代码很重要?
  • 什么才是好的"简单"的代码?
  • 如何写出"简单"的代码?
  • yagni原则和kiss原则是相同的么?

这些问题看上去简单,回答起来却不简单。

为什么要让代码保持"简单"

对于目前国内一些中小厂,甚至大厂的一些部门的程序员,代码写出来就是能运行就行了,为什么要让代码保持"简单"并不了解。其实有以下三点原因:

  1. 防止代码腐坏。这很容易理解,当你公司启动一个项目的时候,这时候需求很简单,业务很简单,可能开发就你们两三个人做后端开发。代码简单可读,逻辑清晰,可是随着时间的推移,业务线越来越多,业务越来越复杂,越来越多的人参与到项目当中。代码数量呈指数级增长,逻辑越来越复杂,代码变的难以理解维护。这个时候,代码就开始腐坏了。保持代码简单性,一方面可以降低模块的理解难度,另一方面也让代码修改的范围边界更清晰,这样能有效的防止代码腐坏。

  2. 减少时间成本的投入。我们知道软件复杂度有三个来源:代码规模,技术复杂度和实现复杂度。通常来说,代码越复杂,在这三方面投入的时间成本就会越多。比如,维护10万行代码和维护1000行代码所需要的投入的理解与逻辑梳理时间是不同的。再比如:使用Redis作为缓存和使用LRU本地缓存所需要的学习时间也是不同的。

    对于开发人员,维护代码必然需要花时间修改,调试,理解内在逻辑并让代码正常运行,一旦代码非常复杂,势必会增加时间成本,进而让开发团队的研发成本增加。如果代码能够保持简单清晰,那么最直接的效果就是降低学习与维护代码的时间,这是很多开发团队都愿意看到的结果。

  3. 第三,快速迭代,拥抱变化。虽然现在越来越多的团队开始采用敏捷开发,但是在现实中,一直陷在“业务需求永远也做不完的死循环”里才是开发团队的现状,而这意味着开发人员只有更少的时间写代码,即便有想要写简单代码的心,迫于进度压力也只能尽快写能工作的代码。久而久之,团队代码变得越来越复杂,开发效率越来越低,本来初衷是想拥抱变化,却被迫变成无力承受变化。要想改变这种现状,有效的办法之一就是保持简单性,比如,更好的测试性、更好的扩展性、更好的理解性、更灵活的设计等。

如何理解代码中的"简单"?

你是不是常常会听到很多类似这样的关于 KISS 原则的讨论?

  • 产品需求文档都出来了,你就自己看怎么简单怎么设计吧,功能要快速上线。
  • 这个功能不是有现成的类库吗?选择一种简单的类库封装一下就行了吧,客户明天就想要这个功能。
  • 现在开源框架这么多,随便选一个简单的来实现,我不关心技术,只关心能不能解决问题。
  • 你看 XX 大厂的设计多简单、体验多好,你们为什么不也搞成那种简单的呢?

你发现没,很多时候我们都有一种"简单"的错觉:简单分析+简单设计+简单编程 = 简单产品。但实际上,这种简单组合就能构造出最终的"简单"吗?

在我看来,在软件开发中,“简单”其实是最终的一个状态。管理层或终端用户的确不需要关心背后复杂的代码逻辑,然而对于开发人员来说,就不得不去考虑背后各种各样的复杂性。换句话说,编程的本质就是控制复杂度。

那到底该如何正确理解代码中的“简单”呢?

首先,简单!=简单设计或简单编程。 简单一旦和动词放在一起,太容易被误解,尤其是在软件开发中。比如,现在我们在很多项目开发中,为了完成进度而做简单设计甚至不设计,认为只要后期有需要时再重构就行,编码时也就采用简单编程,并美其名曰迭代敏捷开发。但实际上,项目到后期几乎没有时间重构,并频繁出现问题(定位时间变长、逻辑嵌套过深、不断打补丁等),最后项目往往以失败告终。(这是目前大部分项目都会出现的问题,前面做好设计,就想着cv,然后快速上线,想着后面优化后面优化,结果后面需求越来越多,前面bug没解决,后面又增加新的bug,最后导致整个项目非常难维护,哪里出问题直接崩溃)

所以说,保持简单并不是只能做简单设计或简单编程,而是做设计或编程时要努力以最终产出简单为目标,过程可能非常复杂也没关系。

其次,简单!=数量少。 我们通常把简单好坏和数量多少挂钩,比如,代码行数少、使用的类库组件少、架构设计少似乎就是简单。但实际上,数量少可能只是表现出来的简单,也会引入更加复杂的问题的。(比如在项目中,我们常常为了想让项目中少引入中间件而让项目变的简单,比如大数据下,我们还在用mysql做一些大数据量的复杂查询)

最后,简单!=过度简洁。如果你见过被加密的代码,那你一定知道什么叫过度简洁的代码。这样的代码机器可读,但是对于人来说几乎不可读。在现实中,我们可能都曾写过这样的代码:没有任何注释说明,不使用任何设计模式,用最直接的数据结构和算法来实现,使用字母缩写来命名变量。然后,过一段时间后,连自己都看不懂这样的简洁代码,除非再从头到尾看一遍才能回想起含义。如果换成别人,那么可想而知其阅读难度有多大。所以说,简单代码可能会很简洁,但一定不是过度简洁。

下面我们再来看“简单”是什么。

  1. 简单应该是坚持实践。 我们常说代码应该简单,但却忽略了更重要的是保持简单的动作。换句话说,道理我们都懂,但实际上写代码时并没有坚持做。保持简单之所以很困难,是因为大多数人都只盯着最后实现简单后的好处,却忘记了在做到简单之前需要付出的努力。

    真正的简单代码通常背后都隐藏了大量不简单的工作,比如,仔细分析需求,选择合适的技术框架,设计更合适的数据结构和算法,实现时保持代码可读性,等等,每一件事都不简单,并且长期坚持才有可得到最后的简单。

  2. 简单应该是尽量简单,但又不能太简单。换句话说,就是要管理合适的代码上下文环境,并且在边界范围内以“最少知识”的方式构建程序,满足要求即可,保持一定的克制。很多时候,我们之所以容易过度开发功能,是因为没有考虑上下文的边界,进而导致需求扩散而不断扩充知识。比如说,需求中只要求你开发一个编辑器,你却在开发过程中发现了你想要试验的新功能,最后你开发了一个 IDE。从用户角度来看,他只需要一个简单的编辑器,虽然你做的事情也满足了要求,但你把简单的事情搞复杂了。

  3. 简单应该是让别人理解代码逻辑时更简单。 代码写出来后,80% 的时间都在被阅读,简单代码的好处在于能让别人一眼就知道代码表达的意图,要想做到这样,就对写代码的人提出了一个更高的要求:不仅需要使用清晰的算法和数据结构实现代码逻辑,还需要使用面向对象编程技巧提升代码复用性,甚至需要写更多的单元测试和注释来提升可维护性。总之,好的代码就是将简单带给别人,复杂留给自己。

如何写出"简单"代码?

  1. 不要长期进行打补丁式的编码。 因为这会给团队树立一个不好的榜样,当你在打补丁式的编码中快速获得收益时(修改快,见效快),就会不知不觉地给其他人一种心理暗示——这段代码只需要不断打补丁就能解决问题,那么维护代码的人一定会优先选用这个方法,而不是重构它。

  2. 不要炫耀编程技巧。 如果你的团队对 Java 8 的 Lamda 表达式语法还不够熟悉,那么你不要一开始就写这样的代码,但可以通过不断分享 Lamda 表达式的优势和知识来帮助团队提升编程实力(间接提升你的技术影响力)。切记不要为了彰显自己厉害而使用一些技巧,尤其是在一些维护项目上使用高级语法,这会很容易导致维护代码的人需要花费大量的精力和时间去学习或研究你的代码。

  3. 不要简单编程。 硬编码、一次性编码、复制粘贴编码、面向搜索编程都是简单编程,如果一直习惯性地简单编程,那么带来的可能就是更复杂、更高成本的重构和重写。这不仅不能提升代码扩展性,还会使得代码在后期无法被维护和重构。局部的简单导致整体的更加复杂,这是现在公认的一种得不偿失的做法。

  4. 不要过早优化。 为了让代码变得简单,优化是必不可少的手段,但是过早的优化会造成很多核心代码逻辑被隐藏,而维护代码的人为了不破坏原有的设计(误认为越早的设计就会越好),只能不断修改现有的设计来适应这种不变,最后反而容易导致架构可能被破坏。

如果说上面是四不要,下面就还有四要。

  1. 要定期做 Code Review。 如果说简单性的判断标准很难统一,但是一眼让人快速理解的代码始终都是好代码,当他人在阅读你的代码时会自然对你的代码做评价,无论好坏,这都是你思考代码是否足够简单的契机。

  2. 要选择合适的编码规范。 编码规范是优秀编码实践的经验总结,能帮助你写出“简单”代码,并发展出一种简单性的编程风格。不过,现在很多大厂的编码规范过于通用,虽然很好,但是不一定适合你,这时你应该进行适度的裁剪优化,逐渐找出真正适合你的经验,发展出适合你的“简单”编码风格。

  3. 要适时重构。 并不是说非得要等到代码完全无法修改时再重构,你完全可以轻松地在每一个小的迭代版本里进行重构,比如,分离一个过多职责的类,抽象一个上次没来得及做的通用服务,减少业务里 if-else 的嵌套层数,对不同业务数据对象分包管理等。及时这样做以后,代码整体就会变得简单。

  4. 要有目标地逐渐优化。优化一定要制定一个目标,不然很容易就成了盲目优化,甚至把优化当成了一种单纯的 KPI。如果程序性能都没有达到真实的性能瓶颈,就没有任何优化的必要。而且优化应该是分阶段和步骤的,不要搞一次性的大优化、大提升,这样会导致频繁的代码修改,反而容易引入更多的 Bug。