点击上方“程序员蜗牛g”,选择“设为星标”跟蜗牛哥一起,每天进步一点点
程序员蜗牛g大厂程序员一枚 跟蜗牛一起 每天进步一点点31篇原创内容**公众号
今天,我精心为大家整理了写代码的16个好习惯,这些习惯个个经典实用,倘若你能将它们融入日常的编程工作中,相信大部分非业务相关的Bug都能被我们轻松规避。
1. 修改代码后,务必进行自测
“改完代码,自测一下”,这应该成为每位程序员刻在骨子里的基本素养。
千万不要心存侥幸,想着“我只是改了一个变量”或者“我仅仅改了一行配置代码,没必要自测”。
2. 对方法入参进行全面校验
入参校验,就像是代码世界的“守门员”,是每个程序员不可或缺的基本技能。
比如,要判断入参是否允许为空,以及入参的长度是否符合我们的预期。
这看似简单的操作,却能帮我们避免很多低级Bug。
举个例子,如果数据库字段设置为varchar(16),而对方却传了一个32位的字符串过来,要是我们不进行参数校验,直接将其插入数据库,那必然会引发异常。
所以,养成入参校验的习惯,能让我们的代码更加健壮。
3. 修改老接口时,充分考虑接口兼容性
在软件开发过程中,很多Bug都是因为修改了对外提供服务的老接口,却没有做好兼容性处理而导致的。
而且,这类问题往往比较严重,甚至可能直接导致系统发版失败,新手程序员尤其容易犯这个错误。
所以,当你的需求是在原来的接口上进行修改时,特别是这个接口是对外提供服务的,一定要充分考虑接口的兼容性。
以Dubbo接口为例,原本接口只接收A、B两个参数,现在需要新增一个参数C,我们可以采用如下方式进行处理:
// 老接口
void oldService(A, B) {
// 兼容新接口,传个null代替C
newService(A, B, null);
}
// 新接口,暂时不能删掉老接口,需要做兼容。
void newService(A, B, C);
4. 为复杂的代码逻辑添加清晰注释
在编写代码时,并不是注释越多越好,一个好的方法名和变量名本身就是最好的注释。
但是,当遇到业务逻辑非常复杂的代码时,清晰的注释就显得尤为重要了。
它就像是黑暗中的明灯,能帮助后续维护代码的人更快地理解代码的意图,减少沟通成本和维护难度。
5. 使用完IO资源流后,及时关闭
相信大家都有过这样的体验,在Windows系统中,如果桌面上打开了太多的文件或者运行了过多的软件,电脑就会变得非常卡顿。
同样的道理,在Linux服务器上,操作文件、数据库连接等IO资源时,如果使用完后没有及时关闭,这些资源就会一直被占用,造成资源浪费。
所以,在使用完IO流后,我们可以使用finally块来确保资源被关闭,示例代码如下:
FileInputStream fdIn = null;
try {
fdIn = new FileInputStream(new File("/jay.txt"));
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
} finally {
try {
if (fdIn != null) {
fdIn.close();
}
} catch (IOException e) {
log.error(e);
}
}
在JDK 7之后,我们还可以使用更简洁的try-with-resource语法来关闭流,示例如下:
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt"))) {
// use resources
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
6. 采取措施避免运行时错误
在日常开发中,我们需要时刻保持警惕,采取有效的措施来规避数组边界溢出、被零整除、空指针等常见的运行时错误。
例如,下面这段代码就存在数组越界的风险:
String name = list.get(1).getName(); // list可能越界,因为不一定有2个元素哈
为了避免这种情况,我们应该先对数组进行检查,示例代码如下:
if (CollectionUtils.isNotEmpty(list) && list.size() > 1) {
String name = list.get(1).getName();
}
7. 避免在循环中进行远程调用或数据库操作
远程操作和数据库操作通常比较消耗网络和IO资源,因此我们应该尽量避免在循环中进行这些操作。
如果可以的话,优先考虑批量处理,一次性将数据查询回来,而不是循环多次去查询。
不过,也要注意一次性查询的数据量不要过大,建议分批处理,每次处理500条左右为宜。
下面是一个正确的示例:
remoteBatchQuery(param);
而下面这个就是错误的示例:
for (int i = 0; i < n; i++) {
remoteSingleQuery(param);
}
8. 考虑代码在多线程环境下的并发一致性问题
在编写代码时,我们经常会遇到这样的业务场景:先查询数据库中是否存在某条记录,然后根据查询结果进行相应的操作(如修改)。
然而,“查询 + 修改”这个过程并不是原子操作,在多线程环境下,很容易出现并发问题。
下面是一个错误的示例:
if (isAvailable(ticketId)) {
// 1、给现金增加操作
// 2、deleteTicketById(ticketId)
} else {
return "没有可用现金券";
}
为了更直观地理解这个问题,我们来看一下下面的流程图:
- 线程A加现金
- 线程B加现金
- 线程A删除票标志
- 线程B删除票标志
显然,这样的代码存在并发问题。为了解决这个问题,我们可以利用数据库删除操作的原子性,示例代码如下:
if (deleteAvailableTicketById(ticketId) == 1) {
// 1、给现金增加操作
} else {
return "没有可用现金券";
}
所以,写完代码后,我们不妨发挥一下想象力,思考一下代码在多线程环境下的执行情况,看看是否会存在并发一致性问题。
9. 获取对象属性前,先判断对象是否为空
空指针异常是我们在编程过程中最常见的异常之一,一个小小的疏忽,就可能导致空指针异常出现在生产环境中,给系统带来严重的影响。
所以,当我们需要获取对象的属性时,不要轻易相信“理论上不为空”这种说法,养成先判断对象是否为空的好习惯。示例代码如下:
if (object != null) {
String name = object.getName();
}
10. 多线程异步优先选用合适线程池,并考虑线程池隔离
在多线程编程中,优先使用线程池是一个非常明智的选择。
使用线程池有诸多好处,首先,它能够帮助我们管理线程,避免了频繁创建和销毁线程所带来的资源损耗。
其次,它可以提高系统的响应速度,当有任务到来时,线程池中的线程可以立即执行任务。
此外,线程池还能实现线程的重复利用,提高资源的利用率。
同时,我们需要注意的是,尽量不要让所有业务都共用一个线程池,而是要考虑线程池隔离。
不同的关键业务应该分配不同的线程池,并且要根据业务的特点合理设置线程池的参数。
例如,对于一些对响应时间要求较高的业务,可以设置较大的核心线程数和较小的队列长度;而对于一些对吞吐量要求较高的业务,可以设置较小的核心线程数和较大的队列长度。
11. 手动编写SQL后,先在数据库中测试并查看执行计划
当我们手动编写完业务代码中的SQL语句后,不要急于将代码部署到测试服务器上,而是应该先把SQL语句拿到数据库中运行一下,检查是否存在语法错误。
这是一个非常简单却又很容易被忽视的步骤,通过在数据库中执行SQL语句,我们可以提前发现并规避很多错误。
同时,我们还应该使用explain命令查看SQL语句的执行计划,特别是要关注SQL语句是否使用了索引。
索引的使用情况直接影响着SQL语句的执行效率,如果SQL语句没有使用索引或者使用了不合适的索引,可能会导致查询性能低下。例如:
explain select * from user where userid = 10086 or age = 18;
12. 调用第三方接口时,考虑异常处理、安全性和超时重试
在调用第三方服务或分布式远程服务时,我们需要考虑多个方面的问题。
首先是异常处理,当调用别人的接口出现异常时,我们需要明确如何处理,是进行重试还是将其视为失败。
其次是超时问题,由于我们无法预估对方接口的响应时间,因此一般需要设置一个超时断开时间,以保护我们自己的接口。
例如,在进行HTTP请求时,我们可以设置连接超时时间和读取超时时间。
另外,还需要考虑重试次数,当接口调用失败时,是否需要进行重试以及重试的次数,都需要从业务的角度进行思考。
如果是涉及转账等重要的第三方服务,还需要考虑签名验签和加密等安全性问题,以确保数据的完整性和保密性。
简单举个例子,在进行HTTP请求时,我们需要考虑设置连接超时时间connect-time和重试次数retry。
13. 确保接口具备幂等性
接口的幂等性是一个非常重要的概念,特别是对于抢红包、转账等重要接口。最直观的业务场景就是用户连续点击两次,我们的接口需要能够保证在这种情况下不会出现重复处理的问题。
幂等性在数学和计算机学中是一个常见的概念,在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。
一般来说,实现幂等性的技术方案有很多种,常见的有查询操作、唯一索引、token机制(防止重复提交)、数据库的delete删除操作、乐观锁、悲观锁、Redis或Zookeeper分布式锁(例如在抢红包需求中可以使用Redis分布式锁)以及状态机幂等。
14. 多线程环境下,关注线性安全问题
在高并发的情况下,我们需要特别关注线性安全问题。以HashMap为例,它在高并发环境下可能会出现死循环的问题,因为它是非线性安全的。
因此,在多线程环境中,我们可以考虑使用ConcurrentHashMap来替代HashMap。
在编程过程中,我们应该养成良好的习惯,不要一上来就直接使用new HashMap()。需要注意的是,像Hashmap、Arraylist、LinkedList、TreeMap等都是线性不安全的,而Vector、Hashtable、ConcurrentHashMap等则是线性安全的。
15. 考虑主从延迟问题
在数据库操作中,先插入数据接着就去查询的代码逻辑比较常见,但这种操作可能会存在问题。
一般来说,数据库都有主库和从库,写入操作通常是在主库进行,而读取操作一般是从从库进行。
如果发生主从延迟,就可能会出现插入成功但却查询不到数据的情况。
对于重要业务,我们需要考虑是否强制读主库,或者是否需要修改设计
16.使用缓存的时候,考虑缓存跟DB的一致性,还有(缓存穿透、缓存雪崩和缓存击穿)
通俗点说,我们使用缓存就是为了「查得快,接口耗时小」。
但是呢,用到缓存,就需要「注意缓存与数据库的一致性」问题。
同时,还需要规避缓存穿透、缓存雪崩和缓存击穿三大问题。
- 缓存雪崩:指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
- 缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
- 缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。
以下为补充的4个写代码的好习惯:
17. 定期进行代码重构
随着项目的推进,代码可能会变得越来越复杂,出现重复代码、耦合度过高等问题。定期对代码进行重构,可以提高代码的可读性、可维护性和可扩展性。
例如,将重复的代码提取成公共方法,对复杂的类进行拆分等。
重构时要遵循“小步快跑”的原则,每次只做小的改动,并及时进行测试,确保不会引入新的bug。
18. 对代码进行版本控制和备份
使用版本控制系统(如Git)对代码进行管理是非常重要的。
它可以记录代码的变更历史,方便团队成员之间的协作,也可以在出现问题时回退到之前的版本。
同时,要定期对代码进行备份,以防意外情况(如服务器故障、硬盘损坏等)导致代码丢失。
可以将代码备份到外部存储设备或云存储中。
19. 编写单元测试和集成测试
单元测试可以对代码中的最小可测试单元(如函数、方法)进行验证,确保其功能的正确性。
集成测试则可以验证多个模块之间的交互是否正常。
编写测试用例可以帮助我们更早地发现代码中的问题,提高代码的质量。在编写代码时,尽量遵循测试驱动开发(TDD)的原则,先编写测试用例,再实现代码逻辑,确保代码满足预期的功能。
20. 关注代码性能和资源占用
在编写代码时,要关注代码的性能和资源占用情况。
避免编写时间复杂度和空间复杂度过高的代码,尽量使用高效的算法和数据结构。例如,在处理大数据集时,使用合适的排序算法可以显著提高性能。
同时,要注意内存的使用,及时释放不再使用的资源,避免内存泄漏。可以使用性能分析工具(如VisualVM、YourKit等)对代码进行性能分析,找出性能瓶颈并进行优化。
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!