Ist es in Ordnung, optimierten Code durch lesbaren Code zu ersetzen?


78

Manchmal stoßen Sie auf eine Situation, in der Sie vorhandenen Code erweitern / verbessern müssen. Sie sehen, dass der alte Code sehr schlank ist, aber auch schwer zu erweitern ist und Zeit zum Lesen braucht.

Ist es eine gute Idee, es durch modernen Code zu ersetzen?

Vor einiger Zeit mochte ich den Lean-Ansatz, aber jetzt scheint es mir besser, viele Optimierungen zugunsten höherer Abstraktionen, besserer Schnittstellen und besser lesbarer, erweiterbarer Codes zu opfern.

Die Compiler scheinen ebenfalls besser zu werden, so dass Dinge wie " struct abc = {}still" in " memsets" umgewandelt werden. " shared_ptrS" produzieren so ziemlich den gleichen Code wie "raw pointer twiddling". Templates funktionieren super gut, weil sie super schlanken Code produzieren und so weiter.

Trotzdem sieht man manchmal stapelbasierte Arrays und alte C-Funktionen mit etwas undurchsichtiger Logik, und normalerweise befinden sie sich nicht auf dem kritischen Pfad.

Ist es eine gute Idee, einen solchen Code zu ändern, wenn Sie ein kleines Stück davon berühren müssen?


20
Lesbarkeit und Optimierungen sind die meiste Zeit nicht zuwider.
Deadalnix

23
Kann sich die Lesbarkeit bei einigen Kommentaren verbessern?
YetAnotherUser

17
Es ist besorgniserregend, dass OOP-ification als "moderner Code" gilt
James

7
wie Slackware-Philosophie: Wenn es nicht kaputt ist, reparieren Sie es nicht, zumindest haben Sie einen sehr, sehr guten Grund, es zu tun
osdamv

5
Mit optimiertem Code meinen Sie den tatsächlichen optimierten Code oder den sogenannten optimierten Code?
Dan04

Antworten:


115

Wo?

  • Auf einer Startseite einer Website im Google-Maßstab ist dies nicht zulässig. Halte die Dinge so schnell wie möglich.

  • In einem Teil einer Anwendung, der einmal im Jahr von einer Person verwendet wird, ist es durchaus akzeptabel, die Leistung zu opfern, um die Lesbarkeit des Codes zu verbessern.

Was sind im Allgemeinen die nicht funktionalen Anforderungen für den Teil des Codes, an dem Sie arbeiten? Wenn eine Aktion unter 900 ms ausgeführt werden muss. In einem bestimmten Kontext (Maschine, Last usw.) ist die Leistung in 80% der Fälle geringer als 200 ms. Stellen Sie sicher, dass der Code in 100% der Fälle besser lesbar ist, auch wenn sich dies geringfügig auf die Leistung auswirkt. Wenn andererseits die gleiche Aktion niemals unter zehn Sekunden ausgeführt wird, sollten Sie lieber versuchen, zu ermitteln, was mit der Leistung (oder der Anforderung an erster Stelle) nicht stimmt.

Auch wie die Lesbarkeit verbessert die Leistung verringern? Häufig passen Entwickler das Verhalten an eine vorzeitige Optimierung an: Sie haben Angst, die Lesbarkeit zu verbessern, da sie glauben, dass dies die Leistung drastisch beeinträchtigt, während der besser lesbare Code einige Mikrosekunden länger für die gleiche Aktion benötigt.


47
+1! Wenn Sie keine Zahlen haben, besorgen Sie sich einige Zahlen. Wenn Sie keine Zeit haben, Nummern abzurufen, haben Sie keine Zeit, diese zu ändern.
Tacroy

49
Oftmals "optimieren" Entwickler aufgrund von Mythen und Missverständnissen, indem sie beispielsweise davon ausgehen, dass "C" schneller ist als "C ++", und C ++ - Funktionen aus einem allgemeinen Gefühl heraus vermeiden, dass die Dinge ohne Zahlen schneller sind, um sie zu sichern. Erinnert mich an einen C-Entwickler, dem ich gefolgt bin und der dachte, er gotosei schneller als für Schleifen. Ironischerweise schnitt der Optimierer mit for-Schleifen besser ab, sodass er den Code langsamer und schwerer lesbar machte.
Steven Burnap

6
Anstatt eine weitere Antwort hinzuzufügen, habe ich diese Antwort +1 gegeben. Wenn es so wichtig ist, Codefragmente zu verstehen, kommentieren Sie sie gut. Ich habe in einer C / C ++ / Assembly-Umgebung mit einem zehn Jahre alten Legacy-Code mit Dutzenden von Mitwirkenden gearbeitet. Wenn der Code funktioniert, lassen Sie ihn in Ruhe und machen Sie sich wieder an die Arbeit.
Chris K

Deshalb neige ich dazu, nur lesbaren Code zu schreiben. Leistung kann erreicht werden, indem die wenigen Hot-Spots geschnitten werden.
Luca

36

Normalerweise nein .

Das Ändern des Codes kann zu unvorhergesehenen Problemen an anderer Stelle im System führen (die manchmal bis zu einem späteren Zeitpunkt in einem Projekt unbemerkt bleiben können, wenn keine soliden Geräte- und Rauchprüfungen durchgeführt werden). Normalerweise gehe ich von der "wenn es nicht kaputt ist, repariere es nicht" -Mentalität aus.

Die Ausnahme von dieser Regel ist, wenn Sie eine neue Funktion implementieren, die diesen Code berührt. Wenn dies zu diesem Zeitpunkt keinen Sinn ergibt und das Refactoring wirklich stattfinden muss, sollten Sie dies tun, solange die Refactoring-Zeit (und ausreichende Tests und Puffer für die Behebung von Problemen) in Schätzungen berücksichtigt sind.

