最近在工作中老是犯一些小错误,哦,当然也不是最近了,其实我一直是个马虎的人,我很讨厌做一些细活,因为这会让我反复改动多次在会成功,而平时的代码由于有debug,即便出错了,再改回来即可,但是,很多时候并没有我们想的那么容易,趁着周末,我写下了这篇总结,希望也能帮助到大家。
我们在开发过程中,一个大忌就是需求都没搞清楚,为了能够更快的完成任务,直接上手写代码,没准写到一半发现需求有问题,或者是之前自己没理解好,导致最终出了问题。
我的建议是,养成记录工作的好习惯,我一般是以导图的形式做一个梳理,比如下图这样子,里面也包含了我周末想做的事,重点关注最后一条即可。
这里不仅包含了写代码前的一些准备工作,也包含了一些开发的小技巧,思路,总的来说就是多记录,当事物繁多时就能体会到好处了。
这里的反复是指,明明开发完成了,测试或者上线又出bug了,我们自我测试是必不可少的,推荐自己的代码使用单元测试跑一下,但我知道很多情况下是做不到的,因为公司代码太繁杂了,测试不一定好跑,所以只是做个小小的推荐,这里推荐使用mocktio框架,它可以以插桩的形式将某些service注入进来,完成虚假的调用,我们只需关注自己想关注的部分即可。
代码示例如下。
service代码
public class UserRepository {
public User findUserById(String userId) {
// 数据库逻辑,这里为了简单省略
return null;
}
}
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(String userId) {
return userRepository.findUserById(userId);
}
}
测试代码
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class UserServiceTest {
@Test
public void testGetUserById() {
// 创建模拟对象
UserRepository mockRepository = mock(UserRepository.class);
// 设置模拟行为
when(mockRepository.findUserById("123")).thenReturn(new User("123", "Test User"));
// 使用模拟对象创建UserService实例
UserService userService = new UserService(mockRepository);
// 调用UserService的方法并断言结果
User result = userService.getUserById("123");
assertNotNull(result);
assertEquals("123", result.getId());
assertEquals("Test User", result.getName());
// 验证模拟对象的方法是否被调用
verify(mockRepository).findUserById("123");
}
}
上面的例子很简单,但很多情况下我们求快或者实在搞不了单元测试,那么只能自测了,如果之前写了伪代码,那么将会减少出错的可能性,但这还不够。
这里我是从吴军老师那学到的方法,就是交叉验证,即便我们debug跑下觉得没什么问题,我们也还是应该掌握逆向思维,通过结果,反推值,看是否没问题。
很多时候我们犯错,其实是因为基础不牢导致的,比如下面一些例子,我们就应该警惕。
空指针异常(NullPointerException):
当试图使用一个未初始化(null)的对象时,就会抛出空指针异常。例如,尝试调用一个未实例化对象的方法或访问其属性。
BigDecimal使用浮点数构造器:
使用new BigDecimal(double)构造函数时,可能不会得到预期的精确值,因为double本身可能无法精确表示。正确的做法是使用字符串构造器,如new BigDecimal("0.1")
。
数组越界(ArrayIndexOutOfBoundsException):
尝试访问数组中不存在的索引时,会抛出数组越界异常。例如,访问长度为5的数组的第6个元素。
类转换异常(ClassCastException):
将对象强制类型转换为不兼容类型时,将抛出类转换异常。
并发修改异常(ConcurrentModificationException):
在迭代集合时(如使用for-each循环),如果同时对集合进行结构性修改(添加、删除元素),则可能抛出此异常。
String比较使用 :
使用来比较字符串,这实际上比较的是对象引用,而不是字符串的内容。应该使用.equals()方法来比较字符串的内容。
忽视了基本类型和包装类之间的区别:
例如,int和Integer的区别,其中Integer是可以为null的,而int是有默认值0的基本类型。
不正确的异常处理:
异常处理不当,如使用空的catch块或仅打印堆栈跟踪而不进行适当处理。
忽视了Java集合框架中的线程安全问题:
如在多线程环境下使用非线程安全的集合,如ArrayList和HashMap,而不是线程安全的版本,如Vector或ConcurrentHashMap。
资源泄露:
在使用资源(如流、数据库连接等)后没有正确关闭,可能导致资源泄露。
我们会因为自己的熟练,而过度自信,导致一些代码觉得太简单了,肯定不会出问题,最终出现了bug。
或者是自我觉得经验还可以,不听他人的劝告,做一些自认为对的事情,但最终却发现自己走入了一个误区,一开始自己想错了。
这里主要是想给大家提个醒,我们在深入专业的同时,也要保留一份怀疑,开放的态度去听取,挖掘更多的可能性。 下面是一些因为编程不规范导致的事故。
Knight Capital Group的交易系统故障(2012年):
Knight Capital Group由于软件部署错误,导致其交易算法失控。一个旧的、未使用的软件功能被意外地激活,导致公司在不到一小时内损失了约4.4亿美元。这个事件突显了代码管理和部署过程中严格遵守规范的重要性。
Ariane 5 Flight 501失败(1996年):
欧洲空间局的Ariane 5火箭在首次飞行中因为软件异常而解体。问题源自于64位浮点数在转换为16位整数时未能适当处理溢出。这个错误表明了代码重用时需要考虑上下文的重要性,以及进行充分测试的必要性。
Heartbleed漏洞(2014年):
OpenSSL的一个严重漏洞,由于未正确检查网络输入的长度,导致攻击者可以读取内存中的敏感数据。这个漏洞凸显了输入验证的重要性以及在安全相关代码中遵守最佳实践的必要性。
Therac-25事故(1985-1987年):
Therac-25是一台辐射治疗机,由于软件错误,导致至少六名患者接受了过量的辐射。软件的设计和编码错误,包括竞争条件和不充分的安全检查,是导致这些事故的主要原因。
这也是最近才感悟到的东西,很多需求急着上线,但因为没有经过充分的测试,或者是因为时间太赶,代码滥竽充数,cv大法,只为了完成某个功能,看似进度很快,后期又是不停的改bug。
另外一点是最近学习的感悟,我们很容易夸下海口,说我要在一个月完成什么什么事,比如学习完某个课程,或者在新一年的目标中,定下了许许多多想要完成的事,但大多都不了了之。
为什么会这样呢?
首先一点就是贪,人总是想要事情做的又快又好,想要知识到了我们脑中就能立刻转化为经验,技能,赚到钱。
但现实不是游戏,很多生活中的大改变往往是每日里的点滴努力汇聚而成的,所以不要有急于求成的心态,只想着每天做一点小改变,就像我前面导图中写到的,周末学一节操作系统,不需要多,平时忙的花就花个10分钟看下视频,即便是躺在床上也能完成,稍微做下笔记。
只要这件事情的进度在往前,那么总能够完成的,如果觉得事情太困难了,不仿切割成一个个的子任务,日积月累,总能够有所成效的。
生命中这些不经意的时刻,最后你付出的点点努力,就像遗失在生命中各个角落的弹珠,有一天突然来了一根线,这些弹珠都被连了起来,你突然发现原来它们不是散落的,它们是一条真正的银河。