it-swarm.dev

Was schadet der Wartbarkeit?

Für jemanden, der noch nicht viel Erfahrung in der realen Welt hat, ist der Begriff des wartbaren Codes etwas vage, obwohl er sich aus typischen Regeln für bewährte Verfahren ergibt. Intuitiv kann ich sagen, dass Code gut geschrieben, aber nicht wartbar ist, wenn er beispielsweise Informationen fest verdrahtet, die sich ständig ändern, aber es fällt mir immer noch schwer, Code zu betrachten und zu entscheiden, ob er wartbar ist oder nicht.

Die Frage ist also: Was schadet der Wartbarkeit? Worauf soll ich achten?

73
EpsilonVector

Die Wartbarkeit hat viele Aspekte, aber IMO sind die wichtigsten lose Kopplung und hohe Kohäsion . Wenn Sie mit gutem Code arbeiten, können Sie hier und da kleine Änderungen vornehmen, ohne die gesamte Codebasis im Kopf behalten zu müssen. Bei schlechtem Code müssten Sie mehr Dinge berücksichtigen: Hier beheben, und es bricht an anderer Stelle.

Wenn Sie mehr als 100 kLOC Code vor sich haben, ist dies entscheidend. Oft erwähnte "Good Practice" -Regeln wie Code-Formatierung, Kommentieren, Benennen von Variablen usw. sind im Vergleich zu Kopplungs-/Kohäsionsproblemen oberflächlich.

Das Problem bei der Kopplung/Kohäsion ist, dass es nicht einfach ist, schnell zu messen oder zu sehen. Es gibt einige akademische Versuche, dies zu tun, und vielleicht einige statische Code-Analyse-Tools, aber nichts, was ich darüber weiß, würde sofort eine verlässliche Schätzung darüber liefern, wie schwer es Ihnen mit dem Code fallen wird.

94
Joonas Pulakka

duplizierter Code:

Copy Paste ist höchstwahrscheinlich der teuerste Vorgang, wenn es um die Wartungskosten von Programmen geht.

Warum sich die Mühe machen, diesen Code in eine gemeinsame Komponente zu verschieben? Ich kopiere ihn einfach und bin damit fertig. Ja ... der Programmierer hat wahrscheinlich eine Stunde gespart von der Arbeit oder so Dinge auf diese Weise zu tun. Später kommt jedoch ein Fehler und ... ja, natürlich wird er nur in der Hälfte des laufenden Codes behoben. Was bedeutet, dass später noch eine Regression protokolliert wird, was zu einem weiteren Fix führt OF THE SAME THING.

Dieser klingt offensichtlich, aber im großen Schema der Dinge ist er heimtückisch. Programmierer sehen gut aus, weil sie mehr erledigen. Entwickler-Teammanager sehen gut aus, weil sie pünktlich geliefert haben. Und da das Problem erst viel später gefunden wird, wird der wahre Schuldige nie beschuldigt.

Ich habe die Zählung verloren, wie oft ich eine Veröffentlichung wegen solcher "Regressionen" anhalten musste . Gerade jetzt habe ich einen ganzen Tag verloren, um einen Fix zu verfolgen, der bereits vorhanden war Wenn Sie fertig sind, reparieren Sie es erneut und schieben Sie den neuen Build durch die Reifen. Also ja, eine Stunde Zeit für einen Entwickler vor sechs Monaten (ca. CAD 40 $)) kostete nur einen ganzen Tag für einen Senior-Entwickler + einen halben Tag für einen Junior-Entwickler + einen ganzen Tag Verspätung in einem Projekt schon spät ....

du machst die Mathematik....

also, der nächste, der mich anzieht, ich schwöre, er sollte besser schnell rennen, weil ich direkt hinter ihm bin !!!

67
Newtopian

