Problemas para entender como é o código limpo na vida real

10

Atualmente, estou lendo e trabalhando em "Código Limpo: Um Manual de Artesanato em Software Ágil", de Robert C. Martin. O autor fala sobre como uma função deve fazer apenas uma coisa e, portanto, é relativamente curta. Martin escreve especificamente:

Isso implica que os blocos dentro de instruções if, else, while e etc. devem ter uma linha. Provavelmente essa linha deve ser uma chamada de função. Isso não apenas mantém a função anexa pequena, mas também agrega valor documental, porque a função chamada dentro do bloco pode ter um nome bem descritivo.

Isso também implica que as funções não devem ser grandes o suficiente para conter estruturas aninhadas. Portanto, o nível de recuo de uma função não deve ser maior que um ou dois. Isso, é claro, facilita as funções de ler e entender

Isso faz sentido, mas parece entrar em conflito com exemplos do que eu vejo como código limpo. Pegue o seguinte método, por exemplo:

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

Este código foi retirado do repositório de código-fonte do Apache Commons em: https://github.com/apache/commons-math/blob/master/src/main/java/org/apache/commons/math4/primes/SmallPrimes.java

O método parece muito legível para mim. Para implementações de algoritmos como este (implementação do Teste Probabilístico de Miller-Rabin), é adequado manter o código como está e ainda o considerar 'limpo', conforme definido no livro? Ou mesmo algo já tão legível como esse se beneficiaria da extração de métodos para tornar o algoritmo essencialmente uma série que chama funções que "fazem apenas uma coisa"? Um exemplo rápido de uma extração de método pode ser mover as três primeiras instruções if para uma função como:

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: Essa pergunta é diferente da duplicata possível (embora também seja útil para mim), porque estou tentando determinar se estou entendendo a intenção do autor do Código Limpo e estou fornecendo um exemplo específico para tornar as coisas mais fáceis. concreto.

1west
fonte
3
até onde eu posso ver essa função faz apenas uma coisa ... ela não tem efeitos colaterais que eu possa ver. O que faz você pensar que pode não estar limpo? Qual parte dessa função você consideraria digna de ser colocada em outra função para torná-la mais limpa?
Newtopian 11/08/19
14
O título da sua pergunta pede uma situação da "vida real" e, em seguida, seu exemplo me parece um exemplo perfeito de uma função que não é da vida real (pelo menos para 99,9% dos desenvolvedores de aplicativos ou da web). Pode ser uma função da vida real para teóricos dos números, matemáticos ou cientistas da computação que trabalham nesse campo específico, é claro.
Doc Brown
2
Sim, para mim isso é a vida real como eu atualmente desenvolvem no campo da teoria dos números algébricos computacional :)
1west
2
Eu provavelmente refatoraria o getTFactor () como você descreve.
user949300

Respostas:

17

"Código limpo" não é um fim em si, é um meio para um fim. O principal objetivo de refatorar funções maiores para funções menores e limpar o código de outras maneiras é manter o código evolutivo e sustentável.

Ao escolher um algoritmo matemático tão específico como o teste principal "Miller-Rabin" de um livro, a maioria dos programadores não deseja evoluí-lo. Seu objetivo padrão é transferi-lo do pseudo-código do livro de texto corretamente para a linguagem de programação de seu ambiente. Para esse propósito, eu recomendaria seguir o livro o mais próximo possível, o que normalmente significa não refatorar.

No entanto, para alguém que trabalha como matemático nesse campo que está tentando trabalhar nesse algoritmo e alterá-lo ou aprimorá-lo, o IMHO dividindo essa função em menores e bem nomeados, ou substituindo o grupo de "números mágicos" por constantes nomeadas, pode ajuda a facilitar as alterações no código, como em qualquer outro tipo de código.

Doc Brown
fonte
1
Era exatamente isso que eu estava procurando. Eu estava com problemas para determinar quando usar as práticas de código limpo no campo em que estou desenvolvendo. Sua resposta fornece a clareza que eu estava procurando. Obrigado!
1west