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!
感謝您閱讀本文,希望對您有所幫助! 另外,我只是開始就此問題發表意見,因此,我很高興聽到您處理錯誤的策略。 評論部分已打開!
翻譯自: https://www.freecodecamp.org/news/how-to-handle-errors-with-grace-failing-silently-is-not-an-option-de6ce8f897d7/
rcu寬限期