C'è qualcosa come avere troppe funzioni / metodi privati?


63

Comprendo l'importanza di un codice ben documentato. Ma capisco anche l'importanza del codice auto-documentante . Più è facile leggere visivamente una particolare funzione, più velocemente possiamo passare durante la manutenzione del software.

Detto questo, mi piace separare le grandi funzioni da altre più piccole. Ma lo faccio fino al punto in cui una classe può averne fino a cinque solo per servire un metodo pubblico. Ora moltiplica cinque metodi privati ​​per cinque metodi pubblici e otterrai circa venticinque metodi nascosti che probabilmente verranno chiamati solo una volta da quei metodi pubblici.

Certo, ora è più facile leggere quei metodi pubblici, ma non posso fare a meno di pensare che avere troppe funzioni sia una cattiva pratica.

[Modificare]

La gente mi ha chiesto perché penso che avere troppe funzioni sia una cattiva pratica.

La semplice risposta: è una sensazione viscerale.

La mia convinzione non è, per un po ', supportata da nessuna ora di esperienza di ingegneria del software. È solo un'incertezza che mi ha dato un "blocco dello scrittore", ma per un programmatore.

In passato, ho programmato solo progetti personali. È solo di recente che sono passato a progetti basati su team. Ora voglio assicurarmi che gli altri possano leggere e comprendere il mio codice.

Non ero sicuro di cosa migliorerà la leggibilità. Da un lato, stavo pensando di separare una grande funzione in altre più piccole con nomi intelligibili. Ma c'era un altro lato di me che diceva che è solo ridondante.

Quindi, sto chiedendo questo per illuminarmi per scegliere la strada giusta.

[Modificare]

Di seguito, ho incluso due versioni di come ho potuto risolvere il mio problema. Il primo lo risolve non separando grossi blocchi di codice. Il secondo fa cose separate.

Prima versione:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Seconda versione:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Negli esempi precedenti, quest'ultimo chiama una funzione che verrà utilizzata una sola volta per tutto il tempo di esecuzione del programma.

Nota: il codice sopra è generalizzato, ma ha la stessa natura del mio problema.

Ora, ecco la mia domanda: quale? Scelgo il primo o il secondo?


1
"avere troppe funzioni è una cattiva pratica"? Perché? Aggiorna la tua domanda per spiegare perché questo ti sembra negativo.
S. Lott

Consiglierei di leggere il concetto di "coesione di classe" - Bob Martin ne discute in "Codice pulito".
JᴀʏMᴇᴇ,

Aggiungendo ad altre risposte, divisioni e nomi, ma ricordiamo tutti che, man mano che una classe cresce, le sue variabili membro diventano sempre più simili a variabili globali. Un modo per migliorare il problema (a parte soluzioni migliori come il refactoring ;-)) è quello di separare quei piccoli metodi privati ​​in funzioni autonome se ciò è possibile (in quanto non hanno bisogno di accedere a molto stato). Alcuni linguaggi consentono funzioni fuori classe, altri hanno classi statiche. In questo modo puoi capire quella funzione da sola.
Pablo H,

Se qualcuno può dirmi perché "questa risposta non è utile", te ne sarei grato. Si può sempre imparare. :-)
Pablo H,

@PabloH La risposta che hai fornito fornisce una buona osservazione e approfondimento al PO, tuttavia in realtà non risponde alla domanda che è stata posta. L'ho spostato nel campo dei commenti.
maple_shaft

Risposte:


36

Ora moltiplica cinque metodi privati ​​per cinque metodi pubblici e otterrai circa venticinque metodi nascosti che probabilmente verranno chiamati solo una volta da quei metodi pubblici.

Questo è ciò che è noto come incapsulamento , che crea un'astrazione di controllo al livello superiore. Questa è una buona cosa.

