C#类方法.如何失败并返回原因
本文关键字:返回 失败 类方法 何失败 | 更新日期: 2023-09-27 18:21:36
我相信有一种"好"的方法可以解决这个问题,但它一直困扰着我。我有一个方法应该返回一个对象,但它的参数有一定的先决条件。这些都超出了我的控制范围,可能会因为"业务逻辑"的原因而失败(如果你能原谅这个过时的术语的话)。
对象的返回值将为null,但我也想返回原因,因此调用代码基本上可以说"我没有取回我的对象,因为没有足够的信息来构建它"。
我觉得尝试接球不是正确的方法,但在这种情况下一直在使用它,因为没有更好的方法。我在stackoverflow、教科书和MSDN上阅读的所有内容似乎都集中在何时或如何使用异常上,但不知何故,我未能想出解决这种情况的方法。
有人能提出一个更合适的模式吗?(第一个帖子所以…请原谅任何失礼行为)
下面是我一直在玩的一个示例:(注意//TODO注释下面的抛出新的Exception行)
public static Packet Parse(string packetString)
{
Packet returnPacket = new Packet();
StringBuilder output = new StringBuilder();
try
{
using (XmlReader reader = XmlReader.Create(new StringReader(packetString)))
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.Indent = true;
using (XmlWriter writer = XmlWriter.Create(output, ws))
{
string rootNodeString = string.Empty;
// Parse the packet string and capture each of the nodes.
while (reader.Read())
{
//test root node is the correct opening node name
if (rootNodeString == string.Empty)
{
if (reader.NodeType != XmlNodeType.Element || reader.Name != PACKETROOT)
{
// TODO: I don't really think this should be an exception, but going with it for now for expediency, since XmlReader is doing the same anyway
throw new Exception(string.Format("The root node of a Packet must be <{0}>", PACKETROOT));
}
else
{
rootNodeString = reader.Name;
}
}
switch (reader.NodeType)
{
case XmlNodeType.Element:
Console.WriteLine(string.Format("start element = {0}", reader.Name));
break;
case XmlNodeType.Text:
Console.WriteLine(string.Format("text = {0}", reader.Value));
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
Console.WriteLine(string.Format("XmlDeclaration/ProcessingInstruction = {0},{1}", reader.Name, reader.Value));
break;
case XmlNodeType.Comment:
Console.WriteLine(string.Format("comment = {0}", reader.Value));
break;
case XmlNodeType.EndElement:
Console.WriteLine(string.Format("end element = {0}", reader.Name));
break;
}
}
}
}
}
catch (XmlException xem)
{
Console.WriteLine(xem.Message);
throw;
}
return returnPacket;
}
我有一个方法,它应该返回一个对象,但它的参数有一定的先决条件。这些超出了我的控制范围,可能会因"业务逻辑"原因而失败
如果前置条件确实是前置条件,则未能遵守该前置条件的调用者将出现错误。正确的做法是让被调用者抛出一个异常,最好是一个严重破坏整个进程的异常。这将强烈鼓励调用者修复他们的错误。
但根据你的描述,听起来你所拥有的并不是一个先决条件;前提条件是必须为真,如果它们不总是为真,则调用者有缺陷。相反,听起来你所拥有的是一种做得太多的方法;验证提供的参数是否被某些业务策略分类为有效,计算参数是否有效的结果。
因此,我倾向于将其分为两种方法。
第一个方法分析其参数,查看业务策略是否将其归类为有效,并返回一个报告对象,详细描述参数有效或无效的原因。
第二种方法计算结果。第二种方法可以以第一种方法验证参数为前提。
对象的返回值将为null,但我也想返回原因,因此调用代码基本上可以说"我没有取回我的对象,因为没有足够的信息来构建它"。
同样,这是更多的证据表明你试图做得太多了。您希望该方法的返回值既是描述为什么不满足业务规则的分析,也是业务流程的结果。这是两件非常不同的事情,因此应该用两种不同的方法来计算。
我在Noda Time中使用的方法是使用ParseResult<T>
类型,这是解析操作的结果。它知道它是否成功,如果你向解析失败者询问结果,它会抛出异常,但否则不会抛出异常。(您目前无法获得在没有抛出的情况下会抛出的异常,但我稍后可能会添加它。)
这比抛出异常要好,因为这里的失败是所期望的——这并不是真正的异常。
它比使用int.TryParse
等模式要好,因为它为您提供了一个封装关于解析操作的所有的单个结果值-不再干扰参数,并且在失败的情况下提供了更多的细节。
现在我还不清楚这在你的特定情况下是否合适,但当你基本上处理的数据可能是无效的,并且不表明你的系统的任何部分有任何问题时,是合适的(IMO):这里没有什么令人惊讶的或异常,只是有人给你提供了不好的输入:(
如果你测试的任何东西"都不应该发生",那么使用异常正是你应该做的。毕竟这是一种特殊情况。你为什么认为你不应该使用一个?
我同意Jon Skeet的观点。除非是真正的特殊情况,否则您不希望出现异常。用户输入验证是我最不建议使用异常的地方。
主要是我认为这会使逻辑更难遵循,因为您要么必须将所有调用封装在try/catch块中以保持所需的粒度级别,要么将错误逻辑推迟到代码的后面。使用返回类/结构可以确保对正确性的检查是明确的。
这可以极大地提高代码的可读性。请参阅下面的示例。
public class ActionResult<T>
{
public T Result { get; set; }
public bool IsSuccessful { get; set; }
public string DetailMessage { get; set; }
//Any other properties you might find useful like statuscode, etc.
}
public ActionResult<Packet> ParsePacket(string input)
{
Packet result = null;
bool parseSuccess = true;
string message = null;
// Do your work, create packet and check conditions.
// Assign to your local variables.
return new ActionResult<Packet> {
Result = result,
IsSuccessful = parseSuccess,
DetailMessage = message
};
}
public void SomeCallingMethodLikeGetPacket(string userInput)
{
ActionResult<Packet> parseResult = ParsePacket(userInput);
if (!parseResult.IsSuccessful)
{
//Error handling.
}
else
{
Packet packet = parseResult.Result;
//Do something with packet.
}
}
通过在存在无效输入时抛出异常,您实际上是在说,调用方法有责任确保数据有效,或者在数据无效时处理异常。这是完全可以接受的,尽管理想情况下应该将其纳入方法文档中。
如果你正在这样做,最好有一个方法来验证输入;通过提供这一点,调用者可以知道该方法将不起作用,而不需要遭受try/catch的性能打击。如果该类与一个或几个其他类紧密耦合,而这些类总是为它提供正确的输入,那么这就变得不那么重要了(但仍然不是一个坏主意)。
我还没有真正用C#开发过,但根据我使用其他语言(Java、PHP)的经验,我认为这是一个非常好的方法。如果你想区分你抛出的错误和XMLReader抛出的错误,你可以创建自己的自定义Exception来扩展Exception对象。
典型的模式是始终返回一个int,这是错误代码。实际结果通过out
或ref
参数传递。一些开发工厂确实要求将此作为必要条件。