Quando dovrei creare una funzione separata (o classe)

7

Faccio programmi per diversi anni.
E ora so dai miei colleghi i miei pro e contro:
Pro: Posso risolvere un problema molto complesso
contro: faccio soluzioni complicate per compiti semplici.

Ora sto cercando di risolvere i miei contro e alla ricerca di principi e linee guida generali per questa domanda:

Quando dovrei creare un ulteriore pezzo di codice (una funzione, una classe, un modulo, qualunque cosa). E quando non dovrei.

Ho visto una risposta da un ragazzo Python: ogni volta che puoi, non creare, renderlo il più semplice possibile.

Questo è un ottimo consiglio, ma avere una grande funzione non è una buona idea.

Il problema per me è che ho un principio quando creare una funzione separata di sicuro, ma non ho uno contrario (quando non lo creo). Il principio è:

Crea una funzione quando dovrai copiare e incollare del codice e, di conseguenza, avrai il codice duplicato.

(Perché: il codice duplicato è sbagliato, perché ti dimentichi di correggere alcune copie quando correggi un bug in esso, dovrai creare più volte il numero di test che copi e le volte è più lettere da leggere per la tua squadra e te stesso più tardi, ecc ...) Questo può sembrare un esempio del tipo di risposte che sto cercando.

Quindi, come decidi quando creare una funzione separata o semplicemente estenderne una esistente?

Ringrazia tutti coloro che hanno già risposto a questa domanda, ma per favore aggiungi almeno alcuni principi quando dovresti non dividere la funzione (quando dovresti mantenerla nel suo complesso).

Perché per ora la maggior parte (tutte?) le risposte qui riguardano solo quando dovresti dividere. Il mio problema è che faccio troppe funzioni, mi divido troppo spesso, come mi dicono i programmatori, ed è per questo che la mia domanda differisce da questa Dovrei estrarre funzionalità specifiche in una funzione e perché? .

    
posta Yuri Yaryshev 01.09.2017 - 20:26
fonte

9 risposte

12

Sembra una domanda abbastanza ampia, ma condividerò felicemente il mio "senso" FWIW

  1. Porzioni di codice che potrebbero essere estratte in pure funzioni (output determinato esclusivamente dagli input) dovrebbero essere estratte in pure funzioni. Ad esempio, se a metà della funzione si scopre che è necessario formattare una stringa in un determinato modo, è possibile estrarre la logica di formattazione della stringa in una libreria di stringhe. Anche se usi una sola volta la funzione, questo renderà il codice più leggibile e ridurrà la complessità della tua funzione principale.

  2. Se il nome della tua funzione è ridicolo, forse deve essere diviso, ad es. OpenFileParseContentsAndCreateObjects sembra che potrebbe essere meglio separato in tre funzioni.

  3. Se la funzione non può essere letta e compresa da uno sviluppatore medio entro 1 minuto, probabilmente potrebbe beneficiare della semplificazione o di essere suddivisa in bit più piccoli.

  4. Se la funzione richiede circa 20 o più test unitari per esercitare il 100% dei percorsi logici, dovrebbe essere suddivisa.

  5. Se la funzione è più alta di una singola pagina visualizzabile nell'IDE, a meno che non vi sia un motivo valido, dovrebbe essere considerato per la ristrutturazione.

risposta data 01.09.2017 - 22:35
fonte
9

Un modo per dire se dovresti interrompere una funzione è quando vedi uno schema come questo:

error_t getInfoFromFile(const char* filename)
{
    // Open the file
    ... 5 lines to open a file...

    // Check the magic number
    ... 3 lines read some bytes and make sure they match the magic number...

    // Read the header
    ... 5 lines to read the header

    // Find the offset of the info
    ... 10 lines to find the offset of the information you need

    // Read the info
    ... 5 lines to read the information you need

    // Reformat the info
    ... 10 lines to change the format of the information into whatever you need ...

    // etc.
}

