it-swarm.com.de

Mein Chef bittet mich, keine kleinen Funktionen mehr zu schreiben und alles in derselben Schleife zu erledigen

Ich habe ein Buch mit dem Titel Clean Code von Robert C. Martin gelesen. In diesem Buch habe ich viele Methoden zum Bereinigen von Code gesehen, z. B. das Schreiben kleiner Funktionen, das sorgfältige Auswählen von Namen usw. Es scheint bei weitem das interessanteste Buch über das Bereinigen von Code zu sein, das ich gelesen habe. Heute hat es meinem Chef jedoch nicht gefallen, wie ich nach dem Lesen dieses Buches Code geschrieben habe.

Seine Argumente waren

  • Das Schreiben kleiner Funktionen ist schmerzhaft, da Sie gezwungen sind, in jede kleine Funktion zu wechseln, um zu sehen, was der Code tut.
  • Legen Sie alles in eine große Hauptschleife, auch wenn die Hauptschleife mehr als 300 Zeilen umfasst, ist das Lesen schneller.
  • Schreiben Sie nur kleine Funktionen, wenn Sie Code duplizieren müssen.
  • Schreiben Sie keine Funktion mit dem Namen des Kommentars, sondern setzen Sie Ihre komplexe Codezeile (3-4 Zeilen) mit einem Kommentar oben; Ebenso können Sie den fehlerhaften Code direkt ändern

Das ist gegen alles, was ich gelesen habe. Wie schreibt man normalerweise Code? Eine große Hauptschleife, keine kleinen Funktionen?

Die Sprache, die ich benutze, ist hauptsächlich Javascript. Ich habe jetzt wirklich Schwierigkeiten beim Lesen, da ich alle meine kleinen, klar benannten Funktionen gelöscht und alles in eine große Schleife gestellt habe. Meinem Chef gefällt es jedoch so.

Ein Beispiel war:

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

In dem Buch, das ich zum Beispiel gelesen habe, werden Kommentare als Fehler beim Schreiben von sauberem Code angesehen, da sie veraltet sind, wenn Sie kleine Funktionen schreiben, und häufig zu nicht aktualisierten Kommentaren führen (Sie ändern Ihren Code und nicht den Kommentar). Ich lösche jedoch den Kommentar und schreibe eine Funktion mit dem Namen des Kommentars.

Nun, ich hätte gerne einen Rat, wie/in welcher Praxis ist es besser, sauberen Code zu schreiben?

209

Nehmen Sie zuerst die Codebeispiele. Sie bevorzugen:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Und Ihr Chef würde es schreiben als:

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

Meiner Ansicht nach haben beide Probleme. Als ich Ihren Code las, war mein unmittelbarer Gedanke "Sie können dieses if durch einen ternären Ausdruck ersetzen". Dann las ich den Code Ihres Chefs und dachte: "Warum hat er Ihre Funktion durch einen Kommentar ersetzt?".

Ich würde vorschlagen, dass der optimale Code zwischen den beiden liegt:

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Das gibt Ihnen das Beste aus beiden Welten: Ein vereinfachter Testausdruck und der Kommentar werden durch testbaren Code ersetzt.

In Bezug auf die Ansichten Ihres Chefs zum Code-Design:

Das Schreiben kleiner Funktionen ist schmerzhaft, da Sie gezwungen sind, in die einzelnen kleinen Funktionen zu wechseln, um zu sehen, was der Code tut.

Wenn die Funktion gut benannt ist, ist dies nicht der Fall. isApplicationInProduction ist selbstverständlich und es sollte nicht notwendig sein, den Code zu untersuchen, um zu sehen, was er tut. In der Tat ist das Gegenteil der Fall: Wenn Sie den Code untersuchen, wird weniger auf die Absicht hingewiesen als auf den Funktionsnamen (weshalb Ihr Chef auf Kommentare zurückgreifen muss).

Legen Sie alles in eine große Hauptschleife, auch wenn die Hauptschleife mehr als 300 Zeilen umfasst, ist das Lesen schneller

Das Durchsuchen ist möglicherweise schneller, aber um den Code wirklich zu "lesen", müssen Sie ihn effektiv in Ihrem Kopf ausführen können. Das ist mit kleinen Funktionen einfach und mit Methoden, die Hunderte von Zeilen lang sind, sehr, sehr schwierig.

Schreiben Sie nur kleine Funktionen, wenn Sie Code duplizieren müssen

Ich stimme dir nicht zu. Wie Ihr Codebeispiel zeigt, verbessern kleine, gut benannte Funktionen die Lesbarkeit von Code und sollten immer dann verwendet werden, wenn Sie beispielsweise nicht am "Wie", sondern nur am "Was" einer Funktionalität interessiert sind.

Schreiben Sie keine Funktion mit dem Namen des Kommentars, sondern setzen Sie Ihre komplexe Codezeile (3-4 Zeilen) mit einem Kommentar oben. Auf diese Weise können Sie den fehlerhaften Code direkt ändern

Ich kann die Gründe dafür wirklich nicht verstehen, vorausgesetzt, es ist wirklich ernst. Es ist die Art von Dingen, von denen ich erwarten würde, dass sie von The Expert Beginner Twitter-Account in Parodie geschrieben werden. Kommentare haben einen grundlegenden Fehler: Sie werden nicht kompiliert/interpretiert und können daher nicht auf Einheiten getestet werden. Der Code wird geändert und der Kommentar wird in Ruhe gelassen, und Sie wissen am Ende nicht, welcher richtig ist.

Das Schreiben von selbstdokumentierendem Code ist schwierig, und manchmal werden zusätzliche Dokumente (auch in Form von Kommentaren) benötigt. Aber die Ansicht von "Onkel Bob", dass Kommentare ein Codierungsfehler sind, trifft allzu oft zu.

Lassen Sie Ihren Chef das Clean Code-Buch lesen und versuchen Sie nicht, Ihren Code weniger lesbar zu machen, nur um ihn zufrieden zu stellen. Wenn Sie ihn jedoch nicht zum Wechsel überreden können, müssen Sie sich entweder anstellen oder einen neuen Chef finden, der besser codieren kann.

214
David Arno

Es gibt andere Probleme

Keiner der Codes ist gut, da beide im Grunde genommen den Code mit einem Debug-Testfall aufblähen. Was ist, wenn Sie aus irgendeinem Grund mehr Dinge testen möchten?

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

oder

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

Möchten Sie noch mehr Zweige hinzufügen?

Das wesentliche Problem besteht darin, dass Sie einen Teil Ihres Codes grundsätzlich duplizieren und somit den tatsächlichen Code nicht tatsächlich testen. Sie schreiben Debug-Code, um den Debug-Code zu testen, nicht jedoch den Produktionscode. Es ist, als würde man teilweise eine parallele Codebasis erstellen.

Sie streiten sich mit Ihrem Chef darüber, wie Sie schlechten Code klüger schreiben können. Stattdessen sollten Sie das inhärente Problem des Codes selbst beheben.

Abhängigkeitsinjektion

So sollte Ihr Code aussehen:

phoneNumber = headers.resourceId;

Hier gibt es keine Verzweigung, da die Logik hier keine Verzweigung hat. Das Programm sollte die Telefonnummer aus der Kopfzeile ziehen. Zeitraum.

Wenn Sie als Ergebnis DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY Haben möchten, sollten Sie es in headers.resourceId Einfügen. Eine Möglichkeit besteht darin, einfach ein anderes headers -Objekt für Testfälle einzufügen (sorry, wenn dies kein richtiger Code ist, sind meine JavaScript-Kenntnisse etwas verrostet):

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

Angenommen, headers ist Teil einer Antwort, die Sie von einem Server erhalten: Idealerweise haben Sie einen ganzen Testserver, der headers verschiedener Art zu Testzwecken liefert. Auf diese Weise testen Sie den tatsächlichen Produktionscode wie er ist und nicht einen halb duplizierten Code, der möglicherweise wie der Produktionscode funktioniert oder nicht.

222
null

Darauf gibt es keine "richtige" oder "falsche" Antwort. Ich werde jedoch meine Meinung auf der Grundlage von 36 Jahren Berufserfahrung beim Entwerfen und Entwickeln von Softwaresystemen abgeben ...

  1. Es gibt keinen "selbstdokumentierenden Code". Warum? Weil diese Behauptung völlig subjektiv ist.
  2. Kommentare sind niemals Fehler. Was ist ein Fehler ist Code, der überhaupt nicht verstanden werden kann ohne Kommentare.
  3. 300 ununterbrochene Codezeilen in einem Codeblock sind ein Wartungsalptraum und sehr fehleranfällig. Ein solcher Block weist stark auf schlechtes Design und schlechte Planung hin.

Es ist klug, direkt mit dem von Ihnen bereitgestellten Beispiel zu sprechen ... isApplicationInProduction() in eine eigene Routine zu setzen. Heute ist dieser Test einfach eine Überprüfung der "Header" und kann in einem ternären (?:) Operator durchgeführt werden. Morgen kann der Test weitaus komplexer sein. Darüber hinaus hat "headers.resourceId" keine eindeutige Beziehung zum "Produktionsstatus" der Anwendung. Ich würde argumentieren, dass ein Test für einen solchen Status muss von den zugrunde liegenden Daten entkoppelt werden muss; Ein Unterprogramm wird dies tun und ein ternäres nicht. Zusätzlich wäre ein hilfreicher Kommentar von warum resourceId ein Test für "in Produktion".

