问题描述
问题:
处理错误被认为是最佳实践 - 以及为什么在一个构造函数?
最佳实践可以是Schwartz的报价,或50%的CPAN模块使用它等等;但是,即使解释了为什么常见的最佳做法并不是真正的最佳方法,我也对任何人的理性意见感到满意。
就我自己的观点而言主题(通过Perl软件开发许多年),我已经看到了Perl模块中出现的三种错误处理方法(从我看来最好到最坏):
-
构造一个对象,设置一个无效的标志(通常是
is_valid
)。经常通过你的班级的错误处理设置错误信息。
优点:
-
允许标准(与其他方法调用相比)错误处理,因为它允许使用
$ obj-> errors()
type调用一个糟糕的构造函数就好像在调用任何其他方法之后。 -
允许传递其他信息(例如> 1个错误,警告等) )
-
允许轻量级的重做/fixme功能,换句话说,如果构造的对象非常繁重, 100%总是OK,唯一的原因是因为有人输入的日期不正确,您可以简单地执行
$ obj-> setDate()
的再次执行整个构造函数的开销。
缺点
em>:没有我知道的。 -
-
返回
undef
。 >
缺点:无法实现第一个解决方案的任何优点(全局变量之外的每个对象错误消息和轻量级fixme功能)对象)。
-
在构造函数内部。在一些非常狭窄的边缘案例之外,我个人认为这是一个可怕的选择,太多理由列在这个问题的边缘。
-
更新:只是为了清楚的是,我认为(非常有价值和伟大的设计)解决方案具有非常简单的构造函数,根本不能失败,并且重的初始化方法,其中所有的错误检查发生仅仅是情况#1的子集(如果初始化程序设置错误标志)或case#3(如果初始化程序死机)为此问题的目的。显然,选择这样的设计,你会自动拒绝选项#2。
这取决于您希望您的构造函数的行为方式。
其余的回应属于我的个人观察,但与大多数事情一样Perl,最佳实践真的归结为这里有一种方法,您可以根据自己的需要采取或离开。您所描述的您的喜好是完全有效和一致的,没有人应该告诉你。
如果施工失败,我实际上更喜欢因为我们设置它,以便在对象构造中可能发生的唯一类型的错误真的是很大的,明显的错误,应该停止执行。
另一方面,如果你喜欢不会发生,我想我更喜欢2超过1,因为检查一个未定义的对象也很容易因为它是检查一些标志变量。这不是C,所以我们没有强大的打字约束告诉我们,我们的构造函数必须返回一个这样的对象。所以返回 undef
,并检查它是否建立成功或失败,是一个很好的选择。
在某些边缘情况下(在开发之前不能快速失败),建筑失败是一个考虑因素,所以对于那些你可能更喜欢方法1的人来说,这也取决于你为对象构造定义了什么语义。例如,我喜欢在施工之外进行重量级初始化。对于标准化,我认为检查一个构造函数返回一个定义的对象是否像检查一个标志变量一样好。
编辑:为了响应您对初始化器拒绝#2的编辑,我不明白为什么初始化程序不能简单地返回一个指示成功或失败的值,而不是设置一个标志变量。实际上,您可能希望使用这两者,具体取决于您想要发生的错误的详细程度。但是,一旦初始化程序成功返回true,并且失败, undef
将是完全有效的。
Question:
What is considered to be "Best practice" - and why - of handling errors in a constructor?.
"Best Practice" can be a quote from Schwartz, or 50% of CPAN modules use it, etc...; but I'm happy with well reasoned opinion from anyone even if it explains why the common best practice is not really the best approach.
As far as my own view of the topic (informed by software development in Perl for many years), I have seen three main approaches to error handling in a perl module (listed from best to worst in my opinion):
Construct an object, set an invalid flag (usually "
is_valid
" method). Often coupled with setting error message via your class's error handling.Pros:
Allows for standard (compared to other method calls) error handling as it allows to use
$obj->errors()
type calls after a bad constructor just like after any other method call.Allows for additional info to be passed (e.g. >1 error, warnings, etc...)
Allows for lightweight "redo"/"fixme" functionality, In other words, if the object that is constructed is very heavy, with many complex attributes that are 100% always OK, and the only reason it is not valid is because someone entered an incorrect date, you can simply do "
$obj->setDate()
" instead of the overhead of re-executing entire constructor again. This pattern is not always needed, but can be enormously useful in the right design.
Cons: None that I'm aware of.
Return "
undef
".Cons: Can not achieve any of the Pros of the first solution (per-object error messages outside of global variables and lightweight "fixme" capability for heavy objects).
Die inside the constructor. Outside of some very narrow edge cases, I personally consider this an awful choice for too many reasons to list on the margins of this question.
UPDATE: Just to be clear, I consider the (otherwise very worthy and a great design) solution of having very simple constructor that can't fail at all and a heavy initializer method where all the error checking occurs to be merely a subset of either case #1 (if initializer sets error flags) or case #3 (if initializer dies) for the purposes of this question. Obviously, choosing such a design, you automatically reject option #2.
It depends on how you want your constructors to behave.
The rest of this response goes into my personal observations, but as with most things Perl, Best Practices really boils down to "Here's one way to do it, which you can take or leave depending on your needs." Your preferences as you described them are totally valid and consistent, and nobody should tell you otherwise.
I actually prefer to die if construction fails, because we set it up so that the only types of errors that can occur during object construction really are big, obvious errors that should halt execution.
On the other hand, if you prefer that doesn't happen, I think I'd prefer 2 over 1, because it's just as easy to check for an undefined object as it is to check for some flag variable. This isn't C, so we don't have a strong typing constraint telling us that our constructor MUST return an object of this type. So returning undef
, and checking for that to establish success or failure, is a great choice.
The 'overhead' of construction failure is a consideration in certain edge cases (where you can't quickly fail before incurring overhead), so for those you might prefer method 1. So again, it depends on what semantics you've defined for object construction. For example, I prefer to do heavyweight initialization outside of construction. As to standardization, I think that checking whether a constructor returns a defined object is as good a standard as checking a flag variable.
EDIT: In response to your edit about initializers rejecting case #2, I don't see why an initializer can't simply return a value that indicates success or failure rather than setting a flag variable. Actually, you may want to use both, depending on how much detail you want about the error that occurred. But it would be perfectly valid for an initializer to return true on success and undef
on failure.
这篇关于如果Perl构造函数返回undef或“无效”目的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!