Ciascuno di questi commenti indica probabilmente una funzione separata che potrebbe essere scritta per l'attività in corso. Potresti non aver bisogno di tutti loro. Forse puoi lasciare il codice di apertura del file lì nudo, per esempio. Ma probabilmente avrai un altro codice che deve leggere l'intestazione e trovare anche un offset nel file, in modo che queste possano essere funzioni separate. Leggere le informazioni è ciò che la funzione dovrebbe fare, quindi lo lascerei. Riformattarlo dovrebbe probabilmente essere una funzione separata, ma potrebbe essere ragionevole chiamarlo da questa funzione, a seconda del suo ambito. Fondamentalmente, se ci sono più passaggi da eseguire per l'attività in corso, se questi passaggi sono più di una o due righe, li inserirò in una funzione.

    
risposta data 02.09.2017 - 05:52
fonte
5

Creare una funzione ogni volta che si ha del codice duplicato è una cattiva idea. La maggior parte di tale duplicazione è probabilmente incidentale e soggetta a modifiche, a seconda di quanto sia profondo l'aspetto.

Una linea guida migliore consiste nel creare un'astrazione (funzione, classe, costante denominata, spazio dei nomi, ...) quando hai identificato che qualcosa è un blocco predefinito chiaramente definito con un buon nome.

Il fatto che blocchi validi per costruire più cose con, oltre ad astrarre i dettagli, riduce la duplicazione del codice è solo un felice effetto collaterale.

    
risposta data 01.09.2017 - 21:52
fonte
2

Oltre alle altre risposte, alcuni altri indicatori che suggeriscono potrebbe essere una buona idea dividere una funzione / metodo:

  • Gli strumenti di analisi del codice statico mettono in guardia sulla complessità ciclomatica
  • Violare il principio CQS .
  • Contiene una logica che non si adatta alla sua semantica del nome, ad esempio un metodo chiamato ReadFile che esegue anche una query su un database SQL.
  • Contiene switch con blocchi case voluminosi o non banali.
  • Accetta gli argomenti boolean flag che alterano significativamente il suo comportamento.
  • Uno o più dei suoi argomenti sono pertinenti solo nei rami nidificati condizionali o sembrano non correlati agli altri, ad esempio Save(File[] files, string sqlConnectionString, TextBoxControl directoryTextBox, IpAddress webServer) .
risposta data 02.09.2017 - 11:46
fonte
2

I moduli, le funzioni delle classi sono gli strumenti del programmatore che stiamo usando per creare un'applicazione.

Il programmatore dovrebbe usare gli strumenti liberamente senza alcuna restrizione per la loro quantità. Se durante lo sviluppo finisci con 100 funzioni e classi questo significa che decidi di averne bisogno - ed è ok.
Il programmatore da solo non sarà mai in grado di rispondere a una domanda è la mia soluzione eccessiva per questo particolare compito.
Ecco perché abbiamo delle visioni in codice - dove altri programmatori possono vedere il tuo approccio e dire la loro opinione. Quindi, mettendo la tua opinione e le opinioni dei revisori, finisci con un approccio che soddisfa la tua squadra.

La programmazione riguarda solo il contesto, solo avendo piena conoscenza del problema concreto possiamo dire quale approccio dovrebbe essere scelto e quanta astrazione sarà sufficiente per questo compito.
Ma il nostro problema è che all'inizio del compito abbiamo meno informazioni sul contesto del problema (sul dominio del problema), quindi alla fine del compito. Questo è il motivo per cui di solito, una volta terminato il compito, il programmatore vedrà / sentirà che dovrebbe essere fatto in modo diverso. Ovviamente non è possibile riscrivere la soluzione, perché è necessario iniziare l'attività successiva. Che portano dopo 1-2 anni a creare soluzioni alternative per fare le cose senza una riscrittura massiccia del codice esistente.

Ecco perché è così importante scrivere un'applicazione che può essere modificata con meno sforzo / costi.

Accoppiamento, le dipendenze dirette sono alcune delle ragioni di molte che impediscono cambiamenti efficienti. Si finisce con situazioni in cui i cambiamenti in un luogo seguiranno una grande quantità di cambiamenti negli altri.

Ecco perché è molto importante separare la logica in diversi "blocchi" che possono essere sostituiti, combinati insieme con il minimo sforzo.

