Problemi nella comprensione di come appare il codice pulito nella vita reale


10

Attualmente sto leggendo e lavorando su "Clean Code: A Handbook of Agile Software Craftsrafts" di Robert C. Martin. L'autore parla di come una funzione dovrebbe fare solo una cosa, e quindi essere relativamente breve. In particolare Martin scrive:

Ciò implica che i blocchi all'interno delle istruzioni if, else, while, e così via dovrebbero essere lunghi una riga. Probabilmente quella linea dovrebbe essere una chiamata di funzione. Questo non solo mantiene piccola la funzione che racchiude, ma aggiunge anche valore documentale perché la funzione chiamata all'interno del blocco può avere un nome ben descrittivo.

Ciò implica anche che le funzioni non dovrebbero essere abbastanza grandi da contenere strutture nidificate. Pertanto, il livello di rientro di una funzione non deve essere maggiore di uno o due. Ciò, ovviamente, semplifica la lettura e la comprensione delle funzioni

Questo ha senso, ma sembra essere in conflitto con esempi di quello che vedo come codice pulito. Prendi ad esempio il seguente metodo:

    public static boolean millerRabinPrimeTest(final int n) {
        final int nMinus1 = n - 1;
        final int s = Integer.numberOfTrailingZeros(nMinus1);
        final int r = nMinus1 >> s;
        //r must be odd, it is not checked here
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;
        } // works up to 3.2 billion, int range stops at 2.7 so we are safe :-)
        BigInteger br = BigInteger.valueOf(r);
        BigInteger bn = BigInteger.valueOf(n);

        for (int i = 0; i < t; i++) {
            BigInteger a = BigInteger.valueOf(SmallPrimes.PRIMES[i]);
            BigInteger bPow = a.modPow(br, bn);
            int y = bPow.intValue();
            if ((1 != y) && (y != nMinus1)) {
                int j = 1;
                while ((j <= s - 1) && (nMinus1 != y)) {
                    long square = ((long) y) * y;
                    y = (int) (square % n);
                    if (1 == y) {
                        return false;
                    } // definitely composite
                    j++;
                }
                if (nMinus1 != y) {
                    return false;
                } // definitely composite
            }
        }
        return true; // definitely prime
    }
}

Questo codice è tratto dal repository del codice sorgente di Apache Commons all'indirizzo: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/primes/SmallPrimes.java

Il metodo mi sembra molto leggibile. Per implementazioni di algoritmi come questo (implementazione del test di primalità probabilistica di Miller-Rabin), è opportuno mantenere il codice così com'è e ancora considerarlo "pulito", come definito nel libro? O anche qualcosa di già leggibile come questo trarrebbe beneficio dall'estrazione di metodi per rendere l'algoritmo essenzialmente una serie chiama a funzioni che "fanno solo una cosa"? Un rapido esempio di estrazione di un metodo potrebbe essere lo spostamento delle prime tre istruzioni if ​​in una funzione come:

private static int getTValue(int n)
    {
        int t = 1;
        if (n >= 2047) {
            t = 2;
        }
        if (n >= 1373653) {
            t = 3;
        }
        if (n >= 25326001) {
            t = 4;    
        }
        return t;
    }

Nota: questa domanda è diversa dal possibile duplicato (anche se quella domanda è utile anche a me), perché sto cercando di determinare se sto capendo l'intenzione dell'autore di Clean Code e sto fornendo un esempio specifico per rendere le cose più calcestruzzo.


3
per quanto posso vedere questa funzione fa solo una cosa ... non ha effetti collaterali che posso vedere. Cosa ti fa pensare che potrebbe non essere pulito? Quale parte di questa funzione considereresti degna di essere inserita in un'altra funzione per renderla più pulita?
Newtopian,

14
Il titolo della domanda richiede una situazione di "vita reale", e quindi il tuo esempio mi sembra un esempio perfetto di una funzione non reale (almeno per il 99,9% degli sviluppatori di applicazioni o web). Potrebbe essere una funzione reale per teorici dei numeri, matematici o scienziati informatici che lavorano in quel campo specifico, ovviamente.
Doc Brown,


2
Sì, per me questa è la vita reale mentre attualmente mi sto sviluppando nel campo della teoria dei numeri algebrica computazionale :)
1west

2
Probabilmente rifatterò getTFactor () come descrivi.
user949300,

Risposte:


17

Il "codice pulito" non è fine a se stesso, è un mezzo per raggiungere un fine. Lo scopo principale del refactoring di funzioni più grandi in funzioni più piccole e di ripulire il codice in altri modi è quello di mantenere il codice evolutivo e mantenibile.

Quando si sceglie un algoritmo matematico così specifico come il test primo "Miller-Rabin" da un libro di testo, la maggior parte dei programmatori non vuole evolverlo. Il loro obiettivo standard è trasferirlo correttamente dallo pseudo codice del libro di testo nel linguaggio di programmazione del loro ambiente. A tal fine, consiglierei di seguire il libro di testo il più vicino possibile, il che significa in genere non rifattorizzare.

Tuttavia, per qualcuno che lavora come matematico in quel campo che sta cercando di lavorare su quell'algoritmo e cambiarlo o migliorarlo, IMHO suddividendo questa funzione in più piccoli, ben definiti o sostituendo il gruppo di "numeri magici" con costanti nominate, può aiuta a rendere più semplici le modifiche al codice come per qualsiasi altro tipo di codice.


1
Questo e 'esattamente quello che stavo cercando. Ho avuto difficoltà a determinare quando utilizzare le pratiche di codice pulito nel campo in cui sto sviluppando. La tua risposta fornisce la chiarezza che stavo cercando. Grazie!
ovest
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.