写点什么

作为一个老程序员,想对新人说什么?

作者:这我可不懂
  • 2023-07-20
    福建
  • 本文字数:7422 字

    阅读完需:约 24 分钟

在我是新人时,如果有前辈能够指导方向一下,分享一些踩坑经历,或许会让我少走很多弯路,节省更多的学习的成本。

这篇文章根据我多年的工作经验,给新人总结了 25 条建议,希望对你会有所帮助。

1.写好注释

很多小伙伴不愿意给代码写注释,主要有以下两个原因:

  • 开发时间太短了,没时间写注释。

  • 《重构》那本书说代码即注释。

我在开发的前面几年也不喜欢写注释,觉得这是一件很酷的事情。

但后来发现,有些两年之前的代码,业务逻辑都忘了,有些代码自己都看不懂。特别是有部分非常复杂的逻辑和算法,需要重新花很多时间才能看明白,可以说自己把自己坑了。

没有注释的代码,不便于维护。

因此强烈建议大家给代码写注释。

但注释也不是越多越好,注释多了增加了代码的复杂度,增加了维护成本,给自己增加工作量。

我们要写好注释,但不能太啰嗦,要给关键或者核心的代码增加注释。我们可以写某个方法是做什么的,主要步骤是什么,给算法写个 demo 示例等。

这样以后过了很长时间,再去看这段代码的时候,也会比较容易上手。

2.多写单元测试

我看过身边很多大佬写代码有个好习惯,比如新写了某个 Util 工具类,他们会同时在 test 目录下,给该工具类编写一些单元测试代码。

很多小伙伴觉得写单元测试是浪费时间,没有这个必要。

假如你想重构某个工具类,但由于这个工具类有很多逻辑,要把这些逻辑重新测试一遍,要花费不少时间。

于是,你产生了放弃重构的想法。

但如果你之前给该工具类编写了完整的单元测试,重构完成之后,重新执行一下之前的单元测试,就知道重构的结果是否满足预期,这样能够减少很多的测试时间。

多写单元测试对开发来说,是一个非常好的习惯,有助于提升代码质量。

即使因为当初开发时间比较紧,没时间写单元测试,也建议在后面空闲的时间内,把单元测试补上。

3.主动重构自己的烂代码

好的代码不是一下子就能写成的,需要不断地重构,修复发现的 bug。

不知道你有没有这种体会,看自己 1 年之前写的代码,简直不忍直视。

这说明你对业务或者技术的理解,比之前更深入了,认知水平有一定的提升。

如果有机会,建议你主动重构一下自己的烂代码。把重复的代码,抽取成公共方法。有些参数名称,或者方法名称当时没有取好的,可以及时修改一下。对于逻辑不清晰的代码,重新梳理一下业务逻辑。看看代码中能不能引入一些设计模式,让代码变得更优雅等等。

通过代码重构的过程,以自我为驱动,能够不断提升我们编写代码的水平。

4.代码 review 很重要

有些公司在系统上线之前,会组织一次代码评审,一起 review 一下这个迭代要上线的一些代码。

通过相互的代码 review,可以发现一些代码的漏洞,不好的写法,发现自己写代码的坏毛病,让自己能够快速提升。

当然如果你们公司没有建立代码的相互 review 机制,也没关系。

可以后面可以多自己 review 自己的代码。

5.多用 explain 查看执行计划

我们在写完查询 SQL 语句之后,有个好习惯是用 explain 关键字查看一下该 SQL 语句有没有走索引。

对于数据量比较大的表,走了索引和没有走索引,SQL 语句的执行时间可能会相差上百倍。

我之前亲身经历过这种差距。

因此建议大家多用 explain 查看 SQL 语句的执行计划。

关于 explain 关键字的用法,如果你想进一步了解,可以看看我的另外一篇文章《explain | 索引优化的这把绝世好剑,你真的会用吗?》,里面有详细的介绍。

6.上线前整理 checklist

在系统上线之前,一定要整理上线的清单,即我们说的:checklist。

系统上线有可能是一件很复杂的事情,涉及的东西可能会比较多。

假如服务 A 依赖服务 B,服务 B 又依赖服务 C。这样的话,服务发版的顺序是:CBA,如果顺序不对,可能会出现问题。

