【发布时间】:2010-12-10 21:37:40
【问题描述】:
创建一个自定义异常是否是个好主意,该异常会在预期处理的应用程序逻辑失败时引发?
我猜最好返回一个对象或一个值;但是:
如果不是,那么在我们的代码中引入一些自定义异常的正确条件是什么?
谢谢!
【问题讨论】:
标签: c# .net c++ exception exception-handling
创建一个自定义异常是否是个好主意,该异常会在预期处理的应用程序逻辑失败时引发?
我猜最好返回一个对象或一个值;但是:
如果不是,那么在我们的代码中引入一些自定义异常的正确条件是什么?
谢谢!
【问题讨论】:
标签: c# .net c++ exception exception-handling
是的,假设您要创建它的问题确实是异常情况(即,应该很少发生,如果有的话)。
如果这是一个正常的错误情况(您期望将定期发生并且是应用程序正常使用的一部分),您应该为它显式编码 - 在这种情况下,创建和抛出自定义异常是一种代码异味。
此外,只有在添加信息时才创建自定义异常(超过内置信息)。
编辑:(跟随 cmets)
使用异常处理作为流控制的一种形式(即函数 X 是否正确完成)几乎总是是个坏主意,并且是明确的代码味道。
【讨论】:
当您希望根据抛出的异常类型执行操作时,您应该创建自己的自定义异常。如果您的目的是向用户显示消息,那么抛出的异常类型意义不大。
但是,当您创建自己的异常类型时需要小心,因为 .NET 平台中已经有许多非常好的异常类型,您应该首先使用其中一种。与 JoesBestFileNotFound 异常相比,拥有 FileNotFound 异常要好得多。
【讨论】:
DocumentLoad 方法在加载文档失败时抛出 DocumentFileNotFound 异常,则捕获该异常的代码可以知道无法加载文档因为未找到 document 文件。相比之下,如果只是抛出FileNotFoundException,调用代码将不知道是找不到文档文件,还是找不到DLL、配置文件或其他类似的东西。
通常,您可以通过标准例外处理。如果您想提供更多有用的调试信息,boost 的东西可能会有所帮助。
当您想要提供超出其他选项提供给您的信息或当您希望用户能够以独特的方式处理它时,您想要创建自己的例外。如果您所做的只是提到有特殊情况,那么只需使用已经可以使用的轮子。
我建议始终从 std::exception 或其派生类之一继承。
【讨论】:
最好的做法是在编码时避免一开始就抛出异常。如果你需要一个理由:他们很慢。在大多数情况下,即使是最不优雅的方式,也比围绕它们进行编码要慢得多。话虽如此,有时它们是不可避免的,因为其他人编写的代码或者它们是需要的,因为您确实希望程序在用户面前爆炸(参见灾难性故障)。只有在这两种情况下,异常处理才是可行的。
但这并不意味着您将每个IEnumerable<T>.First() 包装在if(sequence.Count() > 0) 中。学会合理地构建你的代码,这样你就不会首先在一个空序列上调用.First()。优雅的代码就是胜利。
我最近为我的雇主编写了一个快速应用程序,用于一个自动化系统(用户不坐在键盘前),该系统必须要求用户在命令行参数中输入登录信息(想想批处理文件)或配置文件,否则它会按照规范故意炸毁他们的脸。在这种情况下,例外是有保证的。
下面的所有代码都展示了我关于异常的理念。
private static string _password;
public static string Password
{
get
{
if (_password.IsNullOrWhiteSpace())
throw new NullReferenceException(string.Format("{0} {1}",
"Password was neither found in the .cfg file nor the",
"command line arguments."));
return _password;
}
set
{
_password = value ?? string.Empty;
}
}
我刚刚写的这个扩展方法中的无异常编码示例。
public static bool All<T>(this IEnumerable<T> list, Func<T, T, bool> func)
{
if (list.Count() < 2)
return true;
T first = list.First();
return list.Skip(1).Aggregate(true, (i, k) => i && func(first,k));
}
注意下面我在 I/O 方法的范围内处理了 I/O 异常,但是我以 my 逻辑永远不会抛出异常的方式解析配置文件中的数据.请注意 .Count() 如何在所有数据中被验证为 2。在这一点上,我可以确信 .First() 和 .Last() 不仅对编译器有效并且不会引发异常,而且可以保证产生潜在有效的数据片段(非空白,不同的值)。当您考虑异常处理时,我建议您采用这种哲学 -> 如何以引用透明的方式过滤和控制我的数据,以完全消除方法范围内的错误?
public static class ConfigParser
{
public static Dictionary<string, string> PullFromConfigFile()
{
ParallelQuery<Tuple<string, string>> data;
try
{
TextReader tr = new StreamReader("config.cfg");
data = tr.ReadToEnd()
.Split('\n')
.AsParallel()
.Select(i => new string(i.TakeWhile(k => k != '#').ToArray()))
.Where(i => !i.IsNullOrWhiteSpace())
.Select(i => i.Split('\t')
.Where(k => !k.IsNullOrWhiteSpace())
.Select(k => k.Trim())
)
.Where(i => i.Count() == 2)
.Select(i => new Tuple<string, string>(i.First(), i.Last()));
tr.Close();
}
catch (IOException)
{
Logger.Bad("config.cfg file was not found");
return new Dictionary<string, string>();
}
return ConfigParser.ParseIntoDict(data);
}
private static Dictionary<string, string> ParseIntoDict(ParallelQuery<Tuple<string, string>> data)
{
var agg = new Dictionary<string, string>();
foreach (var entry in data)
{
if (!agg.ContainsKey(entry.Item1))
agg.Add(entry.Item1, entry.Item2);
}
var width = agg.Keys.Max(k => k.Length);
agg.ForAll(i => Logger.Log("Loaded Data: {0} {1}",
i.Key.SetWidth(width, '-'), i.Value));
return agg;
}
}
使用的扩展:
public static class Extensions
{
public static string SetWidth(this string item, int width, char padder, bool right = true)
{
if (item == null)
return new string(padder, width);
if (width > item.Length)
return right ? item.PadRight(width, padder) : item.PadLeft(width, padder);
return item.Substring(0, width);
}
public static bool IsNullOrWhiteSpace(this string str)
{
return string.IsNullOrWhiteSpace(str);
}
}
【讨论】:
一般来说,您不应该对正常的程序逻辑使用异常。但是,可以为特定于您的应用程序的场景创建例外。有很多关于异常处理的好建议——例如这个 MSDN exception design guidelines 初学者文档。
【讨论】:
如果您查看 .NET FW,则有许多 Exceptions 来指定某些条件。 IndexOutOfBoundsException、NotImplementedException 等...它们都存在并派生自 Exception 以处理异常(双关语并非有意)的情况。
在将什么归类为异常与简单地返回 null 之间划清界限通常会变成灰色。使用您的最佳判断力,并尝试在团队中保持该判断力不变。
【讨论】: