Dichiarazioni sugli interruttori di refactoring e c'è qualche reale utilità per le dichiarazioni degli interruttori?


28

Stavo leggendo questo articolo e mi chiedevo: ci liberiamo di tutte le istruzioni switch sostituendole con un dizionario o una fabbrica in modo che non ci siano affatto istruzioni switch nei miei progetti.

Qualcosa non si è aggiunto del tutto.

La domanda è: le istruzioni switch hanno qualche reale utilità o andiamo avanti e le sostituiamo con un dizionario o un metodo factory (usando un metodo factory, ovviamente, ci sarà un uso minimo delle istruzioni switch per creare gli oggetti usando la fabbrica ... ma questo è tutto).


10
Supponendo di implementare una fabbrica, come deciderai quale tipo di oggetto creare?
CodeART


@CodeWorks: ovviamente avrò delle condizioni da qualche parte, decidendo quale implementazione concreta usare.
Kanini,

@CodeWorks: ovviamente con un costruttore virtuale. (E se non puoi implementare un modello di fabbrica in quel modo, hai bisogno di un linguaggio migliore.)
Mason Wheeler

Risposte:


44

Entrambe le switchaffermazioni e il polimorfismo hanno il loro uso. Si noti tuttavia che esiste anche una terza opzione (in lingue che supportano i puntatori / lambda di funzioni e le funzioni di ordine superiore): mappare gli identificatori in questione sulle funzioni del gestore. Questo è disponibile ad es. In C che non è un linguaggio OO, e C # che è *, ma non (ancora) in Java che è anche OO *.

In alcuni linguaggi procedurali (senza polimorfismo né funzioni di ordine superiore) switch/ if-elsedichiarazioni erano l'unico modo per risolvere una classe di problemi. Tanti sviluppatori, abituati a questo modo di pensare, hanno continuato a utilizzare switchanche nei linguaggi OO, dove il polimorfismo è spesso una soluzione migliore. Questo è il motivo per cui si raccomanda spesso di evitare switchdichiarazioni / refattori a favore del polimorfismo.

Ad ogni modo, la soluzione migliore dipende sempre dal caso. La domanda è: quale opzione offre un codice più pulito, più conciso e più gestibile a lungo termine?

Le istruzioni switch possono spesso diventare ingombranti, con dozzine di casi, rendendo difficile la loro manutenzione. Dal momento che devi tenerli in una sola funzione, quella funzione può diventare enorme. In tal caso, è necessario considerare il refactoring verso una soluzione basata su mappa e / o polimorfica.

Se lo stesso switchinizia a comparire in più punti, il polimorfismo è probabilmente l'opzione migliore per unificare tutti questi casi e semplificare il codice. Soprattutto se si prevede di aggiungere altri casi in futuro; più luoghi è necessario aggiornare ogni volta, maggiori sono le possibilità di errori. Tuttavia, spesso i singoli gestori di casi sono così semplici, o ce ne sono così tanti, o sono così correlati, che il refactoring in una gerarchia di classi polimorfiche completa è eccessivo, o si traduce in un sacco di codice duplicato e / o aggrovigliato, difficile mantenere la gerarchia di classe. In questo caso, potrebbe essere più semplice utilizzare funzioni / lambdas (se la tua lingua lo consente).

Tuttavia, se hai un switchin un unico posto, con solo pochi casi che fanno qualcosa di semplice, potrebbe essere la soluzione migliore per lasciarlo così com'è.

* Uso il termine "OO" liberamente qui; Non mi interessano i dibattiti concettuali su ciò che è OO "reale" o "puro".


4
+1. Inoltre, quelle raccomandazioni tendono ad essere formulate, " preferiscono il polimorfismo a cambiare" e questa è una buona scelta di parole, molto in linea con questa risposta. Riconosce che ci sono circostanze in cui un programmatore responsabile potrebbe fare l'altra scelta.
Carl Manaster,

1
E ci sono momenti in cui desideri l'approccio switch anche se nel tuo codice ce ne sono molti. Ho in mente un pezzo di codice che genera un labirinto 3D. L'uso della memoria aumenterebbe se le celle a un byte dell'array fossero sostituite con le classi.
Loren Pechtel,

14

Questo è dove torno ad essere un dinosauro ...

Le istruzioni switch non sono male in se stesse, è l'uso che ne viene fatto che è in questione.

La più ovvia è la "stessa" istruzione switch ripetuta più e più volte attraverso il codice che è male (essere stato lì, fatto ciò, farebbe ogni sforzo per non ripetere ancora) - ed è quest'ultimo caso che potresti essere in grado di gestire con l'utilizzo del polimorfismo. Di solito c'è anche qualcosa di abbastanza orribile nei casi nidificati (ero solito avere un mostro assoluto - non del tutto sicuro di come lo gestissi ora se non "meglio").

Dizionario come switch Trovo più impegnativo - fondamentalmente sì se lo switch copre il 100% dei casi, ma dove si desidera avere casi predefiniti o senza azioni, inizia a diventare un po 'più interessante.

Penso che sia una questione di evitare la ripetizione e di assicurarsi che stiamo componendo i grafici dei nostri oggetti nei posti giusti.

Ma c'è anche l'argomento di comprensione (manutenibilità) e questo taglia entrambi i modi: una volta capito come funziona tutto (il modello e l'app in cui è stato implementato) è facile ... ma se arrivate a una singola riga di codice in cui devi aggiungere qualcosa di nuovo, quindi devi saltare dappertutto per capire cosa devi aggiungere / cambiare.

Nonostante tutto ciò che i nostri ambienti di sviluppo sono in grado di supportare in maniera massiccia, continuo a pensare che sia desiderabile poter capire il codice (come se fosse) stampato su carta: puoi seguire il codice con un dito? Accetto che in realtà no, con un sacco di buone pratiche oggi non puoi e per buoni motivi, ma ciò significa che è più difficile iniziare con il codice (o forse sono solo vecchio ...)


4
Avevo un insegnante che diceva che idealmente il codice doveva essere scritto "in modo che qualcuno che non sapesse nulla della programmazione (come forse tua madre) potesse capirlo". Sfortunatamente questo è un ideale, ma forse dovremmo includere comunque le nostre mamme nelle recensioni dei codici!
Michael K,

+1 per "ma se arrivi a una singola riga di codice in cui devi aggiungere qualcosa di nuovo, devi saltare dappertutto per capire cosa devi aggiungere / cambiare"
rapidamente_ adesso

8

Switch-statement vs sottotipo-polimorfismo è un vecchio problema e viene spesso citato nella comunità FP nelle discussioni sul problema delle espressioni .

Fondamentalmente, abbiamo tipi (classi) e funzioni (metodi). Come possiamo codificare le cose in modo che sia facile aggiungere nuovi tipi o nuovi metodi in seguito?

Se si programma in stile OO, è difficile aggiungere un nuovo metodo (poiché ciò significherebbe refactoring di tutte le classi esistenti), ma è molto facile aggiungere nuove classi che utilizzano gli stessi metodi di prima.

D'altra parte, se usi un'istruzione switch (o il suo equivalente OO, l'Observer Pattern), allora è molto facile aggiungere nuove funzioni ma è difficile aggiungere nuovi casi / classi.

Non è facile avere una buona estensibilità in entrambe le direzioni, quindi quando si scrive il codice, determinare se utilizzare il polimorfismo o cambiare le istruzioni a seconda della direzione in cui è più probabile estenderlo.


4
ci liberiamo di tutte le istruzioni switch sostituendole con un dizionario o una fabbrica in modo che non ci siano affatto dichiarazioni switch nei miei progetti.

No. Tali assoluti raramente sono una buona idea.

In molti luoghi, un invio dizionario / ricerca / fabbrica / polimorfico fornirà un design migliore rispetto a un'istruzione switch ma è comunque necessario popolare il dizionario. In alcuni casi, ciò oscurerà ciò che sta realmente accadendo e semplicemente avere l'istruzione switch in linea è più leggibile e gestibile.


E davvero, se srotolassi la fabbrica, il dizionario e la ricerca, si tratterebbe sostanzialmente di un'istruzione switch: se array [hash (ricerca)] chiama array [hash (ricerca)]. Anche se sarebbe estendibile dal runtime quali switch compilati non lo sono.
Zan Lynx,

@ZanLynx, l'esatto contrario è vero, almeno con C #. Se guardi l'IL prodotto per un'istruzione switch non banale, vedrai che il compilatore lo trasforma in un dizionario, poiché è più veloce.
David Arno,

@DavidArno: "l'esatto contrario"? Il modo in cui l'ho letto abbiamo detto esattamente la stessa cosa. Come può essere opposto?
Zan Lynx,

2

Visto che questo è indipendente dalla lingua, il codice fall-through funziona meglio con le switchistruzioni:

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

Equivalente a if, con un caso di default molto imbarazzante. E tieni a mente che più ci sono cadute, più tempo avrà l'elenco delle condizioni:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(Tuttavia, dato quello che dicono alcune delle altre risposte, mi sento come se avessi perso il punto della domanda ...)


Considererei l'uso delle dichiarazioni fall-through in switcha allo stesso livello di a goto. Se l'algoritmo da eseguire richiede flussi di controllo che si adattano a costrutti di programmazione strutturati, si dovrebbero usare tali costrutti, ma se l'algoritmo non si adatta a tali costrutti, usare gotopotrebbe essere meglio che provare ad aggiungere flag o altra logica di controllo per adattarsi ad altre strutture di controllo .
supercat

1

Cambia le istruzioni in quanto la differenziazione dei casi ha un reale utilizzo. I linguaggi di programmazione funzionale usano qualcosa chiamato pattern matching che consente di definire le funzioni in modo diverso a seconda dell'input. Tuttavia, nei linguaggi orientati agli oggetti, lo stesso obiettivo può essere raggiunto più elegantemente usando il polimorfismo. È sufficiente chiamare il metodo e in base al tipo effettivo dell'oggetto, verrà eseguita l'implementazione corrispondente del metodo. Questo è il motivo dietro l'idea, che le istruzioni switch sono un odore di codice. Tuttavia, anche nei linguaggi OO, potresti trovarli utili per implementare il modello astratto di fabbrica.

tl; dr - sono utili, ma abbastanza spesso puoi fare di meglio.


2
Sento un po 'di pregiudizio lì - perché il polimorfismo è più elegante della corrispondenza del modello? E cosa ti fa pensare che il polimorfismo non esista in FP?
tdammers,

@tdammers Prima di tutto non intendevo implicare che il polimorfismo non esiste in FP. Haskell supporta il polimorfismo ad hoc e parametrico. La corrispondenza dei modelli ha senso per un tipo di dati algebrico. Ma per i moduli esterni questo richiede l'esposizione dei costruttori. Chiaramente questo rompe l'incapsulamento di un tipo di dati astratto (realizzato con tipi di dati algebrici). Ecco perché ritengo che il polimorfismo sia più elegante (e possibile in FP).
scarfridge,

Stai dimenticando il polimorfismo attraverso le macchine da scrivere e le istanze.
tdammers,

Hai mai sentito parlare del problema dell'espressione ? OO e FP sono migliori in diverse situazioni, se ti fermi a pensarci.
hugomg,

@tdammers Di che tipo di polimorfismo pensi che stavo parlando quando ho menzionato il polimorfismo ad hoc? Il polimorfismo ad hoc è realizzato in Haskell attraverso classi di tipi. Come puoi dire che l'ho dimenticato? Semplicemente non vero.
scarfridge,
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.