在继续之前,验证一堆变量的有效方法是什么?

本文关键字:变量 一堆 有效 方法 是什么 继续 验证 | 更新日期: 2023-09-27 18:07:47

我已经阅读了关于方法的理想大小和单一职责原则,然后我去看看我的一些代码。我觉得我可以把我的很多东西(>90%)分解成小的可管理的方法,但然后我要验证一个数据或一个表单。它总是看起来又大又臃肿。我倾向于用嵌套的if语句验证数据,并尝试在每个级别捕获错误或问题。但当我开始得到6、8、10级以上的验证时,这就非常麻烦了。但是我不知道怎样把它分解才能更有效。

下面是一个我认为很麻烦但不知道如何改进的例子。每个级别都有一个与之相关联的唯一动作,只有当所有条件都返回true时,整个东西才能返回true,但这很难阅读,特别是在一个月左右回到程序后。

if (InitialUsageSettings.zeroed || sender.Equals(btnZero))
{   
    if (InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard))
    {   
        if (InitialUsageSettings.ReferenceFilterRun || sender.Equals(btnReference) || sender.Equals(btnStandard))
        {   
            if (InitialUsageSettings.PrecisionTestRun || sender.Equals(btnPrecision) || sender.Equals(btnReference) || sender.Equals(btnStandard))
            {   
                if (txtOperatorID.Text.Length > 0 && cboProject.Text.Length > 0 && cboFilterType.Text.Length > 0 && cboInstType.Text.Length > 0)
                {   
                    if (txtFilterID.Text.Length > 0 && txtLot.Text.Length > 0)
                    {   
                        return true;
                    }
                    else
                    {
                        if (txtFilterID.Text.Length == 0)
                        {
                            //E
                        }
                        if (txtLot.Text.Length == 0)
                        {
                            //D
                        }
                    }
                }
                else
                {
                    if (txtOperatorID.Text.Length == 0)
                    {
    //A
                    }
                    if (cboProject.Text.Length == 0)
                    {
    //B
                    }
                    if (cboFilterType.Text.Length == 0)
                    {
    //C
                    }
                    if (cboInstType.Text.Length == 0)
                    {
    //D
                    }
                    //return false;
                }
            }
            else
            {
                outputMessages.AppendLine("Please correct the folloring issues before taking a reading: X");
            }
        }
        else
        {
            outputMessages.AppendLine("Please correct the folloring issues before taking a reading: Y");
        }
    }
    else
    {
        outputMessages.AppendLine("Please correct the folloring issues before taking a reading: Z");
    }
}
else
{
    outputMessages.AppendLine("Please correct the folloring issues before taking a reading: A");
}

在继续之前,验证一堆变量的有效方法是什么?

如果您的主要目的是将方法分解成可管理的块,那么您可以将每个if块封装在自己的方法中。例如:

 if (InitialUsageSettings.zeroed || sender.Equals(btnZero))
 {
     ValidateStandardFilter();
 }
 else
 {   
     outputMessages.AppendLine("Please correct the folloring issues before taking a reading: A");
 }       

但是在我看来,这个方法有太多的责任:你试图使它验证并输出消息。相反,该方法应该单独负责验证。

public ValidationResult Validate(Sender sender)
{
    if (!(InitialUsageSettings.zeroed || sender.Equals(btnZero)))
    {   
        return ValidationResult.Error("A");
    }
    if (!(InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard)))
    {   
        return ValidationResult.Error("Z");
    }
    // Etc...
    if (txtOperatorID.Text.Length == 0)
    {
        errors.Add("A");
    }
    if (cboProject.Text.Length == 0)
    {
        errors.Add("B");
    }
    if (cboFilterType.Text.Length == 0)
    {
        errors.Add("C");
    }
    if (cboInstType.Text.Length == 0)
    {
        errors.Add("D");
    }
    if(errors.Count > 0)
    {
        return ValidationResult.Errors(errors);
    }
    if (txtFilterID.Text.Length == 0)
    {
        errors.Add("E");
    }
    if (txtLot.Text.Length == 0)
    {
        errors.Add("D");
    }
    return errors.Count > 0 
        ? ValidationResult.Errors(errors) 
        : ValidationResult.Success();
}

然后调用代码可以考虑输出:

var result = Validate(sender);
if (result.IsError)
{
    outputMessages.AppendLine("Please correct...: " + result.Issue);
}

要了解ValidationResult类可能是什么样子,请参阅我的回答。

更新

