20年Java老鸟,教你:如何写出“简单“代码

如何写出"简单"代码?

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

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

这句话其实就跟大家熟悉的设计模式中的简单(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。

一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各种大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。

一千道互联网 Java 工程师面试题

内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)
20年Java老鸟,教你:如何写出“简单“代码_第1张图片

初级—中级—高级三个级别的大厂面试真题

阿里云——Java 实习生/初级

List 和 Set 的区别 HashSet 是如何保证不重复的

HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

HashMap 的扩容过程

HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

对象的四种引用

Java 获取反射的三种方法

Java 反射机制

Arrays.sort 和 Collections.sort 实现原理 和区别

Cloneable 接口实现原理

异常分类以及处理机制

wait 和 sleep 的区别

数组在内存中如何分配

答案展示:
20年Java老鸟,教你:如何写出“简单“代码_第2张图片
20年Java老鸟,教你:如何写出“简单“代码_第3张图片

美团——Java 中级

BeanFactory 和 ApplicationContext 有什么区别

Spring Bean 的生命周期

Spring IOC 如何实现

说说 Spring AOP

Spring AOP 实现原理

动态代理(cglib 与 JDK)

Spring 事务实现方式

Spring 事务底层原理

如何自定义注解实现功能

Spring MVC 运行流程

Spring MVC 启动流程

Spring 的单例实现原理

Spring 框架中用到了哪些设计模式

为什么选择 Netty

说说业务中,Netty 的使用场景

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

什么是 TCP 粘包/拆包

TCP 粘包/拆包的解决办法

Netty 线程模型

说说 Netty 的零拷贝

Netty 内部执行流程

答案展示:
20年Java老鸟,教你:如何写出“简单“代码_第4张图片
20年Java老鸟,教你:如何写出“简单“代码_第5张图片

蚂蚁金服——Java 高级

题 1:

  1. jdk1.7 到 jdk1.8 Map 发生了什么变化(底层)?

  2. ConcurrentHashMap

  3. 并行跟并发有什么区别?

  4. jdk1.7 到 jdk1.8 java 虚拟机发生了什么变化?

  5. 如果叫你自己设计一个中间件,你会如何设计?

  6. 什么是中间件?

  7. ThreadLock 用过没有,说说它的作用?

  8. Hashcode()和 equals()和==区别?

  9. mysql 数据库中,什么情况下设置了索引但无法使用?

  10. mysql 优化会不会,mycat 分库,垂直分库,水平分库?

  11. 分布式事务解决方案?

  12. sql 语句优化会不会,说出你知道的?

  13. mysql 的存储引擎了解过没有?

  14. 红黑树原理?

题 2:

  1. 说说三种分布式锁?

  2. redis 的实现原理?

  3. redis 数据结构,使⽤场景?

  4. redis 集群有哪⼏种?

  5. codis 原理?

  6. 是否熟悉⾦融业务?记账业务?蚂蚁⾦服对这部分有要求。

好啦~展示完毕,大概估摸一下自己是青铜还是王者呢?

前段时间,在和群友聊天时,把今年他们见到的一些不同类别的面试题整理了一番,于是有了以下面试题集,也一起分享给大家~

如果你觉得这些内容对你有帮助,可以加入csdn进阶交流群,领取资料

基础篇


JVM 篇


MySQL 篇



Redis 篇




由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

需要的小伙伴,可以一键三连,下方获取免费领取方式!
20年Java老鸟,教你:如何写出“简单“代码_第6张图片

你可能感兴趣的:(java,编程语言)