Beiträge

Gute und schlechte Software-Praktiken gestern und heute

Wodurch sich robuste Software auszeichnet und warum Entwickler (und Unternehmen) sie brauchen.

Fachbeitrag von Dr. Bernhard Scheffold, OXID eSales AG

Einen wesentlichen Teil bei der Entwicklung einer OXID eShop Lösung für ein mittelständisches Unternehmen nimmt die Integration des Shops mit seinen Umsystemen ein. Hierzu zählen Warenwirtschaftssysteme, Produktdatenmanagementsysteme, CRM-Systeme und externe Suchserver. Letztere sind häufig eine Notwendigkeit, um eine leistungsfähige Suche zu verwirklichen.

Wenn die Suche eine „On-Premise-Lösung“ ist, kann sie zwar als Teil des Shops aufgefasst werden, stellt aber dennoch eine Integrationsaufgabe da, die korrekt, robust und performant realisiert werden muss. Anhand eines kleinen Stück Codes aus einer solchen Integrationaufgabe möchte ich diskutieren, wodurch sich gute und schlechte Praktiken unterscheiden.

Guter Code existiert jenseits vom Buzz

Aus dem Blickwinkel von nahezu 40 Jahren Praxis in der Software-Entwicklung ziehen sich immer wiederkehrende gute und schlechte Praktiken wie ein roter Faden durch das Tätigkeitsfeld des Software-Entwicklers. Statt die schlechten Praktiken zu beseitigen, werden gerne neue Hypes und Buzzwords in den Ring geworfen. Von ihnen wird immer wieder die quasi ‚automatische Lösung‘ von Softwareproblemen erwartet. Dabei ist Software oftmals Auftragsarbeit, die die sogenannte „Bottom Line“ eines Wirtschaftsunternehmen verbessern und den Kunden robuste und betreibbare Lösungen liefern soll. Ich möchte daher dafür plädieren, nicht zu viel Zeit mit der Suche nach dem Heiligen Gral in Form des aktuellen Hypes zu verbringen, sondern die eigenen Fähigkeiten zu entwickeln, wie man robuste Software schreibt.

Ein Typisches Anti-Pattern…

Illustrieren möchte ich das an einem hübschen kurzen Beispiel aus der Praxis, das ein hartnäckig verfolgtes Anti-Pattern zeigt:
Eine Software zur Erstellung eines Suchindexes zeigt merkwürdiges Verhalten in Bezug auf die Handhabung mehrerer Sprachen. So landet im englischen Sprachindex deutscher Text, obwohl die Indizierungslogik die Sprache doch augenscheinlich korrekt setzt. Zentral ist hier eine Klasse Indexer, die in reduzierter Form in Pseudocode wie folgt aussieht:


class Indexer 
{
  private int langId = 0;
  private String langIsoCode = null;


  public Indexer()
  {
  }

  public setLanguageId(int id)
  {
    this.langId = id;
  }

  public int getLanguageId()
  {
    return this.langId;
  }

  public setLanguageIsoCode(String isoCode)
  {
    this.langIsoCode = isoCode;
  }

  public String getLanguageIsoCode()
  {
    retrurn this.langIsoCode;
  }

  public index(String key)
  {
    String view = Database.getLanguageViewFor(this.langId);
    String value = Database.readFromView(view, key);
    this.sendToIndex(key, value);
  }

  private sendToIndex(key, value)
  {
    // not included here
  }
}

Diese hübsche Klasse macht noch ein paar weitere Dinge (vielleicht keine klare „Separation of Concerns“?), aber sie wird wie folgt verwendet:


String keyToIndex = 'myKey';
Indexer indexer = new Indexer();

foreach (Languages: String langIsoCode) {
  indexer.setLanguageIsoCode(langIsoCode);
  indexer.index(keyToIndex);
}

