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