Achten Sie darauf, dass Sie nicht mit "kleinen, klar benannten Funktionen" über Bord gehen. Eine Routine sollte eine Idee mehr als "nur Code" enthalten. Ich unterstütze Amons Vorschlag von phoneNumber = getPhoneNumber(headers) und füge hinzu, dass getPhoneNumber() den "Produktionsstatus" -Test mit isApplicationInProduction() durchführen sollte

59
LiamF

"Entitäten dürfen nicht über die Notwendigkeit hinaus multipliziert werden."

- Ockhams Rasiermesser

Code muss so einfach wie möglich sein. Bugs verstecken sich gerne zwischen Komplexität, weil sie dort schwer zu erkennen sind. Was macht Code einfach?

Kleine Einheiten (Dateien, Funktionen, Klassen) sind eine gute Idee . Kleine Einheiten sind leicht zu verstehen, da Sie weniger Dinge gleichzeitig verstehen müssen. Normale Menschen können jeweils nur ~ 7 Konzepte jonglieren. Die Größe wird jedoch nicht nur in Codezeilen gemessen. Ich kann so wenig Code wie möglich schreiben, indem ich den Code „spiele“ (kurze Variablennamen auswählen, „clevere“ Verknüpfungen verwenden, so viel Code wie möglich in eine einzelne Zeile zerschlagen), aber das Endergebnis ist nicht einfach. Der Versuch, einen solchen Code zu verstehen, ähnelt eher dem Reverse Engineering als dem Lesen.

Eine Möglichkeit, eine Funktion zu verkürzen, besteht darin, verschiedene Hilfsfunktionen zu extrahieren. Das kann eine gute Idee sein, wenn es ein in sich geschlossenes Stück Komplexität extrahiert. Für sich genommen ist diese Komplexität viel einfacher zu verwalten (und zu testen!) Als wenn sie in ein nicht verwandtes Problem eingebettet ist.

Aber jeder Funktionsaufruf hat einen kognitiven Overhead : Ich muss nicht nur den Code verstehen innerhalb mein aktuelles Stück von Code muss ich auch verstehen, wie es mit Code auf der außerhalb interagiert. Ich denke, es ist fair zu sagen, dass die von Ihnen extrahierte Funktion mehr Komplexität in die Funktion einbringt als sie extrahiert . Das meinte Ihr Chef mit " kleine Funktionen [sind] ein Schmerz, weil es Sie zwingt, in jede kleine Funktion zu wechseln, um zu sehen, was der Code tut."

Manchmal können langweilige Funktionen unglaublich einfach zu verstehen sein, selbst wenn sie Hunderte von Zeilen lang sind. Dies tritt tendenziell beim Initialisierungs- und Konfigurationscode auf, z. beim manuellen Erstellen einer GUI ohne Drag & Drop-Editor. Es gibt keine in sich geschlossene Komplexität, die Sie vernünftigerweise extrahieren könnten. Aber wenn die Formatierung lesbar ist und es einige Kommentare gibt, ist es wirklich nicht schwierig zu verfolgen, was passiert.

Es gibt viele andere Komplexitätsmetriken: Die Anzahl von Variablen in einem Bereich sollte so klein wie möglich sein. Das heißt nicht, dass wir vermeiden Variablen sollten. Dies bedeutet, dass wir jede Variable auf den kleinstmöglichen Bereich beschränken sollten, in dem sie benötigt wird. Variablen werden auch einfacher, wenn wir den darin enthaltenen Wert nie ändern.

Eine sehr wichtige Metrik ist zyklomatische Komplexität (McCabe-Komplexität). Es misst die Anzahl unabhängiger Pfade durch einen Code. Diese Zahl wächst exponentiell mit jeder Bedingung. Jede Bedingung oder Schleife verdoppelt die Anzahl der Pfade. Es gibt Hinweise darauf, dass eine Punktzahl von mehr als 10 Punkten zu komplex ist. Dies bedeutet, dass eine sehr lange Funktion mit einer Punktzahl von 5 möglicherweise besser ist als eine sehr kurze und dichte Funktion mit einer Punktzahl von 25. Wir können die Komplexität reduzieren, indem wir den Kontrollfluss in separate Funktionen extrahieren.

Ihre Bedingung ist ein Beispiel für eine Komplexität, die vollständig extrahiert werden kann:

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

Dies ist immer noch sehr am Rande der Nützlichkeit. Ich bin mir nicht sicher, ob dies die Komplexität wesentlich verringert, da diese Bedingung nicht sehr bedingt ist . In der Produktion wird es immer den gleichen Weg nehmen.


