Problemy z uchwyceniem, jak wygląda czysty kod w prawdziwym życiu

10

Obecnie czytam i pracuję nad „Clean Code: A Handbook of Agile Software Craftsmanship” Roberta C. Martina. Autor mówi o tym, jak funkcja powinna robić tylko jedną rzecz, a zatem być stosunkowo krótka. W szczególności Martin pisze:

Oznacza to, że bloki wewnątrz instrukcji if, instrukcji else, instrukcji while i tak dalej powinny mieć długość jednego wiersza. Prawdopodobnie ta linia powinna być wywołaniem funkcji. Nie tylko powoduje to, że funkcja zamykająca jest niewielka, ale także dodaje wartość dokumentalną, ponieważ funkcja wywoływana w bloku może mieć ładnie opisową nazwę.

Oznacza to również, że funkcje nie powinny być wystarczająco duże, aby pomieścić zagnieżdżone struktury. Dlatego poziom wcięcia funkcji nie powinien być większy niż jeden lub dwa. To oczywiście sprawia, że ​​funkcje są łatwiejsze do odczytania i zrozumienia

Ma to sens, ale wydaje się kolidować z przykładami tego, co uważam za czysty kod. Weźmy na przykład następującą metodę:

    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
    }
}

Ten kod pochodzi z repozytorium kodu źródłowego Apache Commons pod adresem : https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/primes/SmallPrimes.java

Metoda wydaje mi się bardzo czytelna. Czy w przypadku implementacji algorytmów takich jak ta (implementacja probabilistycznego testu pierwszeństwa Millera-Rabina) właściwe jest zachowanie kodu w niezmienionej postaci i nadal uważanie go za „czysty”, zgodnie z definicją w książce? A może nawet coś tak czytelnego jak ta skorzysta z wyodrębnienia metod, aby algorytm był w zasadzie szeregiem wywołań funkcji, które „wykonują tylko jedną rzecz”? Jednym szybkim przykładem ekstrakcji metody może być przeniesienie pierwszych trzech instrukcji if do funkcji takiej jak:

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;
    }

Uwaga: to pytanie różni się od możliwego duplikatu (choć to pytanie jest również dla mnie pomocne), ponieważ próbuję ustalić, czy rozumiem intencję autora Clean Code i podam konkretny przykład, aby uczynić więcej beton.

1west
źródło
3
o ile widzę ta funkcja robi tylko jedną rzecz ... nie ma żadnych skutków ubocznych, które widzę. Co sprawia, że ​​myślisz, że może nie być czyste? Którą część tej funkcji uważasz za wartą zastosowania innej funkcji, aby była czystsza?
Newtopian
14
W tytule pytania pytasz o sytuację „z prawdziwego życia”, a następnie twój przykład wydaje mi się doskonałym przykładem funkcji nierealnej (przynajmniej dla 99,9% twórców aplikacji lub stron internetowych). Oczywiście może to być rzeczywista funkcja dla teoretyków liczb, matematyków lub informatyków pracujących w tej konkretnej dziedzinie.
Doc Brown
2
Tak, dla mnie to jest prawdziwe życie, ponieważ obecnie rozwijam się w dziedzinie obliczeniowej teorii liczb algebraicznych :)
1
2
Prawdopodobnie zmienię metodę getTFactor () tak, jak to opisujesz.
user949300,

Odpowiedzi:

17

„Czysty kod” nie jest celem samym w sobie, jest środkiem do celu. Głównym celem przefakturowania większych funkcji na mniejsze i oczyszczenia kodu na inne sposoby jest utrzymanie jego ewolucji i utrzymania.

Wybierając z podręcznika tak specyficzny algorytm matematyczny, jak test podstawowy „Miller-Rabin”, większość programistów nie chce go rozwijać. Ich standardowym celem jest prawidłowe przeniesienie go z pseudokodu podręcznika do języka programowania ich środowiska. W tym celu zaleciłbym jak najściślejsze zapoznanie się z podręcznikiem, co zwykle oznacza brak refaktoryzacji.

Jednak dla kogoś pracującego jako matematyk w tej dziedzinie, który próbuje pracować nad tym algorytmem i zmienić go lub ulepszyć, IMHO może podzielić tę funkcję na mniejsze, dobrze nazwane lub zastąpić grupę „magicznych liczb” nazwanymi stałymi pomagają wprowadzać zmiany w kodzie, tak jak w przypadku każdego innego rodzaju kodu.

Doktor Brown
źródło
1
Właśnie tego szukałem. Miałem problem z określeniem, kiedy zastosować praktyki czyszczenia kodu w dziedzinie, w której się rozwijam. Twoja odpowiedź zapewnia jasność, której szukałem. Dzięki!
1west