有时候新功能上线时,需要提前执行 sql 脚本初始化数据,否则新功能有问题。

要先配置定时任务。

上线之前,要在 apollo 中增加一些配置。

上线完成之后,需要增加相应的菜单,给指定用户或者角色分配权限。

等等。

系统上线,整个过程中,可能会涉及多方面的事情,我们需要将这些事情记录到 checklist 当中,避免踩坑。

7.写好接口文档

接口文档对接口提供者,和接口调用者来说,都非常重要。

如果你没有接口文档,别人咋知道你接口的地址是什么,接口参数是什么,请求方式时什么,接口多个参数分别代码什么含义,返回值有哪些字段等等。

他们不知道,必定会多次问你,无形当中,增加了很多沟通的成本。

如果你的接口文档写的不好,写得别人看不懂,接口文档有很多错误,比如:输入参数的枚举值,跟实际情况不一样。

这样不光把自己坑了,也会把别人坑惨。

因此,写接口文档一定要写好,尽量不要马马虎虎应付差事。

如果对写接口文档比较感兴趣,可以看看我的另一篇文章《瞧瞧别人家的 API 接口,那叫一个优雅》,里面有详细的介绍。

8.接口要提前评估请求量

我们在设计接口的时候,要跟业务方或者产品经理确认一下请求量。

假如你的接口只能承受 100qps,但实际上产生了 1000qps。

这样你的接口,很有可能会承受不住这么大的压力,而直接挂掉。

我们需要对接口做压力测试,预估接口的请求量,需要部署多少个服务器节点。

压力测试的话,可以用 jmeter、loadRunner 等工具。

此外,还需要对接口做限流,防止别人恶意调用你的接口,导致服务器压力过大。

限流的话,可以基于用户 id、ip 地址、接口地址等多个维度同时做限制。

可以在 nginx 层,或者网关层做限流。

9.接口要做幂等性设计

我们在设计接口时,一定要考虑并发调用的情况。

比如:用户在前端页面,非常快的点击了两次保存按钮,这样就会在极短的时间内调用你两次接口。

如果不做幂等性设计,在数据库中可能会产生两条重复的数据。

还有一种情况时,业务方调用你这边的接口,该接口发生了超时,它有自动重试机制,也可能会让你这边产生重复的数据。

因此,在做接口设计时,要做幂等设计。

当然幂等设计的方案有很多,感兴趣的小伙伴可以看看我的另一篇文章《高并发下如何保证接口的幂等性?》。

如果接口并发量不太大,推荐大家使用在表中加唯一索引的方案,更加简单。

10.接口参数有调整一定要慎重

有时候我们提供的接口,需要调整参数。

比如:新增加了一个参数,或者参数类型从 int 改成 String,或者参数名称有 status 改成 auditStatus,参数由单个 id 改成批量的 idList 等等。

建议涉及到接口参数修改一定要慎重。

修改接口参数之前,一定要先评估调用端和影响范围,不要自己偷偷修改。如果出问题了,调用方后面肯定要骂娘。

我们在做接口参数调整时,要做一些兼容性的考虑。

其实删除参数和修改参数名称是一个问题,都会导致那个参数接收不到数据。

因此,尽量避免删除参数和修改参数名。

对于修改参数名称的情况,我们可以增加一个新参数,来接收数据,老的数据还是保留,代码中做兼容处理。

11.调用第三方接口要加失败重试

我们在调用第三方接口时,由于存在远程调用,可能会出现接口超时的问题。

如果接口超时了,你不知道是执行成功,还是执行失败了。

这时你可以增加自动重试机制。

接口超时会抛一个 connection_timeout 或者 read_timeout 的异常,你可以捕获这个异常,用一个 while 循环自动重试 3 次。

这样就能尽可能减少调用第三方接口失败的情况。

当然调用第三方接口还有很多其他的坑,感兴趣的小伙伴可以看看我的另一篇文章《我调用第三方接口遇到的 13 大坑》,里面有详细的介绍。

12.处理线上数据前,要先备份数据

有时候,线上数据出现了问题,我们需要修复数据,但涉及的数据有点多。

这时建议在处理线上数据前,一定要先备份数据。

备份数据非常简单,可以执行以下 sql:

复制

create table order_2022121819 like `order`;insert into order_2022121819 select * from `order`;1.2.
复制代码

