维护过他人代码的开发者,大多有过类似的痛苦经历:一个方法动辄几百行,充斥着层层嵌套的if-else,逻辑晦涩难懂,注释寥寥无几,而原作者早已离职,无处询问。这种困境往往源于糟糕的代码规范。
遵循良好的编程实践,能显著提升代码的可读性、可维护性与健壮性。本文从代码编写规范、格式优化、设计原则及常见优化技巧等方面,梳理了45个实用建议,希望能帮助你写出更“漂亮”的代码。

1、规范命名
命名是编程中最频繁的操作之一。好的命名应遵循以下原则:
见名知意
int i = 0; // 不佳,无法体现含义
int count = 0; // 更好,清晰表达“计数”
能够读得出来
private String sfzh; // 不佳,不知所云
private String dhhm;
private String idCardNo; // 更好,身份证号
private String phone; // 更好,电话
2、规范代码格式
清晰的代码格式能极大提升阅读体验。应注重:
- 合适的空格:在运算符、关键字后添加空格。
- 代码对齐:尤其是大括号
{}要保持对齐。
- 及时换行:单行代码不宜过长,避免水平滚动。
幸运的是,现代IDE(如IntelliJ IDEA、Eclipse)都提供一键格式化功能,可以轻松实现代码美化。
3、写好代码注释
《代码整洁之道》指出,注释的恰当用途是弥补我们用代码表达意图时的失败。这意味着,当代码本身不足以清晰传达其目的时,就需要注释。
好的注释应:
- 解释意图:说明为什么这么写,目的是什么。
- 说明参数与返回值:明确入参和出参的含义。
- 提供警示:例如提醒参数不可为
null,或指出潜在的“坑”。
- 标记待办事项:对于未完成的功能,使用
// TODO进行标注。
for (Integer id : ids) {
if (id == 0) { // id为0代表测试数据,需要跳过
continue;
}
// 处理实际业务逻辑
}
4、try catch 内部代码抽成一个方法
try-catch块有时会干扰我们对核心业务逻辑的理解。此时,可以将try块内的主逻辑抽取成一个独立的方法。
下图是Eureka服务端源码中服务下线实现的一段代码,try块内的逻辑非常长:

我们可以将其重构,使结构更清晰:
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
doInternalCancel(appName, id, isReplication);
} finally {
read.unlock(); // 确保锁被释放
}
// 剩余代码...
}
private boolean doInternalCancel(String appName, String id, boolean isReplication) {
// 真正处理服务下线的核心逻辑
}
5、方法别太长
过长的函数是代码的“坏味道”。它会让读者望而生畏,难以理清不同代码段所属的业务功能。我曾维护过一个超过2000行的方法,后来使用设计模式成功将其重构。
当方法过长时,应考虑将同一业务功能的代码块抽取成独立的小方法。
6、抽取重复代码
重复代码是维护的噩梦。当同一段逻辑出现在多处,任何修改都需同步更新所有地方,极易出错且扩展性差。解决方法通常是将其抽取成公共工具类或父类方法。
7、多用return
深层嵌套的if条件会严重影响可读性:
if (条件1) {
if (条件2) {
if (条件3) {
if (条件4) {
if (条件5) {
System.out.println("三友的java日记");
}
}
}
}
}
使用return进行“卫语句”优化,可以使逻辑扁平化、更直观:
if (!条件1) {
return;
}
if (!条件2) {
return;
}
if (!条件3) {
return;
}
if (!条件4) {
return;
}
if (!条件5) {
return;
}
System.out.println(“三友的java日记“);
8、if条件表达式不要太复杂
复杂的条件表达式难以一眼看穿:
if (((StringUtils.isBlank(person.getName())
|| "三友的java日记".equals(person.getName()))
&& (person.getAge() != null && person.getAge() > 10))
&& "汉".equals(person.getNational())) {
// 处理逻辑
}
可以将其拆解为多个有意义的布尔变量:
boolean sanyouOrBlank = StringUtils.isBlank(person.getName()) || "三友的java日记".equals(person.getName());
boolean ageGreaterThanTen = person.getAge() != null && person.getAge() > 10;
boolean isHanNational = "汉".equals(person.getNational());
if (sanyouOrBlank && ageGreaterThanTen && isHanNational) {
// 处理逻辑
}
9、优雅地参数校验
手动进行参数校验会导致代码冗长:
@PostMapping
public void addPerson(@RequestBody AddPersonRequest addPersonRequest) {
if (StringUtils.isBlank(addPersonRequest.getName())) {
throw new BizException(“人员姓名不能为空“);
}
if (StringUtils.isBlank(addPersonRequest.getIdCardNo())) {
throw new BizException(“身份证号不能为空“);
}
// 处理新增逻辑
}
使用如hibernate-validator这样的框架,可以通过注解优雅地完成校验:
@Data
@ToString
private class AddPersonRequest {
@NotBlank(message = “人员姓名不能为空“)
private String name;
@NotBlank(message = “身份证号不能为空“)
private String idCardNo;
// 忽略其他字段
}
Controller层只需添加@Valid注解:
@PostMapping
public void addPerson(@RequestBody @Valid AddPersonRequest addPersonRequest) {
// 处理新增逻辑
}
10、统一返回值
设计接口时,应统一响应格式。这便于前端和调用方以固定模式解析,减少额外判断。例如:
{
"code": 0,
"message": “成功“,
"data": “返回数据“
}
在Spring框架中,可以通过AOP或自定义HandlerMethodReturnValueHandler轻松实现全局统一返回值封装。
11、统一异常处理
如果没有全局异常处理,每个Controller方法都可能需要try-catch:
@GetMapping("/{id}")
public Result<T> selectPerson(@PathVariable("id") Long personId) {
try {
PersonVO vo = personService.selectById(personId);
return Result.success(vo);
} catch (Exception e) {
//打印日志
return Result.error(“系统异常“);
}
}
Spring提供了@ControllerAdvice和@ExceptionHandler机制,可以让我们在单个地方集中处理所有异常,避免污染业务代码。
12、尽量不传递null值
明确方法是否接受null参数,可以使用@NonNull和@Nullable注解来声明,增强代码的意图表达并让IDE或静态分析工具给出提示。
// 示例1:可以接受null,内部做判断
public void updatePerson(@Nullable Person person) {
if (person == null) {
return;
}
personService.updateById(person);
}
// 示例2:明确禁止null,调用者需保证
public void updatePerson(@NonNull Person person) {
personService.updateById(person);
}
13、尽量不返回null值
返回null会迫使调用者进行判空,增加出错风险。如果无法避免,可以考虑返回Optional类型来明确表达“可能无值”的语义。
public Optional<Person> getPersonById(Long personId) {
return Optional.ofNullable(personService.selectById(personId));
}
同样,也可以使用@NonNull和@Nullable注解来标记方法的返回值特性。
14、日志打印规范
良好的日志是排查问题的生命线。应注意:
- 可搜索性:日志信息应包含明确的关键字(如业务ID、用户ID)。
- 异常堆栈:记录异常时,必须打印完整的堆栈信息(
e.getMessage()远远不够)。
- 合适的级别:
error用于错误,warn用于警告,info用于一般信息,debug用于调试。
- 内容精简:避免打印过大的数据(如完整图片的Base64编码)。
15、统一类库
一个项目中应避免引入多个功能相似的类库。例如,JSON解析统一使用Jackson或Fastjson,工具类统一使用Hutool或Apache Commons。这能减少依赖冲突,降低团队的学习和维护成本。
16、尽量使用工具类
不要手动编写常见的判断逻辑,应使用成熟工具类。
// 不佳
if (persons != null && persons.size() > 0) { ... }
// 推荐
if (!CollectionUtils.isEmpty(persons)) { ... }
字符串判空、集合操作、IO流关闭等,都应优先寻找标准库或常用工具类中已封装好的方法。
17、尽量不要重复造轮子
在时间允许的情况下,优先使用经过社区验证的、成熟稳定的开源库。例如,日期格式化应避免使用线程不安全的SimpleDateFormat,而可以选择Hutool的DateUtil或Java 8的DateTimeFormatter。
自己造轮子容易忽略边界条件和隐藏的坑(如上述的线程安全问题)。
18、类和方法单一职责
这是设计模式的七大原则之一。一个类或方法应该只做一件事,并且做好。例如,在Nacos 1.x中,HttpAgent接口的唯一职责就是封装HTTP请求参数,并向服务端发送请求。