Natürlich Profil, Profil, Profil , besonders wenn es sich um einen kritischen Pfad handelt.


2
Ja, aber Sie gehen davon aus, dass die Optimierung erforderlich war. Wir wissen nicht immer, ob dies der Fall war, und wir möchten dies wahrscheinlich zuerst ermitteln.
Haylem

2
@ Haylem: Nein, ich gehe davon aus, dass der Code so funktioniert, wie er ist. Ich gehe auch davon aus, dass das Umgestalten des Codes ausnahmslos zu Problemen an anderer Stelle im System führen wird (es sei denn, Sie haben es mit einem unbedeutenden Codeblock zu tun, der keine externen Abhängigkeiten aufweist).
Demian Brecht

In dieser Antwort steckt etwas Wahres, und das liegt ironischerweise daran, dass die Entwickler nur selten Probleme dokumentieren, verstehen, kommunizieren oder sogar beachten. Wenn Entwickler die Probleme, die in der Vergangenheit aufgetreten sind , besser verstehen, wissen sie , was zu messen ist , und sind sicherer, Codeänderungen vorzunehmen.
rwong

29

In Kürze: es kommt darauf an

  • Benötigen oder verwenden Sie Ihre überarbeitete / verbesserte Version wirklich?

    • Gibt es einen konkreten Gewinn, sofort oder langfristig?
    • Ist dieser Gewinn nur für die Wartbarkeit oder wirklich architektonisch?
  • Muss es wirklich optimiert werden?

    • Warum?
    • Welchen Zielgewinn müssen Sie anstreben?

Im Detail

Wirst du das aufgeräumte, glänzende Zeug brauchen?

Hier gilt es Vorsicht walten zu lassen, und Sie müssen die Grenze zwischen dem, was real und messbar ist, und Ihrer persönlichen Präferenz und der potenziellen schlechten Angewohnheit, Code zu berühren, der nicht sein sollte, herausfinden.

Genauer gesagt:

Es gibt so etwas wie Over-Engineering

Es ist ein Anti-Pattern und es gibt einige Probleme:

  • es kann erweiterbarer sein , aber es kann nicht einfacher zu erweitern sein,
  • es ist vielleicht nicht einfacher zu verstehen ,
  • last, but not least hier: Sie könnten den gesamten Code verlangsamen.

Einige könnten auch das KISS-Prinzip als Referenz erwähnen , aber hier ist es kontraintuitiv: Ist der optimierte Weg der einfache Weg oder der rein architektonische Weg? Die Antwort ist nicht unbedingt absolut, wie im Folgenden erläutert.

Du wirst es nicht brauchen

Das YAGNI-Prinzip ist nicht ganz orthogonal zum anderen Thema, aber es hilft, sich die Frage zu stellen: Werden Sie es brauchen?

Ist die komplexere Architektur für Sie wirklich von Vorteil, abgesehen davon, dass Sie den Anschein haben, dass sie wartbarer ist?

Wenn es nicht kaputt ist, reparieren Sie es nicht

Schreiben Sie dies auf ein großes Poster und hängen Sie es neben Ihren Bildschirm, in die Küche bei der Arbeit oder in den Entwickler-Besprechungsraum. Natürlich gibt es noch viele andere Mantras, die es wert sind, wiederholt zu werden, aber gerade dieses ist wichtig, wenn Sie versuchen, "Wartungsarbeiten" durchzuführen und den Drang verspüren, es "zu verbessern".

Es ist für uns selbstverständlich, den Code "verbessern" zu wollen oder ihn sogar nur unbewusst zu berühren, während wir ihn lesen, um zu versuchen, ihn zu verstehen. Das ist eine gute Sache, da es bedeutet, dass wir uns eine Meinung bilden und versuchen, ein tieferes Verständnis für die Interna zu erlangen, aber es hängt auch von unserem Können und unserem Wissen ab (wie entscheiden Sie, was besser ist oder nicht?) ...) und all die Annahmen, die wir darüber machen, was wir glauben, dass wir die Software kennen ...:

  • tatsächlich tut,
  • muss eigentlich tun,
  • wird irgendwann tun müssen,
  • und wie gut es das macht.

Muss es wirklich optimiert werden?

All dies sagte, warum wurde es in erster Linie "optimiert"? Sie sagen, dass vorzeitige Optimierung die Wurzel allen Übels ist, und wenn Sie undokumentierten und anscheinend optimierten Code sehen, könnten Sie normalerweise annehmen, dass er wahrscheinlich nicht den Optimierungsregeln entsprach und den Optimierungsaufwand nicht so dringend benötigte und dass es nur der war Die Hybris eines gewöhnlichen Entwicklers setzt ein. Vielleicht redet es jetzt nur noch bei Ihnen.

Wenn ja, innerhalb welcher Grenzen wird es akzeptabel? Wenn es nötig ist, gibt es dieses Limit und es gibt dir Raum, Dinge zu verbessern, oder eine harte Linie, um zu entscheiden, es loszulassen.

Achten Sie auch auf unsichtbare Merkmale. Es besteht die Möglichkeit, dass Ihre "erweiterbare" Version dieses Codes auch zur Laufzeit mehr Arbeitsspeicher zur Verfügung stellt und sogar einen größeren statischen Speicherbedarf für die ausführbare Datei aufweist. Shiny OO-Funktionen sind mit solchen Kosten verbunden, die für Ihr Programm und die Umgebung, auf der sie ausgeführt werden sollen, von Bedeutung sein können.

Messen, Messen, Messen

Wie die Google-Leute jetzt, dreht sich alles um Daten! Wenn Sie es mit Daten sichern können, ist es notwendig.

Es gibt diese nicht so alte Geschichte, dass für jeden in die Entwicklung investierten Dollar mindestens ein Test- Dollar und mindestens ein Support-Dollar hinzukommen (aber es ist wirklich viel mehr).

Veränderungen wirken sich auf viele Dinge aus:

  • Möglicherweise müssen Sie einen neuen Build erstellen.
  • Sie sollten neue Komponententests schreiben (definitiv, wenn es keine gäbe, und Ihre erweiterbarere Architektur lässt wahrscheinlich Platz für mehr, da Sie mehr Oberfläche für Fehler haben).
  • Sie sollten neue Leistungstests schreiben (um sicherzustellen, dass diese auch in Zukunft stabil bleiben und um zu sehen, wo die Engpässe liegen), und dies ist schwierig .
  • Sie müssen es dokumentieren (und erweiterbar bedeutet mehr Platz für Details);
  • Sie (oder jemand anderes) müssen es in der Qualitätssicherung ausgiebig erneut testen.
  • Code ist (fast) nie fehlerfrei, und Sie müssen ihn unterstützen.

Es ist also nicht nur Hardware - Ressourcen Verbrauch (Ausführungsgeschwindigkeit oder Speicherbedarf) , die Sie hier messen müssen, es ist auch Team - Ressourcen Verbrauch. Beide müssen vorhergesagt werden, um ein Ziel zu definieren, gemessen, berücksichtigt und basierend auf der Entwicklung angepasst zu werden.

Und für Sie als Manager bedeutet dies, dass Sie es in den aktuellen Entwicklungsplan integrieren. Kommunizieren Sie also darüber und beschäftigen Sie sich nicht mit der Kodierung von Cowboys, U-Booten und Black-Ops.


Im Allgemeinen...

Ja aber...

Versteht mich im Allgemeinen nicht falsch, ich würde es begrüßen, wenn ihr mir vorschlägt, warum, und ich befürworte es oft. Sie müssen sich jedoch der langfristigen Kosten bewusst sein.

In einer perfekten Welt ist es die richtige Lösung:

  • Computerhardware wird mit der Zeit besser,
  • Compiler und Laufzeitplattformen werden mit der Zeit besser,
  • Sie erhalten nahezu perfekten, sauberen, wartbaren und lesbaren Code.

In der Praxis:

  • Sie können es noch schlimmer machen

    Sie benötigen mehr Augäpfel, um es zu betrachten, und je komplexer Sie es gestalten, desto mehr Augäpfel benötigen Sie.

  • Sie können die Zukunft nicht vorhersagen

    Sie können nicht mit absoluter Gewissheit wissen, ob Sie es jemals brauchen werden und auch nicht, ob die "Erweiterungen", die Sie brauchen, einfacher und schneller in der alten Form zu implementieren gewesen wären und ob sie selbst superoptimiert werden müssten .

  • Aus Sicht des Managements bedeutet dies enorme Kosten ohne direkten Gewinn.

Machen Sie es Teil des Prozesses

Sie erwähnen hier, dass es sich um eine eher kleine Änderung handelt und Sie einige spezifische Probleme im Auge haben. Ich würde sagen, dass es in diesem Fall normalerweise in Ordnung ist, aber die meisten von uns haben auch persönliche Geschichten über kleine Änderungen, fast chirurgische Eingriffe, die schließlich zu einem Alptraum für die Instandhaltung wurden und beinahe versäumte oder explodierte Fristen, weil Joe Programmer keine sah der Gründe hinter dem Code und berührte etwas, das nicht hätte sein sollen.

Wenn Sie einen Prozess haben, um solche Entscheidungen zu treffen, nehmen Sie ihnen den persönlichen Vorteil:

  • Wenn Sie die Dinge richtig testen, werden Sie schneller wissen, ob die Dinge kaputt sind.
  • Wenn Sie sie messen, werden Sie wissen, ob sie sich verbessert haben,
  • Wenn Sie es überprüfen, wissen Sie, ob es Leute abschreckt.

Testabdeckung, Profilerstellung und Datenerfassung sind schwierig

Aber natürlich leiden Ihr Testcode und Ihre Messdaten möglicherweise unter denselben Problemen, die Sie bei Ihrem eigentlichen Code vermeiden möchten: Testen Sie die richtigen Dinge und sind sie die richtigen für die Zukunft und messen Sie die richtigen Dinge?

Im Allgemeinen gilt: Je mehr Sie testen (bis zu einem bestimmten Grenzwert) und messen, desto mehr Daten erfassen Sie und desto sicherer sind Sie. Schlechte Analogiezeit: Betrachten Sie es als Autofahren (oder als Leben im Allgemeinen): Sie können der beste Fahrer der Welt sein, wenn das Auto auf Ihnen kaputt geht oder wenn sich jemand entschlossen hat, heute mit seinem eigenen Auto in Ihr Auto zu fahren Fähigkeiten könnten nicht genug sein. Es gibt sowohl Umweltsachen, die Sie treffen können, als auch menschliche Fehler, die von Bedeutung sind.

Code Reviews sind die Flurtests des Entwicklungsteams

Und ich denke, der letzte Teil ist der Schlüssel hier: Codeüberprüfungen durchführen. Sie werden den Wert Ihrer Verbesserungen nicht kennen, wenn Sie sie solo machen. Codeüberprüfungen sind unsere "Flurtests": Befolgen Sie die Raymond-Version des Linus-Gesetzes, um sowohl Fehler als auch Überentwicklungen und andere Anti-Muster zu erkennen und sicherzustellen, dass der Code den Fähigkeiten Ihres Teams entspricht. Es hat keinen Sinn, den "besten" Code zu haben, wenn niemand anders als Sie ihn verstehen und pflegen kann, und das gilt sowohl für kryptische Optimierungen als auch für 6-Schichten-Architekturentwürfe.