Ciò significa che chiunque legga il codice, quando arrivano al startTheEngine()metodo nel codice, può ignorare tutti i dettagli di livello inferiore, come openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), e tutti gli altri complessi, piccoli, funzioni che devono essere eseguiti al fine di facilitare la funzione molto più ampia e astratta di startTheEngine().

A meno che il problema che stai affrontando nel tuo codice non riguardi direttamente uno di quei componenti, i manutentori del codice possono continuare, ignorando la funzionalità sandbox e incapsulata.

Ciò ha anche l'ulteriore vantaggio di rendere più semplice il test del codice. . Ad esempio, posso scrivere un test case turnAlternatorMotor(int revolutionRate)e testarne la funzionalità completamente indipendente dagli altri sistemi. Se c'è un problema con quella funzione e l'output non è quello che mi aspetto, allora so qual è il problema.

Il codice che non è suddiviso in componenti è molto più difficile da testare. Improvvisamente, i tuoi manutentori stanno solo guardando l'astrazione invece di essere in grado di scavare in componenti misurabili.

Il mio consiglio è di continuare a fare ciò che state facendo man mano che il vostro codice si ridimensionerà, sarà facile da mantenere e potrà essere utilizzato e aggiornato per gli anni a venire.


3
Quindi credi che sia una buona "incapsulamento" rendere la logica disponibile per un'intera classe che viene chiamata solo in un posto?
Steven Jeuris,

9
@Steven Jeuris: Finché quelle funzioni sono rese private, non vedo alcun problema; infatti, trovo che, come affermato in questa risposta, sia più facile da leggere. È molto più facile comprendere una funzione pubblica di alto livello che chiama solo una serie di funzioni private di basso livello (che sono state opportunamente denominate, ovviamente). Certo, tutto quel codice avrebbe potuto essere messo nella stessa funzione di alto livello, ma poi ci vorrebbe più tempo per capire il codice, dato che devi esaminarne molto di più nello stesso posto.
gablin,

2
@gablin: le funzioni private sono ancora accessibili dall'intera classe. In questo articolo (apparentemente controverso) discuto di come si perde la "leggibilità globale" quando si hanno troppe funzioni. In realtà un principio comune oltre a quello che le funzioni dovrebbero fare solo una cosa, è che non dovresti averne troppe. Ottieni solo "leggibilità locale" suddividendo solo per brevità, che può anche essere ottenuto con semplici commenti e "paragrafi di codice". Ok, il codice dovrebbe essere auto-documentato, ma i commenti non sono obsoleti!
Steven Jeuris,

1
@Steven - Cosa è più facile da capire? myString.replaceAll(pattern, originalData);oppure sto incollando l'intero metodo ReplaceAll nel mio codice, incluso l'array di caratteri di supporto che rappresenta l'oggetto stringa sottostante ogni volta che devo usare la funzionalità di una stringa?
jmort253,

7
@Steven Jeuris: hai ragione. Se una classe ha troppe funzioni (pubbliche o private), allora forse l'intera classe sta provando a fare troppo. In questi casi, potrebbe essere meglio suddividerlo in più classi più piccole, così come si dividerebbe una funzione troppo grande in più funzioni più piccole.
gablin,

22

Sì e no. Il principio fondamentale è: un metodo dovrebbe fare solo una cosa e una cosa

È corretto "suddividere" il metodo in metodi più piccoli. Inoltre, questi metodi dovrebbero essere sufficientemente generici non solo per servire quel metodo "grande", ma essere riutilizzabili in altri luoghi. In alcuni casi, tuttavia, un metodo seguirà una struttura ad albero semplice, come un metodo Initialize che chiama InitializePlugins, InitializeInterface ecc.

Se hai un metodo / classe davvero grande, di solito è un segno che sta facendo molto e devi fare un po 'di refactoring per rompere il "blob". Perphaps nasconde una certa complessità in un'altra classe sotto un'astrazione e usa l'iniezione di dipendenza


