前语

最近知乎上,有一位大佬约请我回答下面这个问题,看到这个问题我百感交集,感受颇多。

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

在我是新人时,假设有长辈能够指导方向一下,分享一些踩坑阅历,或许会让我少走许多弯路,节省更多的学习的本钱。

这篇文章根据我多年的工作经验,给新人总结了26条主张,期望对你会有所帮助。


1.写好注释

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

  • 开发时刻太短了,没时刻写注释。
  • 《重构》那本书说代码即注释。

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

但后来发现,有些两年之前的代码,事务逻辑都忘了,有些代码自己都看不懂。特别是有部分十分复杂的逻辑和算法,需求从头花许多时刻才能看明白,能够说自己把自己坑了。

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

因而强烈主张咱们给代码写注释。

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

咱们要写好注释,但不能太烦琐,要给要害或许中心的代码添加注释。咱们能够写某个办法是做什么的,首要进程是什么,给算法写个demo示例等。

这样今后过了很长时刻,再去看这段代码的时分,也会比较简略上手。

2.多写单元测验

我看过身边许多大佬写代码有个好习气,比方新写了某个Util东西类,他们会一同在test目录下,给该东西类编写一些单元测验代码。

许多小伙伴觉得写单元测验是浪费时刻,没有这个必要。

假定你想重构某个东西类,但由于这个东西类有许多逻辑,要把这些逻辑从头测验一遍,要花费不少时刻。

于是,你发生了放弃重构的主意。

但假设你之前给该东西类编写了完整的单元测验,重构完结之后,从头履行一下之前的单元测验,就知道重构的成果是否满足预期,这样能够削减许多的测验时刻。

多写单元测验对开发来说,是一个十分好的习气,有助于提高代码质量。

即便由于最初开发时刻比较紧,没时刻写单元测验,也主张在后边空闲的时刻内,把单元测验补上。

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

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

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

这说明你对事务或许技能的了解,比之前更深入了,认知水平有必定的提高。

假设有时机,主张你主动重构一下自己的烂代码。把重复的代码,抽取成公共办法。有些参数称号,或许办法称号其时没有取好的,能够及时修正一下。关于逻辑不明晰的代码,从头收拾一下事务逻辑。看看代码中能不能引入一些规划形式,让代码变得更高雅等等。

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

4.代码review很重要

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

经过彼此的代码review,能够发现一些代码的漏洞,欠好的写法,发现自己写代码的坏毛病,让自己能够快速提高。

当然假设你们公司没有建立代码的彼此review机制,也没联系。

可今后边能够多自己review自己的代码。

5.多用explain检查履行计划

咱们在写完查询SQL句子之后,有个好习气是用explain要害字检查一下该SQL句子有没有走索引。

关于数据量比较大的表,走了索引和没有走索引,SQL句子的履行时刻可能会相差上百倍。

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

因而主张咱们多用explain检查SQL句子的履行计划。

6.多看看优异的东西

太空电梯、MOSS、ChatGPT等,都预兆着2023年注定不会是平凡的一年。任何新的技能都值得琢磨,咱们应要有这种敏感性。

这几年隐约碰过低代码,现在比较热门,许多大厂都相继参加。

低代码平台概念:经过主动代码生成和可视化编程,只需求少数代码,即可快速搭建各种运用。

究竟啥是低代码,在我看来就是拖拉拽,呼呼呼,一通操作,搞出一套能跑的体系,前端,后端,数据库,一把完结。当然这可能是最终目标。

链接:www.jnpfsoft.com/?juejin,假设你感兴趣,也体会一下。

JNPF的优势就在于它能生成前后台代码,提供了极大的灵活性,能够创立更复杂、定制化的运用。它的架构规划也让开发者无需担心底层技能细节,能够专心于运用逻辑和用户体会的开发。

7.上线前收拾checklist

在体系上线之前,必定要收拾上线的清单,即咱们说的:checklist。

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

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

有时分新功能上线时,需求提前履行sql脚本初始化数据,不然新功能有问题。

要先配置守时任务。

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

上线完结之后,需求添加相应的菜单,给指定用户或许角色分配权限。

