C# 异常与特殊返回值
哪一个是编程中更好的实践 我不是说完全的排他性。它更多地用于以下方面:C# 异常与特殊返回值,c#,.net,exception,C#,.net,Exception,哪一个是编程中更好的实践 我不是说完全的排他性。它更多地用于以下方面: list.Find,从中获取default(T)或您的值,而不是ValueNotFound异常(示例) 或 list.IndexOf,从中可以得到-1或正确的索引。答案是这取决于具体情况 在列表中找不到项的情况下,抛出异常是一种可怕的做法,因为该项不在列表中是完全可行的 现在,如果这是某种特殊的列表,并且该项应该绝对地在列表中找到,那么应该抛出一个异常,因为您遇到了不可行/不合理的情况 一般来说,对于业务规则之类的事情,专门
list.Find
,从中获取default(T)
或您的值,而不是ValueNotFound
异常(示例)
或
list.IndexOf
,从中可以得到-1或正确的索引。答案是这取决于具体情况
在列表中找不到项的情况下,抛出异常是一种可怕的做法,因为该项不在列表中是完全可行的
现在,如果这是某种特殊的列表,并且该项应该绝对地在列表中找到,那么应该抛出一个异常,因为您遇到了不可行/不合理的情况
一般来说,对于业务规则之类的事情,专门的错误代码更好,因为您知道这些事情发生的可能性,并且希望对这些可能性做出反应。异常适用于您不期望的情况,如果发生异常,则无法继续执行代码。从纯设计角度来看,我更喜欢在发生“异常”事件时抛出异常,例如,未找到您请求的密钥。主要原因是它使消费者的生活更加轻松-他们不必在每次调用函数后检查值。我还喜欢TrySomething函数,因为这样用户就可以显式地测试操作是否成功
不幸的是,.Net中的异常非常昂贵(根据我的经验,抛出一个异常通常需要50毫秒),因此从实际角度来看,您可能希望返回这些默认值中的一个,而不是抛出一个异常,尤其是在GUI编程中。我认为这有点像情况,但更多地取决于返回事件在逻辑上是否是异常。indexOf示例是一个完全正常的输出响应,其中-1是唯一有意义的(对于ref类型为null)
异常应该是:exception,这意味着您需要从实际异常中获得的所有堆栈跟踪和处理。在您提到的情况下,我更喜欢返回值,因为我知道如何处理它。而且没有理由在同一范围内为捕获而烦恼
当然,如果您的逻辑是以值总是应该在列表中的方式构建的,并且它们的缺失是程序员的逻辑错误-异常就是一种方式。来自Java背景,在大多数情况下,我更喜欢异常。当一半的代码没有花在检查返回值上时,它会使代码更加干净
也就是说,这还取决于某件事情可能导致“失败”的频率。异常可能很昂贵,因此您不希望为了经常失败的事情而不必要地丢弃它们。异常应该是“异常”的东西。所以,如果您调用Find,并且无论发生什么,您都希望找到某个东西,那么如果您没有找到某个东西,则抛出异常是良好的行为 然而,如果是正常的流程,即有时找不到什么东西,那么抛出异常是不好的。更有效的C#由Bill Wagner提出的关于异常的极好建议。这样做的目的是在执行操作时抛出异常,只需确保提供挂钩,以检查该值是否会抛出异常 比如说
// if this throws an exception
list.GetByName("Frank") // throws NotFound exception
// provide a method to test
list.TryGetByName("Frank") // returns false
这样,您可以通过编写以下内容来选择退出异常
MyItem item;
if (list.TryGetByName("Frank"))
item = list.GetByName("Frank");
经验法则是只有在“不应该发生”的事情发生时才使用异常 如果您期望对IndexOf()的调用可能找不到有问题的值(这是一个合理的期望),那么它应该有一个返回代码(如您所说,可能是-1)。一些永远不会失败的事情,比如分配内存,应该在失败的情况下抛出异常
另一件需要记住的事情是,处理异常在性能方面是“昂贵的”。因此,如果您的代码作为正常操作的一部分定期处理异常,那么它的执行速度就不会那么快。您可能会喜欢我的两部分博客系列,其中根据您的编程语言支持的功能讨论了许多优缺点,因为它似乎非常相关: 我要补充的是,我认为这个问题的很多答案都很差(我在我的许多同伙中都投了反对票)。尤其糟糕的是,API与
if (ItWillSucceed(...)) {
DoIt(...)
}
这会产生各种各样的不愉快的问题(详情请参见我的博客)。正如许多与编程相关的问题一样,这一切都取决于 我发现,人们确实应该首先尝试定义API,以便在一开始就不会发生异常情况 使用契约式设计可以帮助做到这一点。这里可以插入抛出错误或崩溃并指示编程错误(而不是用户错误)的函数。(在某些情况下,这些检查将在释放模式下删除。) 然后为无法避免的一般故障保留例外,如DB连接失败、乐观事务失败、磁盘写入失败 这些异常通常在到达“用户”之前不需要捕获。并将导致用户需要重试 如果该错误是用户错误,例如名称或其他内容中的键入错误,则直接在应用程序接口代码本身中处理该错误。由于这是一个常见错误,因此需要使用用户友好的错误消息进行处理 应用程序分层在这里也很有用。因此,让我们以将现金从一个账户转移到另一个账户为例:
transferInternal( int account_id_1, int account_id_2, double amount )
{
// This is an internal function we require the client to provide us with
// valid arguments only. (No error handling done here.)
REQUIRE( accountExists( account_id_1 ) ); // Design by contract argument checks.
REQUIRE( accountExists( account_id_2 ) );
REQUIRE( balance( account_id_1 ) > amount );
... do the actual transfer work
}
string transfer( int account_id_1, int account_id_2, double amount )
{
DB.start(); // start transaction
string msg;
if ( !checkAccount( account_id_1, msg ) ) return msg; // common checking code used from multiple operations.
if ( !checkAccount( account_id_2, msg ) ) return msg;
if ( !checkBalance( account_id_1, amount ) ) return msg;
transferInternal( account_id_1, account_id_2, amount );
DB.commit(); // This could fail with an exception if someone else changed the balance while this transaction was active. (Very unlikely but possible)
return "cash transfer ok";
}
我在某个地方读到一条很好的规则,我非常喜欢。它说:“一个函数应该抛出一个异常,当且仅当它不能执行它想要执行的任务时” 所以我通常做的是决定什么是函数
item = list.Find(x);
If (list.Contains(x))
item = list.Find(x);
else
item = null;
try {
item = list.Find(x);
}
catch {
item = null;
}
user := userDictionary at: id
user := userDictionary at: id ifAbsent: [
"no such id, let's return the user named Anonymous"
users detect: [ :each | each name = 'Anonymous' ] ]
class Maybe<T> {
public Maybe() {
set = false;
value = null;
}
public Maybe(T value) {
set = true;
this.value = value;
}
public T get(T defaultVal) {
if (set)
return value;
return defaultVal;
}
private boolean set;
private T value;
}
public class Option<T>
{
string _msg = "";
T _item;
public bool IsNone
{
get { return _msg != "" ? true : false; }
}
public string Msg
{
get { return _msg; }
}
internal Option(T item)
{
this._item = item;
this._msg = "";
}
internal Option(string msg)
{
if (String.IsNullOrEmpty(msg))
throw new ArgumentNullException("msg");
this._msg = msg;
}
internal T Get()
{
if (this.IsNone)
throw new Exception("Cannot call Get on a NONE instance.");
return this._item;
}
public override string ToString()
{
if (this.IsNone)
return String.Format("IsNone : {0}, {1}", this.IsNone, typeof(T).Name);
else
return String.Format("IsNone : {0}, {1}, {2}", this.IsNone, typeof(T).Name, this._item.ToString());
}
var optionItem = list.TryFind(x => trueorFalseTest() );
if (!optionItem.IsNone)
var myItem = optionItem.Get();