数据备份之后,万一后面哪天数据处理错了,我们可以直接从备份表中还原数据,防止悲剧的产生。

13.不要轻易删除线上字段

不要轻易删除线上字段,至少我们公司是这样规定的。

如果你删除了某个线上字段,但是该字段引用的代码没有删除干净,可能会导致代码出现异常。

假设开发人员已经把程序改成不使用删除字段了,接下来如何部署呢?

如果先把程序部署好了,还没来得及删除数据库相关表字段。

当有 insert 请求时,由于数据库中该字段是必填的,会报必填字段不能为空的异常。

如果先把数据库中相关表字段删了,程序还没来得及发。这时所有涉及该删除字段的增删改查,都会报字段不存在的异常。

所以,线上环境字段不要轻易删除。

14.要合理设置字段类型和长度

我们在设计表的时候,要给相关字段设置合理的字段类型和长度。

如果字段类型和长度不够,有些数据可能会保存失败。

如果字段类型和长度太大了,又会浪费存储空间。

我们在工作中,要根据实际情况而定。

以下原则可以参考一下:

  • 尽可能选择占用存储空间小的字段类型,在满足正常业务需求的情况下,从小到大,往上选。

  • 如果字符串长度固定,或者差别不大,可以选择 char 类型。如果字符串长度差别较大,可以选择 varchar 类型。

  • 是否字段,可以选择 bit 类型。

  • 枚举字段,可以选择 tinyint 类型。

  • 主键字段,可以选择 bigint 类型。

  • 金额字段,可以选择 decimal 类型。

  • 时间字段,可以选择 timestamp 或 datetime 类型。

15.避免一次性查询太多数据

我们在设计接口,或者调用别人接口的时候,都要避免一次性查询太多数据。

一次性查询太多的数据,可能会导致查询耗时很长,更加严重的情况会导致系统出现 OOM 的问题。

我们之前调用第三方,查询一天的指标数据,该接口经常出现超时问题。

在做 excel 导出时,如果一次性查询出所有的数据,导出到 excel 文件中,可能会导致系统出现 OOM 问题。

因此我们的接口要做分页设计。

如果是调用第三方的接口批量查询接口,尽量分批调用,不要一次性根据 id 集合查询所有数据。

如果调用第三方批量查询接口,对性能有一定的要求,我们可以分批之后,用多线程调用接口,最后汇总返回数据。

16.多线程不一定比单线程快

很多小伙伴有一个误解,认为使用了多线程一定比使用单线程快。

其实要看使用场景。

如果你的业务逻辑是一个耗时的操作,比如:远程调用接口,或者磁盘 IO 操作,这种使用多线程比单线程要快一些。

但如果你的业务逻辑非常简单,在一个循环中打印数据,这时候,使用单线程可能会更快一些。

因为使用多线程,会引入额外的消耗,比如:创建新线程的耗时,抢占 CPU 资源时线程上下文需要不断切换,这个切换过程是有一定的时间损耗的。

因此,多线程不一定比单线程快。我们要根据实际业务场景,决定是使用单线程,还是使用多线程。

17.注意事务问题

很多时候,我们的代码为了保证数据库多张表保存数据的完整性和一致性,需要使用 @Transactional 注解的声明式事务,或者使用 TransactionTemplate 的编程式事务。

加入事务之后,如果 A,B,C 三张表同时保存数据,要么一起成功,要么一起失败。

不会出现数据保存一半的情况,比如:表 A 保存成功了,但表 B 和 C 保存失败了。

这种情况数据会直接回滚,A,B,C 三张表的数据都会同时保存失败。

如果使用 @Transactional 注解的声明式事务,可能会出现事务失效的问题,感兴趣的小伙伴可以看看我的另一篇文章《聊聊 spring 事务失效的 12 种场景,太坑了》。

建议优先使用 TransactionTemplate 的编程式事务的方式创建事务。

此外,引入事务还会带来大事务问题,可能会导致接口超时,或者出现数据库死锁的问题。

因此,我们需要优化代码,尽量避免大事务的问题,因为它有许多危害。关于大事务问题,感兴趣的小伙伴,可以看看我的另一篇文章《让人头痛的大事务问题到底要如何解决?》,里面有详情介绍。

18.小数容易丢失精度

