La creazione di funzioni di utilità veramente piccole è una cattiva idea? [duplicare]

2

Recentemente, ho parlato con un amico del codice che ho scritto. Il codice ha un aspetto simile a:

import json

def save_data(destination, data):
  with open(destination, 'w') as out:
    json.dump(data,out, ensure_ascii=False)

# Code...

save_data(path,data)

Simile per il caricamento del file.

Il mio amico ha sostenuto che non si tratta di un salvataggio (sono d'accordo, non era il mio punto). Ha anche detto che rende un codice più difficile da leggere, perché devi cercare la definizione di save_data per sapere cosa fa (non sono d'accordo) e suggerisco di lasciare il codice invece di spostarlo in una funzione.

Penso che il codice sia almeno leggibile, potenzialmente riduce la ridondanza (forse avremo bisogno di salvare più di un file) e potrebbe essere di aiuto durante la rifattorizzazione (invece di modificare ogni posizione in cui il file viene salvato, dovremmo cambia semplicemente save_data), anche se al momento salviamo solo una cosa in un file.

È davvero una cattiva idea spostare anche piccole porzioni di codice usate una volta nelle funzioni?

Ho taggato la domanda con Python, dal momento che il codice è Python, ma penso che la domanda sia universale.

    
posta MatthewRock 09.11.2017 - 19:06
fonte

4 risposte

7

La dimensione è irrilevante. Dovresti pensare in termini di livelli di astrazione e punti di potenziale cambiamento. A quel punto, non esiste una dimensione minima di una funzione. La funzione dovrebbe catturare un'idea. Ad esempio,

def normalize(s):
    return s.strip()

In questo caso, normalize è essenzialmente solo un alias per strip . Tuttavia, strip opera a livello di manipolazione delle stringhe, mentre normalize trasmette la nozione che esistono rappresentazioni non normalizzate e normalizzate. Concettualmente, normalize è solo un'operazione su un tipo di dati astratto che sembra essere rappresentato da una stringa. È anche possibile che ciò che è necessario per "normalizzare" una stringa nel senso rilevante possa cambiare. Quante volte una funzione viene utilizzata in questo contesto è irrilevante. Se creo un tipo di dati astratto Stack, non definisco la definizione di un'operazione pop , solo perché viene utilizzata una sola volta.

D'altra parte, spesso le funzioni funzioni catturano modelli ricorrenti per comodità. Queste sono funzioni che sono definite in termini di funzioni a un livello di astrazione e producono una funzione allo stesso livello di astrazione stesso . Dovresti comunque provare a far sì che queste funzioni catturino un'idea specifica, e ciò potrebbe suggerire di sostituire il pattern ricorrente con un modello di funzioni più piccolo / più semplice piuttosto che una singola funzione. Detto questo, un modello comune di solito indica che esiste un'idea che può essere catturata. Per questi tipi di funzioni, la frequenza con cui si verificano importa. Non ha molto senso fare una funzione di convenienza per qualcosa che accade raramente. Inoltre, non è un buon scambio di tempo e carico cognitivo per far cercare qualcuno e ricordare la definizione di un'operazione che viene raramente utilizzata quando si può semplicemente definire la definizione.

La tua domanda dà troppo poco contesto per fornire una risposta decisiva. Se save_data è parte di una barriera di astrazione dal modo in cui i dati vengono archiviati, devi assolutamente averlo a prescindere da quanto sia breve. Tuttavia, in questo caso, ci dovrebbero (probabilmente) essere operazioni corrispondenti che fanno anche parte di quella barriera di astrazione che legge i dati. Dovrebbe essere possibile, ad esempio, passare da JSON a XML semplicemente modificando le funzioni dell'astrazione barriera e nessuna delle funzioni consumatrici. Se questo non è il caso, devi aumentare il livello di astrazione altrove, oppure save_data non serve uno scopo di astrazione e potrebbe nascondere i dettagli rilevanti e quindi non dovrebbe esistere.

Se non ti aspetti che save_data venga usato altrove e non serva come parte di una barriera di astrazione, allora probabilmente non dovrebbe esistere. Estraendo il codice in una funzione, si afferma che i dettagli non sono così importanti per il consumatore. Tuttavia, se questi dettagli sono importanti, non dovrebbero essere nascosti. Ad esempio, se save_data viene utilizzato all'interno di un metodo serialize_to_json , nascondere il fatto che save_data effettivamente scrive JSON non ha senso. E se decidi, serialize_to_json dovrebbe essere serialize_to_xml , non dovresti aver bisogno di passare ad altre funzioni per modificare quel dettaglio. Quindi, in questo caso, sarei d'accordo con il tuo collega sul fatto che la definizione della funzione debba essere sottolineata e la funzione non dovrebbe esistere.

    
risposta data 10.11.2017 - 04:46
fonte
10

Nella mia esperienza, il calcolo di piccole funzioni riduce la dimensione totale del codice e aumenta la velocità. È vero che per sapere cosa sta succedendo in save_data bisogna leggere la funzione save_data . Ma spesso quando leggiamo una funzione di alto livello ci interessa solo un particolare passo. È molto più facile da comprendere:

a = get_a(...)
b = get_b(...)
c = get_c(...)
save_data(a, b, c)

Than:

// get_a (this comment is present if you are lucky)
... ten lines of code, introducing additional local vars x and y
// get_b
... fifteen lines, introducing p and q
// get_c
...
// save_data
... twenty lines involving f,g,h,i,j

La lettura del codice inserito richiede che il lettore verifichi che i commenti siano corretti (se sono presenti).

Nei sistemi reali queste piccole funzioni finiscono per essere riutilizzate più di quanto inizialmente previsto. L'elaborazione di piccole funzioni crea gradualmente un linguaggio specifico del dominio, spostando l'attenzione degli sviluppatori dai dettagli di implementazione di basso livello agli oggetti specifici di dominio di livello superiore.

    
risposta data 09.11.2017 - 19:34
fonte
1

Hai già messo in conflitto te stesso, dici:

Is it really a bad idea to move even small, once-used pieces of code to functions?

ma prima ho detto:

instead of modifying every place where the file is saved

Quindi, che cos'è, viene utilizzato una volta o più volte?

Vorrei sostenere l'approccio YAGNI. Se il tuo salvataggio di JSON esiste davvero solo in un posto, ed è un processo semplice, non decisionale, e non sai per certo che lo farai altrove, lascialo dov'è. Al momento qualcos'altro deve salvare il json, quindi estrai il metodo e inizia ad astrarre il processo di salvataggio.

    
risposta data 09.11.2017 - 19:13
fonte
1

He ... said that it makes a code harder to read

OK, e cosa dici tu ? Guarda i siti di chiamata. Se pensi che avere una funzione save_data(...) migliora la leggibilità delle funzioni che la chiamano, allora è una vittoria per te.

Is it really a bad idea to move even small, once-used pieces of code to functions?

Penso che dovrai rispondere su una base caso per caso. Ho spesso rifatto le grandi funzioni complicate in sequenze di chiamate a funzioni più piccole e ho ritenuto che fosse un miglioramento. Ma ho anche visto casi in cui spostare alcune righe di codice che non sono tutte strettamente correlate l'una con l'altra in una subroutine con un nome sconsiderato sembrava solo nascondere cosa stava succedendo.

    
risposta data 09.11.2017 - 20:37
fonte

Leggi altre domande sui tag