避免输入验证中的重复代码

时间:2009-05-19 12:26:01

标签: refactoring user-interface validation

假设您有一个子系统可以完成某种工作。它可能是任何东西。显然,在该子系统的入口点,输入会有一些限制。假设该子系统主要由GUI调用。子系统需要检查它收到的所有输入以确保它有效。如果输入无效,我们不想要FireTheMissles()。 UI也对验证感兴趣,因为它需要报告出错的地方。也许用户忘记在启动板本身指定目标或瞄准导弹。当然,您可以只返回一个空值或抛出一个异常,但这并没有告诉用户特定的错误(当然,除非您为每个错误编写一个单独的异常类,如果这是最好的做法)。

当然,即使有例外情况,也有问题。在单击“Fire Missles”之前,用户可能想知道输入是否有效。按钮。你可以编写一个单独的验证函数(当然IsValid()并没有真正帮助太多,因为它没有告诉你出了什么问题),但是你将从按钮点击处理程序再次从FireTheMissles中调用它( )功能(我真的不知道这是如何从一个模糊的子系统变为一个导弹射击程序)。当然,这不是世界末日,但是连续两次调用相同的验证函数而没有任何改变似乎很愚蠢,特别是如果这个验证函数需要,比如计算1gb文件的散列。

如果函数的前提条件是明确的,GUI可以进行自己的输入验证,但是我们只是复制输入验证逻辑,并且一个中的更改可能不会反映在另一个中。当然,我们可以在GUI中添加一个检查以确保导弹目标不在联盟国家内,但如果我们忘记将其复制到FireTheMissles()例程,当我们切换到时,我们会意外地炸毁我们的盟友控制台界面。

因此,简而言之,您如何实现以下目标:

  1. 输入验证,不仅可以告诉您出错的地方,还有什么特别出错的地方。
  2. 能够在不调用依赖它的函数的情况下运行此输入验证。
  3. 没有双重验证。
  4. 没有重复的代码。
  5. 此外,我只想到了这一点,但不应在FireTheMissles()方法中写入错误消息。 GUI负责选择适当的错误消息,而不是GUI正在调用的代码。

5 个答案:

答案 0 :(得分:3)

“子系统需要检查它收到的所有输入,以确保它有效”

认为输入不是一个参数列表,而是作为一条消息,之后它会变得更容易。

消息类有一个IsValid成员函数,它会记住是否调用了IsValid以及结果是什么。它还会记住它的状态,如果状态发生变化则需要重新验证。此消息类还保留了验证错误列表。

现在,UI构建了一个TargetMissiles消息,UI可以对其进行验证,或者直接将其传递给MissileFiring子系统,它会检查消息是否已经过验证,如果没有验证,则继续/失败。 UI返回消息,已填充验证列表。

带有验证的消息位于单独的库中。没有代码重复。

这听起来不错?

答案 1 :(得分:2)

这就是Model-View-Controller的全部内容。

你建立了一个模型(一个由坐标,导弹类型和导弹数量组成的发射),该模型有一个验证方法,它返回一个错误/警告列表。当您更新模型时(在按键上,< ENTER>,按下按钮),您将调用validate方法并向用户显示任何警告,错误等。(Eclipse在对话框的工具栏下方有一小块区域)这样做,你可能想看一下。)

当模型有效时,您激活发射导弹按钮,以便用户知道他们可以发射导弹。如果您有一个特别频繁调用的更新事件或者特别昂贵的验证的一部分,您可以在模型上使用validate_light方法,用于仅验证易于执行的部分。

当您切换到基于控制台的UI时,您可以从命令行参数构建模型,调用相同的validate方法(并将错误报告给stderr),然后启动导弹。

答案 2 :(得分:1)

加倍验证。在许多情况下,验证是三倍的(例如,DB中的FK而不是空字段)。根据您的平台,可以对验证规则进行一次编码。例如,您的前端和后端代码可以共享C#业务类。或者,您可以将验证规则存储为后端和前端都可以访问应用的元数据。

实际上,您需要对验证问题做出不同的响应(例如,在其他输入有效之前甚至不应启用Fire Missile按钮),将会有不同的代码与同一规则相关联。

答案 3 :(得分:0)

我建议使用一个输入验证类,它在其构造函数中获取输入类型(枚举),并提供一个公共的IsValid方法。

IsValid方法应该返回有效的布尔值TRUE和无效的FALSE。它还应该有一个OUT参数,它接受一个字符串并为该字符串分配一个状态消息。如果需要,调用者可以自由地忽略该消息,或者如果适合上下文,则将其报告给GUI。

所以,在伪代码中(原谅类似Delphi的语法,但它应该对任何人都可读):

//different types of data we might want to validate
TValidationType = (vtMissileLaunchCodes, vtFirstName, 
  vtLastName, vtSSN);  

TInputValidator = class
public
  //call the constructor with the validation type
  constructor Create(ValidationType: TValidationType);

  //this should probably be ABSTRACT, implemented by descendants
  //if you took that approach, then you'd have 1 descendant class
  //for each validation type, instead of an enumeration
  function IsValid(InputData: string; var msg: string): boolean;

然后使用它,你会做这样的事情:

procedure ValidateForm;
var
  validator: TInputValidator;
begin
  validator := TInputValidator.Create(vtSSN);
  if validator.IsValid(edtSSN.Text,labelErrorMsg.Text) then 
    SaveData;  //it's valid, so save it!
  //if it wasn't valid, then the error msg is in the GUI in "labelErrorMsg".
end;

答案 4 :(得分:0)

每个数据都有自己的元数据(类型,格式,单位,掩码,范围等)。不幸的是,这并不总是具体说明。

GUI控件需要使用元数据检查输入,并在数据无效时发出警告/错误。

示例:数字有一个范围。范围由元数据提供,但范围检查由控件提供。