如何正确处理错误:沉默并不总是好事



我从未对错误处理有任何特别的看法。 如果我开始使用现有代码,那么我将继续执行源代码作者所从事的任务。 如果我从头开始编写代码,那么我所做的事情对我来说是正确的。

但是最近,我遇到了一个问题,该错误由于代码中的“静默”错误而表现出来。 我意识到有些事情需要反思。 也许我无法更改正在处理的整个代码库中错误的处理方式,但是可以肯定地进行了一些优化。

我们提醒您: 对于所有“哈勃”读者来说,使用“哈勃”促销代码注册任何Skillbox课程时均可享受10,000卢布的折扣。

Skillbox建议:在线教育课程“专业Java开发人员”


并非总是值得砍下肩膀


错误处理的第一步应该是理解“错误”不是“错误!”的时候。 当然,所有这些都取决于应用程序的业务逻辑,但总的来说,某些错误是显式的,可以进行修复而不会出现问题。

  • 您是否有一个日期范围,其中“之前”位于“来自”之前? 重新排序。
  • 您是否有一个以+开头或包含破折号的电话号码,并且您不希望出现特殊字符? 删除它们。
  • 空集合问题? 确保在访问之前(使用惰性初始化或构造函数)对其进行初始化

不要因为您可以修复的错误而中断代码执行,当然也不要干扰您对服务或应用程序用户的操作。 如果您能够理解问题并即时解决-那就做吧。



返回Null或其他幻数


零值,–1(期望为正数)和其他魔术返回值-所有这些都是魔鬼的产物,魔鬼将检查错误的责任转移给调用函数。

有了它们,您的代码将充满这样的块,这将使应用程序逻辑不清楚:

return_value = possibly_return_a_magic_value() if return_value < 0: handle_error() else: do_something() other_return_value = possibly_nullable_value() if other_return_value is None: handle_null_value() else: do_some_other_thing() 

好吧,或更简单,但也很糟糕:
 var item = returnSomethingWhichCouldBeNull(); var result = item?.Property?.MaybeExists; if (result.HasValue) { DoSomething(); } 

将null传递给方法也是一个问题,尽管在某些开发人员的代码中,您可能会发现方法以几行输入验证开头的地方。 但是实际上,所有这些都是不必要的。 大多数现代语言不会一次提供一种工具,而是提供几种工具,使您可以清楚地表明自己的期望。 它们还会跳过无用的检查,例如,将参数定义为非零或使用相应的装饰器。

错误代码


当添加不必要的代码复杂度时,这与null和其他类似值的问题相同。

例如,您可以使用以下构造返回错误代码:

 int errorCode; var result = getSomething(out errorCode); if (errorCode != 0) { doSomethingWithResult(result); } 

结果可以显示如下:
 public class Result<T> { public T Item { get; set; } // At least "ErrorCode" is an enum public ErrorCode ErrorCode { get; set; } = ErrorCode.None; public IsError { get { return ErrorCode != ErrorCode.None; } } } public class UsingResultConstruct { ... var result = GetResult(); if (result.IsError) { switch (result.ErrorCode) { case ErrorCode.NetworkError: HandleNetworkError(); break; case ErrorCode.UserError: HandleUserError(); break; default: HandleUnknownError(); break; } } ActuallyDoSomethingWithResult(result); ... } 

这确实不是最好的代码。 事实证明Item仍然可以为空。 实际上,我们不能保证(协议除外)当结果不包含错误时,您可以安全地访问该项目。

完成所有这些处理后,您仍然必须将错误代码转换为错误消息并对其进行处理。 有时,由于代码过于复杂,会发生此消息本身未正确显示的情况。

还有一个更严重的问题:如果您或其他人更改内部实现以使用新的错误代码处理新的无效状态,则整个结构将完全停止工作。

如果第一次没用,请再试一次


在继续之前,值得强调的是,“静默”程序失败是不好的。 意外的问题可能会在最不适当的时刻发生-例如,在周末,您无法快速解决所有问题。



如果您阅读Clean Code ,那么您很可能想知道为什么不只引发异常? 如果不是,那么您很可能认为例外是邪恶的根源。 我过去也这样认为,但现在我的想法有所不同。
至少对我而言,有趣的一点是C#中新方法的默认实现是引发NotImplementedException,而Python中新方法的默认实现是“ pass”。

因此,大多数情况下,我们为Python输入“静音错误”设置。 我想知道有多少开发人员花费大量时间来了解正在发生的事情以及为什么该程序无法正常工作。 但是最后,在几个小时之后,他们发现他们忘记了实现占位符方法。
但是看看这个:

 public MyDataObject UpdateSomething(MyDataObject toUpdate) { if (_dbConnection == null) { throw new DbConnectionError(); } try { var newVersion = _dbConnection.Update(toUpdate); if (newVersion == null) { return null; } MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { throw new DbConnectionError(); } catch (MyDataObjectUnhappyException dou) { throw new MalformedDataException(); } catch (Exception ex) { throw new UnknownErrorException(); } } 

当然,仅靠异常不能保护您避免创建不可读和不受管的代码。 您应该将例外应用为经过深思熟虑且平衡的策略。 否则,如果项目太大,则您的应用程序可能处于不一致状态。 相反,如果项目太小,您会陷入混乱。

为防止这种情况发生,我建议您谨记以下几点:

一致性高于一切。 您必须始终确保应用程序是一致的。 如果这意味着您必须用try / catch块包装每两行,只需将其全部隐藏在另一个函数中即可。

 def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource() 

合并错误是必要的。 如果您提供不同的类型来处理不同类型的错误,则很好。 但是,这是给您的,而不是用户的。 对于他们来说,抛出唯一的异常,以便用户仅知道出了点问题。 细节是您的责任范围。

 public MyDataObject UpdateSomething(MyDataObject toUpdate) { try { var newVersion = _dbConnection.Update(toUpdate); MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { HandleDbConnectionClosed(); throw new UpdateMyDataObjectException(); } catch (MyDataObjectUnhappyException dou) { RollbackVersion(); throw new UpdateMyDataObjectException(); } catch (Exception ex) { throw new UpdateMyDataObjectException(); } } 

值得立即捕获异常。 最好在最低级别拦截它们。 如上所述,保持一致并隐藏细节是值得的。 错误处理应留在应用程序的顶层。 如果一切正确完成,则可以将应用程序本身的逻辑流与错误处理流分开。 这样您就可以编写清晰易读的代码。

 def my_api(): try: item = get_something_from_the_db() new_version = do_something_to_item(item) return new_version except Exception as ex: handle_high_level_exception(ex) 

现在就这些了,如果您想讨论错误及其处理的话题-Wellcome。

Skillbox建议:

Source: https://habr.com/ru/post/zh-CN441148/


All Articles