业务对象、验证和异常
我一直在阅读一些有关异常及其使用的问题和答案。 似乎有一种强烈的观点认为,仅应针对异常、未处理的情况提出异常。 因此,这让我想知道验证如何与业务对象一起工作。
假设我有一个业务对象,其中包含对象属性的 getter/setter。 假设我需要验证该值是否在 10 到 20 之间。这是一条业务规则,因此它属于我的业务对象。 所以这对我来说似乎意味着验证代码进入我的设置器中。 现在我将 UI 数据绑定到数据对象的属性。 用户输入 5,因此规则需要失败,并且不允许用户移出文本框。 。 UI 数据绑定到属性,因此将调用 setter、检查规则并失败。 如果我从业务对象中引发异常,表明规则失败,UI 会接收到该异常。 但这似乎违背了异常的首选用法。 鉴于它是一个二传手,您实际上不会为二传手获得“结果”。 如果我在对象上设置另一个标志,则意味着 UI 必须在每次 UI 交互后检查该标志。
那么验证应该如何进行呢?
编辑:我可能在这里使用了一个过于简化的示例。 像上面的范围检查之类的事情可以通过 UI 轻松处理,但是如果验证更复杂怎么办,例如业务对象根据输入计算一个数字,如果计算出的数字超出范围,则应该拒绝它。 这是更复杂的逻辑,不应该出现在 UI 中。
还考虑根据已输入的字段输入进一步的数据。 例如,我必须在订单上输入一个项目才能获取某些信息,例如现有库存、当前成本等。用户可能需要此信息来决定进一步输入(例如要订购多少单位),或者订单中可能需要此信息以便进一步验证。 如果该项目无效,用户是否应该能够输入其他字段? 重点是什么?
I’ve been reading a few questions and answers regarding exceptions and their use. Seems to be a strong opinion that exceptions should be raised only for exception, unhandled cases. So that lead me to wondering how validation works with business objects.
Lets say I have a business object with getters/setters for the properties on the object. Let’s say I need to validate that the value is between 10 and 20. This is a business rule so it belongs in my business object. So that seems to imply to me that the validation code goes in my setter. Now I have my UI databound to the properties of the data object. The user enters 5, so the rule needs to fail and the user is not allowed to move out of the textbox. . The UI is databound to the property so the setter is going to be called, rule checked and failed. If I raised an exception from my business object to say the rule failed, the UI would pick that up. But that seems to go against the preferred usage for exceptions. Given that it’s a setter, you aren’t really going to have a ‘result’ for the setter. If I set another flag on the object then that would imply the UI has to check that flag after each UI interaction.
So how should the validation work?
Edit: I've probably used an over-simplified example here. Something like the range check above could be handled easily by the UI but what if the valdation was more complicated, e.g. the business object calculates a number based on the input and if that calculated number is out of range it should be recjected. This is more complicated logic that should not be in th UI.
There is also the consideration of further data entered based on a field already entered. e.g.I have to enter an item on the order to get certain informaion like stock on hand, current cost, etc. The user may require this information to make decisions on further entry (liek how many units to order) or it may be required in order for further validation to be done. Should a user be able to enter other fields if the item isn't valid? What would be the point?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
我认为这取决于你的商业模式有多重要。 如果你想走DDD之路,你的模型是最重要的。 因此,您希望它始终处于有效状态。
在我看来,大多数人都试图对域对象做太多事情(与视图通信、保存到数据库等),但有时您需要更多层和更好的关注点分离,即一个或多个视图模型。 然后,您可以在视图模型上无例外地应用验证(对于不同的上下文,例如 Web 服务/网站/等,验证可能会有所不同),并将异常验证保留在业务模型中(以防止模型被损坏)。 您将需要一个(或多个)应用程序服务层来将您的视图模型与业务模型进行映射。 业务对象不应该被通常与特定框架(例如 NHibernate Validator)相关的验证属性所污染。
I think it depends on how much your business model is important. If you want to go the DDD way, your model is the most important thing. Therefore, you want it to be in a valid state at all time.
In my opinion, most people are trying to do too much (communicate with the views, persist to the database, etc.) with the domain objects but sometimes you need more layers and a better separation of concerns i.e., one or more View Models. Then you can apply validation without exceptions on your View Model (the validation could be different for different contexts e.g., web services/web site/etc.) and keep exception validations inside your business model (to keep the model from being corrupted). You would need one (or more) Application Service layer to map your View Model with your Business Model. The business objects should not be polluted with validation attributes often related to specific frameworks e.g., NHibernate Validator.
如果输入超出了业务对象实现的业务规则,我会说这是业务对象未处理的情况。 因此我会抛出一个异常。 即使在您的示例中设置器会“处理”5,但业务对象不会。
对于更复杂的输入组合,需要一种验证方法,否则您最终会得到分散在各处的相当复杂的验证。
在我看来,您必须根据允许/禁止输入的复杂性来决定走哪条路。
If the input goes beyond the business rule implemented by the business object, I'd say it's a case not handled by the busines object. Therefore I'd throw an exception. Even though the setter would "handle" a 5 in your example, the business object won't.
For more complex combinations of input, a vaildation method is required though, or else you'll end up with quite complex validations scattered about all over the place.
In my opinion you'll have to decide which way to go depending on the complexity of the allowed/disallowed input.
我认为这是一个可以抛出异常的例子。 您的属性可能没有任何上下文来纠正问题,因为这样的异常是正常的,并且调用代码应该处理这种情况(如果可能)。
I my opinion this is an example where throwing an exception is okay. Your property probably does not have any context by which to correct the problem, as such an exception is in order and the calling code should handle the situation, if possible.
如果数据无效,您是否考虑过在设置器中引发事件? 这将避免引发异常的问题,并且无需显式检查对象是否有“无效”标志。 您甚至可以传递一个参数来指示哪个字段验证失败,以使其更可重用。
如果需要,事件处理程序应该能够将焦点放回到适当的控件上,并且它可以包含通知用户错误所需的任何代码。 此外,您可以简单地拒绝连接事件处理程序,并在需要时忽略验证失败。
Have you considered raising an event in the setter if the data is invalid? That would avoid the problem of throwing an exception and would eliminate the need to explicitly check the object for an "invalid" flag. You could even pass an argument indicating which field failed validation, to make it more reusable.
The handler for the event should be able to take care of putting focus back onto the appropriate control if needed, and it could contain any code needed to notify the user of the error. Also, you could simply decline to hook up the event handler and be free to ignore the validation failure if needed.
根据我的经验,验证规则很少在应用程序的所有屏幕/表单/流程中通用。 像这样的场景很常见:在添加页面上,Person 对象没有姓氏可能没问题,但在编辑页面上它必须有姓氏。 在这种情况下,我开始相信验证应该在对象之外进行,或者应该将规则注入到对象中,以便规则可以在给定的上下文中更改。 有效/无效应该是验证后对象的显式状态,或者可以通过检查失败规则的集合来派生的状态。 恕我直言,失败的业务规则也不例外。
In my experience, validation rules are seldom universal across all screens/forms/processes in an application. Scenarios like this are common: on the add page, it may be ok for a Person object not to have a last name, but on the edit page it must have a last name. That being the case I've come to believe that validation should happen outside of an object, or the rules should be injected into the object so the rules can change given a context. Valid/Invalid should be an explicit state of the object after validation or one that can be derived by checking a collection for failed rules. A failed business rule is not an exception IMHO.
在你的情况下抛出异常是可以的。 您可以将这种情况视为真正的异常,因为某些东西正在尝试将整数设置为字符串(例如)。 业务规则缺乏对您的视图的了解意味着他们应该认为这种情况是例外的并将其返回到视图。
在将输入值发送到业务层之前是否对其进行验证取决于您,我认为只要您在整个应用程序中遵循相同的标准,那么您最终将得到干净且可读的代码。
您可以使用上面指定的 spring 框架,但要小心,因为链接文档的大部分内容都指示编写非强类型的代码,IE 可能会在运行时出现错误,而这些错误在编译时无法发现。 这是我尽力避免的事情。
目前我们的做法是从屏幕上获取所有输入值,将它们绑定到数据模型对象,如果值错误则抛出异常。
Throwing an exception in your case is fine. You can consider the case a true exception because something is trying to set an integer to a string (for example). The business rules lack of knowledege of your views means that they should consider this case exceptonal and return that back to the view.
Whether or not you validate your input values before you send them through to the business layer is up to you, I think that as long as you follow the same standard throughout your application then you will end up with clean and readable code.
You could use the spring framework as specified above, just be careful as much of the linked document was indicating writing code that is not strongly typed, I.E. you may get errors at run time that you could not pick up at compile time. This is something I try to avoid as much as possible.
The way we do it here currently is that we take all the input values from the screen, bind them to a data model object and throw an exception if a value is in error.
您可能想考虑Spring 框架采用的方法。 如果您使用 Java(或 .NET),则可以按原样使用 Spring,但即使您不使用,您仍然可以使用该模式; 您只需要编写自己的实现即可。
You might like to consider the approach taken by the Spring framework. If you're using Java (or .NET), you can use Spring as-is, but even if you're not, you could still use that pattern; you'd just have to write your own implementation of it.
正如 Paul Stovell 的文章提到的,您可以在您的业务中实施无差错验证通过实现 IDataErrorInfo 接口来获取对象。 这样做将允许通过 WinForm 的 ErrorProvider 和 WPF 的绑定验证规则。 验证对象属性的逻辑存储在一个方法中,而不是存储在每个属性 getter 中,并且您不一定必须求助于 CSLA 或验证应用程序块等框架。
至于阻止用户将焦点移出文本框:
首先,这通常不是最佳实践。 用户可能想要不按顺序填写表单,或者,如果验证规则依赖于多个控件的结果,则用户可能必须填写虚拟值才能离开一个控件以设置另一个控件。 也就是说,这可以通过将表单的
AllowValidate
属性设置为其默认值EnableAllowFocusChange
并订阅 Control.Validating 事件来实现:为此使用存储在业务对象中的规则验证有点棘手,因为在焦点更改和更新数据绑定业务对象之前调用 Validating 事件。
As Paul Stovell's article mentioned, you can implement error-free validation in your business objects by implementing the IDataErrorInfo interface. Doing so will allow user error notification by WinForm's ErrorProvider and WPF's binding with validation rules. The logic to validate your objects properties is stored in one method, instead of in each of your property getters, and you do not necessarily have to resort to frameworks like CSLA or Validation Application Block.
As far as stopping the user from changing focus out of the textbox is concerned:
First of all, this is usually not the best practice. A user may want to fill out the form out of order, or, if a validation rule is dependent on the results of multiple controls, the user may have to fill in a dummy value just to get out of one control to set another control. That said, this can be implemented by setting the Form's
AllowValidate
property to its default,EnableAllowFocusChange
and subscribing to the Control.Validating event:Using rules stored in the business object for this validation is a little more tricky since the Validating event is called before the focus changes and the data bound business object is updated.
我倾向于认为,当传递的值违反其业务规则时,业务对象应该抛出异常。 然而,winforms 2.0 数据绑定架构似乎采取了相反的假设,因此大多数人都自然而然地支持这种架构。
我同意 shabbyrobe 的最后一个答案,即业务对象应该构建为可用并在多种环境中正确工作,而不仅仅是 winforms 环境,例如,业务对象可以在 SOA 类型的 Web 服务、命令行界面、asp 中使用.net 等。在所有这些情况下,对象应正确运行并保护自身免受无效数据的影响。
经常被忽视的一个方面是管理 1-1、1-n 或 nn 关系中的对象之间的协作时会发生什么,这些对象是否也接受无效协作者的添加,并且只维护一个应该检查或应该检查的无效状态标志它主动拒绝添加无效的协作。 我必须承认我深受 Jill Nicola 等人的 Streamlined Object Modeling (SOM) 方法的影响。 但还有什么是合乎逻辑的呢?
接下来的事情是如何使用 Windows 窗体。 我正在考虑为这些场景的业务对象创建一个 UI 包装器。
I tend to believe business objects should throw exceptions when passed values that violate its business rules. It however seems that winforms 2.0 data binding architecture assumes the opposite and so most people are rail-roaded into supporting this architecture.
I agree with shabbyrobe's last answer that business objects should be built to be usable and to work correctly in multiple environments and not just the winforms environment, e.g., the business object could be used in a SOA type web service, a command line interface, asp.net, etc. The object should behave correctly and protect itself from invalid data in all these cases.
An aspect that is often overlooked is also what happens in managing the collaborations between objects in 1-1, 1-n or n-n relationships, should these also accept the addition of invalid collaborators and just maintain a invalid state flag which should be checked or should it actively refuse to add invalid collaborations. I have to admit that I'm heavily influenced by the Streamlined Object Modeling (SOM) approach of Jill Nicola et al. But what else is logical.
The next thing is how to work with windows forms. I'm looking at creating a UI wrapper for the business objects for these scenarios.
这取决于您将执行何种类型的验证以及在何处执行。 我认为应用程序的每一层都可以轻松地保护免受不良数据的影响,而且这太容易了,因此不值得。
考虑多层应用程序以及每层的验证要求/设施。 中间层,对象,是这里似乎有争议的一层。
数据库
通过列约束和引用完整性保护自身免受无效状态的影响,这将导致应用程序的数据库代码抛出异常
Object
?
ASP.NET/Windows 窗体
使用验证器例程和/或控件保护表单的状态(而不是对象),而不使用异常(winforms 不附带验证器,但 msdn 描述如何实现它们)
假设您有一个包含酒店房间列表的表,每行都有一个床位数量一栏称为“床位”。 该列最合理的数据类型是无符号小整数*。 您还有一个普通的 ole 对象,其具有名为“Beds”的 Int16* 属性。 问题是您可以将 -4555 粘贴到 Int16 中,但是当您将数据保存到数据库时,您将收到异常。 这很好 - 我的数据库不应该允许说酒店房间的床位少于零,因为酒店房间的床位不能少于零。
* 如果你的数据库可以代表它,但我们假设它可以
* 我知道您可以使用 ushort在 C# 中,但出于本示例的目的,我们假设您不能。
对于对象是否应该代表您的业务实体,或者它们是否应该代表表单的状态,存在一些混淆。 当然,在 ASP.NET 和 Windows 窗体中,窗体完全能够处理和验证其自身状态。 如果 ASP.NET 表单上有一个文本框,将用于填充相同的 Int16 字段,则您可能在页面上放置了一个 RangeValidator 控件,该控件在将输入分配给对象之前测试输入。 它会阻止您输入小于零的值,并且可能会阻止您输入大于(例如)30 的值,这希望足以满足您能想象到的跳蚤出没最严重的旅馆的需求。 在回发时,您可能会在构建对象之前检查页面的 IsValid 属性,从而防止您的对象代表小于零的床,并防止您的 setter 被调用为它应该的值撑不住了
但是您的对象仍然能够表示小于零的床,而且,如果您在不涉及集成了验证的层(您的表单和数据库)的场景中使用该对象,那么您我们运气不好。
为什么你会遇到这种情况? 这一定是非常特殊的情况! 因此,您的 setter 在收到无效数据时需要抛出异常。 它永远不应该被抛出,但它可能会被抛出。 您可能正在编写一个 Windows 窗体来管理对象以替换 ASP.NET 窗体,但忘记在填充对象之前验证范围。 您可以在计划任务中使用该对象,其中根本没有用户交互,并且保存到数据库的不同但相关的区域,而不是对象映射到的表。 在后一种情况下,您的对象可能会进入无效状态,但直到其他操作的结果开始受到无效值的影响时您才会知道。 如果您正在检查它们并抛出异常,那就是。
It depends on what sort of validation you will be performing and where. I think that each layer of the application can be easily protected from bad data and its too easy to do for it not to be worth it.
Consider a multi-tiered application and the validation requirements/facilities of each layer. The middle layer, Object, is the one that seems to be up for debate here.
Database
protects itself from an invalid state with column constraints and referential integrity, which will cause the application's database code to throw exceptions
Object
?
ASP.NET/Windows Forms
protects the form's state (not the object) using validator routines and/or controls without using exceptions (winforms does not ship with validators, but there's an excellent series at msdn describing how to implement them)
Say you have a table with a list of hotel rooms, and each row has a column for the number of beds called 'beds'. The most sensible data type for that column is an unsigned small integer*. You also have a plain ole object with an Int16* property called 'Beds'. The issue is that you can stick -4555 into an Int16, but when you go to persist the data to a database you're going to get an Exception. Which is fine - my database shouldn't be allowed to say that a hotel room has less than zero beds, because a hotel room can't have less than zero beds.
* If your database can represent it, but let's assume it can
* I know you can just use a ushort in C#, but for the purpose of this example, let's assume you can't
There's some confusion as to whether objects should represent your business entity, or whether they should represent the state of your form. Certainly in ASP.NET and Windows Forms, the form is perfectly capable of handling and validating its own state. If you've got a text box on an ASP.NET form that is going to be used to populate that same Int16 field, you've probably put a RangeValidator control on your page which tests the input before it gets assigned to your object. It prevents you from entering a value less than zero, and probably prevents you from entering a value greater than, say, 30, which hopefully would be enough to cater for the worst flea-infested hostel you can imagine. On postback, you would probably be checking the IsValid property of the page before building your object, thereby preventing your object from ever representing less than zero beds and preventing your setter from ever being called with a value it shouldn't hold.
But your object is still capable of representing less than zero beds, and again, if you were using the object in a scenario not involving the layers which have validation integrated into them (your form and your DB) you're outta luck.
Why would you ever be in this scenario? It must be a pretty exceptional set of circumstances! Your setter therefore needs to throw an exception when it receives invalid data. It should never be thrown, but it could be. You could be writing a Windows Form to manage the object to replace the ASP.NET form and forget to validate the range before populating the object. You could be using the object in a scheduled task where there is no user interaction at all, and which saves to a different, but related, area of the database rather than the table which the object maps to. In the latter scenario, your object can enter a state where it is invalid, but you won't know until the results of other operations start to be affected by the invalid value. If you're checking for them and throwing exceptions, that is.
您的业务对象应该因错误输入而引发异常,但在正常程序运行过程中决不应该引发这些异常。 我知道这听起来很矛盾,所以我会解释一下。
每个公共方法都应该验证其输入,并在输入不正确时抛出“ArgumentException”。 (私有方法应该使用“Debug.Assert()”验证其输入以简化开发,但这是另一个故事了。)关于验证公共方法(当然还有属性)的输入的规则对于应用程序的每一层都适用。
当然,软件接口的要求应该在接口文档中阐明,调用代码的工作就是确保参数正确并且永远不会抛出异常,这意味着 UI 需要验证在将输入传递给业务对象之前。
虽然上面给出的规则几乎永远不会被打破,但有时业务对象验证可能非常复杂,并且不应将这种复杂性强加到 UI 上。 在这种情况下,BO 的接口最好在接受的内容上留有一定的余地,然后提供显式的 Validate(out string[]) 谓词来检查属性并就需要更改的内容提供反馈。 但请注意,在这种情况下,仍然存在明确定义的接口要求,并且不需要抛出异常(假设调用代码遵循规则)。
按照后一个系统,我几乎从不对属性设置器进行早期验证,因为这种软化使属性的使用变得复杂(但在问题中给出的情况下,我可能会这样做)。 (顺便说一句,请不要仅仅因为字段中有错误数据就阻止我从字段中跳出。当我无法在表单周围跳动时,我会产生幽闭恐惧症!我会在一分钟内返回并修复它,我保证!好吧,我现在感觉好多了,抱歉。)
Your business objects should throw exceptions for bad inputs, but those exceptions should never be thrown in the course of a normal program run. I know that sounds contradictory, so I shall explain.
Each public method should validate its inputs, and throw "ArgumentException"s when they are incorrect. (And private methods should validate their inputs with "Debug.Assert()"s to ease development, but that's another story.) This rule about validating inputs to public methods (and properties, of course) is true for every layer of the application.
The requirements of the software interface should be spelled out in the interface documentation, of course, and it is the job of the calling code to make sure the arguments are correct and the exceptions will never be thrown, which means the UI needs to validate the inputs before handing them to the business object.
While the rules given above should almost never be broken, sometimes business object validation can be very complex, and that complexity shouldn't be foisted onto the UI. In that case it's good for the BO's interface to allow some leeway in what it accepts and then provide for an explicit Validate(out string[]) predicate to check the properties and give feedback on what needs to be changed. But notice in this case that there are still well-defined interface requirements and no exceptions need ever be thrown (assuming the calling code follows the rules).
Following this latter system, I almost never do early validation on property setters, since that soft-of complicates the use of the properties, (but in the case given in the question, I might). (As an aside, please don't prevent me from tabbing out of a field just because it has bad data in it. I get clausterphobic when I can't tab around a form! I'll go back and fix it in a minute, I promise! OK, I feel better now, sorry.)
也许您应该考虑同时进行客户端和服务器端验证。 如果有任何事情未能通过客户端验证,那么如果您的业务对象无效,您可以随意抛出异常。
我使用的一种方法是将自定义属性应用于业务对象属性,它描述了验证规则。 例如:
然后可以处理这些属性并用于自动创建客户端和服务器端验证方法,以避免重复业务逻辑的问题。
Perhaps you should look at having both client-side and server-side validation. If anything slips past the client-side validation you can then feel free to throw an exception if your business object would be made invalid.
One approach I've used was to apply custom attributes to business object properties, which described the validation rules. e.g.:
The attributes can then be processed and used to automatically create both client-side and server-side validation methods, to avoid the problem of duplicating business logic.
我肯定会提倡客户端和服务器端验证(或在各个层进行验证)。 当跨物理层或进程进行通信时,这一点尤其重要,因为抛出异常的成本变得越来越昂贵。 此外,等待验证的时间越长,浪费的时间就越多。
至于是否使用异常进行数据验证。 我认为在流程中使用异常是可以的(尽管仍然不是首选),但在流程之外,调用一个方法来验证业务对象(例如在保存之前),并让该方法返回操作的成功以及任何验证错误。 错误并不罕见。
当验证失败时,Microsoft 会从业务对象引发异常。 至少,企业库的验证应用程序块就是这样工作的。
I'd definitely advocate both client and server-side validation (or validating at the various layers). This is especially important when communicating across physical tiers or processes, as the cost of throw exceptions becomes increasingly expensive. Also, the further down the chain you wait for validation, the more time is wasted.
As to use Exceptions or not for data validation. I think it's ok to use exception in process (though still not preferrable), but outside of process, call a method to validate the business object (eg before saving) and have the method return the success of the operation along with any validation errors. Errors arent' exceptional.
Microsoft throw exceptions from business objects when validation fails. At least, that's how the Enterprise Library's Validation Application Block works.
异常不应作为验证的正常部分而抛出。 从业务对象内部调用的验证是最后一道防线,只有在 UI 无法检查某些内容时才应该发生。 因此,它们可以像任何其他运行时异常一样对待。
请注意,定义验证规则和应用它们之间存在差异。 您可能希望在业务逻辑层中定义(即编码或注释)业务规则,但从 UI 调用它们,以便可以以适合该特定 UI 的方式处理它们。 不同的 UI 的处理方式会有所不同,例如基于表单的 Web 应用程序与 Ajax Web 应用程序。 异常集验证提供的处理选项非常有限。
许多应用程序重复其验证规则,例如在 JavaScript、域对象约束和数据库约束中。 理想情况下,这些信息只会定义一次,但实现这一点可能具有挑战性,并且需要横向思考。
Exceptions should not be thrown as a normal part of validation. Validation invoked from within business objects is a last line of defense, and should only happen if the UI fails to check something. As such they can be treated like any other runtime exception.
Note that here's a difference between defining validation rules and applying them. You might want to define (ie code or annotate) your business rules in your business logic layer but invoke them from the UI so that they can handled in a manner appropriate to that particular UI. The manner of handling will vary for different UI's, eg form based web-apps vs ajax web-apps. Exception-on-set validation offers very limited options for handling.
Many applications duplicate their validation rules, such as in javascript, domain object constraints and database constraints. Ideally this information will only be defined once, but implementing this can be challenge and requires lateral thinking.
您可能希望将验证移至 getter 和 setter 之外。 您可以有一个名为 IsValid 的函数或属性来运行所有验证规则。 t 会用所有“破坏的规则”填充字典或哈希表。 该字典将暴露给外界,您可以使用它来填充错误消息。
这是 CSLA.Net 中采用的方法。
You might want to move the validation outside of the getters and setters. You could have a function or property called IsValid that would run all the validation rules. t would populate a dictionary or hashtable with all of the "Broken Rules". This dictionary would be exposed to the outside world, and you can use it to populate your error messages.
This is the approach that is taken in CSLA.Net.
我一直很喜欢 Rocky Lhotka 在 CSLA 框架 中的方法(正如 Charles 所提到的)。 一般来说,无论是由 setter 驱动还是通过调用显式 Validate 方法,BrokenRule 对象的集合都是由业务对象在内部维护的。 UI 只需检查对象的 IsValid 方法,该方法又检查 BrokenRules 的数量,并进行适当的处理。 或者,您可以轻松地让 Validate 方法引发 UI 可以处理的事件(可能是更简洁的方法)。 您还可以使用 BrokenRules 列表以摘要形式或在相应字段旁边显示错误消息以供使用。 尽管 CSLA 框架是用 .NET 编写的,但总体方法可以用任何语言使用。
我不认为在这种情况下抛出异常是最好的主意。 我绝对遵循这样的思想:异常应该是在特殊情况下出现的,而简单的验证错误则不然。 在我看来,引发 OnValidationFailed 事件将是更干净的选择。
顺便说一句,我从来不喜欢在字段处于无效状态时不让用户离开字段的想法。 在很多情况下,您可能需要暂时离开该字段(也许先设置其他字段),然后再返回并修复无效字段。 我认为这只是不必要的不便。
I've always been a fan of Rocky Lhotka's approach in the CSLA framework (as mentioned by Charles). In general, whether it's driven by the setter or by calling an explicit Validate method, a collection of BrokenRule objects is maintained internally by the business object. The UI simply needs to check an IsValid method on the object, which in turn checks the number of BrokenRules, and handle it appropriately. Alternatively, you could easily have the Validate method raise an event which the UI could handle (probably the cleaner approach). You can also use the list of BrokenRules to display error messages to the use either in summary form or next to the appropriate field. Although the CSLA framework is written in .NET, the overall approach can be used in any language.
I don't think throwing an Exception is the best idea in this case. I definitely follow the school of thought that says Exceptions should be for exceptional circumstances, which a simple validation error is not. Raising an OnValidationFailed event would be the cleaner choice, in my opinion.
By the way, I have never liked the idea of not letting the user leave a field when it is in an invalid state. There are so many situations where you might need to leave the field temporarily (perhaps to set some other field first) before going back and fixing the invalid field. I think it's just an unnecessary inconvenience.
假设您有单独的验证和持久(即保存到数据库)代码,我将执行以下操作:
UI 应该执行验证。 不要在这里抛出异常。 您可以提醒用户错误并阻止保存记录。
您的数据库保存代码应该针对错误数据抛出无效参数异常。 在这里这样做是有意义的,因为此时您无法继续写入数据库。 理想情况下,这种情况永远不会发生,因为 UI 应阻止用户保存,但您仍然需要它来确保数据库一致性。 此外,您可能会从 UI 以外的其他地方调用此代码(例如批量更新),其中没有 UI 数据验证。
Assuming that you have separate validation and persist (i.e. save to database) code, I would do the following:
The UI should perform validation. Don't throw exceptions here. You can alert the user to errors and prevent the record from being saved.
Your database save code should throw invalid argument exceptions for bad data. It makes sense to do it here, since you cannot proceed with the database write at this point. Ideally this should never happen since the UI should prevent the user from saving, but you still need it to ensure database consistency. Also you might be calling this code from something other than the UI (e.g. batch updates) where there is no UI data validation.
您想深入了解 Paul Stovell 在数据验证方面的杰出工作。 他在这篇文章中一度总结了自己的想法。 我碰巧分享了他对此事的看法,我在 我自己的库。
用 Paul 的话说,以下是在 setter 中抛出异常的缺点(基于
Name
属性不应为空的示例):以下是替代解决方案的基本规则:
You want to delve a bit in the remarkable work of Paul Stovell concerning data validation. He summed up his ideas at one time in this article. I happen to share his point of view on the matter, which I implemented in my own libraries.
Here are, in Paul's words, the cons to throwing exceptions in the setters (based on a sample where a
Name
property should not be empty) :And here are basic rules for an alternative solution :