Komplexität kann niemals verschwinden. Es kann nur herumgemischt werden. Sind viele kleine Dinge einfacher als wenige große Dinge? Das hängt sehr stark von den Umständen ab. Normalerweise gibt es eine Kombination, die sich genau richtig anfühlt. Um diesen Kompromiss zwischen verschiedenen Komplexitätsfaktoren zu finden, sind Intuition, Erfahrung und ein bisschen Glück erforderlich.

Zu wissen, wie man sehr kleine und sehr einfache Funktionen schreibt, ist eine nützliche Fähigkeit, da Sie keine Wahl treffen können, ohne die Alternativen zu kennen. Das blinde Befolgen von Regeln oder Best Practices , ohne darüber nachzudenken, wie sie auf die aktuelle Situation angewendet werden, führt bestenfalls zu Durchschnittsergebnissen. im schlimmsten Fall Frachtkultprogrammierung .

Da stimme ich Ihrem Chef nicht zu. Seine Argumente sind nicht ungültig, aber das Clean Code-Buch ist auch nicht falsch. Es ist wahrscheinlich besser, den Richtlinien Ihres Chefs zu folgen, aber die Tatsache, dass Sie über diese Probleme nachdenken und versuchen, einen besseren Weg zu finden, ist sehr vielversprechend. Wenn Sie Erfahrung sammeln, fällt es Ihnen leichter, ein gutes Factoring für Ihren Code zu finden.

(Hinweis: Diese Antwort basiert teilweise auf Gedanken aus dem Reasonable Code Blog-Beitrag auf The Whiteboard von Jimmy Hoffa , der bietet eine allgemeine Ansicht darüber, was Code einfach macht.)

47
amon

Robert Martins Programmierstil ist polarisierend. Sie werden viele Programmierer finden, auch erfahrene, die viele Ausreden finden, warum es zu viel ist, "so viel" aufzuteilen, und warum es "besser" ist, Funktionen ein bisschen größer zu halten. Die meisten dieser "Argumente" sind jedoch oft Ausdruck der Unwilligkeit, alte Gewohnheiten zu ändern und etwas Neues zu lernen.

Hör nicht auf sie!

Wann immer Sie einen Kommentar speichern können, indem Sie einen Code in eine separate Funktion mit einem aussagekräftigen Namen umgestalten, tun Sie dies - dies wird höchstwahrscheinlich Ihren Code verbessern. Sie müssen nicht so weit gehen wie Bob Martin in seinem sauberen Codebuch, aber die überwiegende Mehrheit des Codes, den ich in der Vergangenheit gesehen habe und der Wartungsprobleme verursachte, enthielt zu große Funktionen, nicht zu kleine. Versuchen Sie also, kleinere Funktionen mit selbstbeschreibenden Namen zu schreiben.

Automatische Refactoring-Tools erleichtern das Extrahieren von Methoden. Und bitte nehmen Sie die Leute nicht ernst, die das Schreiben von Funktionen mit> 300 Zeilen empfehlen - solche Leute sind definitiv nicht qualifiziert, Ihnen zu sagen, wie Sie codieren sollen.

27
Doc Brown

In Ihrem Fall: Sie möchten eine Telefonnummer. Entweder ist es offensichtlich, wie Sie eine Telefonnummer erhalten würden, dann schreiben Sie den offensichtlichen Code. Oder es ist nicht offensichtlich, wie Sie eine Telefonnummer erhalten würden, dann schreiben Sie eine Methode dafür.

In Ihrem Fall ist es nicht offensichtlich, wie Sie die Telefonnummer erhalten, also schreiben Sie eine Methode dafür. Die Implementierung ist nicht offensichtlich, aber deshalb setzen Sie sie in eine separate Methode ein, sodass Sie sich nur einmal damit befassen müssen. Ein Kommentar wäre nützlich, da die Implementierung nicht offensichtlich ist.

Die Methode "isApplicationInProduction" ist völlig sinnlos. Wenn Sie es von Ihrer getPhonenumber-Methode aus aufrufen, wird die Implementierung nicht offensichtlicher und es wird nur schwieriger, herauszufinden, was los ist.

Schreiben Sie keine kleinen Funktionen. Schreiben Sie Funktionen, die einen genau definierten Zweck haben und diesen genau definierten Zweck erfüllen.

PS. Die Implementierung gefällt mir überhaupt nicht. Es wird davon ausgegangen, dass das Fehlen einer Telefonnummer bedeutet, dass es sich um eine Entwicklungsversion handelt. Wenn die Telefonnummer in der Produktion nicht vorhanden ist, können Sie sie nicht nur nicht verarbeiten, sondern durch eine zufällige Telefonnummer ersetzen. Stellen Sie sich vor, Sie haben 10.000 Kunden und 17 haben keine Telefonnummer und Sie haben Probleme in der Produktion. Ob Sie sich in der Produktion oder in der Entwicklung befinden, sollte direkt überprüft werden und nicht von etwas anderem abgeleitet werden.