Denken Sie zum Schluss daran:

Jeder weiß, dass das Debuggen doppelt so schwer ist wie das Schreiben eines Programms. Also, wenn Sie so schlau sind, wie Sie können, wenn Sie es schreiben, wie werden Sie es jemals debuggen? - Brian Kernighan


"Wenn es nicht kaputt ist, reparieren Sie es nicht" widerspricht dem Refactoring. Es spielt keine Rolle, ob etwas funktioniert oder nicht gewartet werden muss.
Miyamoto Akira

@MiyamotoAkira: Es ist eine Sache mit zwei Geschwindigkeiten. Wenn es nicht kaputt ist , aber akzeptabel und weniger wahrscheinlich Unterstützung zu sehen, es könnte akzeptabel sein , um es eher in Ruhe zu lassen , als die Einführung potenzielle neue Bugs oder Ausgaben Entwicklungszeit darauf. Es geht darum, den kurzfristigen und langfristigen Nutzen des Refactorings zu bewerten. Es gibt keine eindeutige Antwort, es ist eine Evaluierung erforderlich.
Haylem

einverstanden. Ich nehme an, ich mag den Satz (und die Philosophie dahinter) nicht, weil ich Refactoring als Standardoption sehe, und nur, wenn es so aussieht, als würde das zu lange oder zu schwierig dauern, wird / sollte entschieden werden, nicht mach mit. Wohlgemerkt, ich wurde von Leuten verbrannt, die Dinge nicht änderten, die, obwohl sie funktionierten, eindeutig die falsche Lösung waren, sobald Sie sie warten oder erweitern mussten.
Miyamoto Akira,

@MiyamotoAkira: Kurze Sätze und Meinungsäußerungen können nicht viel ausdrücken. Sie sollen in Ihrem Gesicht sein und nebenbei entwickelt werden, denke ich. Ich bin selbst sehr daran interessiert, Code so oft wie möglich zu überprüfen und zu berühren, auch wenn dies oft ohne großes Sicherheitsnetz oder ohne viel Grund geschieht. Wenn es schmutzig ist, säubern Sie es. In ähnlicher Weise habe ich mich auch ein paar Mal verbrannt. Und wird immer noch verbrannt. Solange es sich nicht um solche 3. Grades handelt, stört es mich nicht so sehr. Bisher waren es immer kurzzeitige Verbrennungen für langfristige Gewinne.
Haylem

8

Im Allgemeinen sollten Sie sich zuerst auf die Lesbarkeit und später auf die Leistung konzentrieren. Meistens sind diese Leistungsoptimierungen ohnehin vernachlässigbar, aber die Wartungskosten können enorm sein.

Natürlich sollten alle "kleinen" Dinge zugunsten der Übersichtlichkeit geändert werden, da, wie Sie bereits betont haben, die meisten davon ohnehin vom Compiler optimiert werden.

Bei den größeren Optimierungen besteht möglicherweise die Möglichkeit, dass die Optimierungen tatsächlich entscheidend für das Erreichen einer angemessenen Leistung sind (obwohl dies nicht überraschend oft der Fall ist). Ich würde Ihre Änderungen vornehmen und dann den Code vor und nach den Änderungen profilieren. Wenn der neue Code erhebliche Leistungsprobleme aufweist, können Sie jederzeit ein Rollback auf die optimierte Version durchführen. Andernfalls können Sie einfach die sauberere Codeversion beibehalten.

Ändern Sie jeweils nur einen Teil des Codes und überprüfen Sie, wie sich dies nach jeder Refactoring-Runde auf die Leistung auswirkt.


8

Dies hängt davon ab, warum der Code optimiert wurde und wie sich die Änderung auswirkt und wie sich der Code auf die Gesamtleistung auswirkt. Es sollte auch davon abhängen, ob Sie eine gute Möglichkeit haben, Teständerungen zu laden.

Sie sollten diese Änderung nicht vornehmen, ohne vorher und nachher ein Profil zu erstellen und dies unter einer Last, die der Last in der Produktion ähnelt. Das bedeutet, dass Sie keine winzigen Datenmengen auf einem Entwicklercomputer verwenden oder testen, wenn nur ein Benutzer das System verwendet.

Wenn die Optimierung kürzlich durchgeführt wurde, können Sie möglicherweise mit dem Entwickler sprechen und herausfinden, was genau das Problem war und wie langsam die Anwendung vor der Optimierung war. Dies kann Ihnen viel darüber aussagen, ob es sich lohnt, die Optimierung durchzuführen, und für welche Bedingungen die Optimierung erforderlich war (ein Bericht, der beispielsweise ein ganzes Jahr abdeckt, ist möglicherweise erst im September oder Oktober langsam geworden, wenn Sie Ihre Änderung testen im Februar ist die Langsamkeit möglicherweise noch nicht erkennbar und der Test ungültig).

Wenn die Optimierung ziemlich alt ist, sind neuere Methoden möglicherweise sogar schneller und besser lesbar.

Letztendlich ist dies eine Frage an Ihren Chef. Es ist zeitaufwändig, etwas zu überarbeiten, das optimiert wurde, und sicherzustellen, dass die Änderung das Endergebnis nicht beeinflusst und dass sie im Vergleich zur alten Methode eine ebenso gute oder zumindest akzeptable Leistung erbringt. Möglicherweise möchte er, dass Sie Ihre Zeit in anderen Bereichen verbringen, anstatt eine riskante Aufgabe zu übernehmen, um ein paar Minuten Programmierzeit zu sparen. Oder er stimmt zu, dass der Code schwer zu verstehen ist, häufige Eingriffe erforderlich sind und dass jetzt bessere Methoden verfügbar sind.