Während Code, der gegen die Regeln in den anderen Antworten verstößt, sicherlich schlechter zu pflegen ist, der gesamte Code ist schwer zu pflegen , also je weniger davon Sie haben, desto besser . Fehler korrelieren stark mit der Codemenge je mehr Code Sie haben, desto mehr Fehler haben Sie . Sobald der Code eine bestimmte Größe überschritten hat, können Sie ihn nicht mehr behalten Ihr gesamtes Design im Kopf und die Dinge werden viel schwieriger. Schließlich bedeutet mehr Code letztendlich mehr Ingenieure, was mehr Kosten, mehr Kommunikationsprobleme und mehr Verwaltungsprobleme bedeutet.

Denken Sie jedoch daran, dass Sie keinen komplexeren, sondern kürzeren Code schreiben sollten. Der am besten zu wartende Code ist einfach und enthält einfach keine Redundanz, um ihn so kurz wie möglich zu halten. Das Delegieren von Aufgaben an andere Programme oder APIs kann ebenfalls eine praktikable Lösung sein, solange die API-Aufrufe einfach genug sind.

kurz gesagt, jeder überflüssige Code ist ein Wartbarkeitsproblem

52
jk.

Ironischerweise erschweren explizite Versuche, ein System "zukunftssicher" zu machen, häufig die Wartung.

Es ist eine Sache, magische Zahlen oder Zeichenfolgen nicht fest zu codieren, aber gehen Sie noch weiter und Sie beginnen, Logik in Konfigurationsdateien einzufügen. Das heißt, Sie fügen Ihrer Wartungslast einen Parser und einen Interpreter für eine obskure neue (und wahrscheinlich schlecht gestaltete) Programmiersprache hinzu.

Abstraktions- und Indirektionsebenen, die aus keinem anderen Grund hinzugefügt wurden, um eine Komponente könnte in Zukunft zu ändern, und von der Sie nicht direkt abhängig sein möchten, sind reines Wartungsgift und ein Paradebeispiel für - YAGNI . Das SLF4J FAQ erklärt die Probleme mit dieser allzu häufigen Idee:

Angesichts der Tatsache, dass das Schreiben eines Protokollierungs-Wrappers nicht so schwierig erscheint, werden einige Entwickler versucht sein, SLF4J zu verpacken und nur dann mit ihm zu verknüpfen, wenn es bereits im Klassenpfad vorhanden ist, was SLF4J zu einer optionalen Abhängigkeit von Wombat macht. Zusätzlich zur Lösung des Abhängigkeitsproblems isoliert der Wrapper Wombat von der SLF4J-API, um sicherzustellen, dass die Anmeldung in Wombat zukunftssicher ist.

Andererseits hängt jeder SLF4J-Wrapper per Definition von SLF4J ab. Es muss dieselbe allgemeine API haben. Wenn in Zukunft eine neue und erheblich andere Protokollierungs-API hinzukommt, ist es ebenso schwierig, Code, der den Wrapper verwendet, auf die neue API zu migrieren wie Code, der SLF4J direkt verwendet. Daher ist es unwahrscheinlich, dass der Wrapper Ihren Code zukunftssicher macht, sondern ihn komplexer macht, indem er zusätzlich zu SLF4J eine zusätzliche Indirektion hinzufügt, die eine Indirektion für sich ist.

Der letzte halbe Satz deutet auf die süße Ironie hin, dass SLF4J selbst ein Protokollierungs-Wrapper ist, für den dieses Argument genauso gut gilt (außer es macht einen glaubwürdigen Versuch, der einzige Wrapper zu sein, den Sie brauchen, der auf und sitzen kann unten alle anderen gängigen Java Protokollierungs-APIs).

31

Nach meiner Erfahrung ist Konsistenz ein wesentlicher Faktor für die Wartbarkeit. Selbst wenn das Programm auf seltsame und absurde Weise funktioniert, ist es immer noch einfacher zu warten, wenn es überall gleich funktioniert. Die Arbeit mit Code, der unterschiedliche Namensschemata, Entwurfsmuster, Algorithmen usw. für ähnliche Aufgaben verwendet, je nachdem, wer ihn wann geschrieben hat, ist eine wichtige PITA.

21
user281377