不知道你在使用小数时,有没有踩过坑,一些运算导致小数丢失了精度。

如果你在项目中使用了 float 或者 double 类型的数据,用他们参与计算,极可能会出现精度丢失问题。

使用 Double 时可能会有这种场景:

复制

double amount1 = 0.02;double amount2 = 0.03;System.out.println(amount2 - amount1);1.2.3.
复制代码

正常情况下预计 amount2 - amount1 应该等于 0.01

但是执行结果,却为:

0.0099999999999999981.
复制代码

实际结果小于预计结果。

Double 类型的两个参数相减会转换成二进制,因为 Double 有效位数为 16 位这就会出现存储小数位数不够的情况,这种情况下就会出现误差。

因此,在做小数运算时,更推荐大家使用 BigDecimal,避免精度的丢失。

但如果在使用 BigDecimal 时,使用不当,也会丢失精度。

BigDecimal amount1 = new BigDecimal(0.02);BigDecimal amount2 = new BigDecimal(0.03);System.out.println(amount2.subtract(amount1));1.2.3.
复制代码

这个例子中定义了两个 BigDecimal 类型参数,使用构造函数初始化数据,然后打印两个参数相减后的值。

结果:

0.00999999999999999847344334114040975691750645637512207031251.
复制代码

使用 BigDecimal 的构造函数创建 BigDecimal,也会导致精度丢失。

如果如何避免精度丢失呢?

BigDecimal amount1 = BigDecimal.valueOf(0.02);BigDecimal amount2 = BigDecimal.valueOf(0.03);System.out.println(amount2.subtract(amount1));1.2.3.
复制代码

使用 BigDecimal.valueOf 方法初始化 BigDecimal 类型参数,能保证精度不丢失。

19.优先使用批量操作

有些小伙伴可能写过这样的代码,在一个 for 循环中,一个个调用远程接口,或者执行数据库的 update 操作。

其实,这样是比较消耗性能的。

我们尽可能将在一个循环中多次的单个操作,改成一次的批量操作,这样会将代码的性能提升不少。

例如:

复制

for(User user : userList) {   userMapper.update(user);}1.2.3.
复制代码

改成:

userMapper.updateForBatch(userList);1.
复制代码

20.synchronized 其实用的不多

我们在面试中当中,经常会被面试官问到 synchronized 加锁的考题。

说实话,synchronized 的锁升级过程,还是有点复杂的。

但在实际工作中,使用 synchronized 加锁的机会不多。

synchronized 更适合于单机环境,可以保证一个服务器节点上,多个线程访问公共资源时,只有一个线程能够拿到那把锁,其他的线程都需要等待。

但实际上我们的系统,大部分是处于分布式环境当中的。

为了保证服务的稳定性,我们一般会把系统部署到两个以上的服务器节点上。

后面哪一天有个服务器节点挂了,系统也能在另外一个服务器节点上正常运行。

当然也能会出现,一个服务器节点扛不住用户请求压力,也挂掉的情况。

这种情况,应该提前部署 3 个服务节点。

此外,即使只有一个服务器节点,但如果你有 api 和 job 两个服务,都会修改某张表的数据。

这时使用 synchronized 加锁也会有问题。

因此,在工作中更多的是使用分布式锁。

目前比较主流的分布式锁有:

  1. 数据库悲观锁。

  2. 基于时间戳或者版本号的乐观锁。

  3. 使用 redis 的分布式锁。

  4. 使用 zookeeper 的分布式锁。

其实这些方案都有一些使用场景。

目前使用更多的是 redis 分布式锁。

当然使用 redis 分布式锁也很容易踩坑,感兴趣的小伙伴可以看看我的另一篇文章《聊聊 redis 分布式锁的 8 大坑》,里面有详细介绍。

21.异步思想很重要

不知道你有没有做过接口的性能优化,其中有一个非常重要的优化手段是:异步。

如果我们的某个保存数据的 API 接口中的业务逻辑非常复杂,经常出现超时问题。

现在让你优化该怎么优化呢?

先从索引,sql 语句优化。

这些优化之后,效果不太明显。

这时该怎么办呢?

这就可以使用异步思想来优化了。

如果该接口的实时性要求不高,我们可以用一张表保存用户数据,然后使用 job 或者 mq,这种异步的方式,读取该表的数据,做业务逻辑处理。

