È uno stile sbagliato controllare in modo ridondante una condizione?

10

Spesso trovo le posizioni nel mio codice dove mi trovo a controllare una condizione specifica più e più volte.

Voglio darti un piccolo esempio: supponiamo che ci sia un file di testo che contiene linee che iniziano con "a", linee che iniziano con "b" e altre linee e in realtà voglio solo lavorare con i primi due tipi di linee . Il mio codice sarebbe simile a questo (usando Python, ma leggerlo come pseudocodice):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

Puoi immaginare che non solo controllerò questa condizione qui, ma forse anche in altre funzioni e così via.

Pensi che sia un rumore o aggiunga qualche valore al mio codice?

    
posta marktani 05.11.2012 - 02:54
fonte

7 risposte

14

Questa è una pratica estremamente popolare e il modo di affrontarlo è tramite filtri a>.

In sostanza, si passa una funzione al metodo di filtro, insieme all'elenco / sequenza da filtrare e l'elenco / sequenza risultante contiene solo quegli elementi che si desidera.

Non ho familiarità con la sintassi python (sebbene contenga una funzione simile a quella vista nel link sopra), ma in c # / f # sembra proprio questa:

c #:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (si presuppone che sia numerabile, altrimenti sarebbe usato List.filter):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

Quindi, per essere chiari: se si utilizzano codice / schemi provati e testati, è uno stile sbagliato. Questo, e la mutazione dell'elenco in memoria nel modo in cui ti appare tramite clear_lines () ti fa perdere sicurezza e speranze di parallelismo che avresti potuto avere.

    
risposta data 05.11.2012 - 05:06
fonte
14

Recentemente ho dovuto implementare un programmatore firmware utilizzando il formato Motorola S-record , molto simile a cosa descrivi Poiché abbiamo avuto un po 'di tempo, la mia prima bozza ha ignorato le ridondanze e ho apportato semplificazioni basate sul sottoinsieme che effettivamente avevo bisogno di usare nella mia applicazione. Ha superato le mie prove facilmente, ma ha fallito duramente non appena qualcuno l'ha provato. Non c'era idea di quale fosse il problema. È arrivato fino in fondo, ma alla fine non è riuscito.

Quindi non ho avuto altra scelta che implementare tutti i controlli ridondanti, al fine di restringere il punto in cui si è verificato il problema. Dopo di ciò, mi ci sono voluti circa due secondi per trovare il problema.

Mi ci sono voluti forse due ore in più per farlo nel modo giusto, ma ho sprecato un giorno anche il tempo di altre persone nella risoluzione dei problemi. È molto raro che alcuni cicli del processore valgano un giorno di risoluzione dei problemi sprecata.

Detto questo, per quanto riguarda i file di lettura, è spesso utile progettare il software affinché lavori con la lettura e l'elaborazione di una riga alla volta, piuttosto che leggere l'intero file in memoria ed elaborarlo in memoria. In questo modo funzionerà ancora su file molto grandi.

    
risposta data 05.11.2012 - 04:33
fonte
5

Puoi sollevare un'eccezione in else caso. In questo modo non è ridondante. Le eccezioni non sono cose che non dovrebbero accadere ma vengono comunque controllate.

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...
    
risposta data 05.11.2012 - 03:02
fonte
3

In progetto per contratto , si indovina ogni funzione deve svolgere il proprio lavoro come descritto nella sua documentazione. Quindi, ogni funzione ha una lista di pre-condizioni, cioè condizioni sugli input della funzione e post-condizioni, cioè condizioni dell'output della funzione.

La funzione deve garantire ai propri clienti che, se gli input rispettano le condizioni preliminari, l'output sarà come descritto dalle condizioni successive. Se almeno una delle pre-condizioni non viene rispettata, la funzione può fare ciò che vuole (crash, restituire qualsiasi risultato, ...). Pertanto le pre e post-condizioni sono una descrizione semantica della funzione.

Grazie al contratto, una funzione è sicura che i suoi client lo usino correttamente e un client è sicuro che la funzione faccia il suo lavoro correttamente.

Alcune lingue gestiscono i contratti in modo nativo o tramite un framework dedicato. Per gli altri, il meglio è controllare le condizioni pre e post grazie all'asserzione, come ha detto @Lattyware. Ma non chiamerei quella programmazione difensiva, poiché nella mia mente questo concetto è più focalizzato sulla protezione contro gli input dell'utente (umano).

Se si sfruttano i contratti, è possibile evitare la condizione di verifica ridondante poiché la funzione chiamata funziona perfettamente e non è necessario il doppio controllo, oppure la funzione chiamata è disfunzionale e la funzione di chiamata può comportarsi come vuole.

La parte più difficile è quindi definire quale funzione è responsabile di cosa e documentare rigorosamente questi ruoli.

    
risposta data 05.11.2012 - 10:02
fonte
1

In realtà non hai bisogno di clear_lines () all'inizio. Se la linea non è né "a" né "b", i condizionali semplicemente non si innescheranno. Se vuoi sbarazzarti di quelle linee, metti il resto in una clear_line (). Così com'è, stai facendo due passaggi nel tuo documento. Se salti all'inizio di clear_lines () e lo fai come parte del ciclo foreach, allora riduci il tempo di elaborazione a metà.

Non è solo cattivo stile, è male computazionale.

    
risposta data 05.11.2012 - 03:01
fonte
0

Se in realtà vuoi fare qualcosa se trovi una stringa non valida (ad esempio il testo di debug di output), direi che è assolutamente soddisfacente. Un paio di righe in più e qualche mese in più quando smette di funzionare per qualche motivo sconosciuto puoi guardare l'output per scoprire perché.

Se, tuttavia, è sicuro ignorarlo, o sai per certo che non otterrai mai una stringa non valida, non è necessario il ramo aggiuntivo.

Personalmente sono sempre disposto a inserire almeno un output di traccia per qualsiasi condizione imprevista - rende la vita molto più semplice quando si verifica un errore con l'output allegato che ti dice esattamente cosa è andato storto.

    
risposta data 06.11.2012 - 16:37
fonte
0

... suppose there is a text file which contains lines starting with "a", lines starting with "b" and other lines and I actually only want to work with the first two sort of lines. My code would look something like this (using python, but read it as pseudocode):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

Detesto if...then...else costruzioni. Eviterei l'intero problema:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
    
risposta data 06.11.2012 - 17:47
fonte

Leggi altre domande sui tag