Das sieht beim flüchtigen Lesen, vor allem, wenn es in eine grössere Code-Base eingebettet ist, vernünftig aus.
Das Problem ist jedoch, dass Indexer zwei Member-Variablen langId und langIsoCode hat, die eigentlich einem Constraint unterworfen sein sollten (die ID muss immer zum ISO-Code passen). Dieser Constraint existiert jedoch nicht im Code, sondern bestenfalls im Kopf des Entwicklers und er wird durch die Konstruktion dieser Klasse nicht erzwungen.

… und die Probleme, die daraus erwachsen

Die Probleme beginnen bereits in der Konstruktionsphase, die das Anlegen eines nicht-funktionsfähigen Objekts erlaubt. Die public Setter und Getter erlauben eine beliebige Änderung von außen, die den Zusammenhang von ID und ISO-Code verletzen können. Diese Probleme bleiben über den gesamten Lebenszyklus des Objektes bestehen: Der Verwender muss peinlich genau aufpassen, immer beide Argumente vorher übereinstimmend zu setzen. Somit hat diese Klasse auch ein großes Usability-Problem.

Am besten wäre es, Indexer als eine nicht-mutierbare Klasse anzulegen, die zudem nur entweder die ID oder den ISO-Code der Sprache als Konstruktor-Parameter nimmt und damit nur korrekt an gelegt werden kann und auch nicht mehr durch den Aufruf einer mutierenden Methode in einen inkorrekten Zustand versetzt werden kann.

Wenn die Klasse schon mutierbar sein soll (was sehr oft vermieden werden kann), dann sollte es keine Mutation geben, die einen ungültigen Zustand erzeugt. Vorstellbar wäre ein Setter für entweder ID und ISO-Code oder ein Setter, der beide als Parameter nimmt und eine fehlerhafte Kombination der beiden Parameter erkennen kann.

Gegenprobe: Punktet das Pattern mit Flexibilität?

Wie oft wurde aber ein Design wie das obige von einem Programmierer als erstrebenswert, weil “flexibel” verteidigt? Schliesslich wird doch in vielen Büchern (die vielleicht nie hätten gedruckt werden sollen) genau solcher Stil vorgemacht. Da ich die Zahl der Bugs, die auf dieses Anti-Pattern zurückzuführen sind aber schon nicht mehr zählen kann, nenne ich es nur noch „Das Schicksal herausfordern“.

Die geliebte “Flexibilität” führt nämlich allzu oft dazu, dass eine Instanz einer solchen Klasse in einen ungültigen Zustand versetzt wird und der Zusammenhang zwischen Ursache und Wirkung oft nicht leicht zu erkennen ist, da beide in der Code-Base weit voneinander entfernt sind. Bei genauer Betrachtung handelt es sich bei dem geschilderten Anti-Pattern nämlich nicht um eine Flexibilisierung des Codes – z.B. durch Interfaces und Polymorphie – sondern um das Einbringen von Non-Determinismen.

Sehr viel mehr zur Robustheit von Software trägt hingegen das Inversion-Of-Control- oder Hollywood-Prinzip („don’t call us, we call you“) bei. In der OO-Welt findet sich das in Frameworks bzw. in Form des „Template Method“-Patterns wieder, in der funktionalen Welt findet es sich in Erlang/OTP als sogenannte Behaviours. Der Grund dafür, dass diese Entwurfstechnik zu robustem Code führt, ist einfach: Hier wird Wiederverwendung in bester DRY-Manier (Don’t repeat yourself) betrieben und durch jede Ausprägung eines Frameworks (oder Behaviours) werden die Fehler in diesem reduziert.

Angenehmer Nebeneffekt ist, dass das Verhalten einer Software, die auf solchen Prinzipien fußt, vorhersagbarer ist und kein unerwartetes Verhalten zeigt. Auf diese Weise fordert der Entwickler das Schicksal nicht mehr heraus, sondern begibt sich selber in dessen Kontrolle.

Flexibilität vs. Robustheit

