C#Clean Code

Informationssysteme nach dem EVA-Prinzip

Das EVA-Prinzip, bestehend aus Eingabe, Verarbeitung und Ausgabe, beschreibt ein grundlegendes Prinzip für Informationssysteme. Dies kommt selbst bei komplexen Suchen wie der 42 zum Einsatz.

Informationssysteme lösen auch komplexe Fragen wie die Suche nach 42
Cerqueira

Die letzten Posts zu den beiden Prinzipien PoMO und IOSP waren eher theoretischer Natur. Deshalb möchte mich in diesem Post an einer weiteren einfachen Aufgabe probieren und ein einfaches Informationssystem bauen. Hierzu habe ich die beliebteste Aufgabe auf SPOJ (SPhere Online Judge) herausgepickt. Bei der Aufgabe sollen Zahlen von der Konsole eingelesen und so lange auf dem Bildschirm ausgegeben werden, bis die Zahl 42 eingegeben wird. Die Zahl 42 ist deshalb so besonders, weil ein gewisser Anhalter, diese als Antwort auf die unklar gestellte Frage „nach dem Leben, dem Universum und dem ganzen Rest“ erhalten hat.

Die Aufgabe lässt sich leicht in folgende Teilschritte zerlegen:

  • Zahlen von der Konsole einlesen
  • Zahlen vor Eingabe der 42 ermitteln
  • Zahlen auf der Konsole Ausgeben

Die Aufgabe erinnert dabei an das klassische EVA-Prinzip. Also Eingabe, Verarbeitung und Ausgabe. Dies trifft quasi auf so gut wie jedes Software-Problem zu. Weshalb sich EVA auch in meinem Flow Design-Entwurf widerspiegelt. Eine mögliche Lösung für die hier gestellte Aufgabe nach Flow Design sieht dann wie folgt aus:

Flow Design für 42

In diesem Flow Design sind schön die einzelnen Teilschritte in Form von Funktionseinheiten zu sehen. Jede der Funktionseinheiten ist in diesem Falle so einfach, dass diese direkt als Operation implementiert werden kann. Eine weitere Zerlegung ist somit nicht notwendig. Zwischen den Funktionseinheiten fließen jeweils beliebig viele Zahlen (0-n). Dies wird durch das Sternchen (*) gekennzeichnet. Eine Umsetzung eines solchen Datenflusses kann z. B. in C# mit dem Interface IEnumerable<int> erfolgen.

Die Integration übernimmt in diesem Falle die Main-Methode. Bei der Konsole handelt es sich genau genommen um eine Ressource. Der Zugriff darauf, und damit die Abhängigkeit, kann in Flow Design modelliert werden. Aus diesem Grund sieht das vollständige Diagramm dann so aus:

Vollständiges Flow Design für 42

Die Umsetzung dieses Flow-Designs kann dann so aussehen (https://github.com/KevinErath/FindTheAnswer):

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
  static void Main()
  {
    var numbers = ReadNumbersFromCmd();
    var answer = FindTheAnswer(numbers);
    PrintNumbers(answer);
  }

  public static IEnumerable<int> ReadNumbersFromCmd()
  {
    while (true)
    {
      var line = Console.ReadLine();
      yield return int.Parse(line);
    }
  }

  private static IEnumerable<int> FindTheAnswer(IEnumerable<int> numbers)
  {
    return numbers.TakeWhile(x => x != 42);
  }

  private static void PrintNumbers(IEnumerable<int> numbers)
  {
    foreach (var number in numbers)
    {
      Console.WriteLine(number);
    }
  }
}

Auf Tests habe ich, aufgrund der Einfachheit des Codes, an dieser Stelle verzichtet. Außerdem wird die Lösung bei Einreichung durch SPOJ selbst getestet. Dort wird sie nach erfolgreichem Test in einem Ranking veröffentlicht. Unter allen damals eingereichten Lösungen (Sprachen übergreifend) ist die Variante doch weit abgeschlagen. Vermutlich, da bei einer .NET Anwendung erst die Laufzeitumgebung initialisiert werden muss und ein JIT-Compiling durchgeführt wird. Gefiltert auf C# Lösungen befand sich die Lösung in 2015 hingegen im Mittelfeld.

Ranking.png Screenshot des Rankings auf der Webseite SPOJ

Für die Bewertung scheint auf SPOJ hauptsächlich die Performance zu zählen. Das Ergebnis verwundert deshalb auch nicht ganz. Habe ich die Lösung doch auf mehrere Methoden verteilt, LINQ verwendet und ein nicht zwingend notwendiges int-Parsing durchgeführt. Vermutlich letzteres ist hier der größte Bremser. Was letztendlich aber wirklich der Zeitfresser ist, müsste jetzt mit einem Profiler ermittelt werden. Einen guten Blog-Artikel zum Thema Performance-Messung habe ich damals hier gelesen.

Trotz der durchwachsenen (Performance-)Bewertung favorisiere ich am Ende des Tages trotzdem die hier vorgestellte Lösung, da sie für mich eine saubere Struktur aufweist.

Anmerkung: Bei diesem Text handelt es sich um einen überarbeiteten Repost eines alten Blog-Artikels aus 2015 von mir.