Select Page

NETWAYS Blog

Objektorientierten Code verbessern in drei Schritten

Objektorientierung ist oft eine überraschend komplexe Sache. Damit man sich nicht in dem Knäuel aus Design Patterns, Best Practices und Vererbung verheddert, sollte man sich am besten an ein paar elementare Regeln halten, die man vor allem Anderen einhält. In diesem Blogpost will ich mit euch drei Regeln teilen, die für mich bisher immer gut funktioniert haben.

Wollknäuel

1. Erfüllt jede Klasse genau eine Aufgabe?

Wenn eine Aufgabe über mehrere Klassen aufgeteilt wird, erkennt man das häufig daran dass Klassen häufig an die Daten anderer Klassen heran wollen. Da das bei privaten Membern natürlich nicht funktioniert, tendiert man dann dazu, diese Klasse weiter zu öffnen als eigentlich gut wäre, oder die man fängt an die ganze Architektur der Klasse anzupassen. Wenn hingegen mehrere Aufgaben in eine einzelne Klasse geschrieben werden, tendiert man dazu den Code in dieser in mehrere Kategorien zu gruppieren. Hier sollte man dann mehrere Unterklassen erstellen die die einzelnen Teilaufgaben des Problems erledigen.

2. Sind die Aufgaben Teil des Problems?

Diese Regel lässt sich am besten Anhand eines Beispiels erklären.

Nehmen wir an, wir haben in einer Anwendung Personen die verschiedene Rollen wie Mitarbeiter, Kunde, Administrator oder Freelancer einnehmen können. Man erstellt jetzt die Klassen Mitarbeiter und Kunde, die von Person erben. Administrator kann dann noch von Mitarbeiter erben und die Architektur ist fertig, richtig? In der Praxis erzeugt so eine Architektur leider sehr viele Probleme. Es ist nicht möglich neue Rollen anzulegen ohne den eigentlichen Code zu verändern, einer Person kann nur eine einzige Rolle zugewiesen werden und Rollen können sich nicht verändern, unser Klassenmodell wird uns im Weg stehen. OOP verleitet einen häufig dazu aus jedem “Ding” das existiert eine Klasse zu machen, jede Klasse sollte aber eigentlich ein Teilproblem aus der Lösung abbilden. Für unser Problem “Personen Rollen zuweisen”, wäre zum Beispiel folgende Struktur besser geeignet:

class Person {
   Role[] role;
   // ...
}
class Role {
   String name;
   // ...
}

Rollen können von Role erben und so eigene Methoden implementieren.

3. Niemals Übertreiben

Benutzer objektorientierter Programmiersprachen machen sich häufig dem Over-Engineering schuldig. Wenn man eine Funktionalität oder Eigenschaft nicht sofort braucht, sollte man sie am besten ganz weg lassen. Auch wenn es trivial klingt, aber gar keine Objektorientierung besser ist als schlechte Objektorientierung. Wenn sich ein Problem nur sehr umständlich in ein Objektorientiertes Schema pressen lässt, dann sollte man es einfach Prozedural lösen.

PHP Remote Debugging mit Xdebug

Die Möglichkeit einfach mit einem grafischen Debugger durch den Code wandern zu können, erleichtert das Finden von Bugs oft extrem. Leider unterstützt PHP von sich aus kein Debugging und man muss deswegen auf Erweiterungen wie zum Beispiel Xdebug zurückgreifen. Wenn man es richtig konfiguriert, ist damit allerdings sogar das Debuggen von entfernten Servern oder VMs möglich, was es zu einem sehr nützlichen Tool bei der Fehlersuche macht. In diesem Blogpost will ich euch deswegen kurz erklären wie man Xdebug einrichtet:

Einrichten des Webservers

Zuerst muss das Modul Xdebug installiert und das Debugging in der php.ini aktiviert sein. Der Eintrag zend_extension sollte immer auf die xdebug.so zeigen und aktiviert die Erweiterung. Die restlichen Einstellungen erlauben dann das eigentliche Debugging von allen verfügbaren Hosts aus. Wer in einer unsicheren Umgebung arbeitet, sollte sich allerdings die Mühe machen und das Debugging nur für bestimmte Hosts aktivieren. Welche Einstellungen dafür nötig sind wird genauer in der Dokumentation beschrieben.

zend_extension = /usr/lib/php/modules/xdebug.so
xdebug.remote_enable = 1
xdebug.remote_connect_back = 1
xdebug.remote_autostart = 1
Remote Debugging in der IDE aktivieren

Das Debugging muss auch in der IDE erst aktiviert werden, da die Debug-Sessions vom Server initiiert werden und die IDE deswegen immer auf eingehende Verbindungen warten muss. In PhpStorm muss man dazu zum Beispiel die Adresse des Webservers in Settings->Server anlegen und dann das Remote Debugging mit Run->Start Listen PHP Debug Connections aktivieren.

Breakpoints setzen