1
È bello leggere che non tutti seguono ciecamente la regola dell'unica cosa! ; p Bella risposta!
Steven Jeuris,

16
Spesso scompongo un metodo di grandi dimensioni in metodi più piccoli, anche se serviranno solo il metodo più grande. La ragione per dividerlo è perché diventa più facile da gestire e capire, invece di avere solo un'enorme quantità di codice (che può essere o meno auto-commentata e ben commentata).
gablin,

1
Va bene anche in alcuni casi in cui non puoi davvero evitare un metodo di grandi dimensioni. Ad esempio, se hai un metodo Initialize potresti averlo chiamare InitializeSystems, InitializePlugins ecc. Ecc. Si dovrebbe sempre verificare se non è necessario il refactoring
Homde,

1
La cosa chiave di ogni metodo (o funzione) è che deve avere una chiara interfaccia concettuale, è "contratto". Se non riesci a fissarlo, sarà un problema e hai sbagliato il factoring. (Documentare esplicitamente il contratto può essere una buona cosa - o usare uno strumento per applicarlo, in stile Eiffel - ma non è così importante come averlo lì in primo luogo.)
Donal Fellows

3
@gablin: un altro vantaggio da considerare: quando divido le cose in funzioni più piccole, non pensare "servono solo un'altra funzione", pensa "servono solo un'altra funzione per ora ". Ci sono state diverse occasioni in cui il re-factoring di grandi metodi mi ha fatto risparmiare un sacco di tempo quando scrivevo altri metodi in futuro perché le funzioni più piccole erano più generiche e riutilizzabili.
GSto

17

Penso che in generale sia meglio sbagliare dal lato di troppe funzioni anziché di troppo poche. Le due eccezioni a quella regola che ho visto in pratica sono per i principi DRY e YAGNI . Se hai molte funzioni quasi identiche, dovresti combinarle e utilizzare i parametri per evitare di ripetere te stesso. Puoi anche avere troppe funzioni se le hai create "per ogni evenienza" e non vengono utilizzate. Non vedo assolutamente nulla di sbagliato nell'avere una funzione che viene usata una sola volta se aggiunge leggibilità e manutenibilità.


10

La grande risposta di jmort253 mi ha ispirato a dare una risposta "sì, ma" ...

Avere molti piccoli metodi privati ​​non è una brutta cosa, ma presta attenzione a quella sensazione assillante che ti sta facendo pubblicare una domanda qui :). Se arrivi a un punto in cui stai scrivendo test che sono focalizzati solo su metodi privati ​​(o chiamandoli direttamente o impostando uno scenario in modo tale che uno venga chiamato in un certo modo solo per poter testare il risultato) allora dovresti fare un passo indietro.

Quello che potresti avere è una nuova classe con la sua interfaccia pubblica più focalizzata che cerca di scappare. A quel punto potresti voler pensare di estrarre una classe per incapsulare quella parte della tua funzionalità di classe esistente che attualmente vive in un metodo privato. Ora la tua classe originale può usare la tua nuova classe come dipendenza e puoi scrivere test più mirati direttamente su quella classe.


Penso che questa sia un'ottima risposta ed è una direzione in cui il codice potrebbe sicuramente andare. Le migliori pratiche dicono di usare il modificatore di accesso più restrittivo che ti offre le funzionalità di cui hai bisogno. Se i metodi non vengono utilizzati al di fuori della classe, private ha più senso. Se ha più senso rendere pubblici i metodi o spostarli in un'altra classe in modo che possano essere usati altrove, allora lo si può fare anche con ogni mezzo. +1
jmort253

8

Quasi tutti i progetti OO a cui mi sono mai unito hanno sofferto molto per le classi troppo grandi e per i metodi troppo lunghi.