Ich glaube, dass Code, der nicht Unit United ist, die Wartbarkeit beeinträchtigt. Wenn Code durch Komponententests abgedeckt wird, können Sie sicher sein, dass Sie beim Ändern wissen, was Sie brechen, da die zugehörigen Komponententests fehlschlagen.

Unit-Tests zusammen mit den anderen Vorschlägen hier machen Ihren Code robust und Sie stellen sicher, dass Sie wissen, welche Auswirkungen Sie haben, wenn Sie Änderungen vornehmen.

18
Deco

Der einzig wahre Weg, um zu lernen, was wartbarer Code ist, besteht darin, sich an der Pflege einer großen Codebasis zu beteiligen (sowohl Fehlerkorrekturen als auch Funktionsanforderungen).

Sie werden lernen über:

  • lose Kopplung und hohe Kohäsion
  • duplizierter Code
  • Regressionstests und Refactoring: Mit einer effizienten Regressionstestsuite fühlen Sie sich sicherer, wenn Sie Dinge ändern.
  • beobachtbarkeit und Fehlerprotokollierung: Wenn die Software nachverfolgt, was falsch läuft, kann der Fehler leichter lokalisiert werden.
  • genaue Dokumentation: nicht die Art, die zu Beginn des Projekts einmal geschrieben und nie geändert wurde.
  • undokumentierter verschleierter Code in Form einer vermeintlich brillanten Designidee oder von Optimierungen ala Fast inverse square root .
  • mündliche Überlieferung von einer Generation von Entwicklern/Betreuern zur nächsten.
15
mouviciel

Ich habe ein paar "Albträume", die, obwohl ich sie nie ganz oben auf diese Liste gesetzt habe, definitiv einige Probleme verursacht haben.

Das Mega-Objekt/Seite/Funktion : (Das sogenannte " Gott-Objekt " Anti-Muster =): Alles in die Methode eines Objekts einfügen (meistens Java world), Seite (meistens PHP und ASP), Methode (meistens VB6).

Ich musste Code von einem Programmierer pflegen, der in einem PHP-Skript hatte: HTML-Code, Geschäftslogik, Aufrufe von MySQL alles durcheinander. Etwas wie:

foeach( item in sql_query("Select * from Items")) {
<a href="/go/to/item['id']">item['name']</a>
}

Unter Berufung auf obskure Merkmale der Sprache , die möglicherweise nicht bekannt sind oder bald veraltet sein werden.

Es war wieder mit PHP und voreingestellten Variablen passiert. Kurzgeschichte: Für einige Einstellungen von PHP V4 wurde einer Variablen automatisch ein Anforderungsparameter zugewiesen. also http://mysite.com?id=44 würde "magisch" eine varaible $ id mit dem Wert '42' erzeugen.

Dies war ein Alptraum für die Wartung, nicht nur, weil Sie möglicherweise nicht wissen konnten, woher die Daten stammen, sondern auch, weil PHP V5 sie vollständig entfernt hat (+1), sodass die nach dieser Version geschulten Personen nicht einmal verstanden haben, was los war und warum.

Was die Wartung notwendig macht, ist, dass Anforderungen ein sich bewegendes Ziel sind . Ich sehe gerne Code, in dem zukünftige mögliche Änderungen erwartet wurden, und überlege, wie ich mit ihnen umgehen soll, falls sie auftreten sollten.

Ich habe ein gewisses Maß an Wartbarkeit. Angenommen, es kommt eine neue Anforderung oder eine Änderung einer vorhandenen Anforderung. Anschließend werden die Änderungen am Quellcode implementiert und alle damit verbundenen Fehler behoben, bis die Implementierung abgeschlossen und korrekt ist. Führen Sie nun ein diff zwischen der Codebasis nach und der Codebasis vor. Wenn dies Dokumentationsänderungen enthält, fügen Sie diese in die Codebasis ein. Aus diff können Sie eine Anzahl N abrufen, wie viele Einfügungen, Löschungen und Ersetzungen von Code erforderlich waren, um die Änderung durchzuführen.