等等。

体系上线,整个进程中,可能会涉及多方面的事情,咱们需求将这些事情记录到checklist傍边,防止踩坑。

8.写好接口文档

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

假设你没有接口文档,他人咋知道你接口的地址是什么,接口参数是什么,恳求办法时什么,接口多个参数别离代码什么含义,回来值有哪些字段等等。

他们不知道,必定会屡次问你,无形傍边,添加了许多交流的本钱。

假设你的接口文档写的欠好,写得他人看不懂,接口文档有许多过错,比方:输入参数的枚举值,跟实践状况不一样。

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

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

假设对写接口文档比较感兴趣,能够看看我的另一篇文章《瞧瞧他人家的API接口,那叫一个高雅》,里边有详细的介绍。

9.接口要提前评价恳求量

咱们在规划接口的时分,要跟事务方或许产品司理确认一下恳求量。

假定你的接口只能接受100qps,但实践上发生了1000qps。

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

咱们需求对接口做压力测验,预估接口的恳求量,需求部署多少个服务器节点。

压力测验的话,能够用jmeter、loadRunner等东西。

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

限流的话,能够根据用户id、ip地址、接口地址等多个维度一同做限制。

能够在nginx层,或许网关层做限流。

10.接口要做幂等性规划

咱们在规划接口时,必定要考虑并发调用的状况。

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

假设不做幂等性规划,在数据库中可能会发生两条重复的数据。

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

因而,在做接口规划时,要做幂等规划。

当然幂等规划的计划有许多,感兴趣的小伙伴能够看看我的另一篇文章《高并发下怎样确保接口的幂等性?》。

假设接口并发量不太大,引荐咱们运用在表中加唯一索引的计划,愈加简略。

11.接口参数有调整必定要稳重

有时分咱们提供的接口,需求调整参数。

比方:新添加了一个参数,或许参数类型从int改成String,或许参数称号有status改成auditStatus,参数由单个id改成批量的idList等等。

主张涉及到接口参数修正必定要稳重。

修正接口参数之前,必定要先评价调用端和影响规模,不要自己偷偷修正。假设出问题了,调用方后边肯定要骂娘。

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

其实删去参数和修正参数称号是一个问题,都会导致那个参数接纳不到数据。

因而,尽量防止删去参数和修正参数名。

关于修正参数称号的状况,咱们能够添加一个新参数,来接纳数据,老的数据仍是保存,代码中做兼容处理。

12.调用第三方接口要加失利重试

咱们在调用第三方接口时,由于存在长途调用,可能会呈现接口超时的问题。

假设接口超时了,你不知道是履行成功,仍是履行失利了。

这时你能够添加主动重试机制。

接口超时会抛一个connection_timeout或许read_timeout的反常,你能够捕获这个反常,用一个while循环主动重试3次。

这样就能尽可能削减调用第三方接口失利的状况。

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

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

有时分,线上数据呈现了问题,咱们需求修正数据,但涉及的数据有点多。

这时主张在处理线上数据前,必定要先备份数据。

备份数据十分简略,能够履行以下sql:

create table order_2022121819 like `order`;
insert into order_2022121819 select * from `order`;

数据备份之后,万一后边哪天数据处理错了,咱们能够直接从备份表中复原数据,防止悲剧的发生。

14.不要简单删去线上字段

不要简单删去线上字段,至少咱们公司是这样规定的。

假设你删去了某个线上字段,可是该字段引证的代码没有删去干净,可能会导致代码呈现反常。

假定开发人员现已把程序改成不运用删去字段了,接下来怎样部署呢?

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

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

假设先把数据库中相关表字段删了,程序还没来得及发。这时一切涉及该删去字段的增修正查,都会报字段不存在的反常。

所以,线上环境字段不要简单删去。

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

咱们在规划表的时分,要给相关字段设置合理的字段类型和长度。

假设字段类型和长度不行,有些数据可能会保存失利。

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

咱们在工作中,要根据实践状况而定。

