避坑小总结
避坑小总结
# 1. 慎用不熟悉的新类
遇到自己不熟悉的新类,在不了解之前不要随意使用。例如 CopyOnWriteArrayList
。如果你仅仅认为 CopyOnWriteArrayList
是 ArrayList
的线程安全版本,在不知晓原理之前把它用于大量写操作的场景,那么很可能会遇到性能问题。
JDK或各种框架随着时间的推移会不断推出各种特殊类,用于极致化各种细化场景下的程序性能。在使用这些类之前,我们需要认清楚这些类的由来以及要解决的问题,在确认自己的场景符合的情况下再去使用。
# 2. 尽量使用更高层次的框架
通常情况下,偏底层的框架趋向于提供更多细节的配置,而较少考虑最佳实践;而高层次的框架则会更多地考虑怎么方便开发者开箱即用。
例如,Apache HttpClient
的并发数限制问题。如果你使用Spring Cloud Feign
搭配HttpClient
就不会遇到单域名默认2个并发连接的问题。因为 Spring Cloud Feign
已经把这个参数设置为了50,足够应对一般场景了。
# 3. 关注各种框架和组件的安全补丁和版本更新
比如,我们使用的Tomcat
服务器、序列化框架等,是黑客关注的安全突破口。我们需要及时关注这些组件和框架的稳定大版本和补丁,并及时更新升级,以避免组件和框架本身的性能问题或安全问题带来的大坑。
# 4. 尽量少自己造轮子,使用流行的框架
流行框架最大的好处是成熟,经过大量用户的使用打磨后,你能想到、能遇到的所有问题几乎别人都遇到了,框架中也有了解决方案。很多时候我们会以“轻量级”为由来造轮子,但其实很多复杂的框架,一开始也是轻量的。只是这些框架经过各种迭代解决了各种问题,做了很多可扩展性预留之后,才变得越来越复杂。
例如,直接使用JDK NIO
来开发网络程序或网络框架的话,我们可能会遇到epoll的selector空轮询Bug,最终导致CPU 100%。而Netty
规避了这些问题,因此使用Netty
开发NIO网络程序,不但简单而且可以少踩很多坑。
# 5. 理解原理而不仅仅是解决问题
开发时遇到错误,除了搜索解决方案外,更重要的是理解原理。
网络上别人给出的解决方案可能只是适合“自己”,不一定适合所有人。并且,各种框架迭代很频繁,今天有效的解决方案,明天可能就无效了;今天有效的参数配置,新版本可能就不再建议使用甚至失效了。
# 6. 依赖官方文档而非网络资料
网上的资料有很多,但不一定可靠,最可靠的还是官方文档。例如,搜索Java 8
的一些介绍,你可以看到有些资料提到了在Java 8
中 Files.lines
方法进行文件读取更高效,但是Demo代码并没使用 try-with-resources
来释放资源。
网上的各种资料本来就是大家自己学习分享的经验和心得,不一定都是对的。这些资料给出的都是Demo,演示的是某个类在某方面的功能,不一定会面面俱到地考虑到资源释放、并发等问题。因此,对于系统学习某个组件或框架,最推荐的还是官方文档。
# 7. 做好单元测试和性能测试
如果你开发的是一个偏底层的服务或框架,有非常多的受众和分支流程,那么单元测试(或者是自动化测试)就是必须的。人工测试一般针对主流程和改动点,只有单元测试才可以确保任何一次改动不会影响现有服务的每一个细节点。
此外,许多坑都涉及线程安全、资源使用,这些问题只有在高并发的情况下才会产生。没有经过性能测试的代码,只能认为是完成了功能,还不能确保健壮性、可扩展性和可靠性。
# 8. 做好设计评审和代码审查工作
人都会犯错,而且任何一个人的知识都有盲区。因此,项目的设计如果能提前有进行评审,每一段代码都能有至少一个人进行代码审核,就可以极大地减少犯错的可能性。
例如,对于熟悉IO的开发者来说,他肯定知道文件的读写需要基于缓冲区。如果他看到另一个同事提交的代码,是以单字节的方式来读写文件,就可以提前发现代码的性能问题。
又比如,一些比较老的资料仍然提倡使用MD5摘要来保存密码。但是,现在MD5已经不安全了。如果项目设计已经由公司内安全经验丰富的架构师和安全专家评审过,就可以提前避免安全疏漏。
# 9. 借助工具帮我们避坑
我们犯很多低级错误时,并不是自己不知道,而是因为疏忽。即使我们知道可能存在这100个坑,但如果让我们一条一条地确认所有代码是否有这些坑,我们也很难办到。但是,如果我们可以把规则明确的坑使用工具来检测,就可以避免大量的低级错误。
例如,使用YYYY进行日期格式化的坑、使用==进行判等的坑、 List.subList
原List和子List相互影响的坑等,都可以通过阿里巴巴的代码规约扫描插件发现,避免踩坑。
此外,应该在代码仓库或者构建流中集成Sonarqube
代码静态扫描平台,对需要构建发布的代码进行全面的代码质量扫描。
# 10. 做好完善的监控报警
如内存泄露、文件句柄不释放、线程泄露等消耗型问题,往往都是量变积累成为质变,最后才会造成进程崩溃。如果一开始我们就可以对应用程序的内存使用、文件句柄使用、IO使用量、网络带宽、TCP连接、线程数等各种指标进行监控,并且基于合理阈值设置报警,那么可能就能在事故的婴儿阶段及时发现问题、解决问题。
此外,在遇到报警的时候,我们不能凭经验想当然地认为这些问题都是已知的,对报警置之不理。我们要牢记,所有报警都需要处理和记录。