如果该接口对实效性要求有点高,我们可以梳理一下接口的业务逻辑,看看哪些是核心逻辑,哪些是非核心逻辑。

对于核心逻辑,可以在接口中同步执行。

对于非核心逻辑,可以使用 job 或者 mq 这种异步的方式处理。

22.Git 提交代码要有好习惯

有些小伙伴,不太习惯在 Git 上提交代码。

非常勤劳的使用 idea,写了一天的代码,最后下班前,准备提交代码的时候,电脑突然死机了。

会让你欲哭无泪。

用 Git 提交代码有个好习惯是:多次提交。

避免一次性提交太多代码的情况。

这样可以减少代码丢失的风险。

更重要的是,如果多个人协同开发,别人能够尽早获取你最新的代码,可以尽可能减少代码的冲突。

假如你开发一天的代码准备去提交的时候,发现你的部分代码,别人也改过了,产生了大量的冲突。

解决冲突这个过程是很痛苦的。

如果你能够多次提交代码,可能会及时获取别人最新的代码,减少代码冲突的发生。因为每次 push 代码之前,Git 会先检查一下,代码有没有更新,如果有更新,需要你先 pull 一下最新的代码。

此外,使用 Git 提交代码的时候,一定要写好注释,提交的代码实现了什么功能,或者修复了什么 bug。

如果有条件的话,每次提交时在注释中可以带上 jira 任务的 id,这样后面方便统计工作量。

23.善用开源的工具类

我们一定要多熟悉一下开源的工具类,真的可以帮我们提升开发效率,避免在工作中重复造轮子。

目前业界使用比较多的工具包有:apache 的 common,google 的 guava 和国内几个大佬些 hutool。

比如将一个大集合的数据,按每 500 条数据,分成多个小集合。

这个需求如果要你自己实现,需要巴拉巴拉写一堆代码。

但如果使用 google 的 guava 包,可以非常轻松的使用:

List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);List<List<Integer>> partitionList = Lists.partition(list, 2);System.out.println(partitionList);1.2.3.
复制代码

如果你对更多的第三方工具类比较感兴趣,可以看看我的另一篇文章《吐血推荐 17 个提升开发效率的“轮子”》。

24.培养写技术博客的好习惯

我们在学习新知识点的时候,学完了之后,非常容易忘记。

往往学到后面,把前面的忘记了。

回头温习前面的,又把后面的忘记了。

因此,建议大家培养做笔记的习惯。

我们可以通过写技术博客的方式,来记笔记,不仅可以给学到的知识点加深印象,还能锻炼自己的表达能力。

此外,工作中遇到的一些问题,以及解决方案,都可以沉淀到技术博客中。

一方面是为了避免下次犯相同的错误。

另一方面也可以帮助别人少走弯路。

而且,在面试中如果你的简历中写了技术博客地址,是有一定的加分的。

因此建议大家培养些技术博客的习惯。

25.多阅读优秀源码

建议大家利用空闲时间,多阅读 JDK、Spring、Mybatis 的源码。

通过阅读源码,可以真正的了解某个技术的底层原理是什么,这些开源项目有哪些好的设计思想,有哪些巧妙的编码技巧,使用了哪些优秀的设计模式,可能会出现什么问题等等。

当然阅读源码是一个很枯燥的过程。

有时候我们会发现,有些源码代码量很多,继承关系很复杂,使用了很多设计模式,一眼根本看不明白。

对于这类不太容易读懂的源码,我们不要一口吃一个胖子。

要先找一个切入点,不断深入,由点及面的阅读。

我们可以通过 debug 的方式阅读源码。

在阅读的过程中,可以通过 idea 工具,自动生成类的继承关系,辅助我们更好的理解代码逻辑。

我们可以一边读源码,一边画流程图,可以更好的加深印象。

作为一个老程序员,想对新人说什么?-51CTO.COM

发布于: 刚刚阅读数: 4
用户头像

低代码技术追随者,为全民开发而努力 2023-02-15 加入

大家好,我是老王,专注于分享低代码图文知识,感兴趣的伙伴就请关注我吧!

评论

发布
暂无评论
作为一个老程序员,想对新人说什么?_程序员_这我可不懂_InfoQ写作社区