Je kleiner N ist, da der grobe Durchschnitt über vergangene und zukünftige Anforderungen sich ändert, desto wartbarer ist der Code. Der Grund ist, dass Programmierer verrauschte Kanäle sind. Sie machen Fehler. Je mehr Änderungen sie vornehmen müssen, desto mehr Fehler machen sie, die alle zu Fehlern werden, und jeder Fehler ist schwieriger zu finden und zu beheben, als es überhaupt zu tun ist.

Daher stimme ich den Antworten zu, die lauten: Folge, dass du dich nicht wiederholst (DRY) oder was als Cookie-Cutter-Code bezeichnet wird.

Ich stimme auch der Bewegung hin zu domänenspezifischen Sprachen (DSLs) zu vorausgesetzt Sie reduzieren N. Manchmal nehmen die Leute an, dass der Zweck einer DSL darin besteht, durch den Umgang mit "codiererfreundlich" zu sein "Abstraktionen auf hoher Ebene". Das reduziert N nicht unbedingt. Der Weg, N zu reduzieren, besteht darin, in eine Sprache zu gelangen (die möglicherweise nur über einer vorhandenen Sprache definiert ist), die den Konzepten der Problemdomäne näher kommt.

Wartbarkeit bedeutet nicht unbedingt, dass jeder Programmierer direkt eintauchen kann. Das Beispiel, das ich aus eigener Erfahrung verwende, ist Differential Execution . Der Preis ist eine bedeutende Lernkurve. Die Belohnung ist eine Reduzierung des Quellcodes um eine Größenordnung für Benutzeroberflächendialoge, insbesondere solche mit sich dynamisch ändernden Elementen. Einfache Änderungen haben N um 2 oder 3. Komplexere Änderungen haben N um 5 oder 6. Wenn N so klein ist, ist die Wahrscheinlichkeit, Fehler einzuführen, stark verringert und es entsteht der Eindruck, dass es "einfach funktioniert".

Im anderen Extrem habe ich Code gesehen, bei dem N normalerweise im Bereich von 20 bis 30 lag.

4
Mike Dunlavey

Dies ist nicht wirklich eine Antwort, sondern ein langer Kommentar (da diese Antworten bereits vorgelegt wurden).

Ich habe festgestellt, dass das religiöse Streben nach zwei Faktoren - saubere, benutzerfreundliche Schnittstellen und keine Wiederholung - meinen Code viel besser gemacht hat und mich im Laufe der Zeit zu einem viel besseren Programmierer gemacht hat.

Manchmal ist es SCHWER, redundanten Code zu eliminieren, und Sie müssen sich einige knifflige Muster einfallen lassen.

Normalerweise analysiere ich, was sich ändern muss, und mache das dann zu meinem Ziel. Wenn Sie beispielsweise eine GUI auf einem Client ausführen, um eine Datenbank zu aktualisieren, was müssen Sie hinzufügen, um "eine andere" (ein anderes mit der Datenbank verknüpftes Steuerelement) hinzuzufügen? Sie müssen der Datenbank eine Zeile hinzufügen, und Sie müssen die Position der Komponente, das war's.

Wenn das also Ihr absolutes Minimum ist, sehe ich keinen Code darin - Sie sollten dies tun können, ohne Ihre Codebasis zu berühren. Dies ist erstaunlich schwer zu erreichen, ein paar Toolkits werden es tun (normalerweise schlecht), aber es ist ein Ziel. Wie schwer ist es, näher zu kommen? Nicht schrecklich. Ich kann es tun und habe es auf verschiedene Arten mit Null-Code getan. Eine besteht darin, neue GUI-Komponenten mit dem Namen der Tabelle in der Datenbank zu versehen, eine andere darin, eine XML-Datei zu erstellen - die XML-Datei (oder YAML, wenn Sie es hassen XML) kann sehr nützlich sein, da Sie Validatoren und spezielle Aktionen mit dem Feld verknüpfen können, wodurch das Muster äußerst flexibel wird.

Außerdem dauert die korrekte Implementierung von Lösungen nicht länger - bis Sie die meisten Projekte ausgeliefert haben, ist dies tatsächlich billiger.

