rcu宽限期
by Rina Artstain
通过丽娜·阿斯特斯坦
I’ve never really had much of an opinion about error handling. This may come as a shock to people who know me as quite opinionated (in a good way!), but yeah. If I was coming into an existing code base I just did whatever they did before, and if I was writing from scratch I just did whatever felt right to me at the time.
对于错误处理,我从来没有真正有过很多意见。 这可能会令那些了解我的人震惊(以一种很好的方式!),但是是的。 如果我要使用现有的代码库,那么我只是做他们以前做过的事情,如果我是从头开始写的,那么我当时所做的一切都是对的。
When I recently read the error handling section in Clean Code by Uncle Bob, that was the first time I gave the subject any thought at all. Sometime later I ran into a bug which was caused by some code failing silently, and I realized it might be time to think about it a bit more. I might not be able to change how errors are handled in the entire code base I’m working on, but at least I would be informed on what approaches exists and what the tradeoffs are, and, you know, have an opinion about the matter.
当我最近阅读Bob叔叔的“ 清理代码”中的错误处理部分时,那是我第一次对主题有任何想法。 以后的某个时候,我遇到了一个错误,该错误是由一些代码无声失败导致的,我意识到可能应该再考虑一下。 我可能无法更改正在处理的整个代码库中错误的处理方式,但是至少我会被告知存在哪些方法以及如何权衡,并且您对此事有意见。
期待西班牙宗教裁判所 (Expect the Spanish Inquisition)
The first step of handling errors is to identify when an “error” is not an “error!”. This of course depends on your application’s business logic, but in general, some errors are obvious and easy to fix.
处理错误的第一步是确定何时“错误”不是“错误!”。 当然,这取决于应用程序的业务逻辑,但是总的来说,有些错误是显而易见的,并且易于修复。
- Got a from-to date range where the “to” is before “from”? Switch the order. 是否有一个从“到”在“从”之前的“到”日期范围? 切换顺序。
- Got a phone number which starts with + or contains dashes where you expect no special characters? Remove them. 是否有一个以+开头或包含破折号的电话号码,而您不希望这些字符带有特殊字符? 删除它们。
Null collection a problem? Make sure you initialize it before accessing (using lazy initialization or in the constructor).
空集合有问题吗? 确保在访问之前将其初始化(使用惰性初始化或在构造函数中)。
Don’t interrupt your code flow for errors you can fix, and certainly don’t interrupt your users. If you can understand the problem and fix it yourself — just do it.
不要因为可以解决的错误而中断代码流,当然也不要中断用户。 如果您可以理解问题并亲自解决,请执行此操作。
返回Null或其他魔术值 (Returning Null or Other Magic Values)
Null values, -1 where a positive number is expected and other “magic” return values — all these are evils which move the responsibility for error checking to the caller of the function. This is not only a problem because it causes error checking to proliferate and multiply, it is also a problem because it depends on convention and requires your user to be aware of arbitrary implementation details.
空值-1(期望为正数)和其他“魔术”返回值-所有这些都是罪恶,将错误检查的责任移交给了函数的调用者。 这不仅是一个问题,因为它会导致错误检查激增并成倍增加,它也是一个问题,因为它依赖于约定并且要求您的用户注意任意实现的细节。
Your code will be full of code blocks like these which obscure the application’s logic:
您的代码将充满像这样的代码块,这些代码块会模糊应用程序的逻辑:
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()
Even if your language has a built in nullable value propagation system — that’s just applying an unreadable patch to flaky code:
即使您的语言具有内置的可为空的值传播系统,也只是对易碎的代码应用了不可读的补丁:
var item = returnSomethingWhichCouldBeNull();var result = item?.Property?.MaybeExists;if (result.HasValue){ DoSomething();}
Passing null values to methods is just as problematic, and you’ll often see methods begin with a few lines of checking that the input is valid, but this is truly unnecessary. Most modern languages provide several tools which allow you to be explicit about what you expect and skip those code-cluttering checks, e.g. defining parameters as non-nullable or with an appropriate decorator.
将null值传递给方法同样存在问题,并且您经常会看到方法从检查输入是否有效的几行开始,但这确实是不必要的。 大多数现代语言提供了几种工具,使您可以清楚地了解期望的内容并跳过那些代码混乱的检查,例如,将参数定义为不可为空或使用适当的修饰符。
错误代码 (Error Codes)
Error codes have the same problem as null and other magic values, with the additional complication of having to, well, deal with error codes.
错误代码与null和其他魔术值具有相同的问题,另外还必须处理错误代码。
You might decide to return the error code through an “out” parameter:
您可能决定通过“ out”参数返回错误代码:
int errorCode;var result = getSomething(out errorCode);if (errorCode != 0){ doSomethingWithResult(result);}
You may choose to wrap all your results in a “Result” construct like this (I’m very guilty of this one, though it was very useful for ajax calls at the time):
您可以选择将所有结果包装在这样的“结果”构造中(我对此非常内,,尽管当时对ajax调用非常有用):
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); ...}
Yep. That’s really bad. The Item property could still be empty for some reason, there’s no actual guarantee (besides convention) that when the result doesn’t contain an error you can safely access the Item property.
是的 真的很糟糕 由于某些原因,Item属性仍可能为空,没有任何实际保证(约定除外),即当结果不包含错误时,您可以安全地访问Item属性。
After you’re done with all of this handling, you still have to translate your error code to an error message and do something with it. Often, at this point you’ve obscured the original problem enough that you might not have the exact details of what happened, so you can’t even report the error effectively.
完成所有这些处理后,您仍然必须将错误代码转换为错误消息并对其进行处理。 通常,在这一点上,您已经掩盖了最初的问题,以至于您可能不知道所发生的事情的确切细节,因此甚至无法有效地报告错误。
On top of this horribly unnecessarily over-complicated and unreadable code, an even worse problem exists — if you, or someone else, change your internal implementation to handle a new invalid state with a new error code, the calling code will have no way of knowing something which they need to handle has changed and will fail in unpredictable ways.
在此可怕不必要过于复杂而无法读取的代码顶部,一个更糟糕的问题存在-如果你或其他人,改变你的内部实现来处理与新的错误代码的新无效状态,调用代码将没有任何的办法知道他们需要处理的事情已经改变,并且将以无法预测的方式失败 。
如果一开始您没有成功,请尝试,然后再抓住 (If At First You Don’t Succeed, Try, Catch, Finally)
Before we continue, this might be a good time to mention that code failing silently is not a good thing. Failing silently means errors can go undetected for quite a while before exploding suddenly at inconvenient and unpredictable times. Usually over the weekend. The previous error handling methods allow you to fail silently, so maybe, just maybe, they’re not the best way to go.
在继续之前,这可能是一个很好的时机,指出代码无声失败不是一件好事。 静默失败意味着很长一段时间都无法发现错误,然后在不方便且不可预测的时间突然爆炸。 通常在周末。 先前的错误处理方法使您能够静默地失败,所以也许,也许不是最好的方法。
At this point, if you’ve read Clean Code you’re probably wondering why anyone would ever do any of that instead of just throwing an exception? If you haven’t, you might think exceptions are the root of all evil. I used to feel the same way, but now I’m not so sure. Bear with me, let’s see if we can agree that exceptions are not all bad, and might even be quite useful. And if you’re writing in a language without exceptions? Well, it is what it is.
在这一点上,如果您已经阅读了Clean Code,您可能想知道为什么有人会这样做而不是仅仅引发异常? 如果没有,您可能会认为例外是万恶之源。 我曾经有过同样的感觉,但是现在我不太确定。 忍受我,让我们看看我们是否可以同意例外并非全都不好,甚至可能非常有用。 而且,如果您使用的语言无一例外? 好吧,就是这样。
An interesting side note, at least to me, is that the default implementation for a new C# method is to throw a NotImplementedException, whereas the default for a new python method is “pass”.
至少对我而言,一个有趣的旁注是,新C#方法的默认实现是抛出NotImplementedException,而新python方法的默认实现是“ pass”。
I’m not sure if this is a C# convention or just how my Resharper was configured, but the result is basically setting up python to fail silently. I wonder how many developers have spent a long and sad debugging session trying to figure what was going on, only to find out they had forgotten to implement a placeholder method.
我不确定这是C#约定还是Resharper的配置方式,但结果基本上是将python设置为静默失败。 我想知道有多少开发人员花了很长时间来进行调试,以弄清楚到底发生了什么,却发现他们忘记了实现占位符方法。
But wait, you could easily create a cluttered mess of error checking and exception throwing which is quite similar to the previous error checking sections!
但是,等等,您可以轻松创建混乱的错误检查和异常抛出,这与前面的错误检查部分非常相似!
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(); }}
So, of course, throwing exceptions will not protect you from unreadable and unmanageable code. You need to apply exception throwing as a well thought out strategy. If your scope is too big, your application might end up in an inconsistent state. If your scope is too small, you’ll end up with a cluttered mess.
因此,当然,抛出异常不会保护您免受无法阅读和无法管理的代码的侵害。 您需要将异常抛出作为一种经过深思熟虑的策略。 如果范围太大,则应用程序可能会处于不一致状态。 如果范围太小,您将陷入混乱。
My approach to this problem is as follows:
我对这个问题的解决方法如下:
Consistency rulezzz. You must make sure that your application is always in a consistent state. Ugly code makes me sad, but not as much as actual problems which affect the users of whatever it is your code is actually doing. If that means you have to wrap every couple of lines with a try/catch block — hide them inside another function.
一致性rulezzz。 您必须确保您的应用程序始终处于一致状态。 丑陋的代码让我很伤心,但不像实际问题那样严重,这些问题会影响用户的实际行为。 如果这意味着您必须用try / catch块包装每两行,请将它们隐藏在另一个函数中。
def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource()
Consolidate errors. It’s fine if you care about different kinds of errors which need to be handled differently, but do your users a favor and hide that internally. Externally, throw a single type of exception just to let your users know something went wrong. They shouldn’t really care about the details, that’s your responsibility.
合并错误。 如果您关心需要以不同方式处理的不同类型的错误,但对您的用户有所帮助并在内部隐藏该错误,那是很好的。 在外部,仅引发一种异常即可让您的用户知道出了什么问题。 他们不应该真正在乎细节,这是您的责任。
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(); }}
Catch early, catch often. Catch your exceptions as close to the source at the lowest level possible. Maintain consistency and hide the details (as explained above), then try to avoid handling errors until the very top level of your application. Hopefully there aren’t too many levels along the way. If you can pull this off, you’ll be able to clearly separate the normal flow of your application logic from the error handling flow, allowing your code to be clear and concise without mixing concerns.
早点捕获,经常捕获。 在可能的最低级别上将异常捕获到尽可能接近源的位置。 保持一致性并隐藏细节(如上所述),然后尝试避免错误直到应用程序的最高层。 希望在此过程中不要有太多的级别。 如果可以做到这一点,您将能够清楚地将应用程序逻辑的正常流程与错误处理流程区分开,从而使您的代码清晰明了,而无需担心。
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)
Thanks for reading this far, I hope it was helpful! Also, I’m only starting to form my opinions on this subject, so I’d be really happy to hear what your strategy is for handling errors. The comments section is open!
感谢您阅读本文,希望对您有所帮助! 另外,我只是开始就此问题发表意见,因此,我很高兴听到您处理错误的策略。 评论部分已打开!
rcu宽限期