Jetzt ist auch schon alles fertig eingerichtet und es müssen nur noch Breakpoints gesetzt werden. Wenn auf dem Webserver eine Seite geöffnet wird die einen Breakpoint auslöst, wird die IDE an diese Stelle im Code springen und man kann die Anwendung in einer vertrauten Umgebung debuggen.

Fehlersuche

Falls es nicht gleich funktionieren sollte, kann man mit dem Befehl php -i alle momentan aktiven Einstellungen überprüfen. Wenn die Optionen für Xdebug überhaupt nicht auftauchen, ist das ein Zeichen dafür, dass das Modul nicht geladen wurde. Wenn die Optionen sich von den eben gesetzten unterscheiden, kann das daran liegen dass noch eine andere Konfiguration die Einstellungen wieder überschreibt. In diesem Fall sollte man überprüfen ob nicht noch eine anderen Konfiguration neben der php.ini existiert, die die Einstellungen überschreibt.

Einfache asynchrone Aufrufe in JavaScript mit Async.js

JavaScript ist eine Programmiersprache die als Non-Blocking bezeichnet wird: Wenn ein Funktionsaufruf längere Zeit in Anspruch nimmt, wie das z.B. bei AJAX oder Timeouts der Fall ist, wird der Thread des Codes nicht schlafen gelegt bis der Funktionsaufruf beendet ist, sondern läuft sofort weiter. Es ist nicht möglich auf die Auswirkungen des Aufrufs zu warten, außer man benutzt eine weitere Funktion die dann als Callback aufgerufen wird.
In den Anfangstagen von JavaScript hatte dieses Vorgehen durchaus seinen Sinn: Da die Sprache nur für einfache Animationen gedacht war und kein Multithreading unterstützt hat (was sie übrigens auch heute nur mithilfe von WebWorkern beherrscht), wären sonst bei jedem Timeout die Animationen der Website stehen geblieben. Heute wird JavaScript sehr oft verwendet um per AJAX Inhalte nachzuladen. Komplexere Funktionalität erfordern dann aber immer öfter mehrfach ineinander verschachtelte Callbacks, was den Code schnell schwer durschaubar werden lässt.
Als Beispiel verwende ich nur setTimeout(), man kann sich an dieser Stelle aber auch beispielsweise einen HTTP-GET per AJAX oder eine beliebige andere asynchrone Funktion vorstellen. Je mehr Funktionen dazu kommen, desto verwirrender wird der Code:

setTimeout(function(){
   alert("Du bist jetzt seit einer Sekunde auf dieser Seite");
   setTimeout(function(){
       alert("Seit der letzten Meldung sind 10 Sekunden vergangen");
       setTimeout(function(){
           alert("Eingeschlafen?");
       }, 3600*1000);
   }, 10000);
}, 1000)

Natürlich könnte man stattdessen auch einfach eine Funktion schreiben, die eine Liste an Funktionen als Argumente annimmt, und diese dann alle in Serie aufruft. Einfacher geht es allerdings mit Async.js:

async.series([
    function(callback) {
        setTimeout(function() {
           alert("Du bist seit einer Sekunde auf dieser Seite");
           callback();
        }, 1000);
    },
    function(callback) {
        setTimeout(function() {
            alert("Seit der letzten Meldung sind 10 Sekunden vergangen.");
            callback();
        }, 10000);
    },
    function(callback) {
        setTimeout(function(callback) {
            alert("Eingeschlafen?");
            callback();
        }, 1000*3600)
    }
]);

Die Funktion series führt alle Funktionen der Reihe nach aus. Mithilfe des Callbacks wird Async.js mitgeteilt wenn die Funktion beendet ist. Dieser Code ist zwar länger, aber dafür wesentlich lesbarer: Es ist auf einen Blick zu erkennen, wie lange die einzelnen Schritte dauern und welcher Code dann ausgeführt wird. Auch bei sehr vielen voneinander abhängigen Aufrufen bleibt die Zahl der Einrückungen übersichtlich.
Dieses Beispiel zeigt nur den einfachsten Anwendungsfall von Async.js. Es gibt neben series noch viele andere Strategien um Aufrufe zu synchronisieren. Mit parallel werden die Funktionen nicht seriell, sondern parallel gestartet. Ein Callback wird aufgerufen sobald alle Funktionen fertig abgearbeitet wurden. Außerdem gibt es noch viele funktionale Strategien wie map, reduce und filter, die benutzt werden können um eine Collection aus Daten einfach mit asynchronen Funktionen zu verändern.

Die Macht von SQL-Triggern

Trigger sind SQL-Anweisungen die bei bestimmten Events wie zum Beispiel Inserts, Deletes oder Updates ausgelöst werden. Mit Triggern ist es beispielsweise möglich komplexe Tabellenconstraints in MySQL zu realisieren, die wesentlich mächtiger als die herkömmlichen MySQL-Constraints sind. In diesem Blogpost will ich euch zeigen wie man Trigger verwendet und von welchen Anwendungen man besser die Finger lassen sollte.