以下原则能够参考一下:

  • 尽可能挑选占用存储空间小的字段类型,在满足正常事务需求的状况下,从小到大,往上选。
  • 假设字符串长度固定,或许不同不大,能够挑选char类型。假设字符串长度不同较大,能够挑选varchar类型。
  • 是否字段,能够挑选bit类型。
  • 枚举字段,能够挑选tinyint类型。
  • 主键字段,能够挑选bigint类型。
  • 金额字段,能够挑选decimal类型。
  • 时刻字段,能够挑选timestamp或datetime类型。

16.防止一次性查询太多数据

咱们在规划接口,或许调用他人接口的时分,都要防止一次性查询太多数据。

一次性查询太多的数据,可能会导致查询耗时很长,愈加严峻的状况会导致体系呈现OOM的问题。

咱们之前调用第三方,查询一天的目标数据,该接口常常呈现超时问题。

在做excel导出时,假设一次性查询出一切的数据,导出到excel文件中,可能会导致体系呈现OOM问题。

因而咱们的接口要做分页规划。

假设是调用第三方的接口批量查询接口,尽量分批调用,不要一次性根据id调集查询一切数据。

假设调用第三方批量查询接口,对功能有必定的要求,咱们能够分批之后,用多线程调用接口,最终汇总回来数据。

17.多线程不必定比单线程快

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

其实要看运用场景。

假设你的事务逻辑是一个耗时的操作,比方:长途调用接口,或许磁盘IO操作,这种运用多线程比单线程要快一些。

但假设你的事务逻辑十分简略,在一个循环中打印数据,这时分,运用单线程可能会更快一些。

由于运用多线程,会引入额外的耗费,比方:创立新线程的耗时,抢占CPU资源时线程上下文需求不断切换,这个切换进程是有必定的时刻损耗的。

因而,多线程不必定比单线程快。咱们要根据实践事务场景,决定是运用单线程,仍是运用多线程。

18.留意事务问题

许多时分,咱们的代码为了确保数据库多张表保存数据的完整性和一致性,需求运用@Transactional注解的声明式事务,或许运用TransactionTemplate的编程式事务。

参加事务之后,假设A,B,C三张表一同保存数据,要么一同成功,要么一同失利。

不会呈现数据保存一半的状况,比方:表A保存成功了,但表B和C保存失利了。

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

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

主张优先运用TransactionTemplate的编程式事务的办法创立事务。

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

因而,咱们需求优化代码,尽量防止大事务的问题,由于它有许多损害。关于大事务问题,感兴趣的小伙伴,能够看看我的另一篇文章《让人头痛的大事务问题究竟要怎样解决?》,里边有概况介绍。

19.小数简略丢掉精度

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

假设你在项目中运用了float或许double类型的数据,用他们参与核算,极可能会呈现精度丢掉问题。

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

double amount1 = 0.02;
double amount2 = 0.03;
System.out.println(amount2 - amount1);

正常状况下估计amount2 – amount1应该等于0.01

可是履行成果,却为:

0.009999999999999998

实践成果小于估计成果。

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

因而,在做小数运算时,更引荐咱们运用BigDecimal,防止精度的丢掉。

但假设在运用BigDecimal时,运用不当,也会丢掉精度。

BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));

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

成果:

0.0099999999999999984734433411404097569175064563751220703125

运用BigDecimal的构造函数创立BigDecimal,也会导致精度丢掉。

假设怎样防止精度丢掉呢?

BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));

运用BigDecimal.valueOf办法初始化BigDecimal类型参数,能确保精度不丢掉。

20.优先运用批量操作

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

其实,这样是比较耗费功能的。

咱们尽可能将在一个循环中屡次的单个操作,改成一次的批量操作,这样会将代码的功能提高不少。

例如:

for(User user : userList) {
   userMapper.update(user);
}

改成:

userMapper.updateForBatch(userList);

21.synchronized其有用的不多

咱们在面试中傍边,常常会被面试官问到synchronized加锁的考题。

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

但在实践工作中,运用synchronized加锁的时机不多。

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

但实践上咱们的体系,大部分是处于分布式环境傍边的。

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

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

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

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

