it-swarm.com.de

Wann ist es angebracht, eine separate Funktion zu erstellen, wenn diese Funktion immer nur einmal aufgerufen wird?

Wir entwerfen Codierungsstandards und haben Meinungsverschiedenheiten darüber, ob es jemals angebracht ist, Code innerhalb einer Klasse in separate Funktionen aufzuteilen, wenn diese Funktionen immer nur einmal aufgerufen werden.

Zum Beispiel:

f1()
{
   f2();  
   f4();
}


f2()
{
    f3()
    // Logic here
}


f3()
{
   // Logic here
}

f4()
{
   // Logic here
}

gegen:

f1()
{
   // Logic here
   // Logic here
   // Logic here
}

Einige argumentieren, dass das Lesen einfacher ist, wenn Sie eine große Funktion mit separaten Unterfunktionen für den einmaligen Gebrauch aufteilen. Beim ersten Lesen von Code finde ich es jedoch mühsam, den Logikketten zu folgen und das gesamte System zu optimieren. Gibt es Regeln, die normalerweise für diese Art von Funktionslayout gelten?

Bitte beachten Sie, dass ich im Gegensatz zu anderen Fragen nach den besten Bedingungen frage, um zulässige und nicht zulässige Verwendungen von Einzelanruffunktionen zu unterscheiden, nicht nur, wenn sie zulässig sind.

46
David

Der Grund für die Aufteilung von Funktionen ist nicht, wie oft sie aufgerufen werden , sondern sie klein zu halten und sie daran zu hindern, verschiedene Dinge zu tun.

Bob Martins Buch Clean Code gibt gute Richtlinien zum Aufteilen einer Funktion:

  • Funktionen sollten klein sein; wie klein? Siehe die Kugel unten.
  • Funktionen sollten nur eines tun.

Wenn die Funktion mehrere Bildschirme lang ist, teilen Sie sie auf. Wenn die Funktion mehrere Aufgaben ausführt, teilen Sie sie auf.

Wenn die Funktion aus aufeinander folgenden Schritten besteht, die auf ein Endergebnis abzielen, muss sie nicht aufgeteilt werden, selbst wenn sie relativ lang ist. Aber wenn die Funktionen eine Sache tun, dann eine andere, dann eine andere und dann eine andere, mit Bedingungen, logisch getrennten Blöcken usw., sollte sie aufgeteilt werden. Aufgrund dieser Logik sollten Funktionen normalerweise klein sein.

Wenn f1() eine Authentifizierung durchführt, analysiert f2() Eingaben in kleinere Teile, wenn f3() Berechnungen durchführt und f4() Ergebnisse protokolliert oder beibehält, dann werden sie sollte natürlich getrennt werden, auch wenn jeder von ihnen nur einmal aufgerufen wird.

Auf diese Weise können Sie sie separat umgestalten und testen, zusätzlich zu dem zusätzlichen Vorteil, dass sie einfacher zu lesen sind .

Auf der anderen Seite, wenn die Funktion nur Folgendes erfüllt:

a=a+1;
a=a/2;
a=a^2
b=0.0001;
c=a*b/c;
return c;

dann besteht keine Notwendigkeit, es zu teilen, selbst wenn die Abfolge der Schritte lang ist.

92

Ich denke, dass die Benennung von Funktionen hier sehr wichtig ist.

Eine stark zerlegte Funktion kann sehr selbstdokumentierend sein. Wenn jeder logische Prozess innerhalb einer Funktion mit minimaler interner Logik in eine eigene Funktion aufgeteilt wird, kann das Verhalten jeder Anweisung durch die Namen der Funktionen und die von ihnen verwendeten Parameter begründet werden.

Natürlich gibt es einen Nachteil: die Funktionsnamen. Wie bei Kommentaren können solche hochspezifischen Funktionen häufig nicht mehr mit dem übereinstimmen, was die Funktion tatsächlich tut. Aber, indem Sie ihm gleichzeitig einen richtigen Funktionsnamen geben, machen Sie es schwieriger, um das Scope Creep zu rechtfertigen. Es wird schwieriger, eine Unterfunktion dazu zu bringen, etwas mehr zu tun, als es eindeutig sollte.

Also würde ich das vorschlagen. Wenn Sie glauben, dass ein Codeabschnitt aufgeteilt werden könnte, obwohl ihn niemand anders nennt, stellen Sie sich folgende Frage: "Welchen Namen würden Sie ihm geben?"

Wenn die Beantwortung dieser Frage länger als 5 Sekunden dauert oder der ausgewählte Funktionsname eindeutig undurchsichtig ist, besteht eine gute Chance, dass es sich nicht um eine separate logische Einheit innerhalb der Funktion handelt. Oder zumindest, dass Sie nicht sicher genug sind, was diese logische Einheit wirklich tut, um sie richtig aufzuteilen.

