转自: https://taosha.club/topic/61a...
对于异样解决, 有几个准则
应用异样而非返回码
在很久以前,许多语言都不反对异样。这些语言解决和汇报谬误的伎俩都无限。你要么设置一个谬误标识,要么返回给调用者查看的错误码。以下代码展现了这些伎俩
public class DeviceController { public void sendShutDown() { DeviceHandle handle = getHandle(DEV1); // Check the state of the device if (handle != DeviceHandle.INVALID) { // Save the device status to the record field retrieveDeviceRecord(handle); // If not suspended, shut down if (record.getStatus() != DEVICE_SUSPENDED) { pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } else { logger.log("Device suspended. Unable to shut down"); } } else { logger.log("Invalid handle for: " + DEV1.toString()); } }}
这类伎俩的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻查看谬误。可怜的是,这个步骤很容易被忘记。所以,遇到谬误时,最好抛出一个异样。调用代码很整洁,其逻辑不会被错误处理搞乱。
以下代码展现了在办法中遇到谬误时抛出异样的情景
public class DeviceController { public void sendShutDown() { try { tryToShutDown(); } catch (DeviceShutDownError e) { logger.log(e); } } private void tryToShutDown() throws DeviceShutDownError { DeviceHandle handle = getHandle(DEV1); DeviceRecord record = retrieveDeviceRecord(handle); pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); }}
留神这段代码整洁了很多。这不仅关乎好看。这段代码更好,因为之前纠结的元素设施敞开算法和错误处理当初被隔离了。你能够查看其中任一元素,别离了解它。
给出异样产生的环境阐明
你抛出的每个异样,都该当提供足够的环境阐明,以便判断谬误的起源和处所。在Java中,你能够从任何异样里失去堆栈形迹(stack trace);然而,堆栈形迹却无奈通知你该失败操作的初衷。
应创立信息充沛的谬误音讯,并和异样一起传递进来。在音讯中,包含失败的操作和失败类型。如果你的应用程序有日志零碎,传递足够的信息给catch块,并记录下来。
别返回null值
要探讨错误处理,就肯定要提及那些容易引发谬误的做法。第一项就是返回null值。我不想去计算已经见过多少简直每行代码都在查看null值的应用程序。上面就是个例子:
public void registerItem (Item item){ if (item != null) { ItemRegistry registry = peristentStore.getItemRegistry(); if (registry != null) { Item existing = registry.getItem(item.getID()); if (existing.getBillingPeriod().hasRetailOwner()) { existing.register(item); } } }}
这种代码看似不坏,其实糟透了!返回null值,基本上是在给本人减少工作量,也是在给调用者添乱。只有有一处没查看null值,应用程序就会失控。
你有没有留神到,嵌套if语句的第二行没有查看null值?如果在运行时persistentStore为null会产生什么事?咱们会在运行时失去一个NullPointerException异样,兴许有人在代码顶端捕捉这个异样,也可能没有捕捉。两种状况都很蹩脚。对于从应用程序深处抛出的NullPointerException异样,你到底该作何反馈呢?
能够搪塞说上列代码的问题是少做了一次null值查看,其实问题多多。如果你打算在办法中返回null值,不如抛出异样,或是返回特例对象。如果你在调用某个第三方API中可能返回null值的办法,能够思考用新办法打包这个办法,在新办法中抛出异样或返回特例对象。
在许多状况下,特例对象都是爽口良药。构想有这么一段代码:
List<Employee> employees = getEmployees();if (employees != null) { for(Employee e : employees) { totalPay += e.getPay(); }}
所幸Java有Collections.emptyList()办法,该办法返回一个预约义不可变列表,可用于这种目标:
public List<Employee> getEmployees () { if ( ..there are no employees .. ) return Collections.emptyList();}
这样编码,就能尽量避免NullPointerException的呈现,代码也就更整洁了。
别传递null值
在办法中返回null值是蹩脚的做法,但将null值传递给其余办法就更蹩脚了。除非API要求你向它传递null值,否则就要尽可能防止传递null值。
举例说明起因。用上面这个简略的办法计算两点的投射:
public class MetricsCalculator{ public double xProjection(Point p1, Point p2) { return (p2.x – p1.x) * 1.5; } …}
如果有人传入null值会怎么?
calculator.xProjection(null, new Point(12, 13));
当然,咱们会失去一个NullPointerException异样。
如何修改?能够创立一个新异样类型并抛出:
public class MetricsCalculator { public double xProjection(Point p1, Point p2) { if (p1 == null || p2 == null) { throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection"); } return (p2.x –p1.x) *1.5; }}
这样做好些吗?可能比null指针异样好一些,但要记住,咱们还得为InvalidArgumentException异样定义处理器。这个处理器该做什么?还有更好的做法吗?
还有代替计划。能够应用一组断言:
public class MetricsCalculator { public double xProjection(Point p1, Point p2) { assert(p1 != null, "p1 should not be null"); assert(p2 != null, "p2 should not be null"); return (p2.x –p1.x) *1.5; }}
看上去很美,但仍未解决问题。如果有人传入null值,还是会失去运行时谬误。思考下, 还能够怎么持续优化?
整洁代码是可读的,但也要强固。可读与强固并不抵触。如果将错误处理隔离对待,独立于次要逻辑之外,就能写出强固而整洁的代码。做到这一步,咱们就能独自解决它,也极大地晋升了代码的可维护性。
我的项目中的最佳实际
- 千万别吞噬
- 应用全局异样捕捉,避免前端用户看到不该看到的
- 异样堆栈残缺打印
- 自定义异样可打印3到5行异样栈就够用
- 定义错误码枚举值,分门别类分明
- 不要在异样catch中做业务操作
对于无需关注的可控异样(checked exception),应用@SneakyThrows, 不要在业务代码里写try/catch来解决
可控异样的代价就是违反凋谢/闭合准则。如果你在办法中抛出可控异样,而catch语句在三个层级之上,你就得在catch语句和抛出异样处之间的每个办法签名中申明该异样。这意味着对软件中较低层级的批改,都将波及较高层级的签名。批改好的模块必须从新构建、公布,即使它们本身所关注的任何货色都没改变过。
如无必要, 不要在业务代码里写try/catch, 对立交由spring boot全局异样拦截器解决(全局异样拦截器会做"异样堆栈残缺打印", "定义错误码枚举值,分门别类分明" 这几件事, 不必业务开发人员时刻操心), 以下状况例外:
- lamda表达式中的必须捕捉异样;
- 新起的线程, 异样会被吞噬;
- 有的异样不能影响业务,要本人解决掉, 这种状况的法则是: 如果产生异样, 心愿逻辑继续执行上来, 就本人try/catch, 否则就不要try/catch, 交由对立异样拦截器解决
依据以上准则和最佳实际实操优化
具体的问题和优化过程review时解说
总结
咱们写的代码根本能够分为三种:
- 一种是实现业务失常逻辑的代码;
- 一种是为了实现业务逻辑, 不得不写的额定操作的代码;
- 一种是小心翼翼地解决各种谬误或意外逻辑的代码;
作为程序猿, 应时刻思考下, 本人写的代码是否是第一种代码, 如果不是, 看看能够如何优化缩小第二or第三种代码, 毕竟真正有价值, 且能带给咱们愉悦感的都是第一种代码;