Quando sento la necessità di un commento che spieghi la prossima sezione di codice, quindi estraggo quella sezione in un metodo separato. A lungo termine, questo rende il codice più breve. Quei piccoli metodi vengono riutilizzati più di quanto ti aspetteresti inizialmente. Inizi a vedere opportunità per raccoglierne un gruppo in una classe separata. Presto stai pensando al tuo problema ad un livello superiore e l'intero sforzo va molto più veloce. Le nuove funzionalità sono più facili da scrivere, perché puoi basarti su quelle piccole classi che hai creato da quei piccoli metodi.


7

Una classe con 5 metodi pubblici e 25 metodi privati ​​non mi sembra così grande. Assicurati solo che le tue classi abbiano responsabilità ben definite e non preoccuparti troppo del numero di metodi.

Detto questo, quei metodi privati ​​dovrebbero concentrarsi su un aspetto specifico dell'intera attività, ma non in modo "doSomethingPart1", "doSomethingPart2". Non ottieni nulla se quei metodi privati ​​sono solo pezzi di una lunga storia arbitrariamente divisa, ognuno dei quali è insignificante al di fuori dell'intero contesto.


+1 per "Non ottieni nulla se quei metodi privati ​​sono solo pezzi di una lunga storia divisa in modo arbitrario, ognuno dei quali è insignificante al di fuori dell'intero contesto."
Mahdi,

4

Estratto dal codice pulito di R. Martin (p. 176):

Anche concetti fondamentali come l'eliminazione della duplicazione, l'espressività del codice e l'SRP possono essere portati troppo lontano. Nel tentativo di ridurre le nostre classi e metodi, potremmo creare troppe piccole classi e metodi. Quindi questa regola [minimizza il numero di classi e metodi] suggerisce che manteniamo bassi anche i nostri conteggi di funzioni e classi. Conteggi di classe e metodi elevati sono talvolta il risultato di un dogmatismo inutile.


3

Alcune opzioni:

  1. Va bene. Basta nominare i metodi privati ​​e nominare i metodi pubblici. E dai un nome ai tuoi metodi pubblici.

  2. Estrarre alcuni di questi metodi di supporto in classi di aiuto o moduli di supporto. E assicurati che queste classi / moduli di supporto abbiano un buon nome e siano solide astrazioni in sé e per sé.


1

Non penso che ci sia mai un problema di "troppi metodi privati" se la sensazione è probabilmente un sintomo di una scarsa architettura del codice.

Ecco come ci penso ... Se l'architettura è ben progettata, è generalmente ovvio dove dovrebbe essere il codice che fa X. È accettabile se ci sono un paio di eccezioni a questo - ma queste devono essere veramente eccezionali, altrimenti rifattenere l'architettura per gestirle come standard.

Se il problema è che aprendo la tua classe, è pieno di metodi privati ​​che stanno dividendo blocchi più grandi in blocchi di codice più piccoli, allora forse questo è un sintomo di architettura mancante. Invece di classi e architettura, quest'area di codice è stata implementata in modo procedurale all'interno di una classe.

Trovare un equilibrio qui dipende dal progetto e dai suoi requisiti. L'argomento opposto a questo è il detto che un programmatore junior può buttare giù la soluzione in 50 righe di codice che prende un architetto 50 classi.


0

C'è qualcosa come avere troppe funzioni / metodi privati?

Sì.

In Python il concetto di "privato" (usato da C ++, Java e C #) non esiste davvero.

C'è una convenzione di denominazione "un po 'privata", ma il gioco è fatto.

Privato può portare a codici difficili da testare. Può anche infrangere il "Principio aperto-chiuso" rendendo semplicemente il codice chiuso.

Di conseguenza, per le persone che hanno usato Python, le funzioni private non hanno alcun valore. Rendi tutto pubblico e falla finita.


1
Come può rompere il principio aperto chiuso? Il codice è sia aperto per estensione che chiuso per modifica indipendentemente dal fatto che i metodi pubblici siano stati suddivisi in metodi privati ​​più piccoli.
byxor,
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.