Complessità ciclica durante la protezione dell'input

4

Devo ridurre la complessità ciclomatica dei miei test di input. Non ho controllo sugli input, quindi devo passare attraverso tutti questi controlli.

if(y1 < 2000 || y2 < 2000 ||
    !(m1 >= 1 && m1 <= 12) ||
    !(m2 >= 1 && m2 <= 12) ||
    !(d1 >= 1 && d1 <= DaysInMonth(m1)) ||
    !(d2 >= 1 && d2 <= DaysInMonth(m2)) ||
    (m1 == 2 && IsLeap(y1) && !(d1 >= 1 && d1 <= DaysInMonth(m1) + 1)) ||
    (m2 == 2 && IsLeap(y2) && !(d2 >= 1 && d2 <= DaysInMonth(m2) + 1)))
{
    return 0;
}

C'è un modo, per renderlo meno complesso. Dovrei avvicinarmi a questo in modo diverso?

Modifica

Grazie a tutti. Sono riuscito ad abbassare la complessità. Fondamentalmente il problema principale era con la funzione DaysInMonth, poiché non gestiva gli anni bisestili. All'inizio era solo una piccola funzione di supporto e non avevo bisogno di gestire gli anni bisestili. Ma alla fine è cambiato, così ho intonato la logica del bisestile al di fuori di esso ovunque mi servisse.

Ho ottimizzato un po 'DaysInMonth con l'aiuto dei tuoi esempi e la complessità ciclomatica è diminuita.

Ho anche creato una funzione per il controllo della data non valido e passata alle disgiunzioni.

Quindi questo è il mio risultato.

int IsInvalidDate(int year, int month, int day) {
return year < 2000 ||
    month < 1 || month > 12 ||
    day < 1 || day > DaysInMonth(year, month);
}

...

if(IsInvalidDate(y1, m1, d1) || IsInvalidDate(y2, m2, d2)) {
    return 0;
}
    
posta Petr D. Svoboda 12.11.2016 - 19:07
fonte

5 risposte

12

Stai eseguendo test identici su due set di valori indipendenti. Calcola una funzione che esegue quei test su un singolo set di valori, quindi chiama due volte:

int DateInvalid (int d, int m, int y)
{
     return y < 2000 ||
         !(m >= 1 && m <= 12) ||
         !(d >= 1 && d <= DaysInMonth(m)) ||
         (m == 2 && IsLeap(y) && !(d >= 1 && d <= DaysInMonth(m) + 1));
}

...
if (DateInvalid (d1, m1, y1) || DateInvalid (d2, m2, y2)) return 0;

O, meglio ancora, usa un'implementazione di calendario esistente per eseguire il controllo, eliminando così la necessità di avere un codice di manipolazione delle date (che è notoriamente facile da sbagliare).

    
risposta data 12.11.2016 - 19:54
fonte
5

Escludiamo la negazione eccessiva, il che rende la logica molto più difficile da leggere.

Gli stessi test possono essere scritti usando solo la disgiunzione anziché come disgiunzione delle congiunzioni negate.

return y < 2000 ||
     m < 1 || m > 12 ||
     d < 1 || d > DaysInMonth(m,y);

dove

int DaysInMonth(m,y) {
    return DaysInNonLeapYearMonth(m) + (m == 2 && IsLeap(y) ? 0 : 1);
}

La versione disgiuntiva è quasi leggibile rispetto alle congiunzioni negate.

    
risposta data 12.11.2016 - 22:34
fonte
2

Come già detto, l'utilizzo delle utilità di data esistenti è migliore. Supponiamo che non siano disponibili così posso presentare i seguenti suggerimenti.

Non utilizzare condizioni negate. IsDateValid è più facile da capire rispetto al contrario, specialmente se combinato con negazioni aggiuntive.

Considera di utilizzare il test del modulo a <= b && b <=c in quanto spesso leggono più facilmente, proprio come fa a ≤ b ≤ c .

Prova a trovare un modulo che non richiede parentesi e nessuna negazione aggiuntiva come

boolean isDateValid(int d, int m, int y) {
     return 2000 <= y 
         && 1 <= m && m <= 12
         && 1 <= d && d <= daysInMonth(m, y);
}
    
risposta data 13.11.2016 - 01:41
fonte
1

Ridurre la complessità ciclomatica non dovrebbe essere il tuo obiettivo principale in questo scenario. La convalida sembra essere una sorta di "se viene rilevato qualcosa di negativo, restituisce false, altrimenti restituisce true". Questi tipi di convalida sono solitamente abbastanza facili da capire, indipendentemente da ciò che riporta lo strumento di analisi.

In questi scenari, a meno che parti dei test di convalida siano usati in altre aree, è ok avere una funzione lunga / complessa.

Soprattutto in questo scenario, poiché la gestione di data e ora è una di quelle zone odiosamente sfumate e difficili da ottenere. Il tuo obiettivo dovrebbe essere la correttezza e utilizzare librerie di data / ora affidabili e consolidate è il modo per farlo.

Come praticamente tutte le "regole" nello sviluppo del software, ci sono delle eccezioni a ogni regola. Mentre la complessità ciclomatica solitamente indica che una funzione sta diventando troppo grande per essere facilmente compresa, a volte non è così. Questo potrebbe essere uno di questi.

    
risposta data 13.11.2016 - 03:07
fonte
0

La tua complessità ciclomatica è così alta che il tuo codice non funziona. Rifiuterà il 29 febbraio negli anni bisestili, perché in primo luogo verifichi che la data sia entro i 28 giorni normali e fallisce.

Il problema immediatamente ovvio è che hai una funzione chiamata DaysInMonth con un solo parametro. È una bugia. Non restituisce il numero di giorni nel mese, restituisce un numero che a volte è corretto e talvolta non lo è. Una funzione denominata DaysInMonth deve avere due parametri, uno per il mese e uno per l'anno.

Per esperienza, una volta che vedi una bandiera rossa ovvia come questa, devi solo sapere che c'è un bug nelle vicinanze, ed era giusto. Il tuo codice non è necessario complesso perché non passi l'anno a DaysInMonth, e di conseguenza è sbagliato.

Renderai anche il tuo codice molto più leggibile (rimuovi le torsioni del cervello!) verificando una data valida.

    
risposta data 13.11.2016 - 07:45
fonte

Leggi altre domande sui tag