Auch wenn ich die Software-Entwicklung mehr als Hobby ansehe als für den Beruf konnte ich da jetzt schon einige Jahre damit verbringen. Kurz: Version 1 ist für mich die klarer verständlichere und einfachere Art. Das wäre auch die, wie ich das einem der mich fragt vermitteln würde. Du willst halt wenn jemanden der ein Problem hat einem schnell helfen das es Klick macht. Wie man dann das Wissen umsetzt und aufbläht ist danach ein anderes Thema, aber den Grundgedanken wie es funktioniert muss erstmal da sein. Meiner Meinung nach.
Stimmt. Verständnis und Einfachheit des Codes sind wesentlich wichtiger, als etwas völlig unnötig zu verkomplizieren. Vor allem kann man dann im Team besser arbeiten und man kann ihn selber später auch besser verstehen. Am Ende muss man die Lösung komplett neu machen, weil man die genutzten "Funktionen" so selten benutzt. Die Lösung ist wirklich: Einfach und elegant halten.
Ich selbst bin seit einiger Zeit an dem Punkt, wo ich mich frage, ob Softwareentwicklung wirklich so kompliziert sein muss. Und oft liegt es meiner Erfahrung nach genau an dem hier beschriebenen Fehler. Lösungen werden unnötig kompliziert aufgebaut. Das beginnt mitunter aber schon auf der Fachebene, wo Anforderungen formuliert werden, die über den eigentlich notwendigen Nutzen hinausgehen, setzt sich fort in overengineerten Architekturen und endet letztlich in der Anwendung zu komplizierter Programmierkonzepte. Es macht wirklich Sinn, sich die Frage zu stellen, was denn wirklich zur Lösung der Aufgabe notwendig ist und dann zunächst die einfachste Variante zu implementieren. Komplizierter kann man es dann später immer noch machen, wenn die Situation es erfordert.
Das richtige Maß zu finden, ist extrem schwierig. Wenn man an legacy Code arbeitet, wünscht man sich wesentlich mehr Abstraktion. Hat man abstrahierten Code vor sich, wird es oft schwierig, noch Änderungen ohne riesen Workarounds vorzunehmen, obwohl die Abstraktion eigentlich ja genau das ermöglichen sollte.
Großartiges Zitat von Chuck Moore, Erfinder der Programmiersprache Forth: "I'm never afraid of simplicity. Show me a simpler way do the things I'm doing and I will go for it."
Ein sehr wichtiger Punkt. Wir haben einen talentierten jungen Entwickler, der jedes neue sprachfeature nutzt… und uns ziemlich alt aussehen lässt :) Er findet es dann einfacher und eleganter… Elegant ist auch abhängig, ob man das Feature kennt…. Die Frage ist, ist es zu kompliziert oder sind wir zu dumm 😅😅😅
Nein, ihr seit nicht zu dumm. Aber dem jungen Entwickler fehlt die Erfahrung, daß es besser ist, einfacher zu entwickeln als jeden neuesten Hype zu nutzen. Er will sein Ego streicheln und euch damit alt aussehen lassen. Er entspricht daher einem Lehrling, der seine Lehrzeit abgeschlossen hat und jetzt als Geselle arbeitet. Bis zum wirklichen Profi und später einem Meister, ist noch ein langer Weg. Ich habe meinen Entwicklern immer gepredigt, entwickelt so einfach wie möglich, die Komplexität kommt schon aus der Anwendung selbst, ihr braucht da nichts im Code hinzufügen. Euer Superentwickler sollte mal ein Problem in einer anderen Programmiersprache lösen müssen, z.B. Lisp, Prolog, Fortran, C, Cobol, Forth oder Modula2.
Es hängt für mich nicht am konkreten Code, sondern am Team welches den Code warten und weiterentwickeln muss. Wer all die tollen, fancy technischen Lösungswege kennt, dennoch versucht, die Flughöhe des Teams zu berücksichtigen und für das Team verständlichen Code schreibt, ist mein eigentlicher Hero. Er kombiniert technisches Know-How mit sozialen Softskills. Ob eine Lösung gut oder schlecht ist, hängt für mich davon ab, ob das Team sie beherrschen kann. Nimm den Super-Hero, der für den Rest unverständliche Magie implementiert, raus und das Team beginnt zu fliegen.
So sehr ich dein Punkt auch teile, muss ich aber sagen, irgendwo setzte ich trotzdem eine Grenze. Ich will mich auch nicht dauernd mit dem Thema beschäftigen, ob meine Arbeit gut bei jedem Kollege ankommt und verstanden wird. Und ich erwarte auch nicht, dass die Kollegen dasselbe mit mir tun. Sonst läuft man wirklich gefahr sich auf einem immer geringeren gemeinsamen Nenner herunter zu schrauben. Und das hilft niemand.
@@ycalan Natürlich. Ich bin jetzt instinktiv von einem ansonsten funktionierendem Team ausgegangen, was sich über Pair-Programming, Retros und Code-Reviews auf dem nahezu gleichen Level hält. Wenn das nicht gegeben ist greift mein Argument nicht und es gibt ganz andere Probleme.
@@dsb4372 stimmt. Allerdings sehe ich in solche Mechanismen auch die Gefahr, dass das Team sich mit der Zeit immer mehr Richtung 'kleinster gemeinsamer Nenner' bewegt, was teilweise vielleicht auch erklären könnte warum in der Branche seit einiger Zeit die Reise nach Jerusalem gespielt wird...
Das gute alte Over-Engineering. Ich habe auch so Kollegen, da weiß man genau, bis zu welcher Seite sie an dem Tag in ihrem Design Patterns-Buch gelesen hatten. Schlimm finde ich auch die inflationäre Verwendung von Dependency Injection (also IoC-Frameworks, die ohne Reflection gar nicht funktionieren können) ohne echte Notwendigkeit.
Hi Golo! Ich stimme deiner Ausführung voll und ganz zu. Du hast es wirklich wieder mal auf den Punkt gebracht und ich fühle mich auch so ein wenig ertappt. Ja, es mag stimmen, dass alle erfahrerenen Entwickler zu komplexerem Code tendieren. Ich habe allerdings über die Jahre (ca. 15 am Stück) meine Erfahrungen sammeln können in dem Thema: Zu Beginn tendierte ich auch immer mehr dazu, wahrscheinlich aufgrund von Wissbegierigkeit, immer ausgefeilteren und "besseren" Code zu schreiben. Ich wollte eben auch alle Sprachmerkmale verstehen und diese auch gleich anwenden. Mit der Zeit stellte es sich heraus, dass ich nach und nach, sobald Code adaptiert werden musste, Schwierigkeiten hatte, diesen einfach zu adaptieren auf neue Anforderungen. Und das obwohl ich mir damals, als ich den Code vor 5 Jahren geschrieben hatte, gemeint hätte, dieser sämtliche neue Anforderungen abdecken wird. Gut, es ging dann schon irgendwie, nur dieses "irgendwie" machte mich schon damals stutzig und es brauchte auch eine Weile, bis ich ans Ziel kam. Also nach ca. 15-20 Jahren Coding-Erfahrung im Berufsumfeld mag ich meinen, dass ich bereits nach den ersten 5 Jahren mich dann eher auf die konservative Seite geschlagen habe und damals sogar das injection (ioc) pattern abgelehnt hatte. Ich hatte allerdings damals noch nicht die Erfahrung, dass IoC tatsächlich etwas gutes in sich habe könnte, weil ich Frameworks komplett ausgeblendet hab. Momentan mag ich Frameworks schon sehr; v.a. Spring Boot. M.E. ein sehr gelungenes Framework im Großen und Ganzen. Wo ich heute noch hadere ist, wenn es darum geht, asynchronen Code in Java zu schreiben. Zumindest in der Hinsicht, dass man Patterns verwendet, die augenscheinlich im Code-Ablauf hintereinander stehen, dann durch irgend eine compiler-magic in irgendwas anderes transferiert werden und man auf diese Weise prozedurale Vorgehensweisen, die eigentlich nicht asynchron sein müssten aufgrund der Anforderung an einen bestimmten Code-Abschnitt, nicht mehr ordentlich lesen kann. Davon abgesehen, dass man solchen Code auch nicht mehr ordentlich debuggen kann m.E. Aber dann gibt es eben auch die Kehrseite der Medaille. Ich habe schon oft erlebt, dass Leute z.B. mit Spring Boot hantieren und dann mehr und mehr static code schreiben, weil cyclic dependencies auftreten. Das hat m.E. auch kein zielführendes Ergebnis mehr. Normalerweise müsste man den Code neu strukturieren bzw. überdenken (stimmen die Abhängigkeiten? Sind die Abhäängigkeiten wirklich gut strukturiert?). ALternative ist, dass man lazy initialization verwendet; was m.E. immer noch besser ist, als statischen Code zu schreiben. Der Code wird immer mehr statisch und schlussendlich, wenn ich mich aus dem Fenster lehnen darf, muss man auch kein Framework mehr verwenden, wenn man nur mehr statische Methoden hat, aller Parameter weiter reicht usw. Dann genügen auch Maven- oder Gradle-dependencies und ein statisches Molloch.
Tolles Video und vor allem sehr anschaulich erklärt! Die Situation habe ich auch schon sehr oft kennengelernt und finde es gar nicht so im einfach, damit immer gut umzugehen. Man blickt auf Code ja meist aus der Sicht von sich selbst.
Mir ist aufgefallen, dass der Punkt kotkommentierung, sorry codekommentierung, hier überhaupt nicht angesprochen wurde 😆. Best Practice über alles und es wird auch das Verständnis derjenigen, die noch auf B1 Niveau sind und dazu lernen wollen, anheben. dieses RUclips Skript wurde ja auch nicht auf a2 Sprachniveau aufgenommen. So sollte man auch in der Programmierung jedem dem seinem Kenntnisstand entsprechend zugestehen, seinen Code zu schreiben.
Das leidige Thema "OverEngineering" Ich habe mich selbst auch schon öfters dabei ertappt, dass ich (in C#) versucht habe, jede Funktion in eigene Assemblies auszulagern und unzählige Interfaces definiert etc. Um dann am Ende festzustellen, dass ich die verschiedenen Assemblies doch jeweils nur an einer Stelle verwende und diese doch immer gleichzeitig aktualisiere etc. Und meiner Erfahrung nach, ist es (zumindest in C#) häufig einfacher, einzelne Komponenten nachträglich auszulösen und getrennt weiter zu entwickeln, als von Anfang an alles getrennt aufzubauen
Bravo, volle Zustimmung. Du hast das Strategy -Pattern erwähnt, und da kommen mir Erinnerungen an frühere Teamkollegen hoch. Sie pflegten es, möglichst jedes neue Problem oder Feature mit einem oder mehreren kombinierten GoF -Patterns zu lösen. Meine deutlich knapperen straight-forward Lösungen wurden daher nur allzu gerne rejected in den Reviews. Ich bin dann schnell wieder weg, weil mir das wirklich unsinnig schien.
Ich fühle mich richtig ertappt von deiner Message :D mir ist aufgefallen, dass ich diese Dinge getan habe, aber seitdem ich andere Verantwortlichkeiten habe finde ich nicht mehr die zeit dafür simplen code so zu verkomplizieren. Also könnte man im nachhinein sagen ich war mit simplem Problemen unterfordert und habe versucht mich anderen Konstrukten zu widmen, auch um neue Dinge zu lernen. Jetzt wo ich weniger Zeit habe greife ich einfach zu der simpelsten und schnellsten Lösung.
Probleme nachvollziehbar zu lösen ist eine hervorragende Eigenschaft eines Entwickles. Es geht so schnell dass bestimmte Feature einer Sprache nicht mehr unterstützt/entfernt werden oder der Entwickler es elegant in einer Zeile lösen wollte und so die Tore zu einem unwartbarem Softwarelabyrinth geöffnet wurden aus dem es kein Ausweg mehr gibt. Jetzt braucht man natürlich den 10x Entwickler mit einem hohen Developer Velocity Index und eine Contribution Analysis um die Kreativität schon im Ansatz zu ersticken 😂
Ein schwieriges Thema, für welches wohl die Aussage "es kommt drauf an ..." wieder mal passend ist. Das Problem ist meistens, dass man zum Zeitpunkt der Erstellung nicht weiß ob an der Stelle eine spätere Erweiterbarkeit notwendig ist. Im Zweifelsfall sollte man es einfach halten, gut kommentieren und später kann man es immer noch komplexer gestalten, falls notwendig. Schöne Woche!
Bin bei diesem Thema ganz bei dir. Ich arbeite häufig mit jüngeren Kollegen zusammen, die super komplexen Code schreiben, den ich mit meiner "Erfahrung" kaum noch verstehe. Das sorgt dann bei mir für Frustration, wenn ich dann mit dem Code arbeiten muss. Die Einarbeitungszeit ist hoch, für die Features oder Bugs die es zu ergänzen gibt. Häufig sind die Lösungen dann "goldene Wasserhähne", die alle Eventualitäten abdecken. Ein Aspekt, der mir in dem Video gefehlt hat oder zumindest nicht so deutlich war. Ja, wir wollen Code so schreiben, dass er flexibel und erweiterbar sein kann. Aber es muss es doch nicht immer sein. Wenn es die Anforderung gibt, dass der Code nur unter 3 Betriebssystemen laufen können soll, dann reicht doch auch einfach ein switch.
Mein Senf hierzu: Es gab mal ein Video von dir, in dem hast du gezeigt wie man mit Typescript Testdriven Development angeht. In diesem Video hast du einen sehr schönen, lesbaren Code geschrieben und ich dachte mit „Wow, toll!“ Dann hast du gesagt „jetzt kann man noch hier die Klammern wegkürzen, dadurch kann das Return entfallen, und überhaupt kann man noch hier und da und guck mal dahinten“… Und ich so: WTF!! DS war hinterher überhaupt nicht mehr einfach lesbar. Ich denke du meinst genau Situationen wie diese! 😉🥳
jain, was er da gemacht hat war refactoring. Eben Code der nicht nötig ist zu entfernen wenn damit das gleiche Ergebnis erzeugt werden kann. Also Komplexität verringern. Hier spricht er von Komplexität die von vornherein implementiert wird nur weil es fancy aussieht. In dem Beispiel würde das refactoring dann wieder zu mehr lesbaren und einfacheren Code führen um am Ende das gleiche Ergebnis zu erzielen.
Danke! Gerade wenn man polyglott unterwegs ist, erkennt man, wie oft und in welchem Maße „overengineered“ wird. Oftmals sprechen die Principal Engineers von „wartbarem Code“. Lustig wird‘s aber, wenn die Funktion komplett in eine andere Richtung verläuft oder die Applikation schon wieder Legacy ist.
Für einfache Dinge mag das besser sein. Nach ein paar Monaten hat man aber genau wieder den Fall: viele ifs, verschachtelte Code, alles in einer Datei und plötzlich ist der Code nur noch mit hohem Bug Risiko wartbar. Spätestens dann hätte man sich gewunschen jemand hätte ein paar patterns verwendet.
Ich habe das Gefühl, dass es bei mir genau anders herum ist. Früher habe ich sehr schwer verständlichen Code geschrieben, weil ich ihn verstanden habe. Ich dachte andere würden es ja dann auch verstehen können. Meine Devise heute ist: jede Person/Team sollte austauschbar sein. Somit muss alles sehr einfach gehalten sein. Wenn dann etwas komplex/kompliziert ist, muss dahinter auch etwas stecken.
Gerade die ganzen Abstraktionen und Impliziten Annahmen machen es echt hart beim Code durchzusteigen. Ein bischen hilft es, bei Visual Studio Code den Definitionen zu folgen. Auch dazu hätte ich gerne ein Video. Also Strategien um Zusammenhänge zu verstehen, Werkzeuge um Abhängigkeiten darzustellen.
Um ehrlich zu sein, würde ich sagen: Wenn Code voll mit Abstraktionen und impliziten Annahmen ist, nützt Dir auch das beste Tooling nichts (oder nicht viel), denn dann liegt das Problem nicht auf der technischen Ebene, sondern - und das hört sich jetzt eventuell böse an - in schlecht geschriebenem Code.
Die Aufgabe war - zu prüfen, auf welchem System man läuft und im Falle nicht unterstützen Betriebssystems eine Meldung auszugeben. Die erste Variante löst das Problem, die zweite ein wenig kompliziert, aber doch - auch. Die dritte Variante ist vollkommen unkorrekt. Denn es geht bei der gestellten Aufgabe nicht darum, eine passende exe zu erzeugen, sondern das passende Betriebssystem zu ermitteln. Und das kann nicht nur aus technischen Sicht relevant, sondern aus strategischen bzw. fachlichen Sicht. Z.B. das Management möchte nicht ein bestimmtes OS supporten. Danke für das Video!
Was ist wenn ich im dritten beißpiel viel code der os spezifisch implementiert wird nicht mit ausliefern will, dann würde das ganze warscheinlich sinn ergeben? Oder wäre das auch wieder nicht ratsam da die paar mb mehr im fertigen binary keinerlei rellevanz haben?
Bei so einem einfachen Beispiel, hätte ich überhaupt keinen Ehrgeiz zu zeigen, dass ich es besser kann. Es kommt aber auf die Umgebung an. Wie groß ist die gesamte Codebasis? Wie sind die Abhängigkeiten mit dem Rest der Umgebung? Kann ich meinen Kollegen vertrauen, dass sie den Code neu strukturieren, wenn die Komplexität doch höher ist als erwartet? Oder erwarten sie gar von mir, dass ich ein Muster hinterlasse, das sie nur erweitern müssen? Wie groß schätze ich überhaupt die Wahrscheinlichkeit, dass hier die Komplexität wächst? Wie sicher bin ich mir in meiner Schätzung? Variante 3 gibt eigentlich vor Allem dann Sinn, wenn man wirklich platformspezifischen Code ausführen will. Wenn das nicht absehbar ist, kann man einfach anfangen und später immer noch zu Variante 3 wechseln. An für sich handelt es sich nicht um komplexe Sprachmerkmale. Wenn man sich das Beispiel mit C aus den Kommentaren anschaut, liest sich die C-Fassung von Variante 3 wie Allerweltscode. Komplexität ergibt sich allerdings noch bei der Integration ins Buildsystem. Idealerweise würde man es bei so einem trivialen Beispiel mit der einfachsten Fassung belassen, wenn man vertrauen kann, dass der Code im Zweifelsfalle neu geschrieben wird. So wie er mit seinen trivialen einzeiligen Handlern da steht ohne eine komplexe Umgebung, absehbare Änderungen, irgendwelche Abhängigkeiten, platformabhängigen Code, gibt es überhaupt keinen Grund, allzu viel zu machen. Ich glaube, dass ich dem gegenüber in einem Review gelassen gegenüberstehen würde. Ich bin bestimmt nicht ohne Grund ein Freund von Python, dass viel Eleganz bietet, aber nicht die extremen Möglichkeiten an Tricks, die Perl anbietet. Mit der Kürze ist da einiges falsch verstanden worden. Sicherlich ist kurzer Code in vielen Fällen besser, klarer und weniger redundant. Die reine Anzahl der Zeilen war aber nie ein Maß für seine Qualität/Wartbarkeit/Lesbarkeit - sondern (höchstens) korreliert. Bezüglich Sprachfeatures sage ich mal das Folgende: Viele neue Sprachfeatures werden nicht unbedingt von den erfahrenen Kollegen verwendet, sondern von denen, die gerade lernen, weil es einfach so gelehrt wird. Dann muss das bewerten, ob es nützlich ist. In vielen Fällen ist das Richtige für mich, es mir abzuschauen, weil es nützlich und lesbar ist. Die eigentliche Frage lautet: Warum wird Code viel öfter einfach neu geschrieben, wenn sich Grundannahmen geändert haben?
Beißpiel: ich implementiere einen db client in node. Der db client wird sich immer zur gleichen db verbinden. Nun könnte man: - Die db connection parameter als globale variablen definieren - eine singleton klasse erstellen und die parameter der connect methode mitgeben - eine standard klasse erstellen und die parameter in konstruktor übergeben - Eine connect funktion erstellen und da die Parameter übergeben Jeder js entwickler wird den code mit der funktion / den globalen variablen verstehen wenn er vorher nur kleine Skripte gebaut hat Jeder Entwickler mit OOP Background wird die anderen Varianten wählen. Ich persönlich mag keine globalen Variablen diese reichen hier aber komplett aus. Wie schreibe ich den Code nun?
Hallo Golo, danke für dieses Video. Ja Du hast recht, es soll einfach sein, aber es soll auch erweiterbar sein. Und hier trennt sich die Spreu vom Weizen. Die Aufgabe, bzw. das Problem beschreibt den Weg. In Deinem Fall, mit den Betriebsystemen, gebe ich Dir vollkommen Recht. Es ist überschaubar, es bleibt kurz und, Erweiterungen werden nicht so schnell kommen, denn die Handvoll von Betriebsysteme wird jetzt nicht monatlich oder täglich länger. Anders sieht es mit cases aus, die eine hohe Fluktuation haben oder regelmäßig erweitert werden müssen. Dann streckt Deine Switch Funktion schnell alle 4re von sich. Bei Tausend Fällen, wird die Switch-Anweisung total unüberschaubar. Gerade im Clean Code Bereich werden genau Deine folgenden Beispiele verwendet, weil sie sicherer sind und das Switch-Case für größere cases als böse gesehen wird. Nicht ohne Grund, wohlgemerkt. Mit dem Strategy Muster bist Du sogar Typesicher, vorausgesetzt Du benutzt OOP. Muster haben ihre Berechtigung, und wer sie kennt, der wird sie lesen können. Aber Zuviels Muster können es versauen. Meine häufigsten Muster sind Strategy, Fassade, Factory. Von Dispose, MVC, MVVM, aaa etc. mal abgesehen. LG Marcus
Ein Anfänger der Programmierung schreibt einfachen Code, weil er es nich anders kann. Ein fortgeschrittener Programmierer schreibt komplizierten Code, weil er es kann. Ein Profi schreibt einfachen Code, weil er es kann. Die Kunst ist es, einfache Lösungen für komplexe Probleme zu finden. Der Code sollte so sein, dass jeder, der drüber schaut, sagt: Jou, genau so hätte ich es auch gemacht. Als Entwickler sollte man im Code nicht ausdrücken, welche Features ich kenne, sondern das Problem eben so einfach wie möglich lösen.
Mein erster Gedanke war das erste Beispiel. Beim zweiten Beispiel dachte ich mir: Ja … ich verstehe … wenn ich noch mal darüber nachdenke, ist das eventuell die bessere Lösung. Beim dritten Beispiel dachte ich mir: Okay, das wäre in nahezu jedem Fall vollkommen übertrieben. Es ist jederzeit möglich, das Tool entsprechend umzubauen, wenn es valide Gründe dafür gibt. Das sage ich mir, wenn ich ins "Overengineering" falle. Ich musste es aber auch erst du eine Therapie lernen.
Ich sage immer: Kommt drauf an. "It depends." Wenn wirklich von vornherein klar ist, daß es nur um diese kurze Aufgabe geht und es ein Shoot-and-forget-Projekt ist, dann die erste Variante. Ansonsten würde ich in jedem Fall den Ansatz bedingter Compilierung verfolgen, nur mit möglichst geringer Komplexität. Ich kenne mich mit Go überhaupt nicht aus. Aber in anderen Sprachen würde ich einen Funktionsnamen aufrufen und die Funktion in mehreren bedingt compilierten Dateien deklarieren/definieren (bei C zentral deklarieren). Dafür brauche ich keine Zeiger, auch wenn ich sie für viele Zwecke toll finde. Der Aufgabenformulierung nach wäre das falsch. Ich würde aber nachfragen, welche Intention die Aufgabe verfolgt. (XY-Problem). Ein C/C++ Programmierer, der keine Präprozessoranweisungen versteht, ist sowieso lost. Mit dem Argument, irgend jemand könnte ein Sprachfeature nicht verstehen, dürfte ich bald keine for-Schreife mehr schreiben. Oder man müßte switch/case kritisieren, für Einsteiger ist doch if-then-else viel verständlicher.
@@pinkeHelga aber genau das ist meiner Meinung nach oft das Problem 'WENN WIRKLICH VON VORNHEREIN KLAR IST....' es ist in der Praxis fast nie der Fall. Andererseits will man auch nicht von vornherein unnötige Komplexität einführen. Es ist wirklich schwierig, gerade WEIL man erfahren ist, und mehrere Möglichkeiten kennt, sich für die langfristig richtige Lösung zu entscheiden. Und damit sind wir beim Golos Thema in diesem Video...
@@pinkeHelga Ich musste in meiner Laufbahn schon einige Tools schrieben die ähnlich trivial sind wie im Video gezeigt. Wenn ich Sprachen und Sprachfeatures verwende die jeder verstehen sollte der Excel Formeln schreiben kann, erhöhe ich auch die Anzahl an Personen die das Tool anpassen kann. Also schon alleine aus egoistischen Gründen würde ich die einfachste und "dümmste" Lösung verwenden. Hier geht es natürlich nicht um komplexe oder kritische Programme. Sobald ein Programm ressourcenkrisch ist, werden solche Lösungen valide. Aber ob triviale Tools nun 1ms oder 50ms Laufzeit haben oder ob sie 10kb oder 10mb groß sind, interesiert meistens niemanden.
@@Marcel-dr5pb Das betrachte ich als zu kurz gedacht. Reißerische Titel führen erst mal zu mehr Aufrufen. Man merkt aber bewußt und unbewußt, daß man nur geködert wurde und nicht den Inhalt bekommt, den man gesucht hat. Viele Kanäle, die mir die Selektion der Inhalte, die ich wirklich sehen will, schwer machen, ignoriere ich schon immer mehr, denn sie rauben meine Zeit und Konzentration. Wenn ich merke, daß ich Benachrichtigungen längere Zeit immer überspringe, klicke ich noch einmal, um zu deabonnieren. Das ist wie mit Drückerkolonnen. Zum einmaligen Geschäft läßt man sich vielleicht noch überrumpeln, aber danach macht man einen weiten Bogen drumrum; es entsteht kein Folgegeschäft. Weiterhin bringen nichtssagende Titel allenfalls etwas zum Zeitpunkt der Veröffentlichung. Wenn ich nach Monaten/Jahren Suchbegriffe eingebe wie "Komplexität, Abstraktion, Design Pattern", werde ich eher andere Videos finden, bevorzugt solche, die die Stichworte auch im Titel enthalten und zusätzlich noch in Beschreibung.
Das zweite Beispiel wäre das beste, wenn es denn nicht absichtlich so redundant wäre, um den Punkt rüberzubringen. Eine eigene handler Funktion ist doch völlig übertrieben, wenn in jedem Beispiel ein println gemacht wird. Und ich glaube eine key=>value Struktur sollte jeder Entwickler kennen. Es reicht also eine Funktion, und im der map steht der os key und der menschenlesbare String des OS als value drin.
Top Video! Dennoch wollte ich anmerken, dass es einen großen Unterschied zwischen "einfachen" und "schlechten" Code gibt. Es ist zum Beispiel immer einfacher KEINE Viewmodels zu nutzen und direkt das Model an den Client auszugeben, ABER ist halt wenn a) doof wenn viele Properties nicht gebraucht werden b) sicherheitsrelevante Infos drin sind c) du mit Typescript arbeitest und dein "type" anders aussieht Was ich damit eigentlich sagen will: Der Weg zwischen "einfachem" und "dummen" Code ist sehr, sehr schmal ^^
In privaten Projekten neige ich auf jeden Fall dazu mehr zu machen und am Ende ist es ein durcheinander und nichts dokumentiert 😇. Auf der Arbeit neige ich dazu, so wenig wie möglich und so viel wie nötig zu machen. Natürlich dokumentiert, dass jemand anderes mit dem Code auch etwas anfangen kann.
Hallo Golo, grundsätzlich bei einfachen Dingen magst du recht haben. Kommen wir aber im professionellen Umfeld an Grenzen müssen wir zu abstrakteren Lösungen greifen. Der Punkt den man herausstellen sollte ist doch ob das Team welches die Software entwickelt diesen auch über lange Zeit versteht. Und da richtig helfen einfache Lösungen. Daher ist jede Lösung richtig und auch nicht die richtige. Ich wette es gibt noch andere Lösungen und es sieht danach aus als wäre Variante 3 die schlechteste. Ist aber auch wieder ein Denkfehler meiner Meinung nach. ;)
Klingt vielleicht doof - aber ich hätte es nach der ersten Variante programmiert. Warum? Nicht weil ich keine Erfahrung habe. Aber die von Dir gestellte Aufgabe mit dem gewünschten Ergebnis war klar und deutlich formuliert. Warum sollte ich da mehr schreiben als nötig :-)
Einfacher Code ist elegant. Es ist aber so, dass die Dosis das Gift macht. Ich arbeite im Moment an einem Projekt, das >5 Mio. Zeilen Code und >40k Klassen hat. Ein paar Level von Abstraktion haben durchaus das Potenzial, eine so rießige Codebase einfacher verständlich zu gestalten.
Mal ab vom Click-Bait-Titel des Videos (hat bei mir dennoch funktioniert), fühle ich mich etwas erwischt. Muss mir mal meine letzten Sourcen anschauen. Ich hab hin und wieder selbst das Problem, dass ich zwar meine super guten lesbaren und wartbaren Code zu schreiben, aber nach der Rückkehr zu solchem Code nach einiger Zeit habe ich manchmal selber Fragezeichen im Kopf, warum das so abstrakt ist. Sieht zwar oft richtig aus, aber auch oft irgendwie nicht „natürlich“.
Völlig einverstanden mit Golo - gute Lesbarkeit ist das Ziel. Ich habe schon Code aus dem Jahr 1985 gesehen der heute (2023) noch läuft und man stelle sich vor, die Kolleg:innen von damals hätten unnötig komplexen Code geschrieben - den versteht dann nach 40 Jahren keiner mehr, weil einfach keiner des ursprünglichen Teams mehr da ist und der Code schon durch 100 Hände gegangen ist...
Ich fühle mich mit diesem Video sehr ertappt :D Jedoch würde mich interessieren wie man in der Praxis den optimalen Weg findet. In deinem Beispiel ist das mMn sehr einleuchtend, dass man sich für eine einfach Lösung entscheiden sollte. Wie sieht es jedoch bei Anwendungen aus, die komplexer sind? Wie kann ich abwägen, ob eine simple Lösung den Anforderungen nicht mehr entsprechend ist? Mir ist klar, dass es da keine allgemeine Antwort gibt, aber vielleicht kennt jemand gute Strategien oder Best Practises. Vielleicht wäre das ja auch eine Idee für ein Folgevideo :)
Puhh. Ich gehe da leider kaum mit. Gut bei der 3. Option könnten wir uns eventuell noch einig werden - die ist schon verdammt Komplex. Aber für mich muss Code vor allem Wartbar sein. Und eben das ist bei Version 2 am ehesten gegeben. Code 3 ist am "Effizientesten" und Code 1 ist am Anfängerfreundlichsten. Wir als Entwickler die Code für teils große Unternehmen schreiben wollen natürlich möglichst Wartbaren, Getesteten und Effizienten Code haben. Weg 1 löst das Problem, ist halt aber absolut nicht Wartbar und muss vollständig entfernt werden wenn die Komplexität steigt. Klar könnten wir jetzt sagen - der Use-Case ist doch aber nur OS Anzeigen. Aber eigentlich in jedem Projekt wächst Code organisch und wird komplexer. Jetzt ist bestimmt die frage offen: "Wie soll den so ein einfacher Code wachsen?" - Der Use-Case geht halt nach 2 - 3 Jahren weiter. Jetzt wollen wir die 4 neuen Betriebssystem mit rein nehmen, den genauen OS Namen haben (kUbuntu, Ubuntu, Debian, Mint, Vista, 7, 10 ...) und dann am besten mit dem jeweiligen Packet Manager noch irgendwas nach installiert. Möglichkeit 2 / 3 hätte uns das gleich erlaubt. Möglichkeit 1 wird da nicht mehr nutzbar.
Mein erster Gedanke war ja: "was passiert, wenn ich die Windows.Exe unter Linux ausführe, oder anders herum". Ist ja inzwischen kein Problem mehr. Da würde die dritte Lösung in jedem Fall scheitern. Aber das ist sicher nicht das Thema des Videos. Ansonsten sehe ich auch die Gefahr, dass man sich nicht mehr weiter entwickelt, wenn man immer nur die alten bekannten Konstrukte verwendet oder im Team alles auf dem kleinsten gemeinsamen Nenner bleibt. Aber ich denke, so ist Golos Beitrag auch nicht gemeint.
Hallo @thenativeweb - Golo zu nächst einmal ist die erste Variante des Codes sehr trivial. Ohne lange darüber nachdenken zu müssen und ohne Kenntnisse zur Sprache selbst versteht man den Inhalt relativ schnell. Ich sehe beim Verkomplizieren von Code vor allem die Gefahr darin, dass man auf kurz oder lang Schwierigkeiten bekommen kann. Entweder es geht um Bugs, die entfernt werden müssen oder das System soll erweitert werden. Je mehr Technologien verwendet werden, die teils völlig sinnlos sind, desto eher stellt man sich und anderen ein Bein, weil man eben sich und anderen die Arbeit schwerer macht. DIe Fogle ist, dass man sich über den bestehenden Code ärgert, frustriert ist anstatt sich auf das Wesentliche zu konzentrieren und produktiv zu sein. Weniger ist machmal einfach mehr.
Für mich ist das wichtigste Prinzip DRY - Don't repeat yourself - gefolgt von Kohäsion. Nicht selten mußte ich Projekte refaktorisieren und dabei viele Tausend Codestellen interaktiv suchen und ersetzten. Wenn sich ein Problem wiederholt, braucht es einen Namen. Das bekommt man schon bei prozeduraler Programmierung hin. Nun kann das wieder dazu führen, daß ein sehr ähnliches Problem auftaucht, wo der benannte Code nicht richtig greift; er bräuchte zusätzliche Parameter, kleine Abwandlungen. Man hat irgendwann Catch-All-Funktionalität wahnsinnig abstrakt. Das ist ein Zeichen, daß der Code weiter in Teilprobleme zerlegt werden muß, aus denen sich zwei leicht unterschiedliche Problemlösungen formulieren lassen. Das Problem in der Entwicklung ist, daß man gerne von Anfang an alles abstrahiert. Häufig hört man Aussagen: "Wir verwenden Design Patterns ja nicht ohne Grund. Es hat sich gezeigt, daß dadurch besser testbarer und wartbarer Code entsteht." Naja, dem kann man schwer widersprechen. Oft ist es aber gar nicht nötig. Sinnvoller ist meist, zunächst funktionierenden, verständliche Code zu entwickeln und von Beginn an zu refaktorisieren. Wenn man feststellt, daß tatsächlich Bedarf für eine Abstraktion entsteht, kann man das möglichst frühzeitig anpassen. Parallele dazu sollte auch eine Dokumentation gepflegt werden, sodaß Code lesen gar nicht mehr nötig ist. Code muß so verwendbar sein, als handelte es sich um eine compilierte Bibliothek ohne Open Source, z.B. eine DLL. Polymorphismus steht heute in der Kritik, meiner Ansicht nach zu Unrecht. Das war die Idee von OOP. Ansonsten kann ich auch mit Records/Verbundtypen und Funktionen arbeiten. Und lose Kopplung per Interfaces wird mir zu exzessiv eingesetzt. Interfaces bedeuten für mich schon Code-Smell, daß die Klassenhierarchie unvorteilhaft gestaltet ist. Die haben eher nur für völlig allgemeine Designs wie Listen etc. ihre Daseinsberechtigung, stellen aber meist mehr einen Workaround für (möglicherweise in der Sprache fehlende) Generics dar.
@@pinkeHelga Refaktorisierung ist ein wichtiges Thema. Es kommt immer wieder vor dass sich Anforderungen ändern, Strukturen nicht mehr passen oder man auf bessere Lösungsansätze stößt. Ich bin mir da nicht zu schade Code wegzuschmeißen auch wenn ich vorher in die Entwicklung viel Zeit investiert habe. Projekte in denen man dies nicht tut laufen Gefahr zu Frankensteins Monster zu verkommen. Die Weiterentwicklung wird aufgrund der Altlasten immer komplizierter und aufwändiger. Ich hatte beruflich selbst mal mit so einem Projekt zu tun das drei Entwicklerteams aus drei Ländern hinter sich hatte und strukturell so kompliziert geworden ist dass eine effiziente Weiterentwicklung nicht mehr möglich war.
@@emgalaxy6576 und genau das trennt hochwertigen vom schlechten Code. Ich finde, alle Technologien mit denen man im Lauf seiner Entwicklerzeit zu tun hat, ergänzen lediglich den "Werkzeugkasten". Das jeweilige Projekt, welches umgesetzt werden soll, ist dann die Messlatte, woran man sich orientiert, sprich welche konkreten Technologien bentzt werden sollten und auch angemessen sind. Aber das Sie sich nicht zu fein sind, bereits geschriebenes wieder zu entfernen ist beeidruckend. Als Student bin ich zwar noch nicht in dieser Situtation gewesen, aber es zeigt, dass Sie Veränderungen nicht ablehnend gegenüber stehen und zur Not auch konsequent sind.
Aber dafür brauchen wir doch einen Microservice und den Kubernetes Cluster dazu auch gleich... Und die Testabdeckung ist auch Mist, der Code ist gar nicht Testbar und TDD wurde auch nicht beachtet. Auch muss sowas asyncron mit Messages Queues und Event Sourcing passieren! Natürlich mit Prometeus und Grafana, Elektron Logs uvm. sonst ist das ja kein Richtiger Kubernetes Cluster. Am Besten noch Camunda also Microservice Orchestrator Ich wünschte das wäre Sarkasmus aber bei Großkonzernen sieht man das häufiger.... KISS wird mittlerweile sehr kreative weise ignoriert. Aber wenn man einen Microservice dafür einführt und einen Kubernetes Cluster, braucht man mehr Entwickler und der Chef bekommt mehr Gehalt weil er für mehr Angestellte verantwortlich ist. Und als Entwickler ist Kubernetes für meinen Lebenslauf gut! Edit: Clickbait ist der Titel trotzdem, wie wäre es mit KISS lässt grüßen?
Da fällt mir das Sprichwort ein: mit Kanonen auf Spatzen schießen. Man knallt sich auch keine Valium wegen einem Mückenstich. 😂 also ich glaube, dass man mit:- so einfach, wie möglich und komplex wie nötig - am besten fährt. Spart ja auch Gehirnschmalz und Energie. 😊
Ich habe einen Kollegen bei mir, den ich als nicht als sehr erfahren ansehe. Er hat immer den Tick, Code zu verkomplizieren und über das Ziel hinauszuschießen. 😂😂
Geben auch mal meinen Senf dazu (komme von C++ und kenne go nicht): Das Argument Sprachmittel nicht zu verwenden, nur weil es ein höheres Wissen der Sprache darstellen oder ungewöhlich ist, kann ich nicht unterstützen. In einem Team würde ich nicht auf den "schwächsten" Rücksicht nehmen, er/sie kann ja was neues lernen. Wenn durch ein tolles Sprachmittel der Code besser (wie auch immer definiert) wird sollte ich das verwenden. KISS Ansonsten geht immer Compilerfehler vor Laufzeitfehler. Alles was der Compiler oder Linker erkennen kann sollte ich ausnutzen, auch wegen der Performance. Ich würde eine einfachere Version von 3 verwenden. Stumpf eine gleichnamige Funktion für jedes OS mit Compilerschalter (wenn es sowas in Go gibt.). Der Linker sagt schon, was fehlt.
Wenn man Lesbarkeit mit Dokumentation gleichsetzt (also so ungefähr…), dann entsteht mehr wie in diesem Beispiel. Pi mal Daumen - es gibt nicht DIE Lösung.
Interessant: ich verdiene mein Geld seit 1986 mit Softwareentwicklung und wäre nie auf eine andere Idee gekommen, als es anders als mit einer Case-Struktur zu lösen... :-/
Lieber Golo, du stellst hier eine sehr interessante Frage. Bei diesem speziellen Beispiel tendiere ich ganz klar für die dritte Lösung. Es macht für mich wirklich keinen Sinn Code zu schreiben, der niemals ausgeführt werden kann. Bei ähnliche Beispiele tendiere ich für die Strategy-Pattern-Lösung. Und zwar deshalb, weil ich viel zu oft den Satz gehört habe 'Es muss ja NUR zwischen Pest und Colera entscheiden was er zu tun hat'. Binnen eines Jahres kommen wochentlich neue Krankheiten hinzu und der Code wird dadurch ausgerechnet für die erfahrenen Entwickler ein Buch mit 7 Siegeln. Und am Ende heißt es 'Bei dem was du mir hier kostest, kriegst du so eine einfache Aufgabe nicht gelöst'... 😂
In diesem speziellen Beispiel würdest du dritte Variante wählen? Für mich macht es, in diesem Beispiel, keinen Sinn komplizierte Sprachkonstrukte zu verwenden, die nur Entwickler dieser Sprache verstehen. Im Zweifel musst du als professioneller Entwickler immer dieses triviale Tool anpassen. Mit Lösung 1 kann das jeder, der halbwegs Ahnung von Scripting hat.
Bei der ersten These bin ich voll dabei. Ich sehe allerdings keinen Anlaß für zusätzliche logische Abstraktion. Vielleicht ist das speziell in Go nötig, da habe ich null Ahnung. In anderen Sprachen mit bedingter Compilierung würde ich einfach 3 Versionen der gleichen Funktion definieren und ggf. eine zentrale Deklaration der Signatur wie bei C/C++.
Meiner Meinung nach muss die technische Umsetzung der Anforderung angemessen sein… Die erste Lösung von Golo ist super simpel und der Anforderung perfekt angemessen. Alles andere ist jetzt Interpretationssache! Für mich ist da der YAGNI Ansatz zielführender als eine technisch ausgeklügelte Lösung!
Jedes deiner Videos ist wunderbar, das hier ganz besonders.
Danke für das Lob!
Auch wenn ich die Software-Entwicklung mehr als Hobby ansehe als für den Beruf konnte ich da jetzt schon einige Jahre damit verbringen.
Kurz: Version 1 ist für mich die klarer verständlichere und einfachere Art. Das wäre auch die, wie ich das einem der mich fragt vermitteln würde.
Du willst halt wenn jemanden der ein Problem hat einem schnell helfen das es Klick macht. Wie man dann das Wissen umsetzt und aufbläht ist danach ein anderes Thema, aber den Grundgedanken wie es funktioniert muss erstmal da sein. Meiner Meinung nach.
Stimmt. Verständnis und Einfachheit des Codes sind wesentlich wichtiger, als etwas völlig unnötig zu verkomplizieren. Vor allem kann man dann im Team besser arbeiten und man kann ihn selber später auch besser verstehen. Am Ende muss man die Lösung komplett neu machen, weil man die genutzten "Funktionen" so selten benutzt.
Die Lösung ist wirklich: Einfach und elegant halten.
Ich selbst bin seit einiger Zeit an dem Punkt, wo ich mich frage, ob Softwareentwicklung wirklich so kompliziert sein muss. Und oft liegt es meiner Erfahrung nach genau an dem hier beschriebenen Fehler. Lösungen werden unnötig kompliziert aufgebaut. Das beginnt mitunter aber schon auf der Fachebene, wo Anforderungen formuliert werden, die über den eigentlich notwendigen Nutzen hinausgehen, setzt sich fort in overengineerten Architekturen und endet letztlich in der Anwendung zu komplizierter Programmierkonzepte.
Es macht wirklich Sinn, sich die Frage zu stellen, was denn wirklich zur Lösung der Aufgabe notwendig ist und dann zunächst die einfachste Variante zu implementieren. Komplizierter kann man es dann später immer noch machen, wenn die Situation es erfordert.
Das richtige Maß zu finden, ist extrem schwierig. Wenn man an legacy Code arbeitet, wünscht man sich wesentlich mehr Abstraktion. Hat man abstrahierten Code vor sich, wird es oft schwierig, noch Änderungen ohne riesen Workarounds vorzunehmen, obwohl die Abstraktion eigentlich ja genau das ermöglichen sollte.
Großartiges Zitat von Chuck Moore, Erfinder der Programmiersprache Forth: "I'm never afraid of simplicity. Show me a simpler way do the things I'm doing and I will go for it."
Ein sehr wichtiger Punkt. Wir haben einen talentierten jungen Entwickler, der jedes neue sprachfeature nutzt… und uns ziemlich alt aussehen lässt :)
Er findet es dann einfacher und eleganter…
Elegant ist auch abhängig, ob man das Feature kennt….
Die Frage ist, ist es zu kompliziert oder sind wir zu dumm 😅😅😅
Nein, ihr seit nicht zu dumm. Aber dem jungen Entwickler fehlt die Erfahrung, daß es besser ist, einfacher zu entwickeln als jeden neuesten Hype zu nutzen. Er will sein Ego streicheln und euch damit alt aussehen lassen. Er entspricht daher einem Lehrling, der seine Lehrzeit abgeschlossen hat und jetzt als Geselle arbeitet. Bis zum wirklichen Profi und später einem Meister, ist noch ein langer Weg.
Ich habe meinen Entwicklern immer gepredigt, entwickelt so einfach wie möglich, die Komplexität kommt schon aus der Anwendung selbst, ihr braucht da nichts im Code hinzufügen.
Euer Superentwickler sollte mal ein Problem in einer anderen Programmiersprache lösen müssen, z.B. Lisp, Prolog, Fortran, C, Cobol, Forth oder Modula2.
Es hängt für mich nicht am konkreten Code, sondern am Team welches den Code warten und weiterentwickeln muss. Wer all die tollen, fancy technischen Lösungswege kennt, dennoch versucht, die Flughöhe des Teams zu berücksichtigen und für das Team verständlichen Code schreibt, ist mein eigentlicher Hero. Er kombiniert technisches Know-How mit sozialen Softskills. Ob eine Lösung gut oder schlecht ist, hängt für mich davon ab, ob das Team sie beherrschen kann. Nimm den Super-Hero, der für den Rest unverständliche Magie implementiert, raus und das Team beginnt zu fliegen.
So sehr ich dein Punkt auch teile, muss ich aber sagen, irgendwo setzte ich trotzdem eine Grenze. Ich will mich auch nicht dauernd mit dem Thema beschäftigen, ob meine Arbeit gut bei jedem Kollege ankommt und verstanden wird. Und ich erwarte auch nicht, dass die Kollegen dasselbe mit mir tun. Sonst läuft man wirklich gefahr sich auf einem immer geringeren gemeinsamen Nenner herunter zu schrauben. Und das hilft niemand.
@@ycalan Natürlich. Ich bin jetzt instinktiv von einem ansonsten funktionierendem Team ausgegangen, was sich über Pair-Programming, Retros und Code-Reviews auf dem nahezu gleichen Level hält. Wenn das nicht gegeben ist greift mein Argument nicht und es gibt ganz andere Probleme.
@@dsb4372 stimmt. Allerdings sehe ich in solche Mechanismen auch die Gefahr, dass das Team sich mit der Zeit immer mehr Richtung 'kleinster gemeinsamer Nenner' bewegt, was teilweise vielleicht auch erklären könnte warum in der Branche seit einiger Zeit die Reise nach Jerusalem gespielt wird...
Das gute alte Over-Engineering.
Ich habe auch so Kollegen, da weiß man genau, bis zu welcher Seite sie an dem Tag in ihrem Design Patterns-Buch gelesen hatten.
Schlimm finde ich auch die inflationäre Verwendung von Dependency Injection (also IoC-Frameworks, die ohne Reflection gar nicht funktionieren können) ohne echte Notwendigkeit.
Hi Golo!
Ich stimme deiner Ausführung voll und ganz zu. Du hast es wirklich wieder mal auf den Punkt gebracht und ich fühle mich auch so ein wenig ertappt. Ja, es mag stimmen, dass alle erfahrerenen Entwickler zu komplexerem Code tendieren. Ich habe allerdings über die Jahre (ca. 15 am Stück) meine Erfahrungen sammeln können in dem Thema:
Zu Beginn tendierte ich auch immer mehr dazu, wahrscheinlich aufgrund von Wissbegierigkeit, immer ausgefeilteren und "besseren" Code zu schreiben. Ich wollte eben auch alle Sprachmerkmale verstehen und diese auch gleich anwenden. Mit der Zeit stellte es sich heraus, dass ich nach und nach, sobald Code adaptiert werden musste, Schwierigkeiten hatte, diesen einfach zu adaptieren auf neue Anforderungen. Und das obwohl ich mir damals, als ich den Code vor 5 Jahren geschrieben hatte, gemeint hätte, dieser sämtliche neue Anforderungen abdecken wird. Gut, es ging dann schon irgendwie, nur dieses "irgendwie" machte mich schon damals stutzig und es brauchte auch eine Weile, bis ich ans Ziel kam.
Also nach ca. 15-20 Jahren Coding-Erfahrung im Berufsumfeld mag ich meinen, dass ich bereits nach den ersten 5 Jahren mich dann eher auf die konservative Seite geschlagen habe und damals sogar das injection (ioc) pattern abgelehnt hatte. Ich hatte allerdings damals noch nicht die Erfahrung, dass IoC tatsächlich etwas gutes in sich habe könnte, weil ich Frameworks komplett ausgeblendet hab. Momentan mag ich Frameworks schon sehr; v.a. Spring Boot. M.E. ein sehr gelungenes Framework im Großen und Ganzen.
Wo ich heute noch hadere ist, wenn es darum geht, asynchronen Code in Java zu schreiben. Zumindest in der Hinsicht, dass man Patterns verwendet, die augenscheinlich im Code-Ablauf hintereinander stehen, dann durch irgend eine compiler-magic in irgendwas anderes transferiert werden und man auf diese Weise prozedurale Vorgehensweisen, die eigentlich nicht asynchron sein müssten aufgrund der Anforderung an einen bestimmten Code-Abschnitt, nicht mehr ordentlich lesen kann. Davon abgesehen, dass man solchen Code auch nicht mehr ordentlich debuggen kann m.E.
Aber dann gibt es eben auch die Kehrseite der Medaille. Ich habe schon oft erlebt, dass Leute z.B. mit Spring Boot hantieren und dann mehr und mehr static code schreiben, weil cyclic dependencies auftreten. Das hat m.E. auch kein zielführendes Ergebnis mehr. Normalerweise müsste man den Code neu strukturieren bzw. überdenken (stimmen die Abhängigkeiten? Sind die Abhäängigkeiten wirklich gut strukturiert?). ALternative ist, dass man lazy initialization verwendet; was m.E. immer noch besser ist, als statischen Code zu schreiben. Der Code wird immer mehr statisch und schlussendlich, wenn ich mich aus dem Fenster lehnen darf, muss man auch kein Framework mehr verwenden, wenn man nur mehr statische Methoden hat, aller Parameter weiter reicht usw. Dann genügen auch Maven- oder Gradle-dependencies und ein statisches Molloch.
Tolles Video und vor allem sehr anschaulich erklärt!
Die Situation habe ich auch schon sehr oft kennengelernt und finde es gar nicht so im einfach, damit immer gut umzugehen.
Man blickt auf Code ja meist aus der Sicht von sich selbst.
Danke für das Lob und Dein Feedback - und das stimmt (leider), dass einfach nicht immer einfach ist 😉
Mir ist aufgefallen, dass der Punkt kotkommentierung, sorry codekommentierung, hier überhaupt nicht angesprochen wurde 😆. Best Practice über alles und es wird auch das Verständnis derjenigen, die noch auf B1 Niveau sind und dazu lernen wollen, anheben. dieses RUclips Skript wurde ja auch nicht auf a2 Sprachniveau aufgenommen. So sollte man auch in der Programmierung jedem dem seinem Kenntnisstand entsprechend zugestehen, seinen Code zu schreiben.
Das leidige Thema "OverEngineering"
Ich habe mich selbst auch schon öfters dabei ertappt, dass ich (in C#) versucht habe, jede Funktion in eigene Assemblies auszulagern und unzählige Interfaces definiert etc.
Um dann am Ende festzustellen, dass ich die verschiedenen Assemblies doch jeweils nur an einer Stelle verwende und diese doch immer gleichzeitig aktualisiere etc.
Und meiner Erfahrung nach, ist es (zumindest in C#) häufig einfacher, einzelne Komponenten nachträglich auszulösen und getrennt weiter zu entwickeln, als von Anfang an alles getrennt aufzubauen
Bravo, volle Zustimmung. Du hast das Strategy -Pattern erwähnt, und da kommen mir Erinnerungen an frühere Teamkollegen hoch. Sie pflegten es, möglichst jedes neue Problem oder Feature mit einem oder mehreren kombinierten GoF -Patterns zu lösen. Meine deutlich knapperen straight-forward Lösungen wurden daher nur allzu gerne rejected in den Reviews. Ich bin dann schnell wieder weg, weil mir das wirklich unsinnig schien.
Ich fühle mich richtig ertappt von deiner Message :D
mir ist aufgefallen, dass ich diese Dinge getan habe, aber seitdem ich andere Verantwortlichkeiten habe finde ich nicht mehr die zeit dafür simplen code so zu verkomplizieren. Also könnte man im nachhinein sagen ich war mit simplem Problemen unterfordert und habe versucht mich anderen Konstrukten zu widmen, auch um neue Dinge zu lernen. Jetzt wo ich weniger Zeit habe greife ich einfach zu der simpelsten und schnellsten Lösung.
Probleme nachvollziehbar zu lösen ist eine hervorragende Eigenschaft eines Entwickles.
Es geht so schnell dass bestimmte Feature einer Sprache nicht mehr unterstützt/entfernt werden oder der Entwickler es elegant in einer Zeile lösen wollte und so die Tore zu einem unwartbarem Softwarelabyrinth geöffnet wurden aus dem es kein Ausweg mehr gibt.
Jetzt braucht man natürlich den 10x Entwickler mit einem hohen Developer Velocity Index und eine Contribution Analysis um die Kreativität schon im Ansatz zu ersticken 😂
Lesbarkeit und Einfachheit sind das oberste Gebot beim Programmieren. Ich programmiere seit Anfang der 80er, also seit 40 Jahren.
Spricht mir aus dem Herzen. Und leider ja, schuldig im Sinne der Anklage. 😱
Ein schwieriges Thema, für welches wohl die Aussage "es kommt drauf an ..." wieder mal passend ist. Das Problem ist meistens, dass man zum Zeitpunkt der Erstellung nicht weiß ob an der Stelle eine spätere Erweiterbarkeit notwendig ist. Im Zweifelsfall sollte man es einfach halten, gut kommentieren und später kann man es immer noch komplexer gestalten, falls notwendig.
Schöne Woche!
Bin bei diesem Thema ganz bei dir.
Ich arbeite häufig mit jüngeren Kollegen zusammen, die super komplexen Code schreiben, den ich mit meiner "Erfahrung" kaum noch verstehe.
Das sorgt dann bei mir für Frustration, wenn ich dann mit dem Code arbeiten muss. Die Einarbeitungszeit ist hoch, für die Features oder Bugs die es zu ergänzen gibt. Häufig sind die Lösungen dann "goldene Wasserhähne", die alle Eventualitäten abdecken. Ein Aspekt, der mir in dem Video gefehlt hat oder zumindest nicht so deutlich war.
Ja, wir wollen Code so schreiben, dass er flexibel und erweiterbar sein kann. Aber es muss es doch nicht immer sein.
Wenn es die Anforderung gibt, dass der Code nur unter 3 Betriebssystemen laufen können soll, dann reicht doch auch einfach ein switch.
Mein Senf hierzu: Es gab mal ein Video von dir, in dem hast du gezeigt wie man mit Typescript Testdriven Development angeht. In diesem Video hast du einen sehr schönen, lesbaren Code geschrieben und ich dachte mit „Wow, toll!“ Dann hast du gesagt „jetzt kann man noch hier die Klammern wegkürzen, dadurch kann das Return entfallen, und überhaupt kann man noch hier und da und guck mal dahinten“… Und ich so: WTF!! DS war hinterher überhaupt nicht mehr einfach lesbar. Ich denke du meinst genau Situationen wie diese! 😉🥳
jain, was er da gemacht hat war refactoring. Eben Code der nicht nötig ist zu entfernen wenn damit das gleiche Ergebnis erzeugt werden kann. Also Komplexität verringern. Hier spricht er von Komplexität die von vornherein implementiert wird nur weil es fancy aussieht. In dem Beispiel würde das refactoring dann wieder zu mehr lesbaren und einfacheren Code führen um am Ende das gleiche Ergebnis zu erzielen.
Was du meinst ist, dass er die arrow function abgekürzt hat ohne Return und Klammern. Das ist objektiv wenn man mit JS vertraut ist weniger komplex
Danke! Gerade wenn man polyglott unterwegs ist, erkennt man, wie oft und in welchem Maße „overengineered“ wird. Oftmals sprechen die Principal Engineers von „wartbarem Code“. Lustig wird‘s aber, wenn die Funktion komplett in eine andere Richtung verläuft oder die Applikation schon wieder Legacy ist.
Für einfache Dinge mag das besser sein. Nach ein paar Monaten hat man aber genau wieder den Fall: viele ifs, verschachtelte Code, alles in einer Datei und plötzlich ist der Code nur noch mit hohem Bug Risiko wartbar. Spätestens dann hätte man sich gewunschen jemand hätte ein paar patterns verwendet.
Ich habe das Gefühl, dass es bei mir genau anders herum ist.
Früher habe ich sehr schwer verständlichen Code geschrieben, weil ich ihn verstanden habe. Ich dachte andere würden es ja dann auch verstehen können.
Meine Devise heute ist: jede Person/Team sollte austauschbar sein. Somit muss alles sehr einfach gehalten sein. Wenn dann etwas komplex/kompliziert ist, muss dahinter auch etwas stecken.
Gerade die ganzen Abstraktionen und Impliziten Annahmen machen es echt hart beim Code durchzusteigen. Ein bischen hilft es, bei Visual Studio Code den Definitionen zu folgen. Auch dazu hätte ich gerne ein Video. Also Strategien um Zusammenhänge zu verstehen, Werkzeuge um Abhängigkeiten darzustellen.
Um ehrlich zu sein, würde ich sagen: Wenn Code voll mit Abstraktionen und impliziten Annahmen ist, nützt Dir auch das beste Tooling nichts (oder nicht viel), denn dann liegt das Problem nicht auf der technischen Ebene, sondern - und das hört sich jetzt eventuell böse an - in schlecht geschriebenem Code.
@@thenativeweb Nun kommt das aber gar nicht so selten vor, das man sich solchen Code angucken muss. :D
Die Aufgabe war - zu prüfen, auf welchem System man läuft und im Falle nicht unterstützen Betriebssystems eine Meldung auszugeben. Die erste Variante löst das Problem, die zweite ein wenig kompliziert, aber doch - auch. Die dritte Variante ist vollkommen unkorrekt. Denn es geht bei der gestellten Aufgabe nicht darum, eine passende exe zu erzeugen, sondern das passende Betriebssystem zu ermitteln. Und das kann nicht nur aus technischen Sicht relevant, sondern aus strategischen bzw. fachlichen Sicht. Z.B. das Management möchte nicht ein bestimmtes OS supporten. Danke für das Video!
Was ist wenn ich im dritten beißpiel viel code der os spezifisch implementiert wird nicht mit ausliefern will, dann würde das ganze warscheinlich sinn ergeben? Oder wäre das auch wieder nicht ratsam da die paar mb mehr im fertigen binary keinerlei rellevanz haben?
Bei so einem einfachen Beispiel, hätte ich überhaupt keinen Ehrgeiz zu zeigen, dass ich es besser kann.
Es kommt aber auf die Umgebung an. Wie groß ist die gesamte Codebasis? Wie sind die Abhängigkeiten mit dem Rest der Umgebung? Kann ich meinen Kollegen vertrauen, dass sie den Code neu strukturieren, wenn die Komplexität doch höher ist als erwartet? Oder erwarten sie gar von mir, dass ich ein Muster hinterlasse, das sie nur erweitern müssen? Wie groß schätze ich überhaupt die Wahrscheinlichkeit, dass hier die Komplexität wächst? Wie sicher bin ich mir in meiner Schätzung?
Variante 3 gibt eigentlich vor Allem dann Sinn, wenn man wirklich platformspezifischen Code ausführen will. Wenn das nicht absehbar ist, kann man einfach anfangen und später immer noch zu Variante 3 wechseln.
An für sich handelt es sich nicht um komplexe Sprachmerkmale. Wenn man sich das Beispiel mit C aus den Kommentaren anschaut, liest sich die C-Fassung von Variante 3 wie Allerweltscode. Komplexität ergibt sich allerdings noch bei der Integration ins Buildsystem.
Idealerweise würde man es bei so einem trivialen Beispiel mit der einfachsten Fassung belassen, wenn man vertrauen kann, dass der Code im Zweifelsfalle neu geschrieben wird. So wie er mit seinen trivialen einzeiligen Handlern da steht ohne eine komplexe Umgebung, absehbare Änderungen, irgendwelche Abhängigkeiten, platformabhängigen Code, gibt es überhaupt keinen Grund, allzu viel zu machen.
Ich glaube, dass ich dem gegenüber in einem Review gelassen gegenüberstehen würde.
Ich bin bestimmt nicht ohne Grund ein Freund von Python, dass viel Eleganz bietet, aber nicht die extremen Möglichkeiten an Tricks, die Perl anbietet. Mit der Kürze ist da einiges falsch verstanden worden. Sicherlich ist kurzer Code in vielen Fällen besser, klarer und weniger redundant. Die reine Anzahl der Zeilen war aber nie ein Maß für seine Qualität/Wartbarkeit/Lesbarkeit - sondern (höchstens) korreliert.
Bezüglich Sprachfeatures sage ich mal das Folgende: Viele neue Sprachfeatures werden nicht unbedingt von den erfahrenen Kollegen verwendet, sondern von denen, die gerade lernen, weil es einfach so gelehrt wird. Dann muss das bewerten, ob es nützlich ist. In vielen Fällen ist das Richtige für mich, es mir abzuschauen, weil es nützlich und lesbar ist.
Die eigentliche Frage lautet: Warum wird Code viel öfter einfach neu geschrieben, wenn sich Grundannahmen geändert haben?
Beißpiel: ich implementiere einen db client in node. Der db client wird sich immer zur gleichen db verbinden. Nun könnte man:
- Die db connection parameter als globale variablen definieren
- eine singleton klasse erstellen und die parameter der connect methode mitgeben
- eine standard klasse erstellen und die parameter in konstruktor übergeben
- Eine connect funktion erstellen und da die Parameter übergeben
Jeder js entwickler wird den code mit der funktion / den globalen variablen verstehen wenn er vorher nur kleine Skripte gebaut hat
Jeder Entwickler mit OOP Background wird die anderen Varianten wählen.
Ich persönlich mag keine globalen Variablen diese reichen hier aber komplett aus.
Wie schreibe ich den Code nun?
Interessanter Beitrag.
Hallo Golo, danke für dieses Video.
Ja Du hast recht, es soll einfach sein, aber es soll auch erweiterbar sein. Und hier trennt sich die Spreu vom Weizen.
Die Aufgabe, bzw. das Problem beschreibt den Weg.
In Deinem Fall, mit den Betriebsystemen, gebe ich Dir vollkommen Recht.
Es ist überschaubar, es bleibt kurz und, Erweiterungen werden nicht so schnell kommen, denn die Handvoll von Betriebsysteme wird jetzt nicht monatlich oder täglich länger.
Anders sieht es mit cases aus, die eine hohe Fluktuation haben oder regelmäßig erweitert werden müssen.
Dann streckt Deine Switch Funktion schnell alle 4re von sich.
Bei Tausend Fällen, wird die Switch-Anweisung total unüberschaubar.
Gerade im Clean Code Bereich werden genau Deine folgenden Beispiele verwendet, weil sie sicherer sind und das Switch-Case für größere cases als böse gesehen wird.
Nicht ohne Grund, wohlgemerkt.
Mit dem Strategy Muster bist Du sogar Typesicher, vorausgesetzt Du benutzt OOP.
Muster haben ihre Berechtigung, und wer sie kennt, der wird sie lesen können.
Aber Zuviels Muster können es versauen.
Meine häufigsten Muster sind Strategy, Fassade, Factory.
Von Dispose, MVC, MVVM, aaa etc. mal abgesehen.
LG
Marcus
Danke für das Video
Ein Anfänger der Programmierung schreibt einfachen Code, weil er es nich anders kann. Ein fortgeschrittener Programmierer schreibt komplizierten Code, weil er es kann. Ein Profi schreibt einfachen Code, weil er es kann.
Die Kunst ist es, einfache Lösungen für komplexe Probleme zu finden. Der Code sollte so sein, dass jeder, der drüber schaut, sagt: Jou, genau so hätte ich es auch gemacht.
Als Entwickler sollte man im Code nicht ausdrücken, welche Features ich kenne, sondern das Problem eben so einfach wie möglich lösen.
Mein erster Gedanke war das erste Beispiel.
Beim zweiten Beispiel dachte ich mir: Ja … ich verstehe … wenn ich noch mal darüber nachdenke, ist das eventuell die bessere Lösung.
Beim dritten Beispiel dachte ich mir: Okay, das wäre in nahezu jedem Fall vollkommen übertrieben.
Es ist jederzeit möglich, das Tool entsprechend umzubauen, wenn es valide Gründe dafür gibt.
Das sage ich mir, wenn ich ins "Overengineering" falle. Ich musste es aber auch erst du eine Therapie lernen.
Ich sage immer: Kommt drauf an. "It depends."
Wenn wirklich von vornherein klar ist, daß es nur um diese kurze Aufgabe geht und es ein Shoot-and-forget-Projekt ist, dann die erste Variante.
Ansonsten würde ich in jedem Fall den Ansatz bedingter Compilierung verfolgen, nur mit möglichst geringer Komplexität. Ich kenne mich mit Go überhaupt nicht aus. Aber in anderen Sprachen würde ich einen Funktionsnamen aufrufen und die Funktion in mehreren bedingt compilierten Dateien deklarieren/definieren (bei C zentral deklarieren). Dafür brauche ich keine Zeiger, auch wenn ich sie für viele Zwecke toll finde.
Der Aufgabenformulierung nach wäre das falsch. Ich würde aber nachfragen, welche Intention die Aufgabe verfolgt. (XY-Problem).
Ein C/C++ Programmierer, der keine Präprozessoranweisungen versteht, ist sowieso lost. Mit dem Argument, irgend jemand könnte ein Sprachfeature nicht verstehen, dürfte ich bald keine for-Schreife mehr schreiben. Oder man müßte switch/case kritisieren, für Einsteiger ist doch if-then-else viel verständlicher.
@@pinkeHelga aber genau das ist meiner Meinung nach oft das Problem 'WENN WIRKLICH VON VORNHEREIN KLAR IST....' es ist in der Praxis fast nie der Fall. Andererseits will man auch nicht von vornherein unnötige Komplexität einführen. Es ist wirklich schwierig, gerade WEIL man erfahren ist, und mehrere Möglichkeiten kennt, sich für die langfristig richtige Lösung zu entscheiden. Und damit sind wir beim Golos Thema in diesem Video...
@@pinkeHelga Ich musste in meiner Laufbahn schon einige Tools schrieben die ähnlich trivial sind wie im Video gezeigt. Wenn ich Sprachen und Sprachfeatures verwende die jeder verstehen sollte der Excel Formeln schreiben kann, erhöhe ich auch die Anzahl an Personen die das Tool anpassen kann. Also schon alleine aus egoistischen Gründen würde ich die einfachste und "dümmste" Lösung verwenden.
Hier geht es natürlich nicht um komplexe oder kritische Programme. Sobald ein Programm ressourcenkrisch ist, werden solche Lösungen valide.
Aber ob triviale Tools nun 1ms oder 50ms Laufzeit haben oder ob sie 10kb oder 10mb groß sind, interesiert meistens niemanden.
Kleine Anmerkung: der Titel ist schon ziemlich click-baiting.
Stimmt schon, aber das macht mittlerweile eigentlich ganz RUclips und ist schon lange kein Indiz für schlechten Content mehr.
@@Marcel-dr5pb aber es bleibt ein schlechter Titel. Mich persönlich stört das.
@@NachtmahrNebenan Ansichtssache. Wenn ein Titel zu mehr Aufrufen führt, ist er für den Ersteller ein guter Titel.
Also ich als Berufseinsteiger finde den Titel zum Inhalt passend und gut.
@@Marcel-dr5pb Das betrachte ich als zu kurz gedacht. Reißerische Titel führen erst mal zu mehr Aufrufen. Man merkt aber bewußt und unbewußt, daß man nur geködert wurde und nicht den Inhalt bekommt, den man gesucht hat. Viele Kanäle, die mir die Selektion der Inhalte, die ich wirklich sehen will, schwer machen, ignoriere ich schon immer mehr, denn sie rauben meine Zeit und Konzentration. Wenn ich merke, daß ich Benachrichtigungen längere Zeit immer überspringe, klicke ich noch einmal, um zu deabonnieren. Das ist wie mit Drückerkolonnen. Zum einmaligen Geschäft läßt man sich vielleicht noch überrumpeln, aber danach macht man einen weiten Bogen drumrum; es entsteht kein Folgegeschäft.
Weiterhin bringen nichtssagende Titel allenfalls etwas zum Zeitpunkt der Veröffentlichung. Wenn ich nach Monaten/Jahren Suchbegriffe eingebe wie "Komplexität, Abstraktion, Design Pattern", werde ich eher andere Videos finden, bevorzugt solche, die die Stichworte auch im Titel enthalten und zusätzlich noch in Beschreibung.
Meine Meinung❤ Ich erstelle code immer so das er einfach zu verstehen ist.
Das zweite Beispiel wäre das beste, wenn es denn nicht absichtlich so redundant wäre, um den Punkt rüberzubringen. Eine eigene handler Funktion ist doch völlig übertrieben, wenn in jedem Beispiel ein println gemacht wird. Und ich glaube eine key=>value Struktur sollte jeder Entwickler kennen. Es reicht also eine Funktion, und im der map steht der os key und der menschenlesbare String des OS als value drin.
Top Video!
Dennoch wollte ich anmerken, dass es einen großen Unterschied zwischen "einfachen" und "schlechten" Code gibt. Es ist zum Beispiel immer einfacher KEINE Viewmodels zu nutzen und direkt das Model an den Client auszugeben, ABER ist halt wenn
a) doof wenn viele Properties nicht gebraucht werden
b) sicherheitsrelevante Infos drin sind
c) du mit Typescript arbeitest und dein "type" anders aussieht
Was ich damit eigentlich sagen will: Der Weg zwischen "einfachem" und "dummen" Code ist sehr, sehr schmal ^^
In privaten Projekten neige ich auf jeden Fall dazu mehr zu machen und am Ende ist es ein durcheinander und nichts dokumentiert 😇.
Auf der Arbeit neige ich dazu, so wenig wie möglich und so viel wie nötig zu machen. Natürlich dokumentiert, dass jemand anderes mit dem Code auch etwas anfangen kann.
Hallo Golo, grundsätzlich bei einfachen Dingen magst du recht haben. Kommen wir aber im professionellen Umfeld an Grenzen müssen wir zu abstrakteren Lösungen greifen. Der Punkt den man herausstellen sollte ist doch ob das Team welches die Software entwickelt diesen auch über lange Zeit versteht. Und da richtig helfen einfache Lösungen. Daher ist jede Lösung richtig und auch nicht die richtige. Ich wette es gibt noch andere Lösungen und es sieht danach aus als wäre Variante 3 die schlechteste. Ist aber auch wieder ein Denkfehler meiner Meinung nach. ;)
Klingt vielleicht doof - aber ich hätte es nach der ersten Variante programmiert. Warum? Nicht weil ich keine Erfahrung habe. Aber die von Dir gestellte Aufgabe mit dem gewünschten Ergebnis war klar und deutlich formuliert. Warum sollte ich da mehr schreiben als nötig :-)
Einfacher Code ist elegant.
Es ist aber so, dass die Dosis das Gift macht. Ich arbeite im Moment an einem Projekt, das >5 Mio. Zeilen Code und >40k Klassen hat.
Ein paar Level von Abstraktion haben durchaus das Potenzial, eine so rießige Codebase einfacher verständlich zu gestalten.
Mal ab vom Click-Bait-Titel des Videos (hat bei mir dennoch funktioniert), fühle ich mich etwas erwischt. Muss mir mal meine letzten Sourcen anschauen. Ich hab hin und wieder selbst das Problem, dass ich zwar meine super guten lesbaren und wartbaren Code zu schreiben, aber nach der Rückkehr zu solchem Code nach einiger Zeit habe ich manchmal selber Fragezeichen im Kopf, warum das so abstrakt ist. Sieht zwar oft richtig aus, aber auch oft irgendwie nicht „natürlich“.
Völlig einverstanden mit Golo - gute Lesbarkeit ist das Ziel. Ich habe schon Code aus dem Jahr 1985 gesehen der heute (2023) noch läuft und man stelle sich vor, die Kolleg:innen von damals hätten unnötig komplexen Code geschrieben - den versteht dann nach 40 Jahren keiner mehr, weil einfach keiner des ursprünglichen Teams mehr da ist und der Code schon durch 100 Hände gegangen ist...
Ich fühle mich mit diesem Video sehr ertappt :D Jedoch würde mich interessieren wie man in der Praxis den optimalen Weg findet. In deinem Beispiel ist das mMn sehr einleuchtend, dass man sich für eine einfach Lösung entscheiden sollte. Wie sieht es jedoch bei Anwendungen aus, die komplexer sind? Wie kann ich abwägen, ob eine simple Lösung den Anforderungen nicht mehr entsprechend ist? Mir ist klar, dass es da keine allgemeine Antwort gibt, aber vielleicht kennt jemand gute Strategien oder Best Practises. Vielleicht wäre das ja auch eine Idee für ein Folgevideo :)
Perfekt!
Puhh. Ich gehe da leider kaum mit. Gut bei der 3. Option könnten wir uns eventuell noch einig werden - die ist schon verdammt Komplex. Aber für mich muss Code vor allem Wartbar sein. Und eben das ist bei Version 2 am ehesten gegeben. Code 3 ist am "Effizientesten" und Code 1 ist am Anfängerfreundlichsten. Wir als Entwickler die Code für teils große Unternehmen schreiben wollen natürlich möglichst Wartbaren, Getesteten und Effizienten Code haben. Weg 1 löst das Problem, ist halt aber absolut nicht Wartbar und muss vollständig entfernt werden wenn die Komplexität steigt. Klar könnten wir jetzt sagen - der Use-Case ist doch aber nur OS Anzeigen. Aber eigentlich in jedem Projekt wächst Code organisch und wird komplexer. Jetzt ist bestimmt die frage offen: "Wie soll den so ein einfacher Code wachsen?" - Der Use-Case geht halt nach 2 - 3 Jahren weiter. Jetzt wollen wir die 4 neuen Betriebssystem mit rein nehmen, den genauen OS Namen haben (kUbuntu, Ubuntu, Debian, Mint, Vista, 7, 10 ...) und dann am besten mit dem jeweiligen Packet Manager noch irgendwas nach installiert. Möglichkeit 2 / 3 hätte uns das gleich erlaubt. Möglichkeit 1 wird da nicht mehr nutzbar.
Finde es immer sehr schwierig. Meistens fängt es kompliziert an, wenn man den Code testbar gestalten möchte.
Mein erster Gedanke war ja: "was passiert, wenn ich die Windows.Exe unter Linux ausführe, oder anders herum". Ist ja inzwischen kein Problem mehr. Da würde die dritte Lösung in jedem Fall scheitern. Aber das ist sicher nicht das Thema des Videos.
Ansonsten sehe ich auch die Gefahr, dass man sich nicht mehr weiter entwickelt, wenn man immer nur die alten bekannten Konstrukte verwendet oder im Team alles auf dem kleinsten gemeinsamen Nenner bleibt. Aber ich denke, so ist Golos Beitrag auch nicht gemeint.
Hallo @thenativeweb - Golo
zu nächst einmal ist die erste Variante des Codes sehr trivial. Ohne lange darüber nachdenken zu müssen und ohne Kenntnisse zur Sprache selbst versteht man den Inhalt relativ schnell. Ich sehe beim Verkomplizieren von Code vor allem die Gefahr darin, dass man auf kurz oder lang Schwierigkeiten bekommen kann. Entweder es geht um Bugs, die entfernt werden müssen oder das System soll erweitert werden. Je mehr Technologien verwendet werden, die teils völlig sinnlos sind, desto eher stellt man sich und anderen ein Bein, weil man eben sich und anderen die Arbeit schwerer macht. DIe Fogle ist, dass man sich über den bestehenden Code ärgert, frustriert ist anstatt sich auf das Wesentliche zu konzentrieren und produktiv zu sein. Weniger ist machmal einfach mehr.
Für mich ist das wichtigste Prinzip DRY - Don't repeat yourself - gefolgt von Kohäsion.
Nicht selten mußte ich Projekte refaktorisieren und dabei viele Tausend Codestellen interaktiv suchen und ersetzten. Wenn sich ein Problem wiederholt, braucht es einen Namen. Das bekommt man schon bei prozeduraler Programmierung hin.
Nun kann das wieder dazu führen, daß ein sehr ähnliches Problem auftaucht, wo der benannte Code nicht richtig greift; er bräuchte zusätzliche Parameter, kleine Abwandlungen. Man hat irgendwann Catch-All-Funktionalität wahnsinnig abstrakt.
Das ist ein Zeichen, daß der Code weiter in Teilprobleme zerlegt werden muß, aus denen sich zwei leicht unterschiedliche Problemlösungen formulieren lassen.
Das Problem in der Entwicklung ist, daß man gerne von Anfang an alles abstrahiert. Häufig hört man Aussagen: "Wir verwenden Design Patterns ja nicht ohne Grund. Es hat sich gezeigt, daß dadurch besser testbarer und wartbarer Code entsteht." Naja, dem kann man schwer widersprechen. Oft ist es aber gar nicht nötig. Sinnvoller ist meist, zunächst funktionierenden, verständliche Code zu entwickeln und von Beginn an zu refaktorisieren. Wenn man feststellt, daß tatsächlich Bedarf für eine Abstraktion entsteht, kann man das möglichst frühzeitig anpassen.
Parallele dazu sollte auch eine Dokumentation gepflegt werden, sodaß Code lesen gar nicht mehr nötig ist. Code muß so verwendbar sein, als handelte es sich um eine compilierte Bibliothek ohne Open Source, z.B. eine DLL.
Polymorphismus steht heute in der Kritik, meiner Ansicht nach zu Unrecht. Das war die Idee von OOP. Ansonsten kann ich auch mit Records/Verbundtypen und Funktionen arbeiten. Und lose Kopplung per Interfaces wird mir zu exzessiv eingesetzt. Interfaces bedeuten für mich schon Code-Smell, daß die Klassenhierarchie unvorteilhaft gestaltet ist. Die haben eher nur für völlig allgemeine Designs wie Listen etc. ihre Daseinsberechtigung, stellen aber meist mehr einen Workaround für (möglicherweise in der Sprache fehlende) Generics dar.
@@pinkeHelga Refaktorisierung ist ein wichtiges Thema. Es kommt immer wieder vor dass sich Anforderungen ändern, Strukturen nicht mehr passen oder man auf bessere Lösungsansätze stößt. Ich bin mir da nicht zu schade Code wegzuschmeißen auch wenn ich vorher in die Entwicklung viel Zeit investiert habe.
Projekte in denen man dies nicht tut laufen Gefahr zu Frankensteins Monster zu verkommen. Die Weiterentwicklung wird aufgrund der Altlasten immer komplizierter und aufwändiger. Ich hatte beruflich selbst mal mit so einem Projekt zu tun das drei Entwicklerteams aus drei Ländern hinter sich hatte und strukturell so kompliziert geworden ist dass eine effiziente Weiterentwicklung nicht mehr möglich war.
@@emgalaxy6576
und genau das trennt hochwertigen vom schlechten Code. Ich finde, alle Technologien mit denen man im Lauf seiner Entwicklerzeit zu tun hat, ergänzen lediglich den "Werkzeugkasten". Das jeweilige Projekt, welches umgesetzt werden soll, ist dann die Messlatte, woran man sich orientiert, sprich welche konkreten Technologien bentzt werden sollten und auch angemessen sind. Aber das Sie sich nicht zu fein sind, bereits geschriebenes wieder zu entfernen ist beeidruckend. Als Student bin ich zwar noch nicht in dieser Situtation gewesen, aber es zeigt, dass Sie Veränderungen nicht ablehnend gegenüber stehen und zur Not auch konsequent sind.
Aber dafür brauchen wir doch einen Microservice und den Kubernetes Cluster dazu auch gleich...
Und die Testabdeckung ist auch Mist, der Code ist gar nicht Testbar und TDD wurde auch nicht beachtet.
Auch muss sowas asyncron mit Messages Queues und Event Sourcing passieren!
Natürlich mit Prometeus und Grafana, Elektron Logs uvm. sonst ist das ja kein Richtiger Kubernetes Cluster. Am Besten noch Camunda also Microservice Orchestrator
Ich wünschte das wäre Sarkasmus aber bei Großkonzernen sieht man das häufiger....
KISS wird mittlerweile sehr kreative weise ignoriert.
Aber wenn man einen Microservice dafür einführt und einen Kubernetes Cluster, braucht man mehr Entwickler und der Chef bekommt mehr Gehalt weil er für mehr Angestellte verantwortlich ist. Und als Entwickler ist Kubernetes für meinen Lebenslauf gut!
Edit: Clickbait ist der Titel trotzdem, wie wäre es mit KISS lässt grüßen?
Featuritis verhindern, da Code als Kommunikationsmittel dienen muss.
Bei den Codebeispielen wuerde ich ein anderes Farbschema bevorzugen.
Der Kontrast der Keywords ist eher so ... meh...
Da fällt mir das Sprichwort ein: mit Kanonen auf Spatzen schießen. Man knallt sich auch keine Valium wegen einem Mückenstich. 😂 also ich glaube, dass man mit:- so einfach, wie möglich und komplex wie nötig - am besten fährt. Spart ja auch Gehirnschmalz und Energie. 😊
Ich habe einen Kollegen bei mir, den ich als nicht als sehr erfahren ansehe. Er hat immer den Tick, Code zu verkomplizieren und über das Ziel hinauszuschießen. 😂😂
Geben auch mal meinen Senf dazu (komme von C++ und kenne go nicht):
Das Argument Sprachmittel nicht zu verwenden, nur weil es ein höheres Wissen der Sprache darstellen oder ungewöhlich ist, kann ich nicht unterstützen. In einem Team würde ich nicht auf den "schwächsten" Rücksicht nehmen, er/sie kann ja was neues lernen. Wenn durch ein tolles Sprachmittel der Code besser (wie auch immer definiert) wird sollte ich das verwenden. KISS
Ansonsten geht immer Compilerfehler vor Laufzeitfehler. Alles was der Compiler oder Linker erkennen kann sollte ich ausnutzen, auch wegen der Performance. Ich würde eine einfachere Version von 3 verwenden.
Stumpf eine gleichnamige Funktion für jedes OS mit Compilerschalter (wenn es sowas in Go gibt.). Der Linker sagt schon, was fehlt.
Ich würde sagen darunter leiden vor allem „erfahrene Juniors“. 1-2 Jahre gecodet, dann gehts los mit dem Overengineering.
Wenn man Lesbarkeit mit Dokumentation gleichsetzt (also so ungefähr…), dann entsteht mehr wie in diesem Beispiel. Pi mal Daumen - es gibt nicht DIE Lösung.
AMEN!
"man 2 uname" ist dein Freund. 🙂
💋: keep it simple & stupid
Interessant: ich verdiene mein Geld seit 1986 mit Softwareentwicklung und wäre nie auf eine andere Idee gekommen, als es anders als mit einer Case-Struktur zu lösen... :-/
Schuldig im Sinne der Anklage 😂. Also: make it stupid simple 😊
Wie bringt man sowas seinen Kollegen bei, die alle einen Gottkomplex haben?
Lieber Golo,
du stellst hier eine sehr interessante Frage. Bei diesem speziellen Beispiel tendiere ich ganz klar für die dritte Lösung. Es macht für mich wirklich keinen Sinn Code zu schreiben, der niemals ausgeführt werden kann.
Bei ähnliche Beispiele tendiere ich für die Strategy-Pattern-Lösung. Und zwar deshalb, weil ich viel zu oft den Satz gehört habe 'Es muss ja NUR zwischen Pest und Colera entscheiden was er zu tun hat'. Binnen eines Jahres kommen wochentlich neue Krankheiten hinzu und der Code wird dadurch ausgerechnet für die erfahrenen Entwickler ein Buch mit 7 Siegeln. Und am Ende heißt es 'Bei dem was du mir hier kostest, kriegst du so eine einfache Aufgabe nicht gelöst'... 😂
In diesem speziellen Beispiel würdest du dritte Variante wählen?
Für mich macht es, in diesem Beispiel, keinen Sinn komplizierte Sprachkonstrukte zu verwenden, die nur Entwickler dieser Sprache verstehen.
Im Zweifel musst du als professioneller Entwickler immer dieses triviale Tool anpassen.
Mit Lösung 1 kann das jeder, der halbwegs Ahnung von Scripting hat.
Bei der ersten These bin ich voll dabei. Ich sehe allerdings keinen Anlaß für zusätzliche logische Abstraktion. Vielleicht ist das speziell in Go nötig, da habe ich null Ahnung. In anderen Sprachen mit bedingter Compilierung würde ich einfach 3 Versionen der gleichen Funktion definieren und ggf. eine zentrale Deklaration der Signatur wie bei C/C++.
Meiner Meinung nach muss die technische Umsetzung der Anforderung angemessen sein…
Die erste Lösung von Golo ist super simpel und der Anforderung perfekt angemessen. Alles andere ist jetzt Interpretationssache!
Für mich ist da der YAGNI Ansatz zielführender als eine technisch ausgeklügelte Lösung!
oder so
#include
#include
#ifdef __MSDOS__
int main(void) {
printf("Programm läuft unter MSDOS
");
return EXIT_SUCCESS;
}
#elif __WIN32__ || _MSC_VER
int main(void) {
printf("Programm läuft unter Win32
");
return EXIT_SUCCESS;
}
#elif __unix__ || __linux__
int main(void) {
printf("Programm läuft unter UNIX/LINUX
");
return EXIT_SUCCESS;
}
#else
int main(void) {
printf("Unbekanntes Betriebssystem!!
");
return EXIT_SUCCESS;
}
#endif
und jetzt noch die wiederholten Teile aus dem #if ausklammern