Defensive Programmierung

Vertraue keinen, validiere alle Eingaben. In C# 7 sind null Prüfungen leider immer noch nötig.

Ich gruppiere Inhalte semantisch, modular. Daher werde ich auch variablen Initialisierung und die dazugehörige Typenprüfung immer zusammen lassen.

Hier ein Beispiel an einem Konstruktor, der einen. Nullceck und eine Validierung beinhaltet, aber wie es aus meiner Sicht nicht sein sollte:

public MyConstructor(string inputString, string validationPattern){
    if (inputString == null){
        throw new ArgumentNullException(name(inputString));
    } 
    if (validationPattern == null){
        throw new ArgumentNullException(name(validationPattern));
    } 
    if (validatinPattern.length == 0){
        throw new FormatException(nameof(validationPattern));
    }
    
    _pattern = validationPattern;
    _input = inputString;
}

Pro

  • Prüfungen / Validierungen sind an einer Stelle und dadurch wirkt der Code auf den ersten Blick simpler / aufgeräumter.
  • Der obere Teil der Methode wird dann immer nur aus den Validierungen bestehen unten sind dann immer nur die Zuweisungen.

Contra

  • Umso mehr Variablen es gibt (grundsätzlich sollten es so wenig möglich sein) desto weiter wird der Abstand zwischen Prüfung und Zuweisung sein.
  • Zuweisung und (fachliche-)Prüfung innerhalb des Beispiel Konstruktors. Sind, ob sie nun direkt zusammen stehen oder nicht, miteinander Verbunden.

Alternative I

Man könnte einfach die Zuweisung und Null-Check in einen Block zusammenziehen und die fachliche Prüfung auslagern:

public MyConstructor(string inputString, string validationPattern){
    if (inputString == null){
        throw new ArgumentNullException(name(inputString));
    } 
    _input = inputString;

    if (validationPattern == null){
        throw new ArgumentNullException(name(validationPattern));
    } 
    _pattern = validationPattern;
}

....

public string Pattern() {
   if(_value.Length == 0){
       throw new FormatException($"{_name} should not be empty");
   }
   return _pattern;
}

Pro

  • Zuweisung und Null-Check sind direkt zusammen, modular.
  • Anzahl der Zeilen Code die voneinander Abhängig sind, sind zusammen.

Contra

  • Der code sieht beim ersten Blick etwas unaufgeräumter aus.
  • Die Überprüfung von _pattern wird nun in der Methode Pattern durchgeführt. Theoretisch muss nun immer bei der Verwendung die Prüfung gemacht werden. Hier besteht nun aber Gefahr von Code Duplikation.

Alternative II

Um code duplication zu vermeiden kann man die Logik in eine Methode auslagern.

private T NullCheckedValue<T>(T value, string name)
{
    if (value == null){
        throw new ArgumentNullException(name);
    }
    return value;
}

private ValidatedPattern(string pattern, string name)
{
   if(_value.Length == 0){
       throw new FormatException($"{name} should not be empty");
   }
   return pattern
}

....

public MyConstructor(string inputString, string validationPattern){
    _input = NullCheckedValue(inputString);
    _pattern = NullCheckedValue(validationPattern);
}

public Pattern() => ValidatedPattern(_pattern);

Pro

  • Bessere Lesbarkeit, Zuweisung und Null-Check an einer Stelle
  • Fachliche Prüfung von Pattern in einer Methode ausgelagert und wieder verwendbar.

Contra

  • Methode ist innerhalb der Klasse wiederverwendbar, jedoch ist grundsätzliche Wiederverwendung nur durch Kopieren der Methode möglich.
  • Eine extra Methode innerhalb der Klasse, die nicht direkt mit dem Ziel der Klasse übereinstimmt.
  • Die Fachliche Prüfung kann nicht unabhängig geprüft werden.

Alternative III

Um die Wiederverwenbarkeit und Testbarkeit zu erhöhen, können hier eigene Klassen erstellt werden.

public class StringParameter
{
    public StringParameter(string value, string name)
    {
        if (value == null){
            throw new ArgumentNullException(name);
        }
        _value = value;
    }
    public string Value() => _value;
}

public class NonEmptyStringParameter
{
    public StringParameter(string value, string name)
    {
        _value = new StringParameter(value, name).Value();
        _name = new StringParameter(
            name,
            name(nameof(name))
        ).Value();
    }

    public string Value() {
        if(_value.Length == 0){
            throw new FormatException($"{name} should not be empty");
        }
    };
}

....

public MyConstructor(string inputString, string validationPattern){
    _input = new StringParameter(inputString, name(inputString)).Value();
    _pattern = new NonEmptyStringParameter(validationPattern).Value();
}

Pro

  • Die Klassen können wiederverwendet werden.
  • Der code ist lesbar und deklarativ.
  • Testbarkeit, Klassen können unabhängig voneinander getestet werden

Contra

  • Mehr Code
  • Im Konstruktur wird ausgeführt, ggf. werden komplexere nicht Prüfungen durchgeführt. Das instanziieren von Klassen sollte so schnell wir nur möglich passieren. Nur die NULL-Prüfung, und nicht mehr, sollte im Konstruktor durchgeführt werden.

Alternative IV

public class StringParameter: IValidatedParameter
{
    private readonly string _value;

    public StringParameter(string value, string name)
    {
        if (value == null){
            throw new ArgumentNullException(name);
        }
        _value = value;
    }
    public string Value() => _value;
}

public class NonEmptyStringParameter: IValidatedParameter<string>
{
    private readonly IValidatedPrameter<string> _value;
    private readonly IValidatedPrameter<string> _name;

    public StringParameter(string value, string name)
    {
        _value = new StringParameter(value, name);
        _name = new StringParameter(
            name,
            name(nameof(name))
        );
    }

    public string Value() {
        if(_value.Length == 0){
            throw new FormatException($"{_name} should not be empty");
        }
        return value;
    };
}

....

public MyConstructor(string inputString, string validationPattern){
    _input = new StringParameter(inputString, name(inputString));
    _pattern = new NonEmptyStringParameter(validationPattern);
}

Print Friendly, PDF & Email