Reinraumentwicklung
Nachdem wir die letzten zwei Tage das Glück hatten den Worten von Martin und Ralf lauschen zu dürfen, will ich heute mal wieder etwas verfassen. Ich weiß noch nicht, ob ich meine Idee sauber kommunizieren kann, aber zumindest in meinem Kopf existiert ein klares Bild. Ich würde sagen, ich versuche es einfach.
Ich habe schon in einer Menge Projekten gearbeitet. Nach einer ganzen Weile waren sie alle unsauber und erodiert. Einige davon waren auf Frameworks wie Zend oder Symfony aufgebaut. Dabei war man irgendwann an der Stelle an der die Abhängigkeiten zwischen Komponenten, Plugins und Co. so stark waren, dass man kaum noch durchblicken konnte. Einen Überblick über das Gesamtsystem gab es natürlich nie.
Wie kommt man von einer solchen Brownfield-Applikation (kluges Wort, oder?) wieder auf einen grünen Zweig? Hier hätte ich eine Idee. Ob sie gut ist, weiß ich noch nicht, das dürft ihr dann entscheiden. Nehmen wir in unserem Beispiel eine Symfony-Anwendung. Alle Plugins sind so stark gekoppelt, dass man … hab ich ja schon beschrieben. Mein Gefühl ist, dass jedes Plugin, dass ich in dieser Umgebung schreibe wieder so enden wird, denn das rauslösen der Abhängigkeiten in diesem großen Tohuwabohu ist schwer und macht keinen Spaß, wenn man diesen riesigen Ball-of-Mud vor sich hat.
Wäre es vielleicht eine Lösung wenn man ein neues Plugin erstmal in einer sauberen Symfony-Basis-Installation schreibe. Alle Abhängigkeiten würde ich dann rausholen, aber in der saubere System überführen. Ich glaube es könnte einfacher sein in einem solchen Reinraum sein Plugin zu entwickeln und es danach an das Projekt einzubauen. Wenn ich alle folgenden Plugins so entwickle und auch alles Abhängigkeiten, die ich aus dem realen Projekt raushole in eine saubere Struktur packe, dann sollte ich doch irgendwann eine Applikation haben, mit der ich glücklich bin.
Natürlich könnte ich den gleichen Aufwand auch in dem Projekt selbst leisten. Ich glaube nur nicht, dass man in schon scheinbar verhunzten System das auch wirklich macht. So jetzt bin ich gespannt, ob ihr mich überzeugen könnt, dass das eine doofe Idee ist.
Nein das klingt überhauptnicht unverständlich oder gar falsch. Ganz im Gegenteil.
Um mal für symfony zu sprechen. Wenn man die Basisstruktur eines Plugins generiert, dann ist dort ein sog. Fixture Projekt enthalten. Eine Standard symfony Projektstruktur innerhalb des Plugins. Dieses ist dafür da, dass man Unit und Functional Tests laufen lassen kann, ohne ein konkretes Projekt zu benötigen.
Ich benutze das beispielsweise in meinem Plugin http://www.symfony-project.org/plugins/sfImageTransformExtraPlugin, welches ich auf http://automat.ical.ly/job/sfImageTransformExtraPlugin/ mit einem Continuous Integration Server teste.
Ich habe dabei keine konkrete symfony Anwendung in der mein Plugin läuft, sondern habe lediglich mein Plugin ausgecheckt und alle benötigten Abhängigkeiten. In meinem Fall also symfony selbst und das sfImageTransformPlugin. So muss man sehr bewusst über seine Abhängigkeiten nachdenken.
Aber ich verfolge aktuell noch einen weiteren Ansatz. Ich betreue zwei Projekte, die einige Schnittmengen in ihren Anforderungen aufweisen. Diese werden in einem gemeinsamen Plugin umgesetzt. Sollte da eine Projekt A spezifische Abhängigkeit entstehen wird jemand aus Projekt B schon dafür sorgen, dass diese entfernt wird. 🙂
Am wichtigsten ist aber, dass die Entwickler sich, sobald sie in einem Plugin entwickeln, den Plugin-Entwickler-Hut aufsetzen und sich klarmachen, dass ihr Code auch in fremden Projekten laufen muss.
Im Idealfall kann man sogar Open Source-en, so dass das Community Feedback das Plugin schon reinschleifen wird.
Moin Nils,
da hast du mich heute morgen aber mal so richtig in die Irre geführt. Es gibt nämlich auch einen Entwicklungsprozess[1] mit ähnlichem Namen, der das Ziel verfolgt die Verrottung von Software, sei es durch Bugs oder schlechtes Design, von vornherein zu vermeiden.
Beste Grüße
Manuel
[1] http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=1695817
Ich denke nur, man hat ne Menge Overhead damit. Das Problem ist ja nicht die Entwicklung eines solchen Plugins, sondern die wirklich saubere Trennung als solches vom Rest, und die bekommt man meist nicht unbedingt hin, schon gar nicht, wenn man nicht allein dran arbeitet. Ich habe manchmal das Problem (beim ZF), das ich nicht genau weiß, nehme ich das Plugin/die Klasse nu als globale Erweiterung oder brauch ich sie nur Applikations-intern. Da geht’s dann schon los: Applikations-intern kann ich eine starke Bindung ohne Probleme herstellen, andersrum nicht. Und alles im Voraus so durchzuplanen, das man während des Programmierens nicht an diese Fragen stößt, halte ich schlichtweg für unmöglich, oder zumindest sehr schwer.
@Manuel: Danke für den Link. Klingt interessant und ist vll. auch mal nen Artikel wert.
Auf jeden Fall ein interessanter Ansatz. Ich sehe jetzt auch nicht den großen Overhead dabei, wenn man davon ausgeht, dass die Herangehensweise eine saubere Struktur in den produktiven Applikationen erzwingt und die Integration entsprechend einfach ist.
Ein anderes Risiko würde ich höher einschätzen: die Entwicklung abseits der Geschäftsanwendung kann dazu führen, dass Lösungen nicht nur die geforderten Anforderungen umsetzen, over engineered umgesetzt werden und darum letztendlich mehr Aufwand für nicht erforderliche Features bedürfen.
@Mario I kann den Overhead auch nur bedingt erkennen. Sicher stellt eine saubere Software Architektur einen initialen Mehraufwand dar im Gegensatz zu quick & dirty, aber bereits mittelfristig werden die Aufwände geringer, weil die Pfleg- und Erweiterbarkeit besser gewährleistet ist.
@Martin Die Gefahr des Overengineerings besteht auf jeden Fall bei einer strikten Trennung zur Applikation. Deswegen muss so ein Plugin auch regelmässig in eine reale Anwendung geworfen werden um einen Alltagstest zu bestehen. Auch hier ist das Open Source-en eine super Lösung, wenn es sich mit dem aktuellen Geschäftsgebaren versteht.
@Christian:
War zu früh. Wenn ich ein bestehendes Projekt habe, in dem dies nicht konsequent umgesetzt wurde, und ich dann weitere Erweiterungen/Plugins implementieren muss, dann habe ich aber nen gewaltigen Overhead. Habe ich ein sauberes, jungfräuliches Projekt, dann sieht die Sache natürlich komplett anders aus. Über den initialen Mehraufwand bei einen solchen Projekt brauchen wir nicht reden. Leider sieht die Realität aber nu mal so aus, das man oftmals schon irgendwelche bestehenden Codes nimmt/nehmen muß und damit weiterarbeiten darf/soll. Jedes Projekt „from scratch“ ist sowieso der Garten Eden! 🙂
Die erste Idee war; Hey hört sich nicht schlecht an. Aber der zweite Gedanke war; Gelegentlich muss ich dann auch dafür sorgen, dass die Abhängigkeiten der Abhängigkeiten mit in den „Reinraum“ müssen.
So eine Entwicklungsart ist vielleicht auch nur dann geeignet, wenn ich sehr fix meine komplette Umgebung die ich benötige „mit einem Klick“ aufsetzen kann. Und dann hab ich die benötigten Komponenten noch nicht „ein gehangen“ – Der Overhead könnte vielleicht ein wenig zu groß sein.
So ungern ich es auch sagen mag… An dieser Stelle muss der Entwickler vorher denken und dann Tippen, und zwar nicht nur 5 Minuten kurz vor der Implementierung der Methode xy. Er ist an dieser Stelle verantwortlich sein „Wald“ sauber zu halten. Und wenn er Missstände erkennt diese zumindest nach der Pfadfinderregel auszumerzen (soviel zu meiner idealistischen Vorstellung zur Entwicklung einer Software).
In der Realität trifft aber Zeitdruck auf Kreativität und das beides kaum zu vereinbaren ist, wissen wir glaube ich alle. Ich denke dass es einige Entwickler gibt die gerne sauber entwickeln würden, aber nicht können/dürfen. Und viele es einfach gar nicht erst wollen und nicht mal eine Vorstellung entwickelt haben wie saubere Software aussehen kann.
Hi,
prinzipiell finde ich das ebenfalls eine gute Idee auch wenn ich eher in großen Projekten zu einer anderen Struktur neige.
Plugins mit Abhängigkeiten untereinander sind sicher nicht falsch, und architektonisch sinnvoll und richtig.
Ich tendiere aber eher dazu neben dem eigentlichen Framework- oder CMS-Core ein Coreplugin zu bauen in das ich alle funktionalitäten überführe die in mehr als einem Plugin vorkommen oder die von mehr als dem eigentlichen Plugin genutzt werden. So stelle ich sicher das ich immer eine 1 zu n relation vom Core zu den Plugins habe und keine n zu n unter den Plugins.
Wie wir alle aus Erfahrung wissen is es so das es zwar eine PhpDoc Doku gibt, evtl. auchnoch eine Anwenderdoku aber meist hat sich niemand die Mühe gemacht die Architektur in Form von Uml o.ä. zu Dokumentieren und Abhängigkeiten irgendwo festzuhalten.
@Chris: Aber wird das CorePlugin dann nicht irgendwann der Big Ball Of Mud?
@Nils Die Idee ist nicht schlecht. Im Gegenteil. Tatsächlich machen wir das seit Jahren so – oder zumindest so ähnlich.
Wir haben ein Plugin-System, welche über eine dynamisch zur Laufzeit ausgehandelte Schnittstelle kommunizieren – ohne direkte Funktions- oder Klassenaufrufe. Die Plugins selbst arbeiten nach einem Vererbungsmuster ohne multiple Vererbung. Es hat maximal genau 1 Abhängigkeit. Third-Party Bibliotheken ausgenommen – für die haben wir ein zentrales „libs“-Verzeichnis: also etwas Vergleichbares zu Windows‘ „Common Files“.
Den Rest der Sauberkeit haben wir über Annotations und Generatoren erreicht, welche Codedopplungen vermeiden.
Codebeispiel:
/**
* @extends base
*/
class Foo implements IsPlugin
{
/**
* a * (a + b)
*
* @subscribe
*/
public function add($a, $b)
{
$pm = PluginManager::getInstance();
$result = $pm->getLastResult();
return $a * $result;
}
/**
* a / abs(b) instead of a / b
*
* @overwrite
* @param int $a
* @param int $b
* @return float
*/
public function divide($a, $b)
{
return $a / abs($b);
}
/**
* reuse functionality of foo()
*
* @type default
* @return bool
*/
public function reuse(array $args)
{
$pm = PluginManager::getInstance();
// Send to whoever implements function foo().
// We don’t care if foo() exists!
$pm->broadcastEvent(‚foo‘, $args);
$result = $pm->getLastResult();
if (!empty($result)) {
$db = self::getDatabase();
$db->insert(„foo.*“, $result);
return $db->commit(); // bool
}
return false;
}
}
Die Klasseninfos lesen wir über Reflections aus und speichern sie in einem Repository.
Das gleiche Muster zieht sich durch bei den Templates, den Skins, den Dateireferenzen, den Übersetzungen und den Datenbankdefinitionen.
Da jedes Plugin nur exakt eine Aufgabe hat und kein doppelter Code entsteht bleibt der Big Ball of Mud langfristig aus.
Hauptproblem (vmtl. aber bei jeder Idee) ist, dass Entwickler, welche diesen Stil nicht gewohnt sind, schnell anfangen die Logik nicht mehr zu benutzen und wieder prozedural zu arbeiten.
Man muss einfach die Disziplin haben an der Stelle, an der man, nach erfolgreichem Abschluss einer Aktion, zusätzlich vielleicht noch eine Mail verschicken würde, zu sagen: „Stopp – das ist keine Grundfunktionalität. Dies gehört in ein eigenes Plugin, damit ich es später ein- und ausschalten kann.“
Viele Entwickler tun sich genau damit noch sehr schwer.
Mich hat das Topic eher an Clean Room Design / Clean Room Engineering (nach Wikipedia) erinnert, aber gut zu wissen, dass da doch ein kleiner Unterschied besteht