Piccole funzioni vs. mantenimento della funzionalità dipendente nella stessa funzione

15

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

  1. Mantieni la funzionalità per inizializzare e connettere i nodi in una funzione
  2. Avere la funzionalità di inizializzazione e connessione in due diverse funzioni (e avere un ordine dipendente in base al quale le funzioni devono essere chiamate, anche se tenere a mente che queste funzioni sono private.)

Metodo 1: (Male in quella funzione sta facendo due cose, MA mantiene le funzionalità dipendenti raggruppate insieme - i nodi non dovrebbero mai essere connessi senza essere inizializzati per primi.)

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 è autodocumentante, MA connectNodes () non dovrebbe mai essere chiamato prima di setupNodes (), quindi chiunque lavori con gli interni della 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
}

Entusiasti di ascoltare qualsiasi pensiero.

    
posta mcfroob 28.12.2016 - 03:16
fonte

5 risposte

23

Il problema che hai a che fare è chiamato accoppiamento temporale

Hai ragione a essere preoccupato di quanto sia comprensibile questo codice:

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

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

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

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

Questo rende esplicita la dipendenza da connectNodes() sui nodi.

    
risposta data 28.12.2016 - 04:56
fonte
11

Funzioni separate , per due motivi:

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

La tua funzione init è pubblica, e l'interfaccia, il comportamento e il valore di ritorno sono ciò di cui hai bisogno di preoccuparti di proteggere e cambiare. Il risultato che ti aspetti da quel metodo sarà lo stesso indipendentemente dall'implementazione che utilizzi.

Poiché il resto della funzionalità è nascosto dietro quella parola chiave privata, può essere implementato come preferisci ... quindi potresti anche renderlo bello e modulare, anche se un bit dipende dall'essere chiamato prima.

2. La connessione reciproca dei nodi potrebbe non essere una funzione privata

Che cosa succede se ad un certo punto si desidera aggiungere altri nodi all'array? Distruggi il setup che hai ora e re-inizialo completamente? O aggiungi dei nodi alla matrice esistente e poi esegui nuovamente connectNodes ?

Forse connectNodes può avere una risposta sensata se la matrice di nodi non è stata ancora creata (lanciare un'eccezione? restituire un set vuoto? devi decidere cosa ha senso per la tua situazione).

    
risposta data 28.12.2016 - 04:26
fonte
4

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

(Non sono sicuro se Swift funzioni in questo modo ma lo 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 i nodi in classi separate: NodeGenerator si preoccupa solo della creazione / recupero dei nodi, mentre YourClass interessa solo della connessione dei nodi che viene fornita.

    
risposta data 03.01.2017 - 02:38
fonte
3

Oltre a questo è lo scopo esatto dei metodi privati, Swift ti dà la possibilità di usare le funzioni interiori.

I metodi interni sono perfetti per le funzioni che hanno un unico sito di chiamata, ma si sentono come se non giustificassero di essere funzioni private separate.

Ad esempio, è molto comune avere una funzione di "entrata" ricorsiva pubblica, che verifica le condizioni preliminari, imposta alcuni parametri e delega a una funzione ricorsiva privata che esegue 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
    }
}

Fai attenzione a come uso i valori di ritorno e i parametri per passare i dati, invece di mutare uno stato condiviso. Ciò rende il flusso dei dati molto più ovvio a prima vista, senza la necessità di passare all'implementazione.

    
risposta data 03.01.2017 - 02:03
fonte
0

Ogni funzione che dichiari porta con sé l'onere di aggiungere documentazione e renderla generalizzata in modo che sia utilizzabile da altre parti del programma. Trasporta anche l'onere di capire come altre funzioni nel file potrebbero usarlo per qualcuno che legge il codice.

Se tuttavia non viene utilizzato da altre parti del programma, non lo esporrò come funzione separata.

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

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

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

Anche se li dichiari come funzioni private, presumo che siano ancora visibili all'intero file. Dovresti quindi dichiararli vicino alla dichiarazione della funzione principale e aggiungere della documentazione che chiarisca che devono essere usati solo dalla funzione esterna.

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

Rompenderlo in più funzioni aggiunge sicuramente un sovraccarico nel capire perché ci sono 3 funzioni e come funzionano tutte insieme, ma se la logica è complessa, questo sovraccarico potrebbe essere molto meno della semplicità introdotta dalla rottura la logica complessa in parti più semplici.

    
risposta data 28.12.2016 - 06:20
fonte

Leggi altre domande sui tag