Undurchschaubare Softwarearchitektur



  • Hallo zusammen,

    ich muss nur kurz meinen Frust loswerden:

    Ich hab hier ein C++-Projekt, das so undurchschaubar ist, dass ich wahnsinnig werde. Jetzt würde man erwarten, dass hier einfach draus los gehackt wurde und man deswegen nicht versteht, was wo passiert und wo man etwas ändern muss. Nein, so ist es nicht.

    Hier waren die "ich muss jedes Pattern das ich kenne einsetzen"-Profis am Werk.

    Mit dem Ergebnis das ALLES abstrahiert wurde und die Komplexität dadurch unnötig erhöht wurde.

    Da gibt es dann zig "xxxHandler", wobei xxx für irgendwas ganz Allgemeines steht. Vor lauter Factories, Handler und Callern und Frameworks rafft man gar nichts mehr. Es gibt auch nicht einen src-Ordner, nein, alle Sourcen+Header sind genau so organisiert, wie die namespaces, so dass man sich durch eine unglaubliche Ordnerhierarchie klicken muss, wenn man außerhalb der IDE mal was sucht. Im Quelltext sieht dann die Instanziierung von Variablen stellenweise so aus:

    mynamespace::additionalnamespace::database::handling::caller::MyCaller<othernamespace::management::caller::SubCaller> myCaller;
    

    Ich schwöre bei Gott, die gesamte Funktionalität des Programms ließe sich mit maximal 20 wunderschönen Klassen erschlagen und wäre trotzdem wartbar und vor allem: wiederverwendbar 😞 😞 Warum tut man sowas?



  • es gibt viele wege etwas zu loesen, gerade bei software architektur. das was einem dann am kompliziertesten vorkommt ist was man am seltensten nutzt. gibt c leute die c++ code fuer unnoetig kompliziert halten und c++ leute die c code fuer unnoetig aufwendig sehen.
    gegenseitig natuerlich unwartbar.

    ich persoenlich halte es auch fuer schlechten stil alle dateien in einen pfad zu legen und nutze lieber das dateisystem um es sauber zu sortieren. beide seiten haben vor und nachteile, kennst du dich in der hierarchy aus, kommst du schnell zum ziel und musst keine 50dateien in einem pfad pflegen, kennst du dich nicht aus und such tools sind dein freund, dann ist eine flat hierarchy besser.

    ich kann verstehen dass du dich aufregst wenn du deine programmier methology hast und dem gegenteil gegenueberstehst, aber statt dich aufzuregen, versuch lieber durchzusteigen. manche dinge haben sinn und die leute die es erstellt haben, haben sicherlich nicht versucht anti pattern zum spass zu verfolgen, sondern haben sich was gedacht.

    die langen namespace konstrukte sind natuerlich weniger schoen, da sollte man definitiv typedefs verwenden. namespace an sich, finde ich, sind am besten dafuer geeignet um implementationen austauschen zu koennen und gerade dann sind typedefs wirklich praktische wege "non-invasive" zu kapseln.

    kaepf dich durch und lern die paar guten dinge fuer dich daraus 😉



  • pro namespace ein ordner ist z.b. in java ein muss glaub ich



  • Ich kenne sowas auch. Selbst folge ich dem Motto:

    Jon Bentley schrieb:

    The purpose of software engineering is to control complexity, not to create it.

    Aehnliches beschreibt Rich Hickey in Simple Made Easy.

    Wobei die Grenzen bei Overengineering durchaus nicht hart sind, gibt es doch Extreme. Es leidet die Softwarequalitaet erheblich(gemessen an Lesbarkeit, Verstaendlichkeit, Wartbarkeit, Einarbeitungszeit oder auch Performance). Oft fehlt auch die noetige Dokumentation und ausreichend Tests, da solche Bibliotheken meist zu umfangreich sind und in Eigenregie entstanden sind.

    Warum machen Entwickler sowas: Keine Erfahrung, keine Verantwortung, keine Qualitaetskontrolle.



  • knivil schrieb:

    Warum machen Entwickler sowas: Keine Erfahrung, keine Verantwortung, keine Qualitaetskontrolle.

    Spass? Ich muss zugeben, ich neige auch ab und zu Overengineering. Weil mir das Spass macht und ich was ausprobieren will, was ich grad interessant finde. Ich seh mich nicht als Roboter, der jede noch so langweilige Aufgabe straight forward runtertippen muss, sodass es jeder C Entwickler sofort versteht.
    Natürlich muss man das noch irgendwie im Griff haben. Wenn die Qualität drunter leidet, hat man sicher was falsch gemacht. Allerdings seh ichs nicht als Qualitätskriterium, dass jeder sofort alles verstanden haben muss.



  • Schluchz schrieb:

    Warum tut man sowas?

    Verb::Ist Adjektiv::leichter Konjunktion::zu Verb::lesen.
    Adverb::Darum Verb::schreiben Partikel::doch Partikel::auch Pronomen::alle

    std::cout << "hello world" << std::endl;
    


  • Schluchz schrieb:

    Hier waren die "ich muss jedes Pattern das ich kenne einsetzen"-Profis am Werk.

    Mit dem Ergebnis das ALLES abstrahiert wurde und die Komplexität dadurch unnötig erhöht wurde.

    Hatte mal Code, wo die Klassen fast keine Attribute und Methoden hatten, die sich mit der Anwendungslogik beschäftigten, sondern zur Laufzeit alle Caller und Handler von irgendwoher reingesteckt bekamen, um damit zu arbeiten. Also versuchte ich nachzulesen, woher die Steuerdaten kommen, Pustekuchen(!), auch das reinsteckende Framework hatte überall was drin stecken. Und jeder der's sieht, staunt begeistert "Boah, ja noch eleganter implementiert als boost!".



  • @Schluchz
    Mir fällt dabei der Begriff "inner platform effect" ein.
    Lies mal die Wikipedia-Seite wenn dir der Begriff nix sagt.



  • volkard schrieb:

    Hatte mal Code, wo die Klassen fast keine Attribute und Methoden hatten, die sich mit der Anwendungslogik beschäftigten, sondern zur Laufzeit alle Caller und Handler von irgendwoher reingesteckt bekamen, um damit zu arbeiten. Also versuchte ich nachzulesen, woher die Steuerdaten kommen, Pustekuchen(!), auch das reinsteckende Framework hatte überall was drin stecken.

    Da hilft nur Durchsteppen mit dem Debugger.

    Ich habe selber einige Zeit solchen Code gebaut. Weil ich jetzt meinen eigenen Code warten darf, ärgert's mich natürlich, und mittelfristig will ich die ganzen Abstraktionsschichten auch wieder loswerden. Warum man das macht, wurde größtenteils schon genannt: Selbstüberschätzung, zu wenig Praxiserfahrung, zu viele Freiheiten, einziger Entwickler im Projekt (=> keine Code-Reviews), keine klaren Deadlines. Meine ersten Programme waren das genaue Gegenteil: geschrieben in einem BASIC-Dialekt, voll von GOTOs, keine unnötige Zeile, keine Abstraktionen. (Würde ich auch heute noch so machen, weil die Programme nicht für den PC waren und es auf der Plattform auf jedes Byte ankam.) Es dauert halt ein bißchen, bis man die richtige Balance findet.

    Übrigens finde ich, daß auch Sprachen wie C++ und Java und die zugehörigen Kulturen zum Overengineering beitragen. In C++ bin ich dauernd versucht, meinen Code durch Template-Meta-Compilezeit-Zeug von irgendeiner Abhängigkeit zu entfernen und weiter zu abstrahieren oder gar sprachinterne domain-specific languages zu bauen, und in Java bekommt man unausweichlich so viel IoC-Zeug und Patterns beigebracht, daß man sich geradezu zwingen muß, irgendwas ohne DI zu implementieren.

    Mechanics schrieb:

    Ich seh mich nicht als Roboter, der jede noch so langweilige Aufgabe straight forward runtertippen muss, sodass es jeder C Entwickler sofort versteht. [...] Allerdings seh ichs nicht als Qualitätskriterium, dass jeder sofort alles verstanden haben muss.

    Das sehe ich andersrum. Ich finde, daß leichte Verständlichkeit für Nichteingeweihte ein prima Qualitätskriterium ist. (Nur weiß ich nicht, wieso du das mit C assoziierst, denn ich sehe selten C-Code, der diese Anforderung erfüllt.)



  • Ich kenne es auss der Praxis, das verkompliziert wird, insbesondere dann, wenn die Software von externen Firmen gemacht wird. Auf die Art sichert man sich dann seine Anschlußaufträge. Schon 10 geschachtelte includes (hier bei ABAP) die nix ausser nem move irgendwas nach dahin enthalten sorgen dafür, dass man am Bildschirm keinen Überblick über das eigentliche Programm hat usw. Oder call function "meyer" und in meyer steht nix ausser 2 moves oder so - wobei durch diesen "tollen" Entwickler sichergestellt ist, dass die includes und functions auch nicht mehrfach verwendet werden, wo es ja noch Sinn machen würde. Kurz gesagt: Abzocke.



  • Ich glaube es liegt auch daran, dass sich viele nur mit Code beschäftigen. Heißt: "goto ist böse" -> okay ich verwende es nie mehr. "DI ist gut" -> okay ich verwende es überall. etc.

    Besser ist es (imho) sich mit Prinzipien der Softwareentwicklung im Bereich der verwendeten Sprache (also bspw. aus dem Bereich OOP) auseinanderzusetzen. Es sind zwar viele Abkürzungen aber bspw. halte ich es für unabdingbar die Prinzipien von SOLID verinnerlicht zu haben: http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

    Tipp: Wenn man test-driven development fährt, ergeben sich einige der SOLID-Prinzipien von alleine, da automatisch bei Nichteinhaltung das Schreiben des Testcodes zur Qual wird.

    MfG SideWinder



  • sind oft leute die direkt von der uni kommen, und gelernt haben, alles möglichst zu abstrahieren usw...
    je länger ich arbeite, desto mehr gefällt mit der KISS ansatz.

    c++ bietet so viele tolle möglichkeiten, dinge einfach zu machen. vor allem simple klassen bringen recht viel. so dinge wie scoped_ptr von boost finde ich schön einfach und trotzdem effektiv. so versuche ich es auch zu machen, einfache klassen und funktionen zu verwenden, die einem das leben erleichtern, statt es komplizierter zu machen.

    andere dinge hasse ich, z.B. wenn über 20 ebenen vererbt wird, nur um in der untersten ebene 1+1 zusammenzuzählen.
    aber wie schon gesagt, solcher code ist meist von jungen und unerfahrenen entwicklern. oder von leuten die davor nur java programmiert haben 😃



  • @fdsfdsf: Gerade Leute von der Uni haben imho einen größeren Blick auf das Gesamtwerk und fallen weniger oft in diese Problemgruppen.

    Frickeln ist leider hoch im Kurs. Die einen, wie Mechanics, weil sie code-technisch etwas ausprobieren wollen, die anderen, weil sie das neueste Pattern aus dem XYZ Journal einbauen wollen. YAGNI (unter Beachtung von DOGBITE - leider weniger bekannt "Do it, Or Get Bitten In The End") sind wichtige Prinzipien gegen Over-Engineering.

    Auch hier gilt es aber aufzupassen, gerne werden "um alles einfach zu halten", hunderte von XYZUtilities-Klassen (oder in C++ eher freie Funktionen) gebastelt die am Ende plötzlich 80% der Funktionalität übernehmen.

    Code-Reviews und optimalerweise wechselndes Pair-Programming durch den besten Programmierer im Team ("Lead Developer") unterstützen raschen Niveauangleich im Team.

    MfG SideWinder



  • SideWinder schrieb:

    @fdsfdsf: Gerade Leute von der Uni haben imho einen größeren Blick auf das Gesamtwerk und fallen weniger oft in diese Problemgruppen.

    gut bei uns arbeiten hauptsächlich leute von der uni, aber dann sagen wir mal allgemeiner: leute, die SW entwicklung hauptsächlich theoretisch gelernt haben, und erst erfahrung in der praxis machen.

    aber irgendwo muss man halt mal erfahrung sammeln, ist ja jetzt auch nichts schlimmes dran.
    nur blöd wenn ich so eine drecks SW dann warten darf, dann muss ich ab und zu schimpfen 😃



  • fdsfsdf schrieb:

    nur blöd wenn ich so eine drecks SW dann warten darf, dann muss ich ab und zu schimpfen 😃

    Das ist alles sehr relativ. Auch "undurchschaubare Softwarearchitektur" und fast alles was hier genannt wurde, sind erstmal sehr abstrakte Begriffe. Das müsste man sich schon im Detail anschauen um zu entscheiden, ob das wirklich schlimm ist. Und es kommt auch drauf an, was man auch überhaupt machen will/muss. Ich hab in der Arbeit über ein dutzend Projekte, die ich betreue. Paar davon habe ich einfach nur übernommen und fast nichts dran gemacht, muss nur ab und zu Bugs fixen. Da nervt es mich natürlich auch, wenn ich das nicht sofort verstehe und erstmal Zeit investieren muss, um mich reinzudenken. Andererseits nervt es mich aber auch gewaltig, wenn ich etwas neues schreiben muss und viel von der benötigten Funktionalität eigentlich schon da wäre, aber überhaupt nicht wiederverwendbar ist, weil das alles zu direkt programmiert und zu wenig abstrahiert wurde. Das Verhalten ist entweder hartkodiert, oder wird direkt über irgendwelche Configs oder gar GUI gesteuert, alles hängt voneinander ab und dann gibts auch so viele Sonderfälle und Stolperfallen, dass man sich auch nicht traut, das umzubauen, sondern das ganze einfach nochmal schreibt, so wie man das selber braucht. Und bei uns in der Arbeit bin ich das mittlerweile grundsätzlich gewohnt, dass praktisch alles was irgendjemand irgendwo einbaut ganz sicher auch an zig anderen Stellen mit lauter neuen Anforderungen gebraucht wird. Hier einen Kompromiss zwischen Overengineering und viel zu einfach und nicht wiederverwendbar gebastelt zu finden ist nicht einfach, deswegen bin ich bei pauschalen Aussagen wie "undurchschaubar" erstmal skeptisch.



  • SideWinder schrieb:

    Ich glaube es liegt auch daran, dass sich viele nur mit Code beschäftigen. Heißt: "goto ist böse" -> okay ich verwende es nie mehr. "DI ist gut" -> okay ich verwende es überall. etc.

    Exakt.
    Ich sehe auch
    "Vererbung ist gut" und
    "Generizität ist gut" und
    neuerdings "rohe Zeiger sind schlecht"
    dahingehend bei C++ in meiner Kritik.

    SideWinder schrieb:

    Besser ist es (imho) sich mit Prinzipien der Softwareentwicklung im Bereich der verwendeten Sprache (also bspw. aus dem Bereich OOP) auseinanderzusetzen. Es sind zwar viele Abkürzungen aber bspw. halte ich es für unabdingbar die Prinzipien von SOLID verinnerlicht zu haben: http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

    Aber auch wieder mit der dringenden Maßgabe, daß man SOLID nicht einfach nur anwenden soll, weil es "gut" ist. Da ist schließlich wieder DI drin.

    SideWinder schrieb:

    Tipp: Wenn man test-driven development fährt, ergeben sich einige der SOLID-Prinzipien von alleine, da automatisch bei Nichteinhaltung das Schreiben des Testcodes zur Qual wird.

    Naja, ich würde die Bälle flacher halten. Test-driven sorgt automatisch dafür, daß man Fehler verdammt früh erkennt, und verdammt wenige Fehler zum Kunden rausläßt. Müßte schon was böswillig gedanklich konstruieren, wo das mal schlecht wäre.

    SideWinder schrieb:

    YAGNI (unter Beachtung von DOGBITE) sind wichtige Prinzipien gegen Over-Engineering.

    Jo. Damit gewinnt man.



  • SideWinder schrieb:

    Auch hier gilt es aber aufzupassen, gerne werden "um alles einfach zu halten", hunderte von XYZUtilities-Klassen (oder in C++ eher freie Funktionen) gebastelt die am Ende plötzlich 80% der Funktionalität übernehmen.

    Wenn man etwas als freie Funktion formulieren kann, ist das doch perfekt.

    In meinem Code "degenerieren" Klassen gelegentlich beim Refaktorisieren zu freien Funktionen. Die werden bei vernünftiger Aufteilung einfach so klein, dass sie nur noch aus Konstruktor und einer Methode bestehen. Die Klasse wird dann in eine freie Funktion umgewandelt, deren Parameter sich aus denen des Konstruktors und der Methode zusammensetzen. Zum Beispiel mit bind kann das Objekt dann "konstruiert" werden. Das Äquivalent zu virtual heißt function .

    //Eine typische Schnittstelle mit recht ähnlichen Methoden:
    struct Sender
    {
    	virtual ~Sender();
    	virtual void SendA() = 0;
    	virtual void SendB(const std::string &name) = 0;
    	virtual void SendC() = 0;
    };
    
    //Modelliert man die Nachricht getrennt vom Überbringer, braucht man nur noch eine Methode und kann die Nachricht leicht herumreichen, kopieren oder inspizieren:
    struct A {};
    struct B { std::string name; };
    struct C {};
    typedef boost::variant<A, B, C> Message;
    struct Sender
    {
    	virtual ~Sender();
    	virtual void Send(const Message &) = 0;
    };
    
    //Das ist aber nicht Java, also schreibe ich nicht jedes Mal eine neue Klasse, wenn ich eine Implementierung der Schnittstelle brauche. Es gibt ein geeigneteres Mittel für genau diesen Zweck:
    struct A {};
    struct B { std::string name; };
    struct C {};
    typedef boost::variant<A, B, C> Message;
    typedef boost::function<void (const Message &)> Sender;
    

    Das ist schöne objektorientierte Programmierung.
    Es würde kaum jemand auf die Idee kommen einen Downcast auf function zu machen. function hat auch weder Getter noch Setter und man kann den Destruktor bei bind oder Lambda nicht selbst implementieren. Herrlich.



  • D.h. wenn jemand jetzt ein D versenden will, muss er jetzt betteln, dass du in deinem Framework den typedef ausbessert? Hmm...

    Weiß in deinem Fall die Message wie sie sich versenden soll oder weiß Sender wie jegliche Klasse die eine Message ist versendet gehört?

    MfG SideWinder



  • Ist wieder mal typisch und scheint mir an der Tagesordnung zu sein.
    Gefrickel aus der Hand und ohne Konzept. Es wird gleichzeitig Konzept
    und Design gemacht, dabei zwischen drin Fehler behoben und getestet.

    Die meisten sogenannten Softwareentwickler arbeiten im Chaos und
    unverständlicherweise finden sich nur selten Teamleiter, die ihnen das
    abgewöhnen und dafür sorgen, dass sauber entwickelt wird.

    Was ist denn bitte grosses dabei, sich eine Grafik hinzumalen, die die
    einzelnen Blöcke bezeichnet und den Datenfluss skizziert. Dazu noch
    einen groben Ablaufplan über die use cases und abgefangenen fail cases
    und gut ist. Das kostet insgesamt 2 Tage, sowas für eine mittelgrosse SW
    zu leisten.

    Diese Informationen müssen ohnehin von jemandem definiert und vorgegeben
    sein. Dazu gibt es das Konzept des Requirement Engineerings. Die
    funktionellen Anforderungen zu erfassen und zu definieren, kostet in der
    Regel 2-3 Wochen! Selbst dann, wenn der SW-Entwickler sich vieles selber
    sagt und vorgibt, kann er es zunächst skizzieren, sich abhaken lassen
    und dann einfach umsetzen. Das ist viel einfacher und schon bei der
    Erstentwicklung schneller!!!

    Auch bei Änderungen zahlt es sich aus:

    Wenn man die Funktion der SW kennt und sieht, welcher Block was tut,
    dann kann man sehr einfach die Codezeilen interpretieren. Umgekehrt ist
    das eine Ochsentour, kostet viel Zeit und ist fehlerträchtig.

    Aber das Beispiel ist leider kein Einzelfall. In meiner direkten
    Umgebung sehe ich andauernd Duos von Entwicklern, die zu zweit am PC
    sitzen und Codeanalyse betreiben, weil sie an etwas weiterstricken
    sollen, was einer zusammengewichst hat, da weggegangen ist.

    Mir ist es einfach komplett unbegreiflich, warum sowas nach wie vor
    exisitert und man Softwareentwicklern nicht schon an der Uni beibringt,
    geradlinig zu arbeiten.

    Zu Zeiten der 8Bit-Computer mit 3k RAM war es noch möglich, dass sich
    ein Schüler ein komplettes Programm ausdenkt und im Kopf behält, aber
    heute doch nicht mehr.

    Die Zeiten der Hacker sind vorbei!



  • @Kenner der Scene: Ich hoffe, dass im Großen jedes Projekt heutzutage diese Art von Dokumentation erfährt. Das hilft nur leider im Kleinen nicht. Da wird nirgends stehen wie hoch die Abstraktionsschicht von einzelnen Komponenten sein muss.

    In agilen Unternehmen kommt es heutzutage dankenswerterweise oft dann in gemeinsamen, ein- bis zweiwöchentlichen Konzeptionssessions zum Zeichnen, aber falls das nicht passiert kannst du noch so gut 3 Wochen "requirements engineered" haben, du kannst trotzdem in der god.Do<MyProblemA, MyProblemB>()-Falle landen.

    MfG SideWinder


Log in to reply