上面的代码可以进一步重构以减少重复:

public ValidationResult Validate(Sender sender)
{
    if (!(InitialUsageSettings.zeroed || sender.Equals(btnZero)))
    {   
        return ValidationResult.Error("A");
    }
    if (!(InitialUsageSettings.StandardFilterRun || sender.Equals(btnStandard)))
    {   
        return ValidationResult.Error("Z");
    }
    // Etc...
    
    var firstErrorBatch = GetEmptyStringErrors(
        new[]{
            new InputCheckPair(txtOperatorID, "A"),
            new InputCheckPair(cboProject, "B"),
            new InputCheckPair(cboFilterType, "C"),
            new InputCheckPair(cboInstType, "D"),
        })
        .ToList();
    if(firstErrorBatch.Count > 0)
    {
        return ValidationResult.Errors(firstErrorBatch);
    }
        
    var secondErrorBatch = GetEmptyStringErrors(
        new[]{
            new InputCheckPair(txtFilterID, "E"),
            new InputCheckPair(txtLot, "D"),
        })
        .ToList();
    return secondErrorBatch.Count > 0 
        ? ValidationResult.Errors(secondErrorBatch) 
        : ValidationResult.Success();
}
private class InputCheckPair
{
    public InputCheckPair(TextBox input, string errorIfEmpty)
    {
        Input = input;
        ErrorIfEmpty = errorIfEmpty;
    }
    public TextBox Input {get; private set;}
    public string ErrorIfEmpty{get; private set;}
}
public IEnumerable<string> GetEmptyStringErrors(IEnumerable<InputCheckPair> pairs)
{
    return from p in pairs where p.Input.Text.Length == 0 select p.ErrorIfEmpty;
}

类似于

if(errorCondition1)
  errors.add(message1);
if(errorCondition2)
  errors.add(message2);
return errors.Count == 0;

所以每个条件不是嵌套的

你可以用Guard子句来代替if语句。

反向流。而不是

If(cond) {
  if(someothercond) {
     //great sucess!
     return true;
  } else {
    // handle
    return false;
  }
} else {
  // handle
  return false;
}

:

if(!cond1) {
  // handle
  return false;
}
if(!someothercond) {
  // handle
  return false;
}
// great sucess!
return true;

一种方法是在执行其他代码之前调用验证方法。

例如:

private String ValidateThis() {
  StringBuilder result = new StringBuilder();
  if (!cond1) {
    result.AppendLine("error on cond1");
  } 
   if (!cond2) {
     result.AppendLine("error on cond2");
   }
   return result.ToString();
}
public void ButtonClick(object sender) {
    String isValid = ValidateThis();
    if (!String.IsNullOrEmpty(isValid)) {
        // set your error message
        outputMessages.AppendLine(isValid);
        return;
    } 
    // ... perform your other operations.
}

我会尝试将每个验证定义为谓词,就像这样…

delegate bool Validator(object sender, out string message);

然后你可以根据需要把它们串在一起

有很多方法可以解决这个问题。您确实希望限制重复代码的数量,例如添加输出消息的代码,它在四个或更多地方几乎相同。

如果您将这些嵌套的if…else块视为一个序列,一旦其中一个失败,您就采取行动并停止进一步处理,您可以创建一个列表并利用LINQ的FirstOrDefault功能依次处理条件列表,直到一个失败,或者如果它们都通过,您将获得null

创建一个对象来封装条件将有助于合并和减少重复。

下面是一个例子:

public class Validator
{
    public Validator(string code, bool settingsCheck, Button button, object sender)
    {
        Code = code;
        IsValid = sender != null && button != null && sender.Equals(button);
    }
    public bool IsValid { get; private set; }
    public string Code { get; private set; }
}
现在,你的方法看起来更像这样:
var validationChecks = new List<Validator>
{
    new Validator("A", InitialUsageSettings.zeroed, btnZero, sender),
    new Validator("Z", InitialUsageSettings.StandardFilterRun, btnStandard, sender),
    new Validator("Y", InitialUsageSettings.ReferenceFilterRun, btnReference, sender),
    new Validator("X", InitialUsageSettings.PrecisionTestRun, btnPrecision, sender)
}
var failure = validationChecks.FirstOrDefault(check => !check.IsValid);
if (failure != null)
{
    outputMessages.AppendLineFormat(
        "Please correct the following issues before taking a reading: {0}", failure.Code);
    return;
}
else
{
    // further checks; I'm not sure what you're doing there with A-E
}