Das Interessante ist nun, dass es robuste Programmiertechniken schon lange gibt und dass sie auch in modernen Software-Ökosystemen immer noch mit Erfolg angewendet werden. Trotzdem sind schlechte Praktiken immer noch verbreitet und eben deswegen auch so schwer auszurotten. Man muss wohl erst selber die Erfahrung gemacht haben, dass man sein Leben als Software-Entwickler nicht durch „flexible“ Entwürfe erleichtert, sondern eher durch robuste. Der Grund dafür liegt darin, dass der Hauptaufwand nicht im schnellen Erstellen eines „Proof of Concepts“ liegt, sondern in der sogenannten Wartungsphase, in der Fehler behoben und neue Anforderungen so realisiert werden müssen, dass sie möglichst keine neuen Fehler einführen.

Autor:

Dr. B. Scheffold, OXID eSales AG

Dr. Bernhard Scheffold ist seit 2012 Softwarearchitekt bei der OXID eSales AG. Zuvor entwickelte der promovierte Physiker Branchen-Software für Banken, Versicherungen, im Gesundheitswesen sowie für die Industrie und Genforschung.
Für OXID Professional Services betreut Dr. Scheffold Projekte mittelständischer Unternehmen und Konzerne mit dem Schwerpunkt Systemanalyse und -integration sowie Performancetuning.
Die Prinzipien zur Erstellung robuster und evolvierbarer Software und Architektur vermittelt er auch als Kursleiter „Product Line Engineering mit Frameworks“ bei der Digicomp AG, Schweiz.

OXID Contributor Agreement

A few days ago, we published a new document on OXIDforge: the OXID Contributor Agreement or OXID-CA. In this blog post, I’d like to discuss the purpose of this document and our reasons for publishing it.

What is this document for?

This document governs your contributions to the OXID eShop codebase. It is a document that outlines the principles that cover your contributions and how OXID eSales will handle these contributions. These contributions might include new functionality that you contribute and that makes sense to integrate into the default installation of OXID eShop. It might also include bugs that you fix as a developer, or enhancements that you make to the code.

Why does OXID want me to sign a cumbersome form?

The business concept of OXID eSales is the so-called „dual licensing model“. Under this model, we offer different editions of OXID eShop under different licenses. However, all editions of OXID eShop are developed in one branch. This means that if you contribute your code for OXID eShop Community Edition, your code will also be used in OXID eShop Professional Edition and OXID eShop Enterprise Edition. By signing the Contributor Agreement, you simply declare that you accept this modus operandi.

By signing this agreement, what rights am I giving up?

When you agree to the OXID-CA, you grant OXID eSales AG joint ownership to your contributions. You retain all rights, title, and interest in your contributions and may use them for any purpose you wish. However, because the ownership is now shared, you lose the ability to *exclusively* license your copyright on your contributions to others.

Does the OXID-CA conform to generally accepted practices for community
contributions?

Yes, it does. It is based on the Oracle Contributor Agreement. Many other open-source communities and projects have contributor agreements, including the Free Software Foundation, the Apache Software Foundation, and the Eclipse Foundation.

What benefit do I gain?

On the community front, the OXID-CA allows OXID eSales AG to act as stewards of the OXID codebase and supporting materials, holding copyright on these resources on behalf of the OXID eShop community. In other words, once your contribution is taken over into the default installation, OXID’s core developers will be responsible for future maintenance.

However, this doesn’t mean that your contribution will not be recognized. All OXID project participants receive credit for their contributions, even if their contributions are rewritten or removed, on a special contributor’s list.

How do I get started?

There’s a wiki page on OXIDforge that leads you to the OXID Contributor Agreement and to the accompanying FAQ pages. Please read the FAQ carefully and make sure you understand every single point. If you’re happy with the agreement, fill up the form and send it to [email protected]

We’d be glad if you contribute PHP unit tests along with your code.