编程中哪一个更好?
我不是在谈论完全排他性。它更适用于:
list<T>.Find
,您获得default(T)
或您的值,而不是ValueNotFound
例外(示例)。
或
list<T>.IndexOf
,你得到-1或正确的索引。
答案 0 :(得分:16)
嗯,答案取决于它。
如果在列表中找不到项目,则抛出异常是一种可怕的做法,因为该项目可能不在列表中是完全可行的。
现在,如果这是某种特殊列表,并且项目应该在列表中找到绝对,那么应该抛出一个异常,因为你遇到了一个不可行的情况/合理的。
一般来说,对于像业务规则之类的东西,专门的错误代码会更好,因为你知道这些事情发生的可能性,并且想要对这些可能性做出反应。例外情况适用于您不期望的情况,如果发生则无法继续执行代码。
答案 1 :(得分:11)
我已经阅读了一个关于此的一个很好的规则,我非常喜欢。它说 - “当且仅当它不能执行它所要执行的任务时,函数才应该抛出异常。”
所以我通常做的是决定一个函数应该做什么(通常来自业务需求或其他东西),然后为其他一切抛出异常。
如果您已经很好地设计了应用程序,那么您的功能将非常小,并且可以使用简单的返回值执行相对简单的任务。通过上述规则确定例外并不困难。
当然,总是存在模糊的情况(比如在字典中找不到密钥)。那些应该是远在和中间,但在那里你只需要用你的直觉来了解更优雅的解决方案。
哦,所有这一切都永远不会忘记:为了让这个工作得很好,只能抓住你可以处理的异常。大多数情况下,这意味着您只能在上层UI级别捕获它们,您可以在其中显示,向用户显示它们或记录它们。较低级别可能会使用finally
块或在自己处理后重新抛出异常,但低级别的真正捕获异常通常表示设计不良。
答案 2 :(得分:6)
经验法则是仅在发生“不应发生”的事情时才使用例外。
如果您希望调用IndexOf()可能找不到有问题的值(合理的期望),那么它应该有一个返回代码(如您所说,可能为-1)。一些永远不会失败的东西,比如分配内存,应该在失败的情况下抛出异常。
另一件需要记住的事情是,在性能方面处理异常是“昂贵的”。因此,如果您的代码定期处理异常作为正常操作的一部分,则它将无法以尽可能快的速度执行。
答案 3 :(得分:3)
在你提到的情况下,我更喜欢返回值,因为我知道如何处理它。并且没有理由在同一范围内打扰捕获。
当然,如果你的逻辑构建方式使得值总是应该在列表中,并且它们的缺失是程序员逻辑错误 - 异常就是这样。
答案 4 :(得分:3)
您可能会喜欢我的两部分博客系列,根据您的编程语言支持的功能,这里讨论了很多权衡因素,因为它似乎非常相关:
An example of the interplay between language features and library design, part one
An example of the interplay between language features and library design, part two
我要补充一点,我认为这个问题的很多答案都很差(我对我的许多人群进行了投票)。
中的API特别糟糕if (ItWillSucceed(...)) {
DoIt(...)
}
会产生各种不愉快的问题(详见我的博客)。
答案 5 :(得分:3)
您更愿意使用哪种?
A:
item = list.Find(x);
B:
If (list.Contains(x))
item = list.Find(x);
else
item = null;
C:
try {
item = list.Find(x);
}
catch {
item = null;
}
我愿意打赌答案是A.因此,在这种情况下,返回Default(T)是正确的选择。
答案 6 :(得分:2)
更好的语言可以让您做任何符合您需求的事情。就像在Smalltalk-80中一样:
如果没有id的用户,则以下内容会引发异常:
user := userDictionary at: id
这个将评估给定的Block是一个高级函数:
user := userDictionary at: id ifAbsent: [
"no such id, let's return the user named Anonymous"
users detect: [ :each | each name = 'Anonymous' ] ]
请注意,实际方法是:ifAbsent:。
答案 7 :(得分:1)
来自Java背景,在大多数情况下我更喜欢例外。当一半的代码没有用于检查返回值时,它会使代码更加清晰。
也就是说,它还取决于某些事情可能导致“失败”的频率。例外情况可能很昂贵,因此您不希望不必要地将它们扔到经常失败的事物上。
答案 8 :(得分:1)
例如
// 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");
答案 9 :(得分:1)
正如许多与编程有关的问题都取决于......
我发现应该首先尝试定义您的API,以便首先不会发生异常情况。
使用“按合同设计”可以帮助您完成此任务。这里会插入引发错误或崩溃的函数并指示编程错误(不是用户错误)。 (在某些情况下,这些检查会在发布模式中删除。)
然后保留一些无法避免的通用故障的例外,例如,数据库连接失败,乐观事务失败,磁盘写入失败。
通常不需要捕获这些例外,直到它们到达“用户”。并且会导致用户需要重试。
如果错误是用户错误,如名称或其他内容中的拼写错误,则直接在应用程序界面代码本身处理。由于这是一个常见错误,因此需要处理可能已翻译的用户友好错误消息等。
应用程序分层在这里也很有用。因此,我们举一个例子,从一个帐户转移现金和另一个帐户:
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";
}
答案 10 :(得分:0)
从纯粹的设计角度来看,我更喜欢在发生“异常”事件时抛出异常,例如:你找到的钥匙没找到。主要原因是它使消费者的生活更轻松 - 他们不必在每次调用您的函数后检查值。我也喜欢TrySomething函数,因为用户可以显式测试操作是否成功。
不幸的是.Net中的异常非常昂贵(根据我的经验,通常需要大约50ms才能抛出一个),因此从实际角度来看,您可能希望返回其中一个默认值而不是抛出异常,尤其是在GUI编程中。
答案 11 :(得分:0)
我认为它略微具有情境性,但更多地取决于返回事件在逻辑上是否是一个例外。 indexOf示例是一个完全正常的输出响应,其中-1是唯一有意义的(或者对于ref类型为null)。
异常应该是:exception,这意味着你想要从真正的异常中获得的所有堆栈跟踪和处理。
答案 12 :(得分:0)
首先,我不太喜欢default(T)
选项:如果int
列表中有0
(可能是int
的默认值,该怎么办? ;我不使用C#)是一个完全允许的值?
(为Java语法道歉,但如果我试图猜测C#我可能会把它弄得一团糟:))
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;
}
然后当然Find
将返回Maybe<T>
,并且调用者在此上下文中选择一个合理默认值的值。当然,当getDefault
正确答案时,您可以添加default(T)
作为便捷方法。
对于IndexOf
,-1
是一个合理的值,因为有效值显然总是&gt; = 0,所以我只是这样做。
答案 13 :(得分:0)
在这种情况下我通常做的是定义一个Option类(受F#启发)并使用返回Option类的TryFind()方法扩展IEnumerable。
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();
这样,无论项目是否存在,集合只会遍历一次。
答案 14 :(得分:-1)
异常应该是“特殊”的东西。因此,如果你打电话给Find,并且你希望找到一些东西,无论如何,那么如果你没有找到某些东西则抛出异常就是好行为。
如果它是正常的流程,你有时候找不到东西,那么抛出异常是不好的。