Piccole funzioni vs. mantenere la funzionalità dipendente nella stessa funzione


15

Ho una classe che imposta una matrice di nodi e li collega tra loro in una struttura simile a un grafico. È meglio:

  1. Mantenere la funzionalità per inizializzare e connettere i nodi in un'unica funzione
  2. Avere l'inizializzazione e la funzionalità di connessione in due diverse funzioni (e avere un ordine dipendente dal quale devono essere chiamate le funzioni, sebbene si tenga presente che queste funzioni sono private).

Metodo 1: (Bad in quella funzione sta facendo due cose, MA mantiene raggruppate le funzionalità dipendenti - i nodi non dovrebbero mai essere collegati senza essere inizializzati prima.)

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
}

Metodo 2: (Meglio nel senso che è auto-documentante, MA connectNodes () non dovrebbe mai essere chiamato prima di setupNodes (), quindi chiunque lavori con gli interni di classe deve conoscere questo ordine.)

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
}

Entusiasta di sentire qualsiasi pensiero.



Un modo per risolverlo definendo oggetti intermedi che possono essere utilizzati solo per creare gli oggetti finali. Questa non è sempre la soluzione giusta ma è utile se l'utente dell'interfaccia deve manipolare in qualche modo uno stato intermedio.
Joel Cornett,

Risposte:


23

Il problema che stai affrontando si chiama accoppiamento temporale

Hai ragione a preoccuparti di quanto sia comprensibile questo codice:

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

Posso immaginare cosa sta succedendo lì, ma dimmi se questo rende un po 'più chiaro ciò che sta succedendo:

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

Questo ha l'ulteriore vantaggio di essere meno accoppiato alla modifica delle variabili di istanza, ma per me essere leggibile è il numero uno.

Questo rende connectNodes()esplicita la dipendenza dai nodi.


1
Grazie per il link Dato che le mie funzioni sono private e chiamate dal costruttore - init () in Swift - non credo che il mio codice sarebbe cattivo come gli esempi che hai collegato (sarebbe impossibile per un client esterno creare un'istanza con un'istanza con variabile di istanza nulla), ma ho un odore simile.
McFroob,

1
Il codice che hai aggiunto è più leggibile, quindi rifletterò in quello stile.
McFroob,

10

Funzioni separate , per due motivi:

1. Le funzioni private sono private proprio per questa situazione.

La tua initfunzione è pubblica ed è l'interfaccia, il comportamento e il valore di ritorno è ciò di cui devi preoccuparti per la protezione e il cambiamento. Il risultato che ti aspetti da quel metodo sarà lo stesso, indipendentemente dall'implementazione che usi.

Dato che il resto della funzionalità è nascosto dietro quella parola chiave privata, può essere implementato come preferisci ... quindi potresti anche renderlo piacevole e modulare, anche se un bit dipende dall'altro essere chiamato per primo.

2. Il collegamento tra nodi potrebbe non essere una funzione privata

Cosa succede se ad un certo punto si desidera aggiungere altri nodi all'array? Distruggi l'installazione che hai adesso e la reinizializzi completamente? Oppure aggiungi nodi all'array esistente e poi esegui di connectNodesnuovo?

Probabilmente connectNodespuò avere una risposta sana se l'array di nodi non è ancora stato creato (generare un'eccezione? Restituire un set vuoto? Devi decidere cosa ha senso per la tua situazione).


Stavo pensando allo stesso modo di 1 e potrei generare un'eccezione o qualcosa del genere se i nodi non fossero inizializzati, ma non è particolarmente intuitivo. Grazie per la risposta.
McFroob,

4

Potresti anche scoprire (a seconda della complessità di ciascuna di queste attività) che questa è una buona cucitura per dividere un'altra classe.

(Non sono sicuro che Swift funzioni in questo modo ma pseudo-codice :)

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
    }
}

Questo separa le responsabilità di creare e modificare nodi per separare le classi: NodeGeneratorsi preoccupa solo di creare / recuperare nodi, mentre YourClasssi preoccupa solo di collegare i nodi che gli vengono dati.


2

Oltre a essere esattamente lo scopo dei metodi privati, Swift ti dà la possibilità di utilizzare le funzioni interne.

I metodi interni sono perfetti per le funzioni che hanno un solo sito di chiamata, ma sembrano non giustificare l'essere funzioni private separate.

Ad esempio, è molto comune avere una funzione "entry" pubblica ricorsiva, che controlla i presupposti, imposta alcuni parametri e delega una funzione ricorsiva privata che svolge il lavoro.

Ecco un esempio di come potrebbe apparire in questo caso:

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
    }
}

Presta attenzione al modo in cui utilizzo i valori e i parametri di ritorno per passare i dati, piuttosto che mutare uno stato condiviso. Ciò rende il flusso di dati molto più evidente a prima vista, senza la necessità di passare all'implementazione.


0

Ogni funzione dichiarata comporta l'onere di aggiungere documentazione e renderla generalizzata in modo che sia utilizzabile da altre parti del programma. Porta anche l'onere di comprendere come altre funzioni nel file potrebbero usarlo per qualcuno che legge il codice.

Se tuttavia non fosse utilizzato da altre parti del programma, non lo esporrei come funzione separata.

Se la tua lingua lo supporta, puoi comunque avere una funzione-fa-una-cosa usando le funzioni nidificate

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

Il luogo della dichiarazione è molto importante e nell'esempio sopra è chiaro senza bisogno di ulteriori indizi che le funzioni interne debbano essere utilizzate solo all'interno del corpo della funzione esterna.

Anche se li stai dichiarando come funzioni private, suppongo che siano ancora visibili all'intero file. Quindi è necessario dichiararli vicini alla dichiarazione della funzione principale e aggiungere della documentazione che chiarisca che devono essere utilizzati solo dalla funzione esterna.

Non penso che devi fare rigorosamente l'uno o l'altro. La cosa migliore da fare varia caso per caso.

La suddivisione in più funzioni aggiunge sicuramente un sovraccarico di comprensione del perché ci sono 3 funzioni e di come funzionano tutte insieme, ma se la logica è complessa, questo sovraccarico aggiunto potrebbe essere molto inferiore alla semplicità introdotta rompendo la logica complessa in parti più semplici.


Opzione interessante. Come dici tu, penso che potrebbe essere un po 'sconcertante il motivo per cui la funzione è stata dichiarata in questo modo, ma manterrebbe la dipendenza della funzione ben contenuta.
McFroob,

Per rispondere ad alcune delle incertezze in questa domanda: 1) Sì, Swift supporta le funzioni interne e 2) Ha due livelli di "privato". privateconsente l'accesso solo all'interno del tipo allegato (struct / class / enum), mentre fileprivateconsente l'accesso all'intero file
Alexander - Reinstate Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.