23
gnasher729

Selbst wenn ich die Tatsache ignoriere, dass keine der beiden Implementierungen so gut ist, würde ich bemerken, dass dies im Wesentlichen eine Geschmacksfrage ist, zumindest auf der Ebene der Abstraktion von trivialen Funktionen für den einmaligen Gebrauch.

Die Anzahl der Zeilen ist in den meisten Fällen keine nützliche Metrik.

300 Zeilen (oder sogar 3000) Zeilen völlig trivialen rein sequentiellen Codes (Setup oder ähnliches) sind selten ein Problem (könnten aber besser automatisch generiert werden oder als Datentabelle oder so etwas), 100 Zeilen verschachtelter Schleifen mit vielen komplizierten Ausgangsbedingungen und Mathematik, wie sie in der Gaußschen Eliminierung oder Matrixinversion oder dergleichen zu finden sind, können viel zu viel sein, um leicht zu folgen.

Für mich würde ich keine Funktion für den einmaligen Gebrauch schreiben, es sei denn, die Menge an Code, die zum Deklarieren des Dings erforderlich ist, wäre viel geringer als die Menge an Code, die die Implementierung bildet (es sei denn, ich hätte Grund zu sagen, dass ich in der Lage sein möchte, einfach eine Fehlerinjektion durchzuführen). Eine einzige Bedingung passt selten zu dieser Rechnung.

Jetzt komme ich aus einer kleinen eingebetteten Kernwelt, in der wir auch Dinge wie Stapeltiefe und Aufruf-/Rückgabeaufwand berücksichtigen müssen (was wiederum gegen die Art winziger Funktionen spricht, die hier befürwortet zu werden scheinen), und dies könnte mein Design beeinflussen Entscheidungen, aber wenn ich diese ursprüngliche Funktion in einer Codeüberprüfung sehen würde, würde sie als Antwort eine Usenet-Flamme alten Stils erhalten.

Geschmack ist Design ist schwer zu lehren und kommt nur mit Erfahrung, ich bin nicht sicher, ob es auf Regeln über Funktionslängen reduziert werden kann, und selbst die zyklomatische Komplexität hat ihre Grenzen als Metrik (Manchmal sind die Dinge nur kompliziert, wie auch immer Sie sie angehen).
Dies bedeutet nicht, dass sauberer Code einige gute Dinge nicht bespricht, und diese Dinge sollten bedacht werden, aber die lokale Gewohnheit und die vorhandene Codebasis sollten ebenfalls gewichtet werden.

Dieses spezielle Beispiel scheint mir trivial detailliert zu sein. Ich würde mich mehr um Dinge auf viel höherer Ebene kümmern, da dies weitaus wichtiger für die Fähigkeit ist, ein System leicht zu verstehen und zu debuggen.

16
Dan Mills

Stellen Sie nicht alles in eine große Schleife, aber tun Sie dies auch nicht zu oft:

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Das Problem mit der großen Schleife ist, dass es wirklich schwierig ist, ihre Gesamtstruktur zu sehen, wenn sie sich über viele Bildschirme erstreckt. Versuchen Sie also, große Stücke herauszunehmen, idealerweise Stücke, die eine einzige Verantwortung haben und wiederverwendbar sind.

Das Problem mit der winzigen Funktion oben ist, dass Atomizität und Modularität im Allgemeinen gut sind, aber zu weit gehen können. Wenn Sie die oben genannte Funktion nicht wiederverwenden, wird die Lesbarkeit und Wartbarkeit des Codes beeinträchtigt. Um einen Drilldown in das Detail durchzuführen, müssen Sie zur Funktion springen, anstatt das Detail inline lesen zu können, und der Funktionsaufruf nimmt kaum weniger Platz ein als das Detail selbst.

Es besteht eindeutig ein Gleichgewicht zwischen Methoden, die zu viel und Methoden, die zu wenig tun. Ich würde niemals eine winzige Funktion wie oben ausbrechen, wenn sie nicht von mehr als einer Stelle aufgerufen würde, und selbst dann würde ich zweimal darüber nachdenken, weil die Funktion nur ist nicht so wesentlich in Bedingungen für die Einführung neuer Logik und als solche kaum eine eigene Existenz zu rechtfertigen.

15
Brad Thomas

Es scheint so, als ob Sie tatsächlich Folgendes wollen:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

