异常处理遇到过的那些坑

今年有个目标之一就是提升团队代码的质量,所以时常会思索如何把这件事做到更好,不想教条主义,也不想搞出一个代码规范,强制团队照着做,落地的效果不好,反而把大家的积极性给弄没了。所以我的原则是,我们一起看看什么事是我们不能做的,排除掉,剩下的就是我们可以做的,同时真正搞清楚问题在哪里,而不是简单的模仿。从我个人的经验看,代码优化中最重要的一点就是对异常情况的处理。今天,我就借这个话题,谈谈如何来优化我们的代码。


坑1:捕捉异常不做处理


如下段代码所示,不知道各位在自家的代码中见过多少,我是期望大家不要遇见。且不说printStackTrace()这样的代码就不应该出线在正式的上线代码中,不做处理本身这会导致业务的潜在问题被隐藏了,所以这个坑是我认为异常处理中最糟糕的一点。


try

{

....

}

catch(Exception ex)
{

ex.printStackTrace();

System.out.println(XXX);

}



对于异常的捕捉处理,遵循以下的流程:

  1. 处理该异常,执行具体的逻辑处理,加上必要的系统日志记录 (log4j,logcat ...)。

  2. 涉及其他系统或者上一级系统,需要转换成对应的消息通知相关系统。上一级是前端,需要转换成前端可以“读懂”的错误提示。

  3. 无法处理该异常或者该异常需要上一级统一处理,Throw out该异常。


坑2:不处理资源释放


还是见代码说话,如果append出错,那么IO流就不会被关掉,那么最终就会导致整个程序因为溢出崩掉。


FileWriter fileWriter = null;

try

{

fileWriter = new FileWriter("");

fileWriter.append(item.toString());

fileWriter.close();

}

catch (IOException e)

{

...

}

在使用文件、IO流、数据库连接等不会自动释放的资源时,应该在使用完毕后马上将其关闭。关闭资源的代码try...catch...finally的finally内执行,否则就可能因为Exception的原因造成资源无法释放。


坑3:对异常不进行分类处理


代码中,最容易看到的一种情况就是设定catch的异常类型是Exception, 并且只有一套处理逻辑, 如下:

try{

...

}

catch (Exception e){

...

}


这里的问题主要有以下两点:


1. catch的不同Exception,可能需要执行不同的处理逻辑,一个catch要同时处理所有逻辑就很难实现。

2. 由于是捕捉的基类Exception, 那么RuntimeException也会被捕捉到,如果稍微不注意的话,RuntimeException最终没有任何实际处理,代码中的真正错误被掩盖掉了。


所以,正确的做法应该是按照具体的异常进行分类处理, 例如:

try{

...

}

catch (FileNotFoundException e){

// alert that the specified file

// does not exist

}

catch (EOFException e){

// alert that the end of the file

// was reached

}

catch (ObjectStreamException e){

// alert that the file is corrupted

}

catch (IOException e){

// alert that some other I/O

// error occurred

}


坑4:将大段代码放进一个Try-Catch中

  有时可以看到,一些代码的作者恨不得把整个函数里的实现代码都放入单个try中,原因就在于为了图省事,不愿花时间分析一大块代码中哪几行代码会抛出什么异常、异常的具体类型是什么,应该如何处理。这样的做法导致异常发生后,后续调试找问题更麻烦,一大段代码中有太多的地方可能抛出Exception。这样的做法导致,很难去统计和判断要catch哪一些类型的Exception, 只能写一个粗糙的Exception, 又掉进我们说的第3个坑里。


一点总结


在和大家一起分析了上面的异常坑后,如果未来我们想避免踩进一个异常坑,编写异常代码可以遵循以下三个点:


1. 出了什么错?

2. 在哪出的错?

3. 为什么出错?


扫描二维码或手动搜索微信公众号【架构栈】: ForestNotes

欢迎转载,带上以下二维码即可


### Spring Batch 使用过程中的常见问题及解决方案 #### 1. **配置文件加载失败** 当使用 Spring Boot 配置 Spring Batch 应用程序时,可能会遇到无法正确加载配置文件的情况。这通常是因为 `application.properties` 或 `application.yml` 文件未被正确识别。 解决方法:确保在 `spring.batch.job.enabled=false` 的情况下手动触发 Job 执行[^4]。如果仍然存在问题,请验证配置文件路径是否正确以及是否存在拼写错误。 ```java @SpringBootApplication public class MyBatchApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(MyBatchApplication.class, args); // 手动启动 Job JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); Job job = (Job) context.getBean("myJob"); try { jobLauncher.run(job, new JobParameters()); } catch (Exception e) { e.printStackTrace(); } } } ``` --- #### 2. **Job 启动失败** 某些情况下,由于数据库连接池配置不当或事务管理器设置不正确,可能导致 Job 启动失败。 解决方法:确认使用的 DataSource 和 TransactionManager 是否已正确定义并注入到对应的 Bean 中[^5]。此外,检查数据库表结构是否与 Spring Batch 默认模式一致。 ```xml <!-- 数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> ``` --- #### 3. **ItemReader 报错** 在读取大量数据时,可能因内存不足或其他原因导致 ItemReader 出现异常。 解决方法:调整 Chunk Size 参数以减少每次处理的数据量[^3]。同时,考虑优化 SQL 查询语句以提高性能。 ```yaml spring: batch: chunk-size: 100 ``` --- #### 4. **Step 跳过机制失效** 有时即使设置了 Skip Policy,仍可能出现跳过的逻辑未能生效的现象。 解决方法:确保定义的 Skip Exception 类型匹配实际抛出的异常类,并合理配置最大跳过次数限制[^2]。 ```java @Bean public Step myStep() { return stepBuilder.get("myStep") .<String, String>chunk(10) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) .faultTolerant() .skip(IllegalArgumentException.class) .skipLimit(10) .build(); } ``` --- #### 5. **多线程并发冲突** 启用多线程后,可能发生资源竞争或状态同步问题。 解决方法:引入 Thread-Safe 的组件设计,或者采用 Partitioner 来分配独立的任务给不同线程执行。 ```java @Bean public TaskExecutor taskExecutor() { SimpleAsyncTaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor(); asyncTaskExecutor.setConcurrencyLimit(5); // 设置并发数 return asyncTaskExecutor; } @Bean public Step partitionedStep() { return stepBuilderFactory.get("partitionedStep") .partitioner(stepName(), partitioner()) .step(slaveStep()) .gridSize(gridSize()) // 定义分区数量 .taskExecutor(taskExecutor()) .build(); } ``` --- #### 6. **日志记录混乱** 随着批处理规模增大,日志输出变得难以追踪和分析。 解决方法:通过 Logback 或 Log4j 进行精细化的日志分类和级别控制[^1]。例如,为每个 Job 创建单独的日志文件以便于排查问题。 ```properties log4j.logger.org.springframework.batch=INFO log4j.appender.file.File=./logs/batch.log log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n ``` --- #### 7. **远程分片与分区差异理解不清** 开发者常混淆 Remote Chunks 和 Remote Partitions 的应用场景。 解释说明:Remote Chunks 主要适用于 Master 将任务切分为多个子任务发送至 Slave 节点完成;而 Remote Partitions 则侧重于按区域划分整个数据集再交由各节点分别计算。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值