I principi SOLID possono essere una buona guida per programmatore. Meno esperienza di programmazione hai più rigorosamente dovresti seguire questa linea guida.

Nel tuo caso particolare "Principio di responsabilità singola" ("S" in SOLID) è la tua linea guida - "classe / funzione dovrebbe avere solo un motivo per cambiare"

Un approccio utile per trovare candidati per l'estrazione di una propria funzione / classe sta scrivendo il codice nell'approccio "test first" (TDD o Test-Driven Development).

Quando inizi a scrivere test per alcune funzioni e finisci con più casi di test diversi, dove la configurazione del caso di test diventa complicata, puoi mettere parte della logica dietro le astrazioni che possono essere prese in giro nei test.

    
risposta data 02.09.2017 - 07:40
fonte
1

Applicare il principio di responsabilità singola per ottenere un strong separazione delle preoccupazioni . Ciò si traduce in un codice flessibile e gestibile.

Robert C. Martin esprime un principio di responsabilità individuale come "una classe dovrebbe avere solo una ragione per cambiare". Non pensare che questo si applica solo al codice OO. La classe deve essere considerata come un tipo di meta-classe . Il principio è applicabile a qualsiasi approccio per la decomposizione del codice, i moduli, le funzioni, i metodi, le procedure potrebbero sostituire la parola "classe" e il principio è vero.

Ogni pezzo di codice dovrebbe avere la responsabilità su una singola parte della funzionalità. C'è un corollario che la responsabilità dovrebbe essere interamente incapsulata dalla "classe" per minimizzare l'accoppiamento. Qualsiasi servizio software dovrebbe essere strettamente allineato alla responsabilità.

In pratica, se non puoi descrivere la funzionalità in una singola breve frase, dovresti scomporre il codice. Dividi il codice sulla parola "e".

Dato il requisito "Una persona è composta da un nome e un'età", risulterebbe in tre parti separate di codice una persona, un nome e un'età.

Questo approccio massimizza l'affidabilità e la manutenibilità mentre localizza e riduce al minimo la complessità, attraverso una separazione dei dubbi .

    
risposta data 02.09.2017 - 11:17
fonte
1

Se capisco correttamente, la questione centrale è quando modificare una funzione esistente e quando crearne una nuova, quando la creazione di una nuova può portare a un codice duplicato.

In primo luogo, la ragione per creare una nuova funzione è perché hai bisogno di una funzione che faccia qualcosa di significativamente diverso da ciò che fa una funzione esistente. Se la funzione esistente ha il suo scopo, non la modificherò per servire a un altro scopo per evitare la duplicazione del codice.

Ma la creazione di una nuova funzione non significa che è necessario duplicare il codice. Se la tua nuova funzione richiede una porzione di codice identica a quella contenuta nella funzione esistente, puoi estrarla nella sua funzione e chiamarla da entrambe le funzioni esistente e nuova.

IOW, se modificare una funzione esistente e se duplicare il codice sono di solito domande non correlate.

Se il codice duplicato è richiesto più di una volta in una data classe di quella che è possibile rifattorizzare in un metodo. Se è richiesto attraverso le classi, allora è un buon caso per una nuova classe. Se un pezzo di codice è un) abbastanza lungo da non volerlo duplicare eb) abbastanza utile da averne bisogno più di una volta, allora è probabile che valga la pena testare l'unità.

    
risposta data 21.09.2017 - 21:36
fonte
0

Oltre a ciò che hai già scritto su come evitare la duplicazione del codice:

Dovresti farlo ogni volta che trovi un buon nome per la funzione o la classe. Un buon nome dice al lettore cosa fa la funzione o la classe senza dover guardare l'implementazione.

    
risposta data 01.09.2017 - 21:14
fonte
-1

Sono d'accordo con gli altri poster su questo. Io uso la regola di 2. Se sto facendo qualcosa in 2 punti, ad esempio all'interno di una classe, ad esempio, è quando trasformo quel qualcosa in un metodo. Subito prima di scrivere un'altra riga di codice.

    
risposta data 02.09.2017 - 08:46
fonte

Leggi altre domande sui tag