返回复杂对象或使用引用/输出参数是更好的做法吗?
我正在编写一个方法,该方法应该评估输入并在满足所有条件时返回 true,如果某些测试失败则返回 false。如果发生故障,我还希望向调用者提供某种状态消息。
我遇到的设计包括返回 bool 并使用消息的 out(或 ref)参数,返回具有 bool 和 string 属性的(专门设计的)类的实例,甚至返回指示 pass 或特定的枚举错误。从方法中获取所有信息的最佳方法是什么?这些都是“好”的吗?有人有其他建议吗?
I'm putting together a method which is supposed to evaluate the input and return true if all conditions are met or false if some test fails. I'd also like to have some sort of status message available to the caller if there's a failure.
Designs I've come across include returning the bool and using an out (or ref) parameter for the message, returning an instance of a (specifically designed) class with the bool and string properties, or even returning an enum indicating pass or a specific error. what's the best way to get all the information out of the method? Are any of these "good"? Does anyone have other recommendations?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(14)
我通常尝试返回一个复杂的对象,并在必要时使用输出参数。
但是您查看 .NET 转换中的
TryParse
方法,它们遵循返回 bool 和转换值的 out 参数的模式。所以,我不认为有参数是坏事——这实际上取决于你想要做什么。I usually try to return a complex object and fall back to using an out parameter when I have to.
But you look at the
TryParse
methods in .NET's conversions, and they follow the pattern of returning a bool and an out parameter of the converted value. So, I don't think it's bad to have out parameters - it really depends on what you're trying to do.我更喜欢直接返回类型,因为某些 .NET 语言可能不支持 ref 和 out 参数。
这是微软关于原因的很好的解释。
I prefer directly returning the type as some .NET languages may not support ref and out parameters.
Here is a good explanation from MS as to why.
返回对象更具可读性并且需要更少的代码。除了您在跳过“输出参数”要求时自己的性能差异外,没有任何性能差异。
Returning objects is more readable and takes less code. There are no performance differences, except for the your own while you're jumping through the hoops "out parameters" require.
我想这取决于你对好的定义。
对我来说,我更喜欢使用 out 参数返回 bool 。我认为它更具可读性。其次(在某些情况下更好)是枚举。作为个人选择,我倾向于不喜欢仅返回消息数据的类;它抽象了我正在做的事情,只是离我的口味太远了一层。
I guess it depends on your definition of good.
For me, I'd prefer returning the bool with the out parameter. I think it's more readable. Seoncd to that (and better in some instances) is the Enum. As a personal choice, I tend not to like returning a class just for message data; it abstracts what I'm doing just one level too far away for my tastes.
这种类型的东西很容易用
Int32.Parse()
和Int32.TryParse()
来表示。要在失败时返回状态,或者在失败时返回值,需要能够返回两种不同的类型,因此需要TryParse()
中的 out 参数。返回一个专门的对象(在我看来)只会让你的命名空间充满不必要的类型。当然,您也可以随时抛出异常,其中包含您的消息。就我个人而言,我更喜欢 TryParse() 方法,因为您可以检查状态而不必生成异常,这是相当重的。This type of thing is easily represented by
Int32.Parse()
andInt32.TryParse()
. To return a status if it fails, or a value if it doesn't requires you to be able to return two different types, hence the out parameter inTryParse()
. Returning a specialized object (in my opinion) just clutters your namespace with unnecessary types. Of course, you could always thrown an exception with your message inside it too. Personally, I prefer the TryParse() method because you can check a status without having to generate an exception, which is rather heavy.我强烈建议返回一个对象(复杂或其他),因为毫无疑问,您将来可能会发现您需要返回附加信息,并且如果您使用简单返回类型与一个或两个输出引用的组合,您将坚持添加额外的输出参数,这会不必要地扰乱你的方法签名。
如果您使用一个对象,您可以轻松地修改它以支持您需要的其他信息。
我会远离枚举,除非它非常简单并且将来不太可能改变。从长远来看,如果您返回一个对象,您的麻烦就会减少,事情也会变得更简单。如果你给返回“对象”自己的命名空间,但也给每个对象自己的命名空间,那么它会使命名空间变得混乱的论点是站不住脚的。
(请注意,如果您想使用枚举,只需将其放入返回的对象中,这将为您提供枚举器的简单性以及能够满足您所需的对象的灵活性。)
I would strongly recommend returning a object (complex or otherwise), because undoubtedly you may find out in the future that you need to return additional information and if you used a combination of a simple return type with an out reference or two, you will be stuck into adding additional out parameters which will unnecessary clutter up your method signature.
If you use an object you can easily modify it down the road to support additional information that you need.
I would stay away from enumerations unless it is VERY simplistic and will not very likely change in the future. In the long run you'll have less headaches and things will be simpler if you return an object. The argument that it'll clutter the namespace is a weak one if you give return 'objects' their own namespace, but to each their own as well.
(Note if you want to use an enumeration, simply put that inside your returning object, this will give you the simplicity of enumerators combined with the flexibility of an object capable of whatever you need.)
编辑:我在顶部添加了我的观点的摘要,以便于阅读:
如果您的方法返回逻辑上属于同一“事物”的多条数据,那么一定要将它们组合成一个复杂的对象,无论您是否将该对象作为返回值或输出参数返回。
如果您的方法除了数据之外还需要返回某种状态(成功/失败/更新了多少记录等),那么请考虑将数据作为输出参数返回,并使用返回值返回状态< /p>
状态此问题适用的情况有两种变体:
需要返回包含多个属性的数据
需要返回数据以及状态用于获取该数据的操作
对于#1,我的观点是,如果您的数据由多个属性组成,那么它们应该可以组成单个类型作为类或结构,并且单个对象应该作为方法的返回值或作为输出参数返回。
对于#2,我认为这是输出参数真正有意义的情况。从概念上讲,方法通常执行一个操作;在这种情况下,我更喜欢使用方法的返回值来指示操作的状态。这可以是一个简单的布尔值来指示成功或失败,也可以是更复杂的东西,例如枚举或字符串(如果有多种可能的状态来描述该方法执行的操作)。
如果使用输出参数,我会鼓励人们只使用一个(请参阅第 1 点),除非有特定原因需要使用多个参数。不要仅仅因为需要返回的数据由多个属性组成而使用多个输出参数。仅当方法的语义明确规定时才使用多个输出参数。下面是一个我认为多个输出参数有意义的示例:
相反,这是一个我认为多个输出参数没有意义的示例。
数据的属性(personId、地址和phoneNumber)应该是该方法返回的Person 对象的一部分。一个更好的版本如下:
EDIT: I'm adding a summary of my point at the top, for easier reading:
If your method is returning multiple pieces of data that are logically all part of the same "thing", then definitely compose them into a complex object regardless of whether you are returning that object as the return value or an output parameter.
If your method needs to return some kind of status (success/failure/how many records updated/etc.) in addition to data, then consider returning your data as an output parameter and using the return value to return the status
There are two variations of the situations to which this question applies:
Needing to return data that consists of multiple attributes
Needing to return data along with a status of the action used to obtain that data
For #1, my opinion is that if you have data consisting of multiple attributes that all go together, then they should be composed into a single type as a class or struct, and a single object should be returned as either the return value of a method or as an output parameter.
For #2, I think this is the case where output parameters really make sense. Conceptually, methods typically perform an action; in this case, I prefer to use the return value of the method to indicate the status of the action. That could be a simple boolean to indicate success or failure, or it could be something more complicated such as an enum or string if there are multiple possible states to describe the action that the method performed.
If using output parameters I would encourage a person to use only one (refer to point #1), unless there is a specific reason to use more than one. Do not use multiple output parameters simply because the data you need to return consists of multiple attributes. Only use multiple output parameters if the semantics of your method specifically dictate it. Below is an example where I think multiple output parameters make sense:
Conversely, here is an example where I think multiple output parameters do not make sense.
The attributes of the data (personId, address, and phoneNumber) should be part of a Person object returned by the method. A better version of it would be the following:
这可能是主观的。
但与往常一样,我想说这很大程度上取决于情况,并且没有一种“正确”的方法可以做到这一点。例如,字典使用的
TryGet()
模式返回一个 bool(通常立即在if
中使用),有效返回类型为out< /代码>。在这种情况下,这是完全有道理的。
但是,如果您枚举您得到的项
KeyValuePair<,>
- 这也是有意义的,因为您可能需要将键和值作为一个“包”。在您的具体情况下,我可能会倾向于实际期望
bool
作为结果,并传入可选的(允许null
)ICollection
接收错误的实例(ErrorMessage
也可以是String
,如果这就足够了)。这样做的好处是允许报告多个错误。This may be subjective.
But as always, I'd say that it pretty much depends and that there is not one "right" way to do it. For instance, the
TryGet()
pattern used by dictionaries returns a bool (which is often consumed in aif
right away) and the effective return type asout
. This makes perfect sense in that scenario.However, if you enumerate the items you get
KeyValuePair<,>
- which also makes sense since you may need both the key and the value as one "package".In your specific case, I may be tempted to actually expect a
bool
as result and pass in an optional (null
allowed)ICollection<ErrorMessage>
instance which receives the errors (ErrorMessage
may just beString
as well if that's enough). This has the benefit of allowing multiple errors to be reported.我使用枚举,但将它们用作标志/位模式。这样我就可以指示多个失败条件。
因此,在我的方法中,我只是这样做。
这种方法令人讨厌的事情是,确定枚举中是否设置了一个位,看起来像这样,
我使用扩展方法在我的方法上给自己一个
IsFlagSet()
方法枚举。我从这里的另一个答案复制了这段代码,但我不知道此时答案在哪里......
这让我只能说
I use enums, but use them as a flag/bit pattern. That way I can indicate multiple failed conditions.
So then in my method I just do this
The annoying thing about this approach is that determining if a bit is set in the enumeration looks like this
I use extension methods to give myself an
IsFlagSet()
method on my enumerations.I copied this code from another answer here on SO, but I have no idea where that answer is at this point...
This allows me to just say
我更喜欢对象而不是参数,主要是因为您可以对属性的“Set”中的有效值进行更多检查。
这在网络应用程序中经常被忽视,也是一项基本的安全措施。
OWASP 准则规定,每次分配值时都应进行验证(使用白名单验证)。如果您要在多个地方使用这些值,那么创建一个对象或结构来保存这些值并在那里进行检查要容易得多,而不是每次在代码中有一个输出参数时都进行检查。
I prefer an object to out parms mainly because you can do more checking for valid values in the "Set" for the properties.
This is often overlooked in web apps, and a basic security measure.
OWASP guidelines state that values should be validated (using whitelist validation) every time they are assigned. If you're going to use these values in more than one place, it's far easier to create an object or a struct to hold the values, and check there, rather than checking every time you have an out parm in code.
在这种情况下,TryParse(...) 方法不是最好的例子。 TryParse 仅发出信号是否已解析该数字,但不指定解析失败的原因。它不需要,因为它失败的唯一原因是“格式错误”。
当您遇到没有明显好的方法签名来完成您想要做的事情时,您应该退后一步,问问自己该方法是否可能做得太多了。
您正在运行哪些测试?它们有关系吗?是否有使用相同方法的测试会由于完全不同的不相关原因而失败,您必须跟踪这些原因才能向用户/消费者提供有意义的反馈?
重构你的代码会有帮助吗?您能否将测试逻辑地分组为只能由于一个原因(单一目的原则)而失败的单独方法,并从原始调用站点相应地调用它们,而不是您正在考虑的一种多用途方法?
失败的测试是正常情况,只需要记录到某个日志文件中,还是必须以某种可能停止应用程序正常流程的直接方式通知用户?如果是后者,是否可以使用自定义异常并相应地捕获它们?
有很多可能性,但我们需要更多有关您正在尝试做什么的信息,以便为您提供更准确的建议。
The TryParse(...) method is not the best example in this case. TryParse only signals if the number was parsed or not, but it does not specify why the parsing failed. It doesn't need to because the only reason it can fail is due to "bad format".
When you come up with situations where there is no obvious good method signature for what your are trying to do you should step back and ask yourself if the method is maybe doing too much.
What tests are you running? Do they relate at all? Are there tests in this same method that would fail due to completely different unrelated reasons that you have to track in order to give meaningful feedback to the user/consumer?
Would refactoring your code help? Can you group tests logically into seperate methods that can only fail due to one reason (single purpose principle) and call them all accordingly from your original callsite instead of the one multipurpose method you are considering?
Is a failed test a normal situation that only needs to be logged to some log file or is it mandatory that you inform the user in some inmeadiate way probably halting the normal flow of the application? If its the latter, would it be possible to use custom exceptions and catch them accordingly?
There are many possibilities but we need more information about what you are trying to do in order to give you more precise advice.
我们使用 Request 和 Response 对象对 WCF 服务做一些非常相似的事情,异常会一直向上冒泡(不在方法本身中处理)并由自定义属性(用 PostSharp 编写)处理,该属性捕获异常并返回 ' failure' 响应,例如:
属性中的异常处理逻辑可以使用异常消息来设置 StatusMessage,如果您的应用程序中有一组自定义异常,您甚至可以在可能的情况下设置 ErrorCode。
We do something very similar with our WCF services using Request and Response objects, and exceptions are bubbled all the way up (not handled in the method itself) and handled by a custom attribute (written in PostSharp) which catches the exception and returns a 'failure' response, e.g.:
The exception handling logic in the attribute can use the Exception message to set the StatusMessage and if you have a set of custom exceptions in your app you can even set the ErrorCode where possible.
我认为这取决于复杂程度,以及操作是否会失败,导致......什么?
对于
TryParse
示例,您通常不能返回无效值(例如,不存在“无效”int
之类的东西),并且绝对不能返回 null 值类型(相应的Parse
方法不返回Nullable
)。此外,返回Nullable
需要在调用站点引入Nullable
变量,进行条件检查以查看该值是否为null< /code>,然后强制转换为
T
类型,该类型必须重复运行时检查以查看该值是否为 null,然后再返回实际的非 null 值。与将结果作为out
参数传递并通过返回值指示成功或失败相比,这种方法的效率较低(显然,在这种情况下,您不关心为什么失败,只是它做到了)。在以前的 .Net 运行时中,唯一的选择是 Parse 方法,这会引发任何类型的失败。捕获 .Net 异常的成本很高,尤其是相对于返回指示状态的true
/false
标志而言。就您自己的界面设计而言,您需要考虑为什么需要输出参数。也许您需要从函数返回多个值并且不能使用匿名类型。也许您需要返回一个值类型,但无法根据输入生成合适的默认值。
out
和ref
参数并不是禁忌,但在使用它们之前,您的接口应该仔细考虑它们是否必要。在超过 99% 的情况下,标准的按引用返回应该足够了,但在不必要地定义新类型以满足返回之前,我会考虑使用out
或ref
参数“单个”对象而不是多个值。I think it depends on the level of complexity, and whether the operation could fail, resulting in...what?
In the case of the
TryParse
examples, you typically cannot return an invalid value (e.g. there is no such thing as an "invalid"int
) and definitely cannot return a null value type (the correspondingParse
methods don't returnNullable<T>
). Additionally, returningNullable<T>
would require introduction of aNullable<T>
variable at the call site, a conditional check to see if the value isnull
, followed by a cast to theT
type, which has to duplicate the runtime check to see if the value is null before returning that actual non-null value. This is less efficient that passing the result as aout
parameter and indicating via return value success or failure (obviously, in this case, you're not concerned why it failed, only that it did). In prior .Net runtimes, the only choice was theParse
methods, that threw on any sort of failure. Catching .Net exceptions is expensive, especially relative to returning atrue
/false
flag indicating status.In terms of your own interface design, you need to consider why you would need an out parameter. Maybe you need to return multiple values from a function and cannot use anonymous types. Maybe you've need to return a value type, but cannot produce a suitable default value according to inputs.
out
andref
parameters are not no-nos, but your interface should be carefully considered as far as whether they are necessary or not, before you use them. A standard return by reference should be sufficient in more than 99% of cases, but I would look at usingout
orref
parameters before unnecessarily defining a new type just to satisfy returning a "single" object instead of multiple values.你的方法有关于输入的条件并且不满足这些条件,你可以考虑抛出适当的异常。通过抛出适当的异常,调用者将知道到底出了什么问题。这是更易于维护和扩展的方法出于显而易见的原因,返回错误代码并不是一个好的选择,
“我遇到的设计包括返回 bool 并使用消息的 out(或 ref)参数,返回带有 bool 的(专门设计的)类的实例。和字符串属性,甚至返回一个表示通过或特定错误的枚举。 "
复杂对象当然是最明显的选择。(类或结构)
输出参数: "考虑输出参数的一种方法是,它们就像方法的附加返回值。当方法返回多个值(在本例中为firstName 和lastName)时,它们非常方便。然而,输出参数可能被滥用。作为良好编程风格的问题,如果您发现自己编写的方法具有许多输出参数,那么您应该考虑重构代码。一种可能的解决方案是将所有返回值打包到一个结构中。”
太多的 out 或 ref 参数表明存在设计缺陷。
元组:“将一堆其他元素组合在一起某些比类更轻量的结构中的不相关数据”
必须阅读:有关元组的更多信息:
If your method has conditions with regard to input and if they are not met, you can very much consider throwing appropriate exceptions. By throwing proper exceptions the caller will know what exactly went wrong. This is more maintainble and scalable approach. Returning error code is not a good options for obvious reasons.
"Designs I've come across include returning the bool and using an out (or ref) parameter for the message, returning an instance of a (specifically designed) class with the bool and string properties, or even returning an enum indicating pass or a specific error. "
Complex objects are ofcourse most obvious option. (class or structure)
Out parameter: "One way to think of out parameters is that they are like additional return values of a method. They are very convenient when a method returns more than one value, in this example firstName and lastName. Out parameters can be abused however. As a matter of good programming style if you find yourself writing a method with many out parameters then you should think about refactoring your code. One possible solution is to package all the return values into a single struct."
Too many out or ref parameter points to a design flaw.
Tuple: "group together a bunch of otherwise unrelated data in some structure that is more lightweight than a class"
Must read: More on tuples: