Kleine Funktionen im Vergleich zu abhängigen Funktionen in derselben Funktion


15

Ich habe eine Klasse, die ein Array von Knoten erstellt und diese in einer grafischen Struktur miteinander verbindet. Ist es am besten zu:

  1. Behalten Sie die Funktionalität bei, um die Knoten in einer Funktion zu initialisieren und zu verbinden
  2. Haben Sie die Initialisierungs- und Verbindungsfunktionalität in zwei verschiedenen Funktionen (und haben Sie eine abhängige Reihenfolge, in der die Funktionen aufgerufen werden müssen - beachten Sie jedoch, dass diese Funktionen privat sind.)

Methode 1: (Schlecht darin, dass eine Funktion zwei Dinge ausführt, ABER die abhängige Funktionalität bleibt in Gruppen zusammengefasst - die Knoten sollten niemals ohne vorherige Initialisierung verbunden werden.)

init() {
    setupNodes()
}

private func setupNodes() {
    // 1. Create array of nodes
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Methode 2: (Besser in dem Sinne, dass es selbstdokumentierend ist, ABER connectNodes () sollte niemals vor setupNodes () aufgerufen werden, daher muss jeder, der mit den Klasseninternalen arbeitet, über diese Reihenfolge Bescheid wissen.)

init() {
    setupNodes()
}

private func setupNodes() {
    createNodes()
    connectNodes()
}

private func createNodes() {
    // 1. Create array of nodes
}

private func connectNodes() {
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Aufgeregt, irgendwelche Gedanken zu hören.



Eine Möglichkeit, dies zu lösen, besteht darin, Zwischenobjekte zu definieren, mit denen nur die endgültigen Objekte erstellt werden können. Dies ist nicht immer die richtige Lösung, ist jedoch nützlich, wenn der Benutzer der Schnittstelle einen Zwischenzustand auf irgendeine Weise manipulieren muss.
Joel Cornett

Antworten:


23

Das Problem, mit dem Sie sich beschäftigen, heißt zeitliche Kopplung

Sie sind zu Recht besorgt darüber, wie verständlich dieser Code ist:

private func setupNodes() {
    createNodes();
    connectNodes();
}

Ich kann mir vorstellen, was dort vor sich geht, aber sagen Sie mir, ob dies etwas klarer macht, was sonst noch vor sich geht:

private func setupNodes() {
    self.nodes = connectNodes( createNodes() );
}

Dies hat den zusätzlichen Vorteil, dass es weniger mit dem Ändern von Instanzvariablen verbunden ist, aber für mich ist Lesbarkeit die Nummer eins.

Dies macht connectNodes()die Abhängigkeit von Knoten explizit.


1
Danke für den Link. Da meine Funktionen privat sind und vom Konstruktor - init () in Swift aufgerufen werden, glaube ich nicht, dass mein Code so schlecht wäre wie die von Ihnen verknüpften Beispiele (für einen externen Client wäre es unmöglich, eine Instanz mit einem zu instanziieren null Instanzvariable), aber ich habe einen ähnlichen Geruch.
McFroob

1
Der von Ihnen hinzugefügte Code ist besser lesbar, daher überarbeite ich diesen Stil.
McFroob

10

Getrennte Funktionen aus zwei Gründen:

1. Private Funktionen sind für genau diese Situation privat.

Ihre initFunktion ist öffentlich und die Schnittstelle, das Verhalten und der Rückgabewert müssen Sie schützen und ändern. Das Ergebnis, das Sie von dieser Methode erwarten, wird unabhängig von der verwendeten Implementierung dasselbe sein.

Da sich der Rest der Funktionalität hinter diesem privaten Schlüsselwort verbirgt, kann es nach Belieben implementiert werden. Sie können es also auch schön und modular gestalten, selbst wenn ein Bit davon abhängt, dass das andere zuerst aufgerufen wird.

2. Das Verbinden von Knoten untereinander ist möglicherweise keine private Funktion

Was ist, wenn Sie dem Array irgendwann andere Knoten hinzufügen möchten? Zerstören Sie das Setup, das Sie jetzt haben, und initialisieren Sie es vollständig neu? Oder fügen Sie dem vorhandenen Array Knoten hinzu und führen Sie es connectNodeserneut aus?

Möglicherweise connectNodeskann es zu einer vernünftigen Reaktion kommen, wenn das Knotenarray noch nicht erstellt wurde (eine Ausnahme auslösen, eine leere Menge zurückgeben, entscheiden, was für Ihre Situation sinnvoll ist).


Ich habe genauso gedacht wie 1, und ich könnte eine Ausnahme auslösen, wenn die Knoten nicht initialisiert würden, aber es ist nicht besonders intuitiv. Danke für die Antwort.
McFroob

4

Sie werden möglicherweise auch feststellen (je nachdem, wie komplex jede dieser Aufgaben ist), dass dies eine gute Naht ist, um eine andere Klasse aufzuteilen.

(Ich bin mir nicht sicher, ob Swift so funktioniert, aber Pseudocode :)

class YourClass {
    init(generator: NodesGenerator) {
        self.nodes = connectNodes(generator.make())
    }
    private func connectNodes() {

    }
}

class NodesGenerator {
    public func make() {
        // Return some nodes from storage or make new ones
    }
}

Dies trennt die Zuständigkeiten beim Erstellen und Ändern von Knoten zu separaten Klassen: Es geht NodeGeneratornur um das Erstellen / Abrufen von Knoten, während es YourClassnur um das Verbinden der Knoten geht, die angegeben sind.


2

Swift ist nicht nur der genaue Zweck privater Methoden, sondern bietet Ihnen auch die Möglichkeit, innere Funktionen zu verwenden.

Innere Methoden sind perfekt für Funktionen, die nur eine einzige Aufrufstelle haben, aber das Gefühl haben, dass sie keine separaten privaten Funktionen rechtfertigen.

Beispielsweise ist es sehr verbreitet, eine öffentliche rekursive "Eingabe" -Funktion zu haben, die die Voraussetzungen überprüft, einige Parameter einrichtet und an eine private rekursive Funktion delegiert, die die Arbeit erledigt.

Hier ist ein Beispiel, wie das in diesem Fall aussehen könnte:

init() {
    self.nodes = setupNodes()

    func setupNodes() {
        var nodes = createNodes()
        connect(Nodes: nodes)
    }

    private func createNodes() -> [Node]{
        // 1. Create array of nodes
    }

    func connect(Nodes: [Node]) {
        // 2. Go through array, connecting each node to its neighbors 
        //    according to some predefined constants
    }
}

Achten Sie darauf, wie ich Rückgabewerte und Parameter verwende, um Daten weiterzugeben, anstatt einen gemeinsam genutzten Status zu ändern. Dadurch wird der Datenfluss auf den ersten Blick viel deutlicher, ohne dass Sie in die Implementierung einsteigen müssen.


0

Jede Funktion, die Sie deklarieren, ist mit dem Hinzufügen und Verallgemeinern von Dokumentation verbunden, damit sie von anderen Programmteilen verwendet werden kann. Es muss auch verstanden werden, wie andere Funktionen in der Datei sie möglicherweise für jemanden verwenden, der den Code liest.

Wenn es jedoch nicht von anderen Teilen Ihres Programms verwendet wird, würde ich es nicht als separate Funktion verfügbar machen.

Wenn Ihre Sprache dies unterstützt, können Sie mithilfe verschachtelter Funktionen immer noch eine Funktion ausführen

function setupNodes ()  {
  function createNodes ()  {...} 
  function connectNodes ()  {...}
  createNodes() 
  connectNodes() 
} 

Der Ort der Deklaration ist sehr wichtig, und im obigen Beispiel ist klar, ohne dass weitere Hinweise erforderlich sind, dass die inneren Funktionen nur innerhalb des Körpers der äußeren Funktion verwendet werden sollen.

Selbst wenn Sie sie als private Funktionen deklarieren, gehe ich davon aus, dass sie weiterhin für die gesamte Datei sichtbar sind. Sie müssten sie also in der Nähe der Deklaration der Hauptfunktion deklarieren und eine Dokumentation hinzufügen, die klarstellt, dass sie nur von der äußeren Funktion verwendet werden dürfen.

Ich glaube nicht, dass Sie das eine oder andere unbedingt tun müssen. Das Beste, was Sie tun können, ist von Fall zu Fall unterschiedlich.

Das Aufteilen in mehrere Funktionen erhöht den Aufwand für das Verständnis, warum es drei Funktionen gibt und wie sie alle miteinander arbeiten. Wenn die Logik jedoch komplex ist, ist der zusätzliche Aufwand möglicherweise viel geringer als die Einfachheit, die durch das Aufteilen der komplexen Logik entsteht in einfachere Teile.


Interessante Option. Wie Sie sagen, ich denke, es wäre ein bisschen rätselhaft, warum die Funktion so deklariert wurde, aber es würde die Funktionsabhängigkeit in Grenzen halten.
McFroob

Um einige der Unsicherheiten in dieser Frage zu beantworten: 1) Ja, Swift unterstützt innere Funktionen und 2) Es hat zwei Ebenen von "privat". privateErlaubt den Zugriff nur innerhalb des einschließenden Typs (struct / class / enum), während fileprivateder Zugriff in der gesamten Datei erlaubt ist
Alexander - Reinstate Monica
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.