其他模块需要请求Nacos服务端时,只需依赖此接口,无需关心鉴权、参数组装等细节,这就是单一职责的体现。
19、尽量使用聚合/组合代替继承
继承的缺点包括:灵活性低(Java单继承)、耦合性高(父类修改可能影响所有子类)。而聚合(通过setter注入)和组合(在创建时直接构造)能提供更灵活的结构。
组合:
public class OrderService {
private UserService userService = new UserService(); // 组合
}
聚合:
public class OrderService {
private UserService userService; // 聚合
public void setUserService(UserService userService) {
this.userService = userService;
}
}
20、使用设计模式优化代码
恰当使用设计模式能提升代码的扩展性。例如,实现一个支持多种渠道(短信、APP、邮件)的消息通知功能,策略模式就非常适合。
定义策略接口:
public interface MessageNotifier {
boolean support(int type); // 0:短信 1:app 2:邮件
void notify(User user, String content);
}
实现具体策略:
@Component
public class SMSMessageNotifier implements MessageNotifier {
@Override
public boolean support(int type) {
return type == 0;
}
@Override
public void notify(User user, String content) {
// 调用短信API
}
}
@Component
public class AppMessageNotifier implements MessageNotifier {
@Override
public boolean support(int type) {
return type == 1;
}
@Override
public void notify(User user, String content) {
// 调用APP推送API
}
}
统一调度入口:
@Resource
private List<MessageNotifier> messageNotifiers; // Spring会自动注入所有实现
public void notifyMessage(User user, String content, int notifyType) {
for (MessageNotifier notifier : messageNotifiers) {
if (notifier.support(notifyType)) {
notifier.notify(user, content);
break; // 找到即处理
}
}
}
未来新增邮件通知,只需新增一个实现类即可,无需修改现有逻辑。
21、不滥用设计模式
设计模式是利器,但不可滥用。简单的逻辑用简单的代码实现即可。例如,上面打印人员信息的例子,用一连串的if和StringBuilder已经足够清晰,强行套用责任链模式反而会过度设计,增加复杂度。
22、面向接口编程
在可能存在多种实现的场景,应依赖接口或抽象类,而非具体实现。例如,文件存储功能,可能有阿里云OSS、七牛云、自建服务器等多种实现。
public interface FileStorage {
String store(String fileName, byte[] bytes);
}
在业务代码中注入FileStorage接口。当需要更换存储提供商时,只需提供新的接口实现并注入IOC容器,业务代码无需任何改动,实现了代码与具体实现的解耦。
23、经常重构旧的代码
随着业务演进,早期实现的代码可能变得不再适用。应定期审视并重构,以适应新的需求或采用更优的设计。例如,最初只支持短信通知,代码直接耦合了短信API;当需要支持更多渠道时,就是引入策略模式进行重构的好时机。
24、null值判断
空指针异常(NPE)是Java开发中的常客。常见的NPE来源有:数据库查询返回null、自动拆箱(如Integer转为int)、RPC调用返回null等。关键位置必须进行判空处理,或使用Optional进行优雅封装。
25、pojo类重写toString方法
POJO类通常包含多个属性。重写toString()方法,可以在日志打印、调试时方便地查看对象内部状态,是调试的好帮手。
26、魔法值用常量表示
代码中直接出现的、未经解释的字面量称为“魔法值”,它降低了代码的可读性和可维护性。
public void sayHello(String province) {
if (“广东省“.equals(province)) { // 魔法值
System.out.println(“靓仔~~“);
}
}
应定义为常量:
private static final String GUANG_DONG_PROVINCE = “广东省“;
public void sayHello(String province) {
if (GUANG_DONG_PROVINCE.equals(province)) {
System.out.println(“靓仔~~“);
}
}
27、资源释放写到finally
对于锁、IO流、数据库连接等需要显式释放的资源,必须将释放代码写在finally块中,以确保无论是否发生异常,资源都能被正确释放。

上图是CopyOnWriteArrayList.add方法的实现,锁的释放就放在finally中。
28、使用线程池代替手动创建线程
手动创建线程存在诸多问题:线程生命期管理困难、创建销毁开销大。使用线程池的优势显而易见:
- 降低资源消耗:复用已创建的线程。
- 提高响应速度:任务到达时,通常有立即可用的线程。
- 提高可管理性:可以统一监控和调优。
29、线程设置名称
为线程设置一个有意义的名称,在查看日志时能快速定位问题。

如上图日志,[nio-8888-exec-1]清晰地标明了这是Tomcat NIO处理线程。
30、涉及线程间可见性加volatile
在RocketMQ源码中,消费者拉取消息的线程通过一个stopped变量控制循环。

当客户端关闭时,另一个线程会将stopped设为true。由于Java内存模型的可见性问题,拉取线程可能无法立即“看到”这个修改。使用volatile关键字可以保证变量的修改对所有线程立即可见。

31、考虑线程安全问题
高并发场景下,必须警惕线程安全问题。例如,缓存第三方接口的Token时,如果多个线程同时发现缓存失效,可能引发重复获取、覆盖等问题。此时可能需要通过双重检查锁(Double-Checked Locking)等机制来保证安全。根据场景,选择合适的线程安全集合(如ConcurrentHashMap)或加锁策略。
32、慎用异步
异步虽能提升响应速度,但也带来复杂度:
- 事务问题:异步线程与主线程通常不在同一事务中,导致数据一致性问题。
- CPU负载:不合理的线程数设置可能导致CPU使用率飙升。
- 意想不到的异常:并发量激增可能压垮下游服务,导致大量失败。
使用异步前,务必评估其对事务、资源和稳定性的影响。
33、减小锁的范围
锁的范围应尽可能小,只在必须保证线程安全的代码块上加锁。

