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.
źródło
Odpowiedzi:
„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.
źródło