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.