Dies sollte für jeden, der es liest, selbsterklärend sein: Setzen Sie phoneNumber auf resourceId, falls verfügbar, oder standardmäßig auf DEV_PHONE_NUMBER wenn nicht.

Wenn Sie wirklich diese Variable nur in der Produktion festlegen möchten, sollten Sie eine andere, kanonischere App-weite Dienstprogrammmethode (für die keine Parameter erforderlich sind) verwenden, um zu bestimmen, von wo aus Sie ausgeführt werden. Das Lesen der Überschriften für diese Informationen macht keinen Sinn.

Lassen Sie mich stumpf sein: Es scheint mir, dass Ihre Umgebung (Sprache/Framework/Klassendesign usw.) nicht wirklich für "sauberen" Code geeignet ist. Sie mischen alle möglichen Dinge in ein paar Codezeilen, die eigentlich nicht nahe beieinander liegen sollten. Welches Geschäft hat eine einzelne Funktion mit dem Wissen, dass resourceId==undef bedeutet, dass Sie nicht in der Produktion sind, dass Sie in Nicht-Produktionssystemen eine Standardtelefonnummer verwenden, dass die resourceId in einigen "Headern" gespeichert ist und so weiter? Ich gehe davon aus, dass headers HTTP-Header sind, sodass Sie die Entscheidung darüber, in welcher Umgebung Sie sich befinden, sogar dem Endbenutzer überlassen?

Das Zerlegen einzelner Teile davon in Funktionen hilft Ihnen bei diesem zugrunde liegenden Problem nicht viel.

Einige Schlüsselwörter zu suchen:

  • entkopplung
  • zusammenhalt
  • abhängigkeitsspritze

Sie können erreichen, was Sie (in anderen Kontexten) mit null Codezeilen wollen, indem Sie die Verantwortlichkeiten des Codes verschieben und moderne Frameworks verwenden (die für Ihre Umgebung/Programmiersprache möglicherweise vorhanden sind oder nicht).

Aus Ihrer Beschreibung ("300 Codezeilen in einer 'Haupt'-Funktion") geht sogar die "Funktion" des Wortes (anstelle der Methode) davon aus, dass das, was Sie erreichen möchten, keinen Sinn macht. In dieser Programmierumgebung der alten Schule (d. H. Grundlegende imperative Programmierung mit wenig Struktur, sicherlich keinen aussagekräftigen Klassen, keinem Klassenrahmenmuster wie MVC oder so etwas) macht es wirklich nicht viel Sinn, irgendetwas zu tun. Sie werden niemals ohne grundlegende Änderungen aus dem Sumpf kommen. Zumindest scheint Ihr Chef Ihnen zu erlauben, Funktionen zu erstellen, um Codeduplikationen zu vermeiden. Das ist ein guter erster Schritt!

Ich kenne sowohl die Art des Codes als auch die Art des Programmierers, den Sie ziemlich gut beschreiben. Ehrlich gesagt, wenn es ein Mitarbeiter wäre, wäre mein Rat anders. Aber da es Ihr Chef ist, ist es für Sie nutzlos, darüber zu streiten. Nicht nur, dass Ihr Chef Sie überstimmen kann, sondern Ihre Code-Ergänzungen führen in der Tat zu schlechterem Code, wenn Sie nur "Ihr Ding" teilweise machen und Ihr Chef (und wahrscheinlich auch andere Leute) wie zuvor weitermachen. Für das Endergebnis ist es möglicherweise besser, wenn Sie sich an den Programmierstil anpassen (natürlich nur während der Arbeit an dieser bestimmten Codebasis) und in diesem Zusammenhang versuchen, das Beste daraus zu machen.

14
AnoE

"Sauber" ist ein Ziel beim Schreiben von Code. Es ist nicht das einzige Ziel. Ein weiteres würdiges Ziel ist Kolokalität. Informell ausgedrückt bedeutet Kolokalität, dass Leute, die versuchen, Ihren Code zu verstehen, nicht überall herumspringen müssen, um zu sehen, was Sie tun. Die Verwendung einer gut benannten Funktion anstelle eines ternären Ausdrucks mag gut erscheinen, aber je nachdem, wie viele solcher Funktionen Sie haben und wo sie sich befinden, kann diese Praxis zu einem Ärgernis werden. Ich kann Ihnen nicht sagen, ob Sie diese Grenze überschritten haben, außer zu sagen, dass Sie zuhören sollten, wenn sich Leute beschweren, insbesondere wenn diese Leute über Ihren Beschäftigungsstatus mitreden können.

13
user1172763

