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);
}

SSH Verbindung ohne ein Password eingeben zu müssen? (MAC 2 MAC)

Ich muss relative häufig auf meinem Media-Server aufräumen, dazu benutze Midnight Commander und verbinde mich über SSH. Dabei muss man in der Regel immer ein Password eingeben. Es handelt sich aber um einen Rechner der sich in meinem Netzwerk befindet und es ist nicht nötig das Password jedesmal einzugeben. Daher möchte ich gerne darauf verzichten, ohne aber auf Sicherheit zu verzichten.

Dazu habe ich ein einfaches Skript erstellt welche im Terminal ausgeführt werden kann. Einfach „michaelnikolaus@mediapc“ mit euren Werten ersetzen.

ssh-keygen -t rsa && cd ~/.ssh && ssh michaelnikoaus@mediapc mkdir -p .ssh && cat .ssh/id_rsa.pub | ssh michaelnikoaus@mediapc 'cat >> .ssh/authorized_keys' 

Ab sofort muss ich kein Password mehr eingeben. Tip Top!

Indikator #2 – Variablen

So grundlegend Variablen in der Programmierung sind, so falsch kann man sie benutzen.

Es ist nicht überraschend, dass diese oft falsch in den meisten Fällen oder unnötigerweise benutzt werden. Meistens werden sie ähnlich wie Kommentare verwendet. Aber das ist grundlegend falsch.

Eine Variable dient dazu Inhalte für die mehrfache Verwendung einfach zugänglich zu machen. Dabei sollte der Name den nur den Inhalt wieder spiegeln. Fängt man an mehr Variablen zu verwenden, ist dies ein Indikator dafür, das zu viele Dinge gemacht werden die hier gar nicht gemacht werden sollten.

Dinge die man sich merken muss, kosten uns Ressourcen und machen es schwieriger den code zu verstehen.

Aus meiner Sicht können in den seltesten Fällen Variablen dazu verwendet werden den Code lesbarer zu gestalten, das scheint nur auf den ersten Blick so. In der Regel kann der Code so angepasst werden das man keine Variablen benötigt. Lesbarkeit kommt in erster Linie von guten Design.

Beispiele

Wichtig ist zu erkennen was nicht stimmt und das korrigieren zu können. Doch wie erkennt man diese?

Wenn eine Variable verwendet wird um Dinge zu Kürzen

Wenn der benötigte Inhalt innerhalb eines Objektes selektiert wird, dann fängt man oft an Variablen zu verwenden. Beispiel:

var author = Books.Where(e => e.Published()).OrderBy(d => d.PublishingDate()).Author().Name;

Console.WriteLine($"The author of the latest book is: {Author}");

Hier sind gleich mehrere Probleme: Erstens, der Name „author“, ich muss später wissen welche Art von Variable es ist, von Namen her könnte auch eine Author-Klasse sein.

Zweitens, ich benutze die Variable nur einmal. Hmm, aber jetzt das ganze Konstrukt in den ein den WriteLine einzutragen, macht es auch nicht wirklich lesbarer.

Könnte also so aussehen: (Teil I)

Console.WriteLine($"The author of the latest book is: {Books.Where(e => e.Published()).OrderBy(d => d.PublishingDate())
First().Author().Name}");

Okay, aber nein. Das reicht nicht, also warum nicht eine Methode, welche die Selektierung extrahiert?

Also so: (Teil II)

private string LatestPublishedAuthor(List<Book> books)
=> Books
.Where(e => e.Published())
.OrderBy(d => d.PublishingDate())
First()
.Author()
.Name;

Console.WriteLine($"The author of the latest book is: {LatestPublishedAuthor(Books))}");

Es ist wahrscheinlich das diese Logik wiederverwendet wird, aktuell könnte man diese nur in der Klasse benutzen, ggf. will man das aber auch woanders wiederverwenden. Daher, warum nicht, eine eigene Klasse: (Teil III)

public class LatestPublishedBook: IBook
{
public LatestPublishedBook(IEnumerable<IBook> books) {
_origin = book;
}
private IBook Value()
=> _origin
.Where(e => e.Published())
.OrderBy(d => d.PublishingDate())
.First();

public Author Author => Value().Author;
}

Console.WriteLine($"The author of the latest book is: {new LatestPublishedBook(Books).Author())}");

Ja, es ist mehr Code, da stimmt ich zu. Aber es:

  1. Deklarative, sie sagt aus was es ist.
  2. Sauberer, keine Variablen.
  3. Wiederverwendar,die Klasse kann einfach wiederverwendet werden.
  4. Testbarkeit,als Klasse kann dies nun einfach getestet und ausgetauscht werden.

Indikator #1 Zu lange Methodennamen

Zum einen macht es Sinn, Methoden einen sprechenden Namen zu geben. Der Methodenname muss dabei beschreiben was die Methode macht, und es tunlichst vermeiden funktionalitäten zu verstecken. Beispiel:
class Items : IItems<IItem> {
   /* Manipulator -> Verb */
   void Remove(IItem item) {...
   
   /* Builder -> Noun */
   Item Item(ISelector selector) {...
   
   /* A code smell -> does too many things */
   Item ChangeDeliveryDateAndSaveItem(IItem source) {... 
}
Den Gedanken im Kopf, passiert es, das Methoden sehr lange Namen bekommen können. Wenn man nun als Entwickler in der Situation ist und das bemerkt, fängt man an zu überlegen, wie man ihn kürzen kann. Manchmal löst man das Problem durch bessere Namensgebung, aber oftmals liegt das Problem nicht im Methodennamen, sondern in der Klasse selbst. Muss man mehrere funktionalitäten Beschreiben, ist es vermutlich die Klasse die zu viele Dinge tut. Im Beispiel oben, führt das zu mehreren Problemen: 1. IItems wird um methoden immer weiter erweitert und damit erschwert sich deutlich der Zukünftige testaufwand der Klasse. 2. Die Anpassungen an IItems sind spezifisch für den Fall, und eine Wiederverwendung ist immer unwahscheinlicher um so mehr Methoden enthalten sind. Im schlimmsten Fall führt es dazu, dass das Interface mit vielen NotImplementedExceptions genutzt wird. 3. Wird die Klasse bzw. das Interface um weitere Methoden erweitert, so leidet auch die Wartbarkeit. Oftmals sind es Methoden mit Rückgaben, die erklären was verändert wird und was zurückgegeben wird. Hier auch mit alternativen:
String StringWithRemovedCarriageReturnAndLineFeeds() {...

/* Alternative I */
new ModifiableString().RemoveAll(„\r\n”)

/* Alternative II */
new ClearedString {
 public ClearedString(
   string source,
   string valueToRemove
) { ...

Sisyphos, DRY oder auch „nicht nochmal“!

Moment, das habe ich doch schon mal erklärt, Variablen sind so zu benennen, dass man erkennt worum es geht. Ableitungen von Klassen sind zu vermeiden und überhaupt, warum wird die Customer-Klasse von außen verändert?

In Code Reviews sind es oft die gleichen Dinge die besprochen werden. Die gleichen Designprobleme werden gefunden, und ebenso auch die gleichen Bugs. Das macht mich Müde, das laugt mich aus; wohingegen mir das am Anfang noch Spaß gemacht, einem „Junior Software Developer“ Dinge zu erklären. Ich  lernte vieles aus den Gesprächen, fands cool und das fühlte sich gut an. Aber das ist lange her, heute nervt es mich nur noch das zum 100ten mal zu erklären.

„Sisyphos, DRY oder auch „nicht nochmal“!“ weiterlesen