Ich kann darauf hinweisen, dass Sie, wenn Sie sich stark auf Setter & Getters ("Bean Patterns"), Generics & Anonymous-Innenklassen verlassen, wahrscheinlich NICHT generisch so codieren. In den obigen Beispielen wird der Versuch, eines dieser Elemente zu erzwingen, Sie wirklich vermasseln. Setter & Getters zwingen Sie, Code für neue Attribute zu verwenden, Generics zwingen Sie, Klassen zu instanziieren (für die Code erforderlich ist) und anonyme innere Klassen sind an anderer Stelle nicht einfach wiederzuverwenden. Wenn Sie wirklich generisch codieren, sind diese Sprachkonstrukte nicht schlecht, aber sie können es schwierig machen, ein Muster zu visualisieren. Für ein völlig unsinniges Beispiel:

user.setFirstName(screen.getFirstName());
user.setLastName(screen.getLastName());

Sieht gut aus - überhaupt nicht überflüssig - zumindest nicht in einer Weise, die Sie beheben können, oder? Sie werden jedoch eine Zeile hinzufügen, wenn Sie einen "zweiten Vornamen" hinzufügen möchten. Es handelt sich also um "redundanten Code".

user.getNameAttributesFrom(screen);

Benötigt keinen neuen Code für diese Aufgabe - es erfordert lediglich, dass einige Attribute in "Bildschirm" mit einem "Name" -Attribut gekennzeichnet sind, aber jetzt können wir die Adresse nicht kopieren. Wie wäre es damit:

user.getAttributesFrom(screen, NAME_FIELDS, ADDRESS_FIELDS);

Schöner, mit einem var-args können Sie eine Gruppe von Attributen (aus einer Aufzählung) einfügen, die auf dem Bildschirm erfasst werden sollen. Sie müssen jedoch den Code bearbeiten, um die gewünschten Attributtypen zu ändern. Beachten Sie, dass "NAME_FIELDS" eine einfache Aufzählungsinstanz ist - Felder in "Bildschirm" sind mit dieser Aufzählung versehen, wenn sie in die richtige (n) Kategorie (n) eingeordnet werden sollen - es gibt keine Konvertierungstabelle.

Attribute[] attrs=new Attributes[]{NAME_FIELDS, ADDRESS_FIELDS, FRIENDS_FIELDS};
user.getAttributesFrom(screen, attrs);

Jetzt haben Sie es dort, wo Sie gerade "Daten" ändern. Hier lasse ich es normalerweise - mit den Daten im Code - weil es "gut genug" ist. Der nächste Schritt besteht darin, die Daten zu externalisieren, falls dies jemals benötigt wird, damit Sie sie aus einer Textdatei lesen können, dies ist jedoch selten der Fall. Refactorings "Roll up" oft so, sobald Sie sich daran gewöhnt haben, und was ich gerade dort gemacht habe ^ hat eine neue Aufzählung und ein neues Muster erstellt, das in vielen anderen Refactorings rollen wird.

Beachten Sie schließlich, dass gute generische Lösungen für solche Probleme NICHT sprachabhängig sind. Ich habe eine No-Code-per-Loc-Lösung implementiert, um eine Text-GUI über die Befehlszeile eines Routers zu analysieren, sie auf dem Bildschirm zu aktualisieren und auf das Gerät zurückzuschreiben - in VB = 3. Man muss sich nur dem Prinzip widmen, niemals redundanten Code zu schreiben - selbst wenn man dafür doppelt so viel Code schreiben muss!

Das Clean Interface (vollständig berücksichtigt und lässt keine illegalen Inhalte durch) ist ebenfalls wichtig. Wenn Sie eine Schnittstelle zwischen zwei Codeeinheiten korrekt faktorisieren, können Sie den Code auf beiden Seiten der Schnittstelle ungestraft bearbeiten und neue Benutzer können die Schnittstellen sauber implementieren.

2
Bill K

neben den genannten Mustern ist eines der wichtigsten Dinge

lesbarer Code, jede einzelne Codezeile, die Sie in eine Codebasis einfügen, wird mehr als 100 Mal gelesen, und da die Leute Fehler aufspüren, möchten Sie nicht, dass sie 30 Minuten damit verbringen, herauszufinden, was sie jeweils tun Zeit.

Versuchen Sie also nicht, mit "optimiertem" Code klug umzugehen, es sei denn, es handelt sich wirklich um einen Engpass. Kommentieren Sie ihn dann und fügen Sie den ursprünglich lesbaren (und funktionierenden) Code in Kommentare (oder einen alternativen Kompilierungspfad zum Testen) als Referenz für das ein, was er sollte tun.

dazu gehören beschreibende Funktionen, Parameter- und Variablennamen sowie Kommentare, die erklären, WARUM ein bestimmter Algorithmus einem anderen vorgezogen wird

2
ratchet freak

Ich denke, dass die Wartbarkeit auch eng mit der Komplexität des Problems verbunden ist, was eine subjektive Angelegenheit sein kann. Ich habe Situationen gesehen, in denen der einzige Entwickler eine große Codebasis erfolgreich pflegen und konsistent erweitern konnte, aber wenn andere an seine Stelle treten, scheint dies ein nicht zu wartendes Durcheinander zu sein - nur weil sie ganz andere mentale Modelle haben.

Muster und Praktiken helfen wirklich (siehe andere Antworten für gute Ratschläge). Ihr Missbrauch kann jedoch zu noch mehr Problemen führen, wenn die ursprüngliche Lösung hinter Fassaden, Adaptern und unnötigen Abstraktionen verloren geht.

Verständnis geht im Allgemeinen mit Erfahrung einher. Eine gute Möglichkeit zu lernen besteht darin, zu analysieren, wie andere ähnliche Probleme gelöst haben, und zu versuchen, Stärken und Schwächen in ihrer Implementierung zu finden.

2
Oleg Kolosov

Fast alles, was gegen einige gute Prinzipien der Softwareentwicklung verstößt, schadet der Wartbarkeit. Wenn Sie sich einen Code ansehen und beurteilen möchten, wie wartbar er ist, können Sie einen bestimmten auswählen Prinzipien und überprüfen Sie, wie sehr es sie verletzt.

Das [~ # ~] trockene [~ # ~] Prinzip ist ein guter Ausgangspunkt: Wie viele Informationen werden im Code wiederholt? [~ # ~] yagni [~ # ~] ist auch sehr hilfreich: Wie viel Funktionalität gibt es, die nicht benötigt wird?

Wenn Sie etwas über [~ # ~] trocken [~ # ~] und [~ # ~] yagni [~ # ~] Sie finden andere Prinzipien, die als allgemeine Softwareentwicklungsprinzipien gelten. Wenn Sie eine objektorientierte Programmiersprache verwenden und genauer sein möchten, geben die [~ # ~] soliden [~ # ~] Prinzipien einige gute Richtlinien für gute Objekt- orientiertes Design.

Code, der gegen eines dieser Prinzipien verstößt neigt dazu weniger wartbar zu sein. Ich lege hier einen Schwerpunkt auf tend, weil es - allgemein gesprochen - Situationen gibt, in denen es legitim ist, (leicht) gegen irgendeine Art von Prinzip zu verstoßen, insbesondere wenn es aus Gründen der Wartbarkeit gemacht wurde.

Was auch hilft, ist nach Code-Gerüchen zu suchen. Werfen Sie einen Blick auf Refactoring - Verbessern des Designs vorhandenen Codes von Martin Fowler. Dort finden Sie Beispiele für Codegerüche, die Ihren Sinn für weniger wartbaren Code schärfen.

1
Theo Lenndorff

Zu viele Funktionen.

Features selbst beeinträchtigen von Natur aus die Wartbarkeit, da Sie zusätzlichen Code schreiben müssen, um sie zu implementieren. Eine Anwendung ohne Features ist jedoch sinnlos. Daher fügen wir Features hinzu. Wenn Sie jedoch unnötige Funktionen weglassen, können Sie Ihren Code besser warten.

1
The Nail