Die Verwendung kleiner Funktionen ist im Allgemeinen eine gute Vorgehensweise. Aber im Idealfall glaube ich, dass die Einführung einer Funktion entweder große logische Blöcke trennen oder die Gesamtgröße des Codes durch Austrocknen reduzieren sollte. Das Beispiel, das Sie beide gegeben haben, verlängert den Code und benötigt mehr Zeit zum Lesen durch einen Entwickler, während die kurze Alternative nicht erklärt, dass der Wert "resourceId" Nur in der Produktion vorhanden ist. So etwas Einfaches ist leicht zu vergessen und verwirrend, wenn Sie versuchen, damit zu arbeiten, insbesondere wenn Sie noch neu in der Codebasis sind.

Ich werde nicht sagen, dass Sie unbedingt ein Ternär verwenden sollten. Einige Leute, mit denen ich gearbeitet habe, bevorzugen das etwas längere if () {...} else {...}, es ist meistens eine persönliche Wahl. Ich tendiere dazu, einen "One Line Do One Ding Approach" zu bevorzugen, aber ich würde mich grundsätzlich an das halten, was die Codebasis normalerweise verwendet.

Wenn bei Verwendung von ternary die logische Prüfung die Zeile zu lang oder zu kompliziert macht, sollten Sie eine oder mehrere gut benannte Variablen erstellen, die den Wert enthalten.

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

Ich möchte auch sagen, dass die Codebasis, wenn sie sich in Richtung 300 Zeilenfunktionen erstreckt, eine Unterteilung benötigt. Aber ich würde die Verwendung von etwas breiteren Strichen empfehlen.

6
nepeo

Das Codebeispiel, das Sie gegeben haben, Ihr Chef IS RICHTIG. Eine einzelne klare Linie ist in diesem Fall besser.

Im Allgemeinen ist es für die Lesbarkeit, die Codepflege und die Möglichkeit, dass Unterklassen ein anderes Verhalten aufweisen (wenn auch nur geringfügig), besser, komplexe Logik in kleinere Teile zu zerlegen.

Ignorieren Sie nicht die Nachteile : Funktionsaufwand, Verschleierung (Funktion macht nicht das, was Kommentare und Funktionsnamen implizieren), komplexe Spaghetti-Logik, das Potenzial für tote Funktionen (zu einem Zeitpunkt wurden für einen Zweck erstellt, der heißt nicht mehr).

5
Phil M

Ich kann mir mindestens zwei Argumente für lange Funktionen vorstellen:

  • Es bedeutet, dass Sie viel Kontext um jede Zeile haben. Eine Möglichkeit, dies zu formalisieren: Zeichnen Sie das Kontrollflussdiagramm Ihres Codes. An einem Scheitelpunkt (~ = Linie) zwischen Funktionseintrag und Funktionsausgang kennen Sie all der eingehenden Kanten. Je länger die Funktion ist, desto mehr solche Eckpunkte gibt es.

  • Viele kleine Funktionen bedeuten, dass es ein größeres und komplexeres Anrufdiagramm gibt. Wählen Sie eine zufällige Zeile in einer zufälligen Funktion und beantworten Sie die Frage "In welchem ​​Kontext wird diese Zeile ausgeführt?". Dies wird umso schwieriger, je größer und komplexer das Aufrufdiagramm ist, da Sie mehr Scheitelpunkte in diesem Diagramm betrachten müssen.

Es gibt auch Argumente gegen lange Funktionen - die Testbarkeit von Einheiten fällt mir ein. Verwenden Sie Ihre Erfahrung, wenn Sie zwischen den beiden wählen.

Hinweis: Ich sage nicht, dass Ihr Chef Recht hat, nur dass seine Perspektive möglicherweise nicht völlig wertlos ist.


Ich denke, meine Ansicht ist, dass der gute Optimierungsparameter nicht die Funktionslänge ist. Ich denke, eine Desiderata, die nützlicher ist, ist die folgende: Wenn alles andere gleich ist, ist es vorzuziehen, eine allgemeine Beschreibung sowohl der Geschäftslogik als auch der Implementierung aus dem Code auslesen zu können. (Die Implementierungsdetails auf niedriger Ebene können immer gelesen werden, wenn Sie das relevante Codebit finden.)


Kommentar zu David Arnos Antwort :

Das Schreiben kleiner Funktionen ist schmerzhaft, da Sie gezwungen sind, in die einzelnen kleinen Funktionen zu wechseln, um zu sehen, was der Code tut.

Wenn die Funktion gut benannt ist, ist dies nicht der Fall. isApplicationInProduction ist selbstverständlich und es sollte nicht erforderlich sein, den Code zu untersuchen, um festzustellen, was er tut. In der Tat ist das Gegenteil der Fall: Wenn Sie den Code untersuchen, wird weniger auf die Absicht hingewiesen als auf den Funktionsnamen (weshalb Ihr Chef auf Kommentare zurückgreifen muss).