Es gibt jedoch ein zusätzliches Problem, auf das stark zerlegte Funktionen stoßen können: Fehlerbehebung.

Das Aufspüren logischer Fehler innerhalb einer Funktion mit mehr als 200 Zeilen ist schwierig. Aber verfolgen Sie sie durch mehr als 10 einzelne Funktionen, während Sie versuchen, sich an die Beziehungen zwischen ihnen zu erinnern? Das kann noch schwieriger sein.

Auch hier kann die semantische Selbstdokumentation durch Namen eine Schlüsselrolle spielen. Wenn jede Funktion einen logischen Namen hat, müssen Sie zur Validierung einer der Blattfunktionen (neben dem Testen von Einheiten) nur prüfen, ob sie tatsächlich das tut, was sie verspricht. Blattfunktionen sind in der Regel kurz und konzentriert. Wenn also jede einzelne Blattfunktion das tut, was sie verspricht, ist das einzig mögliche Problem, dass jemand ihnen das falsche Zeug übergeben hat.

In diesem Fall kann es also ein Segen für die Fehlerbehebung sein.

Ich denke, es kommt wirklich auf die Frage an, ob man einer logischen Einheit einen aussagekräftigen Namen zuweisen kann. Wenn ja, dann kann es wahrscheinlich eine Funktion sein.

43
Nicol Bolas

Jedes Mal, wenn Sie einen Kommentar schreiben müssen, um zu beschreiben, was ein Textblock tut, haben Sie die Möglichkeit gefunden, eine Methode zu extrahieren.

Eher, als

//find eligible contestants
var eligible = contestants.Where(c=>c.Age >= 18)
eligible = eligible.Where(c=>c.Country == US)

versuchen

var eligible = FindEligible(contestants)
12
dss539

DRY - Wiederhole dich nicht - ist nur eines von mehreren Prinzipien, die ausgewogen sein müssen.

Einige andere, die hier in den Sinn kommen, sind Namen. Wenn die Logik für den Gelegenheitsleser nicht offensichtlich ist, kann die Extraktion in eine Methode/Funktion, deren Name besser verkapselt, was und warum es tut, die Lesbarkeit des Programms verbessern.

Je nachdem, wie viele Zeilen die //logic wird.

Eine Funktion mit Parametern kann auch als API fungieren und die Parameter entsprechend benennen, um dann die Codelogik klarer zu machen.

Sie können auch feststellen, dass Sammlungen solcher Funktionen im Laufe der Zeit eine nützliche Gruppierung der Kuppel ergeben, z. admin und dann können sie leicht darunter gesammelt werden.

5
Michael Durrant

Bei der Aufteilung von Funktionen geht es nur um eines: Einfachheit.

Ein Code-Leser kann nicht mehr als sieben Dinge gleichzeitig im Sinn haben. Ihre Funktionen sollten dies widerspiegeln.

  • Wenn Sie zu lange Funktionen erstellen, sind diese nicht lesbar, da Ihre Funktionen mehr als sieben Dinge enthalten.

  • Wenn Sie eine Menge einzeiliger Funktionen erstellen, werden die Leser in ähnlicher Weise durch das Funktionsgewirr verwirrt. Jetzt müssten Sie mehr als sieben Funktionen in Ihrem Gedächtnis behalten, um den Zweck jeder einzelnen zu verstehen.

  • Einige Funktionen sind einfach, obwohl sie lang sind. Das klassische Beispiel ist, wenn eine Funktion in vielen Fällen eine große switch-Anweisung enthält. Solange die Bearbeitung jedes Einzelfalls einfach ist, sind Ihre Funktionen nicht zu lang.

Beide Extreme (das Megamoth und die Suppe der winzigen Funktionen) sind gleich schlecht, und Sie müssen dazwischen ein Gleichgewicht finden. Nach meiner Erfahrung hat eine Nice-Funktion ungefähr zehn Zeilen. Einige Funktionen sind Einzeiler, andere mehr als zwanzig Zeilen. Wichtig ist, dass jede Funktion eine leicht verständliche Funktion erfüllt und in ihrer Implementierung gleichermaßen verständlich ist.

Es geht nur um Trennung von Bedenken. (ok, nicht alles darüber; dies ist eine Vereinfachung).

Das ist in Ordnung:

function initializeUser(name, job, bye) {
    this.username = name;
    this.occupation = job;
    this.farewell = bye;
    this.gender = Gender.unspecified;
    this.species = Species.getSpeciesFromJob(this.occupation);
    ... etc in the same vein.
}