Change-Logs mithilfe von Triggern in MySQL

Ein wirklich prominentes Beispiel für den Einsatz von Triggern ist das implementieren eines Change-Logs. Nehmen wir an, wir haben eine Anwendung die Blogposts verwaltet und wollen jetzt dem Nutzer zusätzlich anzeigen wann Änderungen an den Posts durchgeführt wurden.
Ausgangspunkt ist folgende Tabelle die einfach nur die Artikel eines Blogs enthält:

CREATE TABLE article (
   `id` INT AUTO_INCREMENT PRIMARY KEY,
   `title` VARCHAR(255),
   `author` VARCHAR(255),
   `text` VARCHAR(4096)
);

Wir können jetzt einen Trigger erstellen, der bei jedem UPDATE einer Tabellenzeile eine bestimmte Aktion durchführt. Für jede Änderung an einem Blogpost wollen wir den Text vor der Änderung, den Änderungszeitpunkt, die Aktion und den betroffenen Post speichern. Dafür erstellen wir uns eine Tabelle die die Einträge enthalten soll und einen Trigger der die Tabelle befüllt:

CREATE TABLE article_change_log (
   `article_id` INT, `time` DATETIME, `action` VARCHAR(16), `old` VARCHAR(4096)
);
CREATE TRIGGER update_article_logger
   BEFORE UPDATE ON article
   FOR EACH ROW
   INSERT INTO article_change_log SET
      `article_id` = NEW.`id`, `action` = 'update', `time` = NOW(), `old` = OLD.`text`;

CREATE TRIGGER erstellt einen Trigger der bei UPDATE einer Zeile der Tabelle “Article” ausgelöst wird. Der Trigger führt dann für jede betroffene Zeile eine bestimmte Anweisung durch. Die Bezeichner OLD und NEW können innerhalb dieser Anweisung verwendet werden um auf die Daten der Zeile vor und nach dem Update zuzugreifen.
Um einen vollständigen Logger zu implementieren müssten noch ähnliche Trigger für INSERT und DELETE erstellt werden. Damit ihr gleich etwas zum selbst ausprobieren habt und um den Rahmen dieses Blogposts nicht zu sprengen, überlasse ich euch diese Schritte allerdings selbst.

Vorsicht, unsichtbare Trigger!

Ein anderes Wort für “automatisches” Verhalten ist beim Programmieren auch oft “unsichtbares” Verhalten. Da Trigger automatisch Dinge tun ohne dass sie der Benutzer der Datenbank direkt angewiesen hat, sind sie auch immer eine eventuelle Fehlerquelle. Wenn möglich sollten Trigger keine Daten erstellen oder verändern, die von der eigentlichen Anwendungslogik erstellt werden, sondern immer nur eigene Datensätze verwalten. Damit kann sichergestellt werden, dass übersehene Trigger bei späteren Änderungen an der Anwendung zu keinem fehlerhaften Verhalten führen.

NETRP – Netways Resource Planner

Gute Nachrichten! Nachdem viel Schweiß und Hirnschmalz investiert wurde, ist es jetzt endlich fertig: NETRP.

NETRP?

NETRP steht für Netways Resource Planner und ist eine Webanwendung zum einfachen und schnellen planen von Ressourcen. Angefangen hat die Entwicklung als ausschließlich Internes Projekt, aber wegen des guten Ergebnisses haben wir uns jetzt entschlossen den Quellcode unter der GPLv3 zu veröffentlichen. Die Anwendung wird an eine Active Directory Domain angebunden und legt für jeden dort vorhandenen Mitarbeiter automatisch einen Time-Track an. Jeder Tag dieses Time-Tracks ist in NETRP eine einzelne Einheit, der eine bestimmte Aufgabe zugewiesen werden kann.
Darüber hinaus bietet die Anwendung noch viele andere sinnvolle Zusatfeatures, wie z.B. Statistiken, iCal-Export, das Anzeigen von Feiertagen und ein umfangreiches Backlog.

Features

Als kleinen Vorgeschmack hier eine Tour durch die wesentlichen Features:

Ansichten

Es wird neben der Kalenderansicht mit dem Time-Track auch eine Statistik generiert, die die Verteilung der Aufgaben je nach Kategorie in einem bestimmten Jahr verteilt anzeigt.
   

Editieren

Jeder Task gehört immer zu einem bestimmten Subject durch den ihm eine global-eindeutige Farbe zugewiesen wird. Außerdem können noch optional Daten aus Drittsystemen wie Sugar und RT abgefragt werden.
Task   Color-Menu

Backlog

Das Backlog kann über eine eigene Ansicht oder den Zellen-Tooltip aufgerufen werden und gibt wertvolle Informationen darüber, wer welche Änderungen wann durchgeführt hat.
Backlog-View   tooltip-backlog

Installation

Wer Interesse hat, findet eine Installationsanleitung, den Quellcode und alles was es sonst noch zu wissen gibt auf der Projektseite.