找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2178

积分

0

好友

290

主题
发表于 3 天前 | 查看: 18| 回复: 0

维护过他人代码的开发者,大多有过类似的痛苦经历:一个方法动辄几百行,充斥着层层嵌套的if-else,逻辑晦涩难懂,注释寥寥无几,而原作者早已离职,无处询问。这种困境往往源于糟糕的代码规范。

遵循良好的编程实践,能显著提升代码的可读性、可维护性与健壮性。本文从代码编写规范、格式优化、设计原则及常见优化技巧等方面,梳理了45个实用建议,希望能帮助你写出更“漂亮”的代码。

写出漂亮代码的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块内的逻辑非常长:
Eureka internalCancel方法代码截图

我们可以将其重构,使结构更清晰:

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解析统一使用JacksonFastjson,工具类统一使用HutoolApache Commons。这能减少依赖冲突,降低团队的学习和维护成本。

16、尽量使用工具类

不要手动编写常见的判断逻辑,应使用成熟工具类。

// 不佳
if (persons != null && persons.size() > 0) { ... }
// 推荐
if (!CollectionUtils.isEmpty(persons)) { ... }

字符串判空、集合操作、IO流关闭等,都应优先寻找标准库或常用工具类中已封装好的方法。

17、尽量不要重复造轮子

在时间允许的情况下,优先使用经过社区验证的、成熟稳定的开源库。例如,日期格式化应避免使用线程不安全的SimpleDateFormat,而可以选择HutoolDateUtilJava 8DateTimeFormatter

自己造轮子容易忽略边界条件和隐藏的坑(如上述的线程安全问题)。

18、类和方法单一职责

这是设计模式的七大原则之一。一个类或方法应该只做一件事,并且做好。例如,在Nacos 1.x中,HttpAgent接口的唯一职责就是封装HTTP请求参数,并向服务端发送请求。
Nacos HttpAgent接口代码截图
其他模块需要请求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、不滥用设计模式

设计模式是利器,但不可滥用。简单的逻辑用简单的代码实现即可。例如,上面打印人员信息的例子,用一连串的ifStringBuilder已经足够清晰,强行套用责任链模式反而会过度设计,增加复杂度。

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块截图
上图是CopyOnWriteArrayList.add方法的实现,锁的释放就放在finally中。

28、使用线程池代替手动创建线程

手动创建线程存在诸多问题:线程生命期管理困难、创建销毁开销大。使用线程池的优势显而易见:

  • 降低资源消耗:复用已创建的线程。
  • 提高响应速度:任务到达时,通常有立即可用的线程。
  • 提高可管理性:可以统一监控和调优。

29、线程设置名称

为线程设置一个有意义的名称,在查看日志时能快速定位问题。
线程名称在日志中显示截图
如上图日志,[nio-8888-exec-1]清晰地标明了这是Tomcat NIO处理线程。

30、涉及线程间可见性加volatile

在RocketMQ源码中,消费者拉取消息的线程通过一个stopped变量控制循环。
RocketMQ拉取消息线程代码截图
当客户端关闭时,另一个线程会将stopped设为true。由于Java内存模型的可见性问题,拉取线程可能无法立即“看到”这个修改。使用volatile关键字可以保证变量的修改对所有线程立即可见。
volatile变量声明截图

31、考虑线程安全问题

高并发场景下,必须警惕线程安全问题。例如,缓存第三方接口的Token时,如果多个线程同时发现缓存失效,可能引发重复获取、覆盖等问题。此时可能需要通过双重检查锁(Double-Checked Locking)等机制来保证安全。根据场景,选择合适的线程安全集合(如ConcurrentHashMap)或加锁策略。

32、慎用异步

异步虽能提升响应速度,但也带来复杂度:

  • 事务问题:异步线程与主线程通常不在同一事务中,导致数据一致性问题。
  • CPU负载:不合理的线程数设置可能导致CPU使用率飙升。
  • 意想不到的异常:并发量激增可能压垮下游服务,导致大量失败。
    使用异步前,务必评估其对事务、资源和稳定性的影响。

33、减小锁的范围

锁的范围应尽可能小,只在必须保证线程安全的代码块上加锁。
CopyOnWriteArrayList addAll方法锁范围截图
如上图,lock.lock()并没有放在方法最开头,因为前面的判断代码(数组长度检查)是线程安全的,这样可以减少锁持有的时间,提高性能。

34、有类型区分时定义好枚举

业务中常有各种类型标识(如附件类型、订单状态)。使用枚举而非魔法数字(0,1,2)或字符串,可以让代码更清晰、安全,编译期就能发现错误。

35、远程接口调用设置超时时间

无论是RPC调用还是HTTP请求第三方接口,必须设置连接超时和读取超时。这是防止因网络问题或下游服务故障导致自身线程池被占满、服务雪崩的基本防线。

36、集合使用应当指明初始化大小

ArrayListHashMap在容量不足时会自动扩容,涉及旧数组拷贝到新数组,有性能开销。如果能预估元素数量,在构造时指定初始容量,可以避免或减少扩容次数。

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注解,默认只对RuntimeExceptionError进行回滚。如果希望检查异常(Checked Exception)也触发回滚,必须通过rollbackFor属性明确指定。
Transactional rollbackOn方法代码截图

@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 *。查询无用字段的弊端:

  • 增加网络传输和内存开销,特别是大文本字段。
  • 可能导致无法使用覆盖索引,造成不必要的回表查询,降低性能。
    应明确指定所需字段。
    // MyBatis-Plus 示例
    Wrappers.query().select(“name“);

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截图
因为join操作在MySQL中通常使用嵌套循环实现,当数据量大时性能较差。建议如上一条所述,拆分为单表查询,在业务代码(内存)中进行数据组装。

44、装上阿里代码检查插件

在追求开发速度时,代码规范容易被忽视。安装阿里发布的《阿里巴巴Java开发手册》配套插件(支持IDEA/Eclipse),可以在编码时实时给出不规范提示。
阿里代码规范插件截图
这对于培养良好编码习惯、保持团队代码风格统一非常有帮助。

45、及时跟同事沟通

编程不是闭门造车。尤其是接手新项目或实现复杂功能时,及时与同事、技术负责人沟通技术方案和业务上下文,能有效避免走弯路、踩大坑,事半功倍。

以上45个技巧,覆盖了从命名到设计,从并发到数据库的多个层面。将它们融入日常开发,你的代码质量将得到稳步提升。记住,写出好代码不仅是对他人的尊重,更是对自己未来维护工作的投资。

欢迎在 云栈社区 分享你的编码心得,与更多开发者一同探讨、精进技艺。




上一篇:Java开源项目源码阅读实战:18条方法论与RocketMQ实例解析
下一篇:企业级统一消息推送系统架构设计:从混乱到统一的实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 10:05 , Processed in 0.576190 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表