此外,即便只要一个服务器节点,但假设你有api和job两个服务,都会修正某张表的数据。

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

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

现在比较主流的分布式锁有:

  1. 数据库失望锁。
  2. 根据时刻戳或许版本号的达观锁。
  3. 运用redis的分布式锁。
  4. 运用zookeeper的分布式锁。

其实这些计划都有一些运用场景。

现在运用更多的是redis分布式锁。

当然运用redis分布式锁也很简略踩坑,感兴趣的小伙伴能够看看我的另一篇文章《聊聊redis分布式锁的8大坑》,里边有详细介绍。

22.异步思想很重要

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

假设咱们的某个保存数据的API接口中的事务逻辑十分复杂,常常呈现超时问题。

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

先从索引,sql句子优化。

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

这时该怎样办呢?

这就能够运用异步思想来优化了。

假设该接口的实时性要求不高,咱们能够用一张表保存用户数据,然后运用job或许mq,这种异步的办法,读取该表的数据,做事务逻辑处理。

假设该接口对实效性要求有点高,咱们能够收拾一下接口的事务逻辑,看看哪些是中心逻辑,哪些对错中心逻辑。

关于中心逻辑,能够在接口中同步履行。

关于非中心逻辑,能够运用job或许mq这种异步的办法处理。

23.Git提交代码要有好习气

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

十分勤劳的运用idea,写了一天的代码,最终下班前,预备提交代码的时分,电脑忽然死机了。

会让你欲哭无泪。

用Git提交代码有个好习气是:屡次提交。

防止一次性提交太多代码的状况。

这样能够削减代码丢掉的风险。

更重要的是,假设多个人协同开发,他人能够尽早获取你最新的代码,能够尽可能削减代码的抵触。

假定你开发一天的代码预备去提交的时分,发现你的部分代码,他人也改过了,发生了许多的抵触。

解决抵触这个进程是很苦楚的。

假设你能够屡次提交代码,可能会及时获取他人最新的代码,削减代码抵触的发生。由于每次push代码之前,Git会先检查一下,代码有没有更新,假设有更新,需求你先pull一下最新的代码。

此外,运用Git提交代码的时分,必定要写好注释,提交的代码完成了什么功能,或许修正了什么bug。

假设有条件的话,每次提交时在注释中能够带上jira任务的id,这样后边便利统计工作量。

24.善用开源的东西类

咱们必定要多了解一下开源的东西类,真的能够帮咱们提高开发功率,防止在工作中重复造轮子。

现在业界运用比较多的东西包有: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);

假设你对更多的第三方东西类比较感兴趣,能够看看我的另一篇文章《吐血引荐17个提高开发功率的“轮子”》。

25.培育写技能博客的好习气

咱们在学习新知识点的时分,学完了之后,十分简略忘掉。

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

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

因而,主张咱们培育做笔记的习气。

咱们能够经过写技能博客的办法,来记笔记,不仅能够给学到的知识点加深印象,还能训练自己的表达能力。

此外,工作中遇到的一些问题,以及解决计划,都能够沉积到技能博客中。

一方面是为了防止下次犯相同的过错。

另一方面也能够帮助他人少走弯路。

并且,在面试中假设你的简历中写了技能博客地址,是有必定的加分的。

因而主张咱们培育些技能博客的习气。

26.多阅览优异源码

主张咱们利用空闲时刻,多阅览JDK、Spring、Mybatis的源码。

经过阅览源码,能够真实的了解某个技能的底层原理是什么,这些开源项目有哪些好的规划思想,有哪些奇妙的编码技巧,运用了哪些优异的规划形式,可能会呈现什么问题等等。

当然阅览源码是一个很枯燥的进程。

有时分咱们会发现,有些源码代码量许多,继承联系很复杂,运用了许多规划形式,一眼根本看不明白。

关于这类不太简略读懂的源码,咱们不要一口吃一个胖子。

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

咱们能够经过debug的办法阅览源码。

在阅览的进程中,能够经过idea东西,主动生成类的继承联系,辅佐咱们更好的了解代码逻辑。

咱们能够一边读源码,一边画流程图,能够更好的加深印象。