
很多后端开发者都有过这样的经历:
这三者看似独立,实则相辅相成:
核心目标:写出 “能抗住业务迭代” 的代码 —— 既让当前开发者省心,也让未来的自己 / 同事少踩坑。
可维护性的核心是 “降低认知负担”:任何人拿到代码,能快速理清逻辑、定位问题、安全修改。
// 反例1:命名模糊public void processData(List // list是什么?用户列表?订单列表? for (String s : list) { if (s.length() > 6) { // 做某些操作 } }}// 反例2:注释冗余/无效public int add(int a, int b) { // a加b,返回结果(代码本身已说明,注释多余) return a + b;}// 正例1:命名精准public void filterInvalidUserIds(List<String> userIds) { // 明确是“过滤无效用户ID” for (String userId : userIds) { if (userId.length() > 6) { // 为什么是6?注释补充背景 // 兼容老系统:老用户ID为6位以上,新用户为6位,仅保留新用户ID validUserIds.add(userId); } }}com.example.user├── service/│ ├── register/ # 注册相关│ │ ├── UserRegisterService.java # 注册核心逻辑│ │ └── UserRegisterValidator.java # 注册参数校验│ ├── login/ # 登录相关│ └── profile/ # 个人信息相关└── model/ ├── dto/ # 入参/出参DTO └── po/ # 数据库实体public String getDiscount(User user, Order order) { if (user != null) { if (user.isVIP()) { if (order.getAmount().compareTo(new BigDecimal(1000)) > 0) { return "8折"; } else { return "9折"; } } else { if (order.getAmount().compareTo(new BigDecimal(1000)) > 0) { return "9.5折"; } else { return "无折扣"; } } } else { return "无折扣"; }}public String getDiscount(User user, Order order) { // 卫语句:提前处理异常场景 if (user == null || !user.isVIP()) { return order.getAmount().compareTo(new BigDecimal(1000)) > 0 ? "9.5折" : "无折扣"; } // 核心逻辑简化 return order.getAmount().compareTo(new BigDecimal(1000)) > 0 ? "8折" : "9折";}可扩展性的核心是 “拥抱变化”:当业务新增需求、修改规则时,无需重构核心逻辑,仅通过 “新增代码” 或 “配置调整” 实现。
public class OrderPaymentService { // 硬编码依赖支付宝实现,新增微信支付需修改此类 private AlipayService alipayService = new AlipayService(); public boolean pay(Order order) { return alipayService.pay(order.getOrderNo(), order.getAmount()); }}// 1. 定义支付接口public interface PaymentProvider { boolean pay(String orderNo, BigDecimal amount);}// 2. 支付宝实现public class AlipayProvider implements PaymentProvider { @Override public boolean pay(String orderNo, BigDecimal amount) { /* 支付宝逻辑 */ }}// 3. 微信支付实现(新增,无需修改原有代码)public class WechatPayProvider implements PaymentProvider { @Override public boolean pay(String orderNo, BigDecimal amount) { /* 微信逻辑 */ }}// 4. 核心服务依赖接口public class OrderPaymentService { private final PaymentProvider paymentProvider; // 构造函数注入,灵活切换实现 public OrderPaymentService(PaymentProvider paymentProvider) { this.paymentProvider = paymentProvider; } public boolean pay(Order order) { return paymentProvider.pay(order.getOrderNo(), order.getAmount()); }}public class DiscountService { // 硬编码:VIP折扣8折,普通用户9.5折,修改需改代码 public BigDecimal calculateDiscount(User user) { return user.isVIP() ? new BigDecimal("0.8") : new BigDecimal("0.95"); }}// 1. 配置文件(application.yml)discount: vip: 0.8 normal: 0.95// 2. 代码读取配置@ConfigurationProperties(prefix = "discount")public class DiscountProperties { private BigDecimal vip; private BigDecimal normal; // getter/setter}// 3. 核心服务public class DiscountService { private final DiscountProperties discountProperties; public BigDecimal calculateDiscount(User user) { return user.isVIP() ? discountProperties.getVip() : discountProperties.getNormal(); }}核心业务中,预判可能的变化,预留扩展接口,避免后续重构。
public class OrderStatusMachine { // 状态变更处理器,支持扩展不同状态的自定义逻辑 private Map<OrderStatus, OrderStatusHandler> handlers = new HashMap(); // 注册处理器(扩展点) public void registerHandler(OrderStatus status, OrderStatusHandler handler) { handlers.put(status, handler); } public void changeStatus(Order order, OrderStatus newStatus) { // 执行默认逻辑 order.setStatus(newStatus); // 执行扩展逻辑(如订单完成后发送通知、更新库存) OrderStatusHandler handler = handlers.get(newStatus); if (handler != null) { handler.handle(order); } }}// 扩展:订单完成后的处理逻辑public class OrderCompletedHandler implements OrderStatusHandler { @Override public void handle(Order order) { // 发送通知、更新库存等 }}可测试性的核心是 “能独立验证逻辑正确性”:无需依赖复杂环境,就能通过测试用例覆盖核心场景。
public class OrderService { // 硬编码依赖库存服务,测试时需启动库存服务 private StockService stockService = new StockServiceImpl(); public boolean createOrder(Order order) { // 扣减库存(依赖真实库存服务) boolean deductSuccess = stockService.deduct(order.getProductId(), order.getQuantity()); if (deductSuccess) { // 创建订单 return true; } return false; }}public class OrderService { private final StockService stockService; private final OrderMapper orderMapper; // 构造函数注入,测试时可传入Mock对象 public OrderService(StockService stockService, OrderMapper orderMapper) { this.stockService = stockService; this.orderMapper = orderMapper; } public boolean createOrder(Order order) { boolean deductSuccess = stockService.deduct(order.getProductId(), order.getQuantity()); if (deductSuccess) { orderMapper.insert(order); return true; } return false; }}// 测试用例(Mockito)@Testpublic void testCreateOrder_success() { // 1. Mock依赖 StockService mockStockService = Mockito.mock(StockService.class); OrderMapper mockOrderMapper = Mockito.mock(OrderMapper.class); Mockito.when(mockStockService.deduct("product_1001", 2)).thenReturn(true); // 2. 初始化服务(注入Mock) OrderService orderService = new OrderService(mockStockService, mockOrderMapper); // 3. 执行测试 Order order = new Order("order_2001", "product_1001", 2); boolean result = orderService.createOrder(order); // 4. 验证结果 Assert.assertTrue(result); Mockito.verify(mockStockService, Mockito.times(1)).deduct("product_1001", 2); Mockito.verify(mockOrderMapper, Mockito.times(1)).insert(order);}public BigDecimal calculateOrderAmount(List> items) { BigDecimal amount = BigDecimal.ZERO; for (OrderItem item : items) { // 业务逻辑:计算金额(含折扣、税费) BigDecimal itemAmount = item.getPrice().multiply(new BigDecimal(item.getQuantity())); amount = amount.add(itemAmount.multiply(new BigDecimal("0.98"))); // 98折 } // 副作用:写入数据库日志(混合逻辑,测试需依赖数据库) orderLogMapper.insert(new OrderLog(amount)); return amount;}// 纯逻辑函数:仅计算金额,无依赖、无副作用,易测试public BigDecimal calculateOrderAmount(List<OrderItem> items) { BigDecimal amount = BigDecimal.ZERO; for (OrderItem item : items) { BigDecimal itemAmount = item.getPrice().multiply(new BigDecimal(item.getQuantity())); amount = amount.add(itemAmount.multiply(new BigDecimal("0.98"))); } return amount;}// 副作用函数:处理数据库操作,依赖纯逻辑函数@Transactionalpublic void saveOrderLog(List<OrderItem> items) { BigDecimal amount = calculateOrderAmount(items); orderLogMapper.insert(new OrderLog(amount));}// 测试纯逻辑(无需任何外部依赖)@Testpublic void testCalculateOrderAmount() { // 准备测试数据 List> items = Arrays.asList( new OrderItem("product_1", new BigDecimal("100"), 2), new OrderItem("product_2", new BigDecimal("200"), 1) ); // 执行测试 BigDecimal result = calculateOrderAmount(items); // 验证结果(100*2 + 200*1 = 400,98折后为392) Assert.assertEquals(new BigDecimal("392.00"), result.setScale(2));}// 测试副作用函数(Mock数据库依赖)@Testpublic void testSaveOrderLog() { // Mock依赖 OrderLogMapper mockMapper = Mockito.mock(OrderLogMapper.class); OrderService orderService = new OrderService(mockStockService, mockOrderMapper, mockMapper); // 准备数据 List> items = Arrays.asList(new OrderItem("product_1", new BigDecimal("100"), 1)); // 执行测试 orderService.saveOrderLog(items); // 验证:数据库日志被正确插入 Mockito.verify(mockMapper, Mockito.times(1)).insert( Mockito.argThat(log -> log.getAmount().equals(new BigDecimal("98.00"))) );}public class OrderValidator { public boolean validate(Order order) { // 依赖静态方法,无法Mock“时间”相关逻辑 return DateUtils.isAfterNow(order.getPayTime()) // 静态方法:判断付款时间是否在当前之后 && StringUtils.isNotBlank(order.getOrderNo()); }}// 1. 封装静态工具为实例类(解耦静态依赖)public class TimeValidator { public boolean isAfterNow(Date date) { return date.after(new Date()); // 底层仍可调用DateUtils,但对外提供实例方法 }}public class StringValidator { public boolean isNotBlank(String str) { return StringUtils.isNotBlank(str); }}// 2. 依赖注入实例,而非静态方法public class OrderValidator { private final TimeValidator timeValidator; private final StringValidator stringValidator; // 构造函数注入 public OrderValidator(TimeValidator timeValidator, StringValidator stringValidator) { this.timeValidator = timeValidator; this.stringValidator = stringValidator; } public boolean validate(Order order) { return timeValidator.isAfterNow(order.getPayTime()) && stringValidator.isNotBlank(order.getOrderNo()); }}// 3. 测试用例:Mock时间逻辑,覆盖“过期”场景@Testpublic void testValidate_fail_whenPayTimeExpired() { // Mock工具类实例 TimeValidator mockTimeValidator = Mockito.mock(TimeValidator.class); StringValidator mockStringValidator = Mockito.mock(StringValidator.class); // 准备测试数据(付款时间已过期) Order order = new Order("order_2001", new Date(System.currentTimeMillis() - 3600000)); // 设定Mock行为:模拟“付款时间已过期” Mockito.when(mockTimeValidator.isAfterNow(order.getPayTime())).thenReturn(false); Mockito.when(mockStringValidator.isNotBlank(order.getOrderNo())).thenReturn(true); // 执行测试 OrderValidator validator = new OrderValidator(mockTimeValidator, mockStringValidator); boolean result = validator.validate(order); // 验证结果:验证失败 Assert.assertFalse(result);}public boolean processOrder(OrderDTO orderDTO) { // 1. 参数校验 if (orderDTO == null || StringUtils.isBlank(orderDTO.getProductId()) || orderDTO.getQuantity() return false; } // 2. 业务计算:转换DTO为PO Order order = new Order(); order.setOrderNo(UUID.randomUUID().toString()); order.setProductId(orderDTO.getProductId()); order.setQuantity(orderDTO.getQuantity()); order.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getQuantity()))); // 3. 外部调用:扣减库存 boolean deductSuccess = stockService.deduct(orderDTO.getProductId(), orderDTO.getQuantity()); if (!deductSuccess) { return false; } // 4. 持久化:保存订单 orderMapper.insert(order); return true;}// 1. 参数校验函数public boolean validateOrderDTO(OrderDTO orderDTO) { return orderDTO != null && StringUtils.isNotBlank(orderDTO.getProductId()) && orderDTO.getQuantity() > 0;}// 2. DTO转PO函数(纯逻辑)public Order convertToOrder(OrderDTO orderDTO) { Order order = new Order(); order.setOrderNo(UUID.randomUUID().toString()); order.setProductId(orderDTO.getProductId()); order.setQuantity(orderDTO.getQuantity()); order.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getQuantity()))); return order;}// 3. 核心流程函数(组合小函数)public boolean processOrder(OrderDTO orderDTO) { // 校验 if (!validateOrderDTO(orderDTO)) { return false; } // 转换 Order order = convertToOrder(orderDTO); // 扣减库存 boolean deductSuccess = stockService.deduct(orderDTO.getProductId(), orderDTO.getQuantity()); if (!deductSuccess) { return false; } // 保存订单 orderMapper.insert(order); return true;}// 测试:单独测试参数校验(无需依赖其他服务)@Testpublic void testValidateOrderDTO_fail_whenQuantityZero() { OrderDTO orderDTO = new OrderDTO("product_1001", new BigDecimal("100"), 0); boolean result = validateOrderDTO(orderDTO); Assert.assertFalse(result);}// 测试:单独测试DTO转PO(纯逻辑,无依赖)@Testpublic void testConvertToOrder() { OrderDTO orderDTO = new OrderDTO("product_1001", new BigDecimal("100"), 2); Order order = convertToOrder(orderDTO); Assert.assertEquals("product_1001", order.getProductId()); Assert.assertEquals(new BigDecimal("200.00"), order.getAmount().setScale(2));}public class OrderService { // 全局静态变量:存储已处理的订单数(所有实例共享) private static int processedCount = 0; public boolean processOrder(Order order) { // 业务逻辑:保存订单 orderMapper.insert(order); processedCount++; // 修改全局状态,所有实例共享该值 return true; } public int getProcessedCount() { return processedCount; }}// 测试用例1:执行后会修改全局状态@Testpublic void testProcessOrder_case1() { OrderService service = new OrderService(); service.processOrder(new Order("order_001")); Assert.assertEquals(1, service.getProcessedCount()); // 预期1,执行通过}// 测试用例2:依赖测试用例1的执行结果,顺序变了就失败@Testpublic void testProcessOrder_case2() { OrderService service = new OrderService(); service.processOrder(new Order("order_002")); // 若先执行case2,预期2但实际是1;若先执行case1,预期2但实际是2——测试结果不稳定 Assert.assertEquals(2, service.getProcessedCount()); }问题本质:全局状态是 “共享资源”,多个测试用例修改同一状态会导致 “测试污染”,测试结果依赖执行顺序,无法保证可靠性。
public class OrderService { // 实例变量:每个服务实例单独持有状态,不共享 private int processedCount = 0; private final OrderMapper orderMapper; // 构造函数注入依赖 public OrderService(OrderMapper orderMapper) { this.orderMapper = orderMapper; } public boolean processOrder(Order order) { orderMapper.insert(order); processedCount++; // 仅修改当前实例的状态,不影响其他实例 return true; } public int getProcessedCount() { return processedCount; }}// 测试用例1:独立实例,状态隔离@Testpublic void testProcessOrder_case1() { OrderMapper mockMapper = Mockito.mock(OrderMapper.class); OrderService service1 = new OrderService(mockMapper); // 实例1 service1.processOrder(new Order("order_001")); Assert.assertEquals(1, service1.getProcessedCount()); // 稳定通过}// 测试用例2:独立实例,与case1状态互不干扰@Testpublic void testProcessOrder_case2() { OrderMapper mockMapper = Mockito.mock(OrderMapper.class); OrderService service2 = new OrderService(mockMapper); // 实例2 service2.processOrder(new Order("order_002")); Assert.assertEquals(1, service2.getProcessedCount()); // 稳定通过,与执行顺序无关}public class OrderStatisticService { private final RedisTemplate> redisTemplate; private static final String KEY = "order:processed:count"; public OrderStatisticService(RedisTemplateTemplate) { this.redisTemplate = redisTemplate; } public void incrementCount() { // 用Redis原子操作记录状态,测试时可Mock Redis redisTemplate.opsForValue().increment(KEY, 1); } public Integer getCount() { return redisTemplate.opsForValue().get(KEY); }}// 测试用例:Mock Redis,隔离外部依赖@Testpublic void testIncrementCount() { RedisTemplateRedis = Mockito.mock(RedisTemplate.class); ValueOperations> mockOps = Mockito.mock(ValueOperations.class); Mockito.when(mockRedis.opsForValue()).thenReturn(mockOps); Mockito.when(mockOps.increment(KEY, 1)).thenReturn(1); OrderStatisticService service = new OrderStatisticService(mockRedis); service.incrementCount(); Mockito.verify(mockOps, Mockito.times(1)).increment(KEY, 1); Assert.assertEquals(1, service.getCount().intValue());}误区 | 后果 | 修正方案 |
|---|---|---|
依赖全局静态变量 / 单例(无注入) | 测试用例相互污染,结果不稳定 | 改用实例变量,单例通过依赖注入暴露 |
函数返回 void,无输出 | 无法验证函数执行效果 | 让函数返回执行结果(如布尔值、状态对象),或通过 Mock 验证副作用 |
业务逻辑与 HTTP 请求 / 响应强耦合 | 测试需启动 Web 容器,效率低 | 拆分 Controller 与 Service,Service 层纯业务逻辑无 Web 依赖 |
测试用例依赖真实数据库 / 缓存 | 测试环境要求高,执行慢 | 用 H2 内存数据库、Mock 工具(Mockito、TestContainers)隔离外部依赖 |
很多开发者会陷入 “过度设计” 的误区:为了追求某一个特性,导致代码复杂度飙升(比如为了可扩展性,设计十几层抽象)。核心原则是 “适度设计,按需优化”:
可维护性、可扩展性、可测试性,本质都是 “降低代码的认知成本和修改成本”。落地的关键不是死记设计模式,而是记住三个核心:
优秀的后端代码,经得起业务迭代的考验,也经得起时间的考验 —— 这不仅是技术能力的体现,更是对团队和未来自己的负责。从今天开始,试着在写代码时多问自己三个问题:
坚持下去,你的编码能力会在潜移默化中实现质的飞跃。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。