如上图,lock.lock()并没有放在方法最开头,因为前面的判断代码(数组长度检查)是线程安全的,这样可以减少锁持有的时间,提高性能。
34、有类型区分时定义好枚举
业务中常有各种类型标识(如附件类型、订单状态)。使用枚举而非魔法数字(0,1,2)或字符串,可以让代码更清晰、安全,编译期就能发现错误。
35、远程接口调用设置超时时间
无论是RPC调用还是HTTP请求第三方接口,必须设置连接超时和读取超时。这是防止因网络问题或下游服务故障导致自身线程池被占满、服务雪崩的基本防线。
36、集合使用应当指明初始化大小
ArrayList和HashMap在容量不足时会自动扩容,涉及旧数组拷贝到新数组,有性能开销。如果能预估元素数量,在构造时指定初始容量,可以避免或减少扩容次数。
List<Person> list = new ArrayList<>(expectedSize);
Map<String, Person> map = new HashMap<>(expectedSize);
37、尽量不要使用BeanUtils来拷贝属性
Spring的BeanUtils.copyProperties虽然方便,但其底层基于反射,性能较差。在性能敏感或高频调用的场景,建议:
- 使用IDE生成
getter/setter代码。
- 使用性能更高的映射工具,如MapStruct,它在编译期生成高效的赋值代码,性能接近手写。
38、使用StringBuilder进行字符串拼接
使用+多次拼接字符串,编译器会创建多个StringBuilder对象,效率低下。
String result = str1 + str2 + str3; // 可能创建多个StringBuilder
对于循环内或已知多次拼接的场景,应显式使用StringBuilder(单线程)或StringBuffer(多线程):
StringBuilder sb = new StringBuilder();
sb.append(str1).append(str2).append(str3);
String result = sb.toString();
39、@Transactional应指定回滚的异常类型
Spring的@Transactional注解,默认只对RuntimeException和Error进行回滚。如果希望检查异常(Checked Exception)也触发回滚,必须通过rollbackFor属性明确指定。

@Transactional(rollbackFor = Exception.class) // 所有异常都回滚
public void update() { ... }
40、谨慎方法内部调用动态代理的方法
Spring AOP(包括事务@Transactional)是通过代理对象实现的。在同一个类中,一个普通方法A调用另一个有@Transactional注解的方法B,此时B上的事务会失效,因为A调用的是this.B(),而非代理对象的B()。
@Service
public class PersonService {
public void update(Person person) {
// 处理...
updatePerson(person); // 事务失效!
}
@Transactional
public void updatePerson(Person person) {
// 处理...
}
}
解决方法:注入自身的代理对象来调用。
@Service
public class PersonService {
@Autowired
private PersonService personService; // 代理对象
public void update(Person person) {
// 处理...
personService.updatePerson(person); // 事务生效
}
@Transactional
public void updatePerson(Person person) {
// 处理...
}
}
41、需要什么字段select什么字段
避免使用SELECT *。查询无用字段的弊端:
42、不循环调用数据库
在循环中执行SQL查询是性能杀手,会产生“N+1”查询问题。
// 错误示例
for (Person person : personList) {
PersonExt ext = personExtMapper.selectById(person.getId()); // 循环查询DB
}
应改为批量查询,在内存中组装数据:
// 正确示例
List<Long> ids = personList.stream().map(Person::getId).collect(Collectors.toList());
List<PersonExt> extList = personExtMapper.selectByIds(ids); // 一次批量查询
Map<Long, PersonExt> extMap = extList.stream().collect(Collectors.toMap(PersonExt::getPersonId, Function.identity()));
for (Person person : personList) {
PersonExt ext = extMap.get(person.getId()); // 内存获取
}
43、用业务代码代替多表join
《阿里巴巴Java开发手册》中禁止超过三个表的join操作。

因为join操作在MySQL中通常使用嵌套循环实现,当数据量大时性能较差。建议如上一条所述,拆分为单表查询,在业务代码(内存)中进行数据组装。
44、装上阿里代码检查插件
在追求开发速度时,代码规范容易被忽视。安装阿里发布的《阿里巴巴Java开发手册》配套插件(支持IDEA/Eclipse),可以在编码时实时给出不规范提示。

这对于培养良好编码习惯、保持团队代码风格统一非常有帮助。
45、及时跟同事沟通
编程不是闭门造车。尤其是接手新项目或实现复杂功能时,及时与同事、技术负责人沟通技术方案和业务上下文,能有效避免走弯路、踩大坑,事半功倍。
以上45个技巧,覆盖了从命名到设计,从并发到数据库的多个层面。将它们融入日常开发,你的代码质量将得到稳步提升。记住,写出好代码不仅是对他人的尊重,更是对自己未来维护工作的投资。
欢迎在 云栈社区 分享你的编码心得,与更多开发者一同探讨、精进技艺。