Der Name macht deutlich, was der Rückgabewert bedeutet, sagt aber nichts über die Effekte der Ausführung des Codes aus (= was der Code tut). Namen (nur) vermitteln Informationen über Absicht, Code vermittelt Informationen über Verhalten (woraus manchmal Teile der Absicht abgeleitet werden können).

Manchmal möchten Sie das eine, manchmal das andere, damit diese Beobachtung keine einseitige, universell gültige Entscheidungsregel erzeugt.

Legen Sie alles in eine große Hauptschleife, auch wenn die Hauptschleife mehr als 300 Zeilen umfasst, ist das Lesen schneller

Das Durchsuchen ist möglicherweise schneller, aber um den Code wirklich zu "lesen", müssen Sie ihn effektiv in Ihrem Kopf ausführen können. Das ist mit kleinen Funktionen einfach und mit Methoden, die Hunderte von Zeilen lang sind, sehr, sehr schwierig.

Ich bin damit einverstanden, dass Sie es in Ihrem Kopf ausführen müssen. Wenn Sie 500 Funktionszeilen in einer großen Funktion im Vergleich zu vielen kleinen Funktionen haben, ist mir nicht klar, warum dies einfacher wird.

Angenommen, es handelt sich um den Extremfall von 500 Zeilen geradlinigen Codes mit starkem Nebeneffekt, und Sie möchten wissen, ob Effekt A vor oder nach Effekt B auftritt. Verwenden Sie im Fall mit großen Funktionen Page Up/Down, um zwei Zeilen zu lokalisieren und dann zu vergleichen Linien Nummern. Im Fall vieler kleiner Funktionen müssen Sie sich merken, wo im Aufrufbaum die Effekte auftreten, und wenn Sie vergessen haben, müssen Sie nicht trivial viel Zeit damit verbringen, die Struktur dieses Baums wiederzuentdecken.

Wenn Sie den Aufrufbaum unterstützender Funktionen durchlaufen, stehen Sie auch vor der Herausforderung, festzustellen, wann Sie von der Geschäftslogik zu den Implementierungsdetails übergegangen sind. Ich behaupte ohne Beweise *, dass es umso einfacher ist, diese Unterscheidung zu treffen, je einfacher das Anrufdiagramm ist.

(*) Zumindest bin ich ehrlich ;-)

Ich denke wieder einmal, dass beide Ansätze Stärken und Schwächen haben.

Schreiben Sie nur kleine Funktionen, wenn Sie Code duplizieren müssen

Ich stimme dir nicht zu. Wie Ihr Codebeispiel zeigt, verbessern kleine, gut benannte Funktionen die Lesbarkeit von Code und sollten immer dann verwendet werden, wenn [z. B.] Sie nicht am "Wie" interessiert sind, sondern nur am "Was" einer Funktionalität.

Ob Sie sich für das "Wie" oder "Was" interessieren, hängt vom Zweck ab, für den Sie den Code lesen (z. B. eine allgemeine Vorstellung davon bekommen oder einen Fehler aufspüren). Der Zweck, für den Sie den Code lesen, ist beim Schreiben des Programms nicht verfügbar, und Sie werden den Code höchstwahrscheinlich für verschiedene Zwecke lesen. Unterschiedliche Entscheidungen werden für unterschiedliche Zwecke optimiert.

Dies ist jedoch der Teil der Ansicht des Chefs, mit dem ich wahrscheinlich am meisten nicht einverstanden bin.

Schreiben Sie keine Funktion mit dem Namen des Kommentars, sondern setzen Sie Ihre komplexe Codezeile (3-4 Zeilen) mit einem Kommentar oben. Auf diese Weise können Sie den fehlerhaften Code direkt ändern

Ich kann die Gründe dafür wirklich nicht verstehen, vorausgesetzt, es ist wirklich ernst. [...] Kommentare haben einen grundlegenden Fehler: Sie werden nicht kompiliert/interpretiert und können daher nicht auf Einheiten getestet werden. Der Code wird geändert und der Kommentar wird in Ruhe gelassen, und Sie wissen am Ende nicht, welcher richtig ist.

Compiler vergleichen nur Namen auf Gleichheit, sie geben Ihnen niemals einen MisleadingNameError. Da mehrere Aufrufstellen eine bestimmte Funktion nach Namen aufrufen können, ist es manchmal schwieriger und fehleranfälliger, einen Namen zu ändern. Kommentare haben dieses Problem nicht. Dies ist jedoch etwas spekulativ; Um dies wirklich zu regeln, würde man wahrscheinlich Daten darüber benötigen, ob Programmierer eher irreführende Kommentare als irreführende Namen aktualisieren, und das habe ich nicht.

2
Jonas Kölker