Diese Funktion befasst sich mit einem einzigen Problem: Sie legt die Anfangseigenschaften eines Benutzers anhand der angegebenen Argumente, Standardeinstellungen, Extrapolation usw. fest.

Das ist nicht gut:

function initializeUser(name, job, bye) {
    // Connect to internet if not already connected.
    modem.getInstance().ensureConnected();
    // Connect to user database
    userDb = connectToDb(USER_DB);
    // Validate that user does not yet exist.
    if (0 != userDb.db_exec("SELECT COUNT(*) FROM `users` where `name` = %d", name)) {
        throw new sadFace("User exists");
    }
    // Configure properties. Don't try to translate names.
    this.username = removeBadWords(name);
    this.occupation = removeBadWords(translate(job));
    this.farewell = removeBadWords(translate(bye));
    this.gender = Gender.unspecified;
    this.species = Species.getSpeciesFromJob(this.occupation);
    userDb.db_exec("INSERT INTO `users` set `name` = %s", this.username);
    // Disconnect from the DB.
    userDb.disconnect();
}

Trennung von Bedenken schlägt vor, dass dies als mehrere Bedenken behandelt werden sollte; die DB-Behandlung, die Validierung der Benutzerexistenz, die Einstellung der Eigenschaften. Jedes dieser Elemente lässt sich sehr einfach als Einheit testen, aber das Testen aller Elemente in einer einzigen Methode führt zu einem sehr komplizierten Komponententestsatz, bei dem Dinge getestet werden, die so unterschiedlich sind wie die Art und Weise, wie die DB weggeht und wie sie erstellt wird leere und ungültige Jobtitel und wie es damit umgeht, einen Benutzer zweimal zu erstellen (Antwort: Es gibt einen Fehler).

Ein Teil des Problems ist, dass es in Bezug auf die Höhe überall hoch ist: Netzwerke auf niedriger Ebene und DB-Inhalte haben hier keinen Platz. Das ist Teil der Anliegenstrennung. Der andere Teil ist, dass Dinge, die das Anliegen von etwas anderem sein sollten, stattdessen das Anliegen der Init-Funktion sind. Zum Beispiel kann es sinnvoller sein, ob schlechte Sprachfilter übersetzt oder angewendet werden, wenn die Felder festgelegt werden.

3
Dewi Morgan

Kommt ziemlich darauf an, was dein // Logic Here ist.

Wenn es sich um einen Einzeiler handelt, benötigen Sie wahrscheinlich keine funktionale Zerlegung.

Wenn es sich andererseits um Zeilen und Codezeilen handelt, ist es viel besser, sie in eine separate Funktion zu setzen und sie entsprechend zu benennen (f1,f2,f3 besteht hier nicht die Musterung).

Dies alles hat mit dem menschlichen Gehirn zu tun, das im Durchschnitt nicht sehr effizient ist, wenn es darum geht, große Datenmengen auf einen Blick zu verarbeiten. In gewissem Sinne spielt es keine Rolle, welche Daten: Mehrzeilenfunktion, belebte Kreuzung, 1000-teiliges Puzzle.

Seien Sie ein Freund des Gehirns Ihres Code-Betreuers und reduzieren Sie die Anzahl der Teile im Einstiegspunkt. Wer weiß, dieser Code-Betreuer kann sogar einige Monate später Sie sein.

Die "richtige" Antwort besteht gemäß den vorherrschenden Codierungsdogmen darin, große Funktionen in kleine, leicht lesbare, testbare und getestete Funktionen mit Selbstdokumentation aufzuteilen Namen.

Das Definieren von "groß" in Form von "Codezeilen" kann jedoch willkürlich, dogmatisch und langweilig erscheinen, was zu unnötigen Meinungsverschiedenheiten, Skrupeln und Spannungen führen kann. Aber keine Angst! Wenn wir erkennen, dass die Hauptziele hinter dem Codezeilenlimit die Lesbarkeit und Testbarkeit sind, können wir leicht das geeignete Zeilenlimit für eine bestimmte Funktion finden! (Und beginnen Sie mit dem Aufbau der Grundlage für ein intern relevantes Soft-Limit.)

Stimmen Sie als Team zu, die Megalithfunktionen zuzulassen und Linien in kleinere, gut benannte Funktionen zu extrahieren, wenn zuerst die Funktion signiert ist entweder als Ganzes schwer zu lesen oder wenn Teilmengen in ihrer Richtigkeit unsicher sind.

... Und wenn jeder bei der ersten Implementierung ehrlich ist und keiner von ihnen einen IQ über 200 ankündigt, können die Grenzen der Verständlichkeit und Testbarkeit oft identifiziert werden, bevor andere ihren Code sehen.

1
svidgen