You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
10 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 结束语 | 写代码时,如何才能尽量避免踩坑?
你好,我是朱晔。
这个课程要告一段落了,在这里我要特别感谢你一直以来的认可与陪伴。于我而言,虽然这半年多以来我几乎所有的业余时间都用了在这个课程的创作,以及回答你的问题上,很累很辛苦,但是看到你的认真学习和对课程内容的好评,看到你不仅收获了知识还燃起了钻研源码的热情,我也非常高兴,深觉一切的辛苦付出都是甜蜜的。
相信一路走来你不仅理解了业务代码开发中常见的130多个坑点的解决方式也知道了其根本原因以及如何使用一些常用工具来分析问题。这样在以后遇到各种坑的时候你就更加能有方法、有信心来解决问题。
## 如何尽量避免踩坑?
不过,学习、分析这些坑点并不是我们的最终目的,在写业务代码时如何尽量避免踩坑才是。所以,接下来,我要重点和你聊聊避免踩坑的一些方法。
所谓坑往往就是我们意识不到的陷进。虽然这个课程覆盖了130多个业务开发时可能会出错的点但我相信在整个Java开发领域还有成千上万个可能会踩的坑。同时随着Java语言以及各种新框架、新技术的产生我们还会不断遇到各种坑很难有一种方式确保永远不会遇到新问题。
而我们能做的就是尽可能少踩坑或者减少踩坑给我们带来的影响。鉴于此我还有10条建议要分享给你。
**第一,遇到自己不熟悉的新类,在不了解之前不要随意使用。**
比如,我在[并发工具这](https://time.geekbang.org/column/article/209494)一讲中提到的CopyOnWriteArrayList。如果你仅仅认为CopyOnWriteArrayList是ArrayList的线程安全版本在不知晓原理之前把它用于大量写操作的场景那么很可能会遇到性能问题。
JDK或各种框架随着时间的推移会不断推出各种特殊类用于极致化各种细化场景下的程序性能。在使用这些类之前我们需要认清楚这些类的由来以及要解决的问题在确认自己的场景符合的情况下再去使用。
而且,越普适的工具类通常用起来越简单,越高级的类用起来越复杂,也更容易踩坑。比如,[代码加锁](https://time.geekbang.org/column/article/209520)这一讲中提到的锁工具类StampedLock就比ReentrantLock或者synchronized的用法复杂得多很容易踩坑。
**第二,尽量使用更高层次的框架。**
通常情况下,偏底层的框架趋向于提供更多细节的配置,尽可能让使用者根据自己的需求来进行不同的配置,而较少考虑最佳实践的问题;而高层次的框架,则会更多地考虑怎么方便开发者开箱即用。
比如,在[HTTP请求](https://time.geekbang.org/column/article/213273)这一讲中我们谈到Apache HttpClient的并发数限制问题。如果你使用Spring Cloud Feign搭配HttpClient就不会遇到单域名默认2个并发连接的问题。因为Spring Cloud Feign已经把这个参数设置为了50足够应对一般场景了。
**第三,关注各种框架和组件的安全补丁和版本更新。**
比如我们使用的Tomcat服务器、序列化框架等就是黑客关注的安全突破口。我们需要及时关注这些组件和框架的稳定大版本和补丁并及时更新升级以避免组件和框架本身的性能问题或安全问题带来的大坑。
**第四,尽量少自己造轮子,使用流行的框架。**
流行框架最大的好处是成熟,在经过大量用户的使用打磨后,你能想到、能遇到的所有问题几乎别人都遇到了,框架中也有了解决方案。很多时候我们会以“轻量级”为由来造轮子,但其实很多复杂的框架,一开始也是轻量的。只不过是,这些框架经过各种迭代解决了各种问题,做了很多可扩展性预留之后,才变得越来越复杂,而并不一定是框架本身的设计臃肿。
如果我们自己去开发框架的话很可能会踩一些别人已经踩过的坑。比如直接使用JDK NIO来开发网络程序或网络框架的话我们可能会遇到epoll的selector空轮询Bug最终导致 CPU 100%。而Netty规避了这些问题因此使用Netty开发NIO网络程序不但简单而且可以少踩很多坑。
**第五,开发的时候遇到错误,除了搜索解决方案外,更重要的是理解原理。**
比如,在[OOM](https://time.geekbang.org/column/article/224784)这一讲我提到的配置超大server.max-http-header-size参数导致的OOM问题可能就是来自网络的解决方案。网络上别人给出的解决方案可能只是适合“自己”不一定适合所有人。并且各种框架迭代很频繁今天有效的解决方案明天可能就无效了今天有效的参数配置新版本可能就不再建议使用甚至失效了。
因此,只有知其所以然,才能从根本上避免踩坑。
**第六,网络上的资料有很多,但不一定可靠,最可靠的还是官方文档。**
比如搜索Java 8的一些介绍你可以看到有些资料提到了在Java 8中Files.lines方法进行文件读取更高效但是Demo代码并没使用try-with-resources来释放资源。在[文件IO](https://time.geekbang.org/column/article/223051)这一讲中,我和你讲解了这么做会导致文件句柄无法释放。
其实网上的各种资料本来就是大家自己学习分享的经验和心得不一定都是对的。另外这些资料给出的都是Demo演示的是某个类在某方面的功能不一定会面面俱到地考虑到资源释放、并发等问题。
因此对于系统学习某个组件或框架我最推荐的还是JDK或者三方库的官方文档。这些文档基本不会出现错误的示例一般也会提到使用的最佳实践以及最需要注意的点。
**第七,做好单元测试和性能测试。**
如果你开发的是一个偏底层的服务或框架,有非常多的受众和分支流程,那么单元测试(或者是自动化测试)就是必须的。
人工测试一般针对主流程和改动点,只有单元测试才可以确保任何一次改动不会影响现有服务的每一个细节点。此外,许多坑都涉及线程安全、资源使用,这些问题只有在高并发的情况下才会产生。没有经过性能测试的代码,只能认为是完成了功能,还不能确保健壮性、可扩展性和可靠性。
**第八,做好设计评审和代码审查工作。**
人都会犯错,而且任何一个人的知识都有盲区。因此,项目的设计如果能提前有专家组进行评审,每一段代码都能有至少三个人进行代码审核,就可以极大地减少犯错的可能性。
比如对于熟悉IO的开发者来说他肯定知道[文件的读写](https://time.geekbang.org/column/article/223051)需要基于缓冲区。如果他看到另一个同事提交的代码,是以单字节的方式来读写文件,就可以提前发现代码的性能问题。
又比如,一些比较老的资料仍然提倡使用[MD5摘要](https://time.geekbang.org/column/article/239150)来保存密码。但是现在MD5已经不安全了。如果项目设计已经由公司内安全经验丰富的架构师和安全专家评审过就可以提前避免安全疏漏。
**第九,借助工具帮我们避坑。**
其实我们犯很多低级错误时并不是自己不知道而是因为疏忽。就好像是即使我们知道可能存在这100个坑但如果让我们一条一条地确认所有代码是否有这些坑我们也很难办到。但是如果我们可以把规则明确的坑使用工具来检测就可以避免大量的低级错误。
比如使用YYYY进行[日期格式化](https://time.geekbang.org/column/article/224240)的坑、使用==进行[判等](https://time.geekbang.org/column/article/213604)的坑、[List.subList](https://time.geekbang.org/column/article/216778)原List和子List相互影响的坑等都可以通过[阿里P3C代码规约扫描插件](https://github.com/alibaba/p3c)发现。我也建议你为IDE安装这个插件。
此外我还建议在CI流程中集成[Sonarqube](https://www.sonarqube.org/)代码静态扫描平台,对需要构建发布的代码进行全面的代码质量扫描。
**第十,做好完善的监控报警。**
诸如[内存泄露](https://time.geekbang.org/column/article/224784)、[文件句柄不释放](https://time.geekbang.org/column/article/223051)、[线程泄露](https://time.geekbang.org/column/article/211388)等消耗型问题往往都是量变积累成为质变最后才会造成进程崩溃。如果一开始我们就可以对应用程序的内存使用、文件句柄使用、IO使用量、网络带宽、TCP连接、线程数等各种指标进行监控并且基于合理阈值设置报警那么可能就能在事故的婴儿阶段及时发现问题、解决问题。
此外,在遇到报警的时候,我们不能凭经验想当然地认为这些问题都是已知的,对报警置之不理。我们要牢记,所有报警都需要处理和记录。
以上就是我要分享给你的10条建议了。用好这10条建议可以帮助我们很大程度提前发现Java开发中的一些坑、避免一些压力引起的生产事故或是减少踩坑的影响。
最后,正所谓师傅领进门,修行靠个人,希望你在接下来学习技术和写代码的过程中,能够养成多研究原理、多思考总结问题的习惯,点点滴滴补全自己的知识网络。对代码精益求精,写出健壮的代码,线上问题少了,不但自己的心情好了,也能得到更多认可,并有更多时间来学习提升。这样,我们的个人成长就会比较快,形成正向循环。
另外,如果你有时间,我想请你帮我填个[课程问卷](https://jinshuju.net/f/pkRg24),和我反馈你对这个课程的想法和建议。今天虽然是结课,但我还会继续关注你的留言,也希望你能继续学习这个课程的内容,并会通过留言区和你互动。
你还可以继续把这个课程分享给身边的朋友和同事我们继续交流、讨论在写Java业务代码时可能会犯的错儿。