【随笔】记一次入职测试
封面来源:本文封面来源于网络,如有侵权,请联系删除。
2022-11-15 更新:两年的时间过得很快,回首再看当初的总结,字里行间透露出的只有稚嫩。本次对部分内容表述进行修改的同时,还加深了对 Objects.requireNonNull()
方法的理解。
1. 一些小感悟
进入公司有将近一个月了,一个月下来就是熟悉业务,写写小 demo,感觉与在校学习也没什么两样,直到考核,或者说测试。经过这次测试,我也深刻认识到了公司和学校的不同。
在学校里,不同的老师对于作业的检查力度是不一样的,一个严格的老师,或许会抠每个字眼,但如果遇到不怎么负责的老师,作业的检查可能也就走个形式,也不管你内容如何、质量如何。
在公司里,每个人都是为了给公司创造价值,需要更严格地要求自己,让自己在公司的所做所为能够为公司带来价值和收益。
还有很肤浅的一点区别,进学校,你要给学校钱,进公司,公司会给你钱。学校从你这里赚钱(虽然也赚不到啥),学校并不要求你能对学校产生多少价值;但是进公司,公司会给你钱,公司就要求你能产生更多的价值,从而某些要求就会更加严格。
总的来说,在公司里,自己得对自己要求严格,不仅为了自己以后的前程,也为了在当前公司内能过的舒坦,谁也不想每天一到公司就被说这说那吧。
2. 考核过程的错误
2.1 注释不规范
Java 中有三种注释方式,单行注释、块注释和文档注释。
单行注释一次只能注释一行,一般是简单的注释,用来简短描述某个变量、属性或程序块。
块注释是为了进行多行简单注释,一般不使用。
文档注释一般用来对 类、接口、成员方法、成员变量、静态字段、静态方法和常量 进行说明,Java Doc 可以用它来产生代码的文档,为了可读性,可以有缩进和格式控制。文档注释还可以采用一些 Java Doc 标签进行文档的特定用途描述,用于帮助产生 Java Doc 文档,常用的有:@author
、@Description
、@Param
等等。
在今天的考核中,我在方法上使用了单行注释,这显然是不规范,应该使用文档注释。
2.2 Objects.requireNonNull()
参考链接:
该方法的源码是这样的:
1 | public static <T> T requireNonNull(T obj) { |
实现逻辑很简单,如果当前对象是 null
就抛出异常,否则返回当前对象。
那这样一个方法有啥用呢?对一个 null
来调用方法本就会抛出异常,这不是多此一举吗?
这里涉及一种编程思想 —— Fail-fast (快速失败)思想。简单来说,就是让错误尽可能早的出现,不要等到很多工作执行到一半之后才抛出异常。
比如现有这样的代码:
1 | public class Foo { |
使用这样的校验后,可以保证创建出的 Foo
对象中的 bars
一定不为 null
。当没有进行校验时,构造出 Foo
对象后,假设使用 foo.getBars().get(0).getName.contains("xxx")
抛出了 NullPointerException
,此时很难知道究竟是哪个对象为 null
才导致了空指针,而在进行校验后,至少能保证 bars
一定不为 null
,也不需要对 bars
进行额外的判断。如果是由于 bars
为 null
导致的,那么在一开始创建 Foo
对象时就会发现问题,让问题更早地暴露出来,而不是运行一些代码后才出现问题。
requireNonNull()
方法还有一个重载方法,可以提供一个错误信息,以便能够更好地进行错误定位。
1 | public static <T> T requireNonNull(T obj, String message) { |
总结下,requireNonNull()
方法有三个用途:
1、控制行为。该方法规定了某个对象不能为空,否则就会抛出异常;
2、方便调试。提供了一个重载方法,使用重载方法可以在出现异常时提供一个明确的错误信息,降低定位错误的难度。
3、如果在执行 requireNonNull()
方法时没有抛出 NullPointerException
异常,那么能保证传入的 obj
对象一定不为 null
,并且在后续代码中也不需要再对 obj
进行 null
值判断,减少了代码中进行非空判断的次数,这在一定程度上还提升了单元测试的分支覆盖率。
使用的细节
requireNonNull()
方法是有返回值的,因此常常会有这样的代码:
1 | public Foo(List<Bar> bars) { |
目的很简单,不想让 bars
为 null
。当 bars
为 null
时抛出异常,并提供了准确的异常信息;当 bars
不为 null
时,将当前对象中的 bars
指向传入的 bars
时。 但在某些情况下,这种使用会带来问题。
1 | /** |
这段代码的目的也很简单,要求传入的 a
和 b
不为 null
,当它们都不为 null
时才赋值给成员变量。因此成员变量 a
和 b
只有两种状态:
- 要么都为
null
(未进行任何赋值时) - 要么都不为
null
(执行了set()
方法后)
事实真的如此吗?
1 | /** |
测试方法成功通过,也就是在这种情况下,成员变量 a
不为 null
,但是 b
为 null
。这是由于程序在执行过程中 catch
了异常,在对 a
进行赋值时没有产生异常,因此对 a
的赋值成功,但对 b
进行赋值时产生了异常,异常又被 catch
,导致对 b
的赋值失败,b
依旧为 null
。
要解决这个问题很简单,先进行判断再进行赋值 即可。
1 | /** |
按照同样的方式进行测试:
1 |
|
测试方法成功通过,此时传入的 b
为 null
,但代码执行后,成员变量 a
和 b
都是 null
,符合预期状态。
参数的错误检查应该首先完成,当所有输入的参数都没问题时才更新对象状态。
2.3 事务的控制
需要对方法中的数据库操作进行事务控制时,可以在类上使用 @Transactional
注解。使用了这个注解后,对类中所有符合要求的方法进行事务控制。
有些方法内部只涉及到数据库的查询,无需进行事务控制,为了更高的效率,可以在这些方法上使用:
1 |
2.4 项目的启动
一个项目是否能够成功启动与数据库中是否存在初始数据无关。
2.5 单元测试
一个成功的单元测试应该是任何时候的测试结果都是一样的,无论是否空库也都应该一样。
同时,每个单元测试方法之间不能有依赖,方法之间存在依赖不是一个合格的单元测试。
编写单元测试应该是在业务编写前就进行单元测试,后续编写会增加不必要的负担。 以实际情况为准,话虽这么说,但两年来无 不例外都是后续编写的。
使用 IDEA 进行本地单元测试时,可以使用 Coverage 插件,使用这个插件后,要保证行覆盖率达到 99%,分支覆盖率达到 100%。 理想情况下是这样的,实际情况下总有些地方覆盖不到。
2.6 DTO 的使用
DTO(Data Transfer Object):数据传输对象,在项目中的业务层应当使用 DTO 来传递数据,而不是 POJO。
如果一个 POJO 有 8 个字段,在业务层的方法 A 需要使用 4 个字段,在方法 B 中需要使用另外 4 个字段,那么可以编写两个 DTO 对这些信息进行封装。
Java 中还有很多 O,这张图或许可以帮助理解:
3. 总结
这次的测试结果很不理想,自己需要提升的地方还有很多,还需要更加努力的学习,不要显摆自己的小聪明或小知识,这点小聪明在别人眼里或许根本不值一提。
铭记下面这几句话:
1、知者不博,博者不知
2、真正的大师永远都怀着一颗学徒的心