6

Wenn die Profilerstellung zeigt, dass die Optimierung nicht benötigt wird (sie befindet sich nicht in einem kritischen Bereich) oder sogar eine schlechtere Laufzeit aufweist (aufgrund einer schlechten vorzeitigen Optimierung), ersetzen Sie sie auf jeden Fall durch den lesbaren Code, der einfacher zu warten ist

Stellen Sie außerdem sicher, dass sich der Code bei den entsprechenden Tests gleich verhält


5

Betrachten Sie es aus geschäftlicher Sicht. Was kostet die Änderung? Wie viel Zeit benötigen Sie, um die Änderung vorzunehmen, und wie viel werden Sie langfristig sparen, wenn Sie den Code einfacher erweitern oder warten möchten? Fügen Sie dieser Zeit nun ein Preisschild hinzu und vergleichen Sie es mit dem Geldverlust durch Leistungsreduzierung. Möglicherweise müssen Sie einen Server hinzufügen oder aktualisieren, um den Leistungsverlust auszugleichen. Möglicherweise entspricht das Produkt nicht mehr den Anforderungen und kann nicht mehr verkauft werden. Vielleicht gibt es keinen Verlust. Möglicherweise erhöht die Änderung die Robustheit und spart an anderer Stelle Zeit. Treffen Sie jetzt Ihre Entscheidung.

In einigen Fällen ist es möglich, beide Versionen eines Fragments beizubehalten. Sie können einen Test schreiben, der zufällige Eingabewerte generiert, und die Ergebnisse mit der anderen Version überprüfen. Verwenden Sie die "clevere" Lösung, um das Ergebnis einer vollkommen verständlichen und offensichtlich korrekten Lösung zu überprüfen und sich damit zu versichern (aber keinen Beweis), dass die neue Lösung der alten entspricht. Oder gehen Sie in die andere Richtung und überprüfen Sie das Ergebnis des kniffligen Codes mit dem ausführlichen Code und dokumentieren Sie so die Absicht hinter dem Hack auf eindeutige Weise.


4

Grundsätzlich fragen Sie, ob Refactoring ein lohnendes Unterfangen ist. Die Antwort darauf lautet mit Sicherheit ja.

Aber...

... Sie müssen es sorgfältig tun. Für jeden Code, den Sie überarbeiten, benötigen Sie solide Einheits-, Integrations-, Funktions- und Leistungstests. Sie müssen sicher sein, dass sie wirklich alle erforderlichen Funktionen testen. Sie müssen sie einfach und wiederholt ausführen können. Sobald Sie das haben, sollten Sie in der Lage sein, Komponenten durch neue Komponenten mit der entsprechenden Funktionalität zu ersetzen.

Martin Fowler hat das Buch dazu geschrieben.


3

Sie sollten Arbeits-, Produktionscode nicht ohne guten Grund ändern. "Refactoring" ist kein guter Grund, es sei denn, Sie können Ihre Arbeit ohne dieses Refactoring nicht erledigen. Selbst wenn Sie Fehler im schwierigen Code selbst beheben, sollten Sie sich die Zeit nehmen, ihn zu verstehen und die kleinstmögliche Änderung vorzunehmen. Wenn der Code so schwer zu verstehen ist, können Sie ihn nicht vollständig verstehen, und alle Änderungen, die Sie vornehmen, haben unvorhersehbare Nebenwirkungen - mit anderen Worten, Fehler. Je größer die Änderung, desto wahrscheinlicher ist es, dass Sie Probleme verursachen.

Es würde eine Ausnahme geben: Wenn der unverständliche Code einen vollständigen Satz von Komponententests hätte, könnten Sie ihn umgestalten. Da ich noch nie einen unverständlichen Code mit vollständigen Komponententests gesehen oder gehört habe, schreiben Sie zuerst die Komponententests, holen die Zustimmung der erforderlichen Personen ein, dass diese Komponententests tatsächlich darstellen, was der Code tun soll, und nehmen DANN die Codeänderungen vor . Ich habe das ein- oder zweimal gemacht. Es ist schmerzhaft im Nacken und sehr teuer, führt aber letztendlich zu guten Ergebnissen.


3

Wenn es sich nur um einen kurzen Code handelt, der etwas relativ Einfaches auf schwer verständliche Weise erledigt, würde ich das "schnelle Verstehen" in einem erweiterten Kommentar und / oder einer nicht verwendeten alternativen Implementierung ändern, wie z

#ifdef READABLE_ALT_IMPLEMENTATION

   double x=0;
   for(double n: summands)
     x += n;
   return x;

#else

   auto subsum = [&](int lb, int rb){
          double x=0;
          while(lb<rb)
            x += summands[lb++];
          return x;
        };
   double x_fin=0;
   for(double nsm: par_eval( subsum
                           , partitions(n_threads, 0, summands.size()) ) )
     x_fin += nsm;
   return x_fin;

#endif

3

Die Antwort lautet ohne Einschränkung der Allgemeinheit ja. Fügen Sie immer modernen Code hinzu, wenn Sie schwer lesbaren Code sehen, und löschen Sie in den meisten Fällen den fehlerhaften Code. Ich benutze den folgenden Prozess:

  1. Suchen Sie nach dem Leistungstest und den unterstützenden Profilinformationen. Wenn es keinen Leistungstest gibt, kann das, was ohne Beweise geltend gemacht werden kann, ohne Beweise abgewiesen werden. Stellen Sie sicher, dass Ihr moderner Code schneller ist, und entfernen Sie den alten Code. Wenn jemand argumentiert (auch Sie selbst), bitten Sie ihn, den Profiling-Code zu schreiben, um zu beweisen, was schneller ist.
  2. Wenn der Profiling-Code vorhanden ist, schreiben Sie den modernen Code trotzdem. Nennen Sie es so etwas wie <function>_clean(). Dann "rennen" Sie Ihren Code gegen den schlechten Code. Wenn Ihr Code besser ist, entfernen Sie den alten Code.
  3. Wenn der alte Code schneller ist, lassen Sie Ihren modernen Code trotzdem drin. Es dient als gute Dokumentation für die Aufgaben des anderen Codes. Da der "Race" -Code vorhanden ist, können Sie ihn weiterhin ausführen, um die Leistungsmerkmale und Unterschiede zwischen den beiden Pfaden zu dokumentieren. Sie können auch einen Komponententest auf Unterschiede im Codeverhalten durchführen. Wichtig ist, dass der moderne Code eines Tages den "optimierten" Code übertrifft, was garantiert ist. Sie können dann den fehlerhaften Code entfernen.

QED.


3

Wenn ich der Welt eine Sache (über Software) beibringen könnte, bevor ich sterbe, würde ich lehren, dass "Leistung gegen X" ein falsches Dilemma ist.

Refactoring ist in der Regel als Segen für Lesbarkeit und Zuverlässigkeit bekannt, kann jedoch ebenso gut die Optimierung unterstützen. Wenn Sie die Leistungsverbesserung als eine Reihe von Umgestaltungen behandeln, können Sie die Campingplatzregel einhalten und gleichzeitig die Anwendung beschleunigen. Tatsächlich obliegt es Ihnen, zumindest meiner Meinung nach, dies ethisch zu tun.

Zum Beispiel ist der Autor dieser Frage auf ein verrücktes Stück Code gestoßen. Wenn diese Person meinen Code lesen würde, würde sie feststellen, dass der verrückte Teil 3-4 Zeilen lang ist. Es ist eine Methode für sich und der Methodenname und die Beschreibung geben an, WAS die Methode tut. Die Methode würde 2-6 Zeilen Inline-Kommentare enthalten, die beschreiben, wie der verrückte Code trotz seines fragwürdigen Erscheinungsbilds die richtige Antwort erhält.

Auf diese Weise kompartimentiert, können Sie Implementierungen dieser Methode beliebig austauschen. Wahrscheinlich habe ich die verrückte Version so geschrieben. Sie können es gerne versuchen oder sich zumindest nach Alternativen erkundigen. Die meiste Zeit werden Sie feststellen, dass die naive Implementierung merklich schlechter ist (normalerweise bemühe ich mich nur um eine 2-10-fache Verbesserung), aber Compiler und Bibliotheken ändern sich ständig und wer weiß, was Sie heute vielleicht finden, wenn es nicht verfügbar war wurde die funktion geschrieben?


Ein wichtiger Schlüssel zur Effizienz ist in vielen Fällen, dass ein Code so viel Arbeit wie möglich auf eine Weise leistet, die effizient erledigt werden kann. Eines der Dinge, die mich an .NET ärgern, ist, dass es keine effizienten Mechanismen gibt, um z. B. Teile einer Sammlung in eine andere zu kopieren. Die meisten Sammlungen speichern große Gruppen von aufeinanderfolgenden Elementen (wenn nicht das Ganze) in Arrays. Wenn Sie also beispielsweise die letzten 5.000 Elemente aus einer Liste mit 50.000 Elementen kopieren, sollten Sie diese in einige Massenkopiervorgänge (wenn nicht nur einen) und einige andere zerlegen Schritte, die höchstens ein paar Mal ausgeführt wurden.
Supercat

Selbst in Fällen, in denen es möglich sein sollte, solche Operationen effizient auszuführen, ist es leider häufig erforderlich, "sperrige" Schleifen für 5.000 Iterationen (und in einigen Fällen 45.000!) Auszuführen. Wenn eine Operation auf Dinge wie Massen-Array-Kopien reduziert werden kann, können diese zu extremen Graden optimiert werden, was zu einer erheblichen Auszahlung führt. Wenn jede Schleifeniteration ein Dutzend Schritte benötigt, ist es schwierig, einen dieser Schritte besonders gut zu optimieren.
Supercat

2

Es ist wahrscheinlich keine gute Idee, sie anzufassen. Wenn der Code aus Leistungsgründen so geschrieben wurde, kann dies dazu führen, dass zuvor behobene Leistungsprobleme behoben werden.

Wenn Sie noch entscheiden , Dinge zu ändern besser lesbar und erweiterbar zu sein: Bevor Sie eine Änderung vornehmen, Benchmark der alte Code unter schweren Last. Noch besser, wenn Sie ein altes Dokument oder ein Trouble Ticket finden, das das Leistungsproblem beschreibt, das mit diesem seltsam aussehenden Code behoben werden soll. Führen Sie die Leistungstests erneut aus, nachdem Sie Ihre Änderungen vorgenommen haben. Wenn es nicht sehr unterschiedlich ist oder sich immer noch innerhalb akzeptabler Parameter befindet, ist es wahrscheinlich in Ordnung.

Es kann manchmal vorkommen, dass dieser leistungsoptimierte Code, wenn sich andere Teile eines Systems ändern, nicht mehr so ​​stark optimiert werden muss, aber ohne strenge Tests kann man das nicht mit Sicherheit wissen.


1
Einer der Jungs, mit denen ich jetzt zusammenarbeite, liebt es , Dinge in Bereichen zu optimieren, die die Benutzer einmal im Monat treffen, wenn auch oft. Es braucht Zeit und verursacht nicht selten andere Probleme, da er gerne Code-and-Commit ausführt und QA oder andere nachgeschaltete Funktionen testen lässt. : / Um fair zu sein, er ist im Allgemeinen schnell, schnell und genau, aber diese "Optimierungen" machen es dem Rest des Teams nur schwerer und ihr permanenter Tod wäre eine gute Sache.
DaveE

@ DaveE: Werden diese Optimierungen wegen oder wirklichen Performanceproblemen angewendet? Oder macht dieser Entwickler das nur, weil er es kann? Ich denke, wenn Sie wissen, dass die Optimierungen keine Auswirkungen haben werden, können Sie sie sicher durch besser lesbaren Code ersetzen, aber ich würde nur jemandem vertrauen, der ein Experte für das System ist.
FrustratedWithFormsDesigner

Sie sind erledigt, weil er es kann. Eigentlich spart er normalerweise einige Zyklen, aber wenn die Benutzerinteraktion mit dem Programmelement einige Sekunden dauert (15 bis 300), ist es dumm, eine Zehntelsekunde der Laufzeit zu rasieren, um "Effizienz" zu erreichen. Besonders wenn die Leute, die ihm folgen, sich Zeit nehmen müssen, um zu verstehen, was er getan hat. Dies ist eine PowerBuilder-Anwendung, die ursprünglich vor 16 Jahren erstellt wurde. Angesichts der Entstehung der Dinge ist die Denkweise vielleicht verständlich, aber er weigert sich, seine Denkweise an die aktuelle Realität anzupassen.
DaveE

@ DaveE: Ich glaube, ich stimme mehr mit dem Typ überein, mit dem Sie arbeiten, als mit Ihnen. Wenn ich aus absolut keinem Grund langsame Dinge reparieren darf, werde ich wahnsinnig. Wenn ich eine C ++ - Zeile sehe, die wiederholt den Operator + verwendet, um einen String zusammenzustellen, oder Code, der jedes Mal / dev / urandom öffnet und liest, nur weil jemand vergessen hat, ein Flag zu setzen, dann repariere ich es. Durch meine Fanatiker habe ich es geschafft, die Geschwindigkeit zu steigern, wenn andere Leute es jeweils eine Mikrosekunde lang laufen ließen.
Zan Lynx

1
Nun, wir müssen zustimmen, dass wir nicht zustimmen. Es ist nicht richtig, eine Stunde damit zu verbringen, etwas zu ändern, um zur Laufzeit Sekundenbruchteile für eine Funktion zu sparen, die wirklich gelegentlich ausgeführt wird, und den Code für die anderen Entwickler in einem kratzenden Zustand zu belassen. Wenn dies Funktionen waren, die wiederholt in stark beanspruchten Teilen der App ausgeführt wurden, ist dies in Ordnung. Aber das ist nicht der Fall, den ich beschreibe. Dies ist eine wirklich unentgeltliche Codefinagulierung, und zwar aus keinem anderen Grund als zu sagen: "Ich habe dieses Ding so gemacht, dass UserX es einmal pro Woche ein bisschen schneller macht". In der Zwischenzeit müssen wir die Arbeit bezahlen, die erledigt werden muss.
DaveE

2

Das Problem hierbei ist, zwischen "optimiert" und "lesbar" und "erweiterbar" zu unterscheiden. Was wir als Benutzer als optimierten Code und was der Compiler als optimiert ansieht, sind zwei verschiedene Dinge. Der Code, den Sie ändern möchten, ist möglicherweise überhaupt kein Engpass, und selbst wenn der Code "schlank" ist, muss er nicht einmal "optimiert" werden. Wenn der Code alt genug ist, führt der Compiler möglicherweise Optimierungen an den integrierten Funktionen durch, sodass die Verwendung einer neueren einfachen integrierten Struktur gleichermaßen oder effizienter ist als der alte Code.

Und "schlanker", nicht lesbarer Code ist nicht immer optimiert.

Früher war ich der Meinung, dass cleverer / schlanker Code guter Code ist, aber manchmal habe ich mich bei der Codekreation eher auf undurchsichtige Regeln der Sprache gestützt als auf Hilfe, als ich dies versuchte Seien Sie clever, weil der Compiler Ihren cleveren Code für die eingebettete Hardware völlig unbrauchbar macht.


2

Ich werde optimierten Code niemals durch lesbaren Code ersetzen, da ich keine Kompromisse bei der Leistung eingehen kann und mich dafür entscheide, in jedem Abschnitt die richtigen Kommentare zu verwenden, damit jeder die in diesem optimierten Abschnitt implementierte Logik verstehen kann, mit der beide Probleme behoben werden.

Daher wird der Code optimiert und durch das richtige Kommentieren auch lesbar.

HINWEIS: Sie können einen optimierten Code mithilfe von Kommentaren lesbar machen, aber keinen optimierten lesbaren Code.


Ich wäre dieser Vorgehensweise überdrüssig, da nur eine Person den Code bearbeitet, um zu vergessen, den Kommentar synchron zu halten. Plötzlich wird jede nachfolgende Überprüfung weggehen und denken, dass sie X ausführt, während sie Y ausführt.
John D

2

Hier ist ein Beispiel, um den Unterschied zwischen einfachem Code und optimiertem Code zu sehen: https://stackoverflow.com/a/11227902/1396264

Gegen Ende der Antwort ersetzt er nur:

if (data[c] >= 128)
    sum += data[c];

mit:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

Um fair zu sein, ich habe keine Ahnung, durch was die if-Anweisung ersetzt wurde, aber wie der Antwortende sagt, gibt es einige bitweise Operationen, die das gleiche Ergebnis liefern (ich werde einfach sein Wort dafür nehmen) .

Dies dauert weniger als ein Viertel der ursprünglichen Zeit (11,54 s gegenüber 2,5 s).


1

Die Hauptfrage ist hier: Ist die Optimierung erforderlich?

Wenn dies der Fall ist, können Sie ihn nicht durch langsameren, besser lesbaren Code ersetzen. Sie müssen Kommentare usw. hinzufügen, um die Lesbarkeit zu verbessern.

Wenn der Code nicht optimiert werden muss, sollte dies nicht der Fall sein (so dass die Lesbarkeit beeinträchtigt wird), und Sie können ihn neu faktorisieren, um die Lesbarkeit zu verbessern.

JEDOCH - stellen Sie sicher, dass Sie genau wissen, was der Code tut und wie Sie ihn gründlich testen, bevor Sie anfangen, Dinge zu ändern. Dies schließt die Spitzenauslastung usw. ein. Wenn Sie keine Reihe von Testfällen erstellen und diese vorher und nachher ausführen müssen, haben Sie keine Zeit für das Refactoring.


1

So mache ich Dinge: Zuerst lasse ich es in lesbarem Code funktionieren, dann optimiere ich es. Ich behalte die Originalquelle und dokumentiere meine Optimierungsschritte.

Wenn ich dann eine Funktion hinzufügen muss, gehe ich zu meinem lesbaren Code zurück, füge die Funktion hinzu und folge den von mir dokumentierten Optimierungsschritten. Weil Sie dokumentiert haben, ist es sehr schnell und einfach, Ihren Code mit der neuen Funktion neu zu optimieren.


0

IMHO-Lesbarkeit ist wichtiger als optimierter Code, da sich die Mikrooptimierung in den meisten Fällen nicht lohnt.

Artikel über unsinnige Mikrooptimierungen :

Wie die meisten von uns bin ich müde, Blog-Posts über unsinnige Mikrooptimierungen wie das Ersetzen von print durch echo, ++ $ i durch $ i ++ oder doppelte Anführungszeichen durch einfache Anführungszeichen zu lesen. Warum? Weil 99,999999% der Zeit, ist es irrelevant.

"print" verwendet einen Opcode mehr als "echo", da er tatsächlich etwas zurückgibt. Wir können daraus schließen, dass das Echo schneller ist als das Drucken. Aber ein Opcode kostet nichts, wirklich nichts.

Ich habe eine neue WordPress-Installation ausprobiert. Das Skript wird angehalten, bevor es mit einem "Busfehler" auf meinem Laptop endet, aber die Anzahl der Opcodes lag bereits bei mehr als 2,3 Millionen. Genug gesagt.


0

Optimierung ist relativ. Zum Beispiel:

Betrachten Sie eine Klasse mit einer Reihe von BOOL-Mitgliedern:

// no nitpicking over BOOL vs bool allowed
class Pear {
 ...
 BOOL m_peeled;
 BOOL m_sliced;
 BOOL m_pitted;
 BOOL m_rotten;
 ...
};

Sie könnten versucht sein, die BOOL-Felder in Bitfelder umzuwandeln:

class Pear {
 ...
 BOOL m_peeled:1;
 BOOL m_sliced:1;
 BOOL m_pitted:1;
 BOOL m_rotten:1;
 ...
};

Da ein BOOL als INT (was auf Windows-Plattformen eine vorzeichenbehaftete 32-Bit-Ganzzahl ist) eingegeben wird, dauert dies sechzehn Bytes und packt sie in eine. Das sind 93% Ersparnis! Wer könnte sich darüber beschweren?

Diese Annahme:

Da ein BOOL als INT (was auf Windows-Plattformen eine vorzeichenbehaftete 32-Bit-Ganzzahl ist) eingegeben wird, dauert dies sechzehn Bytes und packt sie in eine. Das sind 93% Ersparnis! Wer könnte sich darüber beschweren?

führt zu:

Das Konvertieren einer BOOL in ein Einzelbitfeld sparte drei Datenbytes, kostete jedoch acht Code-Bytes, wenn dem Member ein nicht konstanter Wert zugewiesen wurde. Ebenso wird das Extrahieren des Werts teurer.

Was früher war

 push [ebx+01Ch]      ; m_sliced
 call _Something@4    ; Something(m_sliced);

wird

 mov  ecx, [ebx+01Ch] ; load bitfield value
 shl  ecx, 30         ; put bit at top
 sar  ecx, 31         ; move down and sign extend
 push ecx
 call _Something@4    ; Something(m_sliced);

Die Bitfeldversion ist um neun Bytes größer.

Nehmen wir Platz und rechnen. Angenommen, auf jedes dieser Bitfeldfelder wird in Ihrem Code sechsmal zugegriffen, dreimal zum Schreiben und dreimal zum Lesen. Die Kosten für das Codewachstum betragen ungefähr 100 Byte. Es sind nicht genau 102 Bytes, da der Optimierer für einige Operationen möglicherweise bereits in den Registern enthaltene Werte nutzen kann und die zusätzlichen Anweisungen möglicherweise versteckte Kosten in Bezug auf eine verringerte Registerflexibilität verursachen. Der tatsächliche Unterschied kann mehr oder weniger betragen, aber für eine Berechnung auf der Rückseite des Umschlags nennen wir ihn 100. In der Zwischenzeit betrug die Speichereinsparung 15 Byte pro Klasse. Daher ist der Breakeven-Punkt sieben. Wenn Ihr Programm weniger als sieben Instanzen dieser Klasse erstellt, übersteigen die Codekosten die Dateneinsparungen: Ihre Speicheroptimierung war eine Speicherdeoptimierung.

Verweise

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.