Apenas um Ponto de Saída num Método

Ainda não consegui me decidir sobre se sigo essa “melhor prática”: um método deve ter apenas um ponto de saída. Ou seja, apenas um return no fim do método, ou nenhum return.

Estive pesquisando na net e vi que o assunto é controverso. Inclusive, no site do PMD*, OnlyOneReturnPoint está na página Controversial (http://pmd.sourceforge.net/rules/controversial.html), junto com algumas outras regras da ferramenta.

Eu, particularmente, tentei. Gostei da idéia de uma saída apenas no final para facilitar a depuração, mas os flags que apareceram no meio do método bateram de frente com meu estilo mais enxuto.

Tem alguém que trabalha dessa forma? Gostaria de ouvir de quem trabalha com OnlyOneReturnPoint quais os benefícios que tiveram.

*PMD é uma ferramenta para varredura de código-fonte em busca de potenciais problemas.

Nao eh pra ser uma regra aplicada sistematicamente. :wink:

Claro, precisamos ser críticos, queimar neurônios!

Quero justamente decidir onde é que eu uso essa regra, ao invés de simplesmente abandoná-la por completo.

Hmm… pensando aqui, se vc forcar a regra de que metodos tem que ser curtos e fazer apenas uma coisa (e faze-las bem)… vc realmente precisa se preocupar com isso?

Ideia: se o metodo inteiro nao cabe numa tela 80x25 caracteres, ele provavelmente ta fazendo coisa demais.

[quote=cv]
Ideia: se o metodo inteiro nao cabe numa tela 80x25 caracteres, ele provavelmente ta fazendo coisa demais.[/quote]

perfeito. o pmd pega isso tambem.

Eu já trabalhei assim, mas achei que não há grandes benefícios com essa forma.

O primeiro dos problemas com essa abordagem está nos já citados flags. Normalmente quem adota essa prática também não curte breaks em laços, o que aumenta ainda mais o número de variáveis boolean. Essas variáveis são sujeitas a erro e várias vezes você tem que fazer ginástica para que funcionem direto que simplesmente não seriam feitas se você não usar essa técnica.

Segundo, quando você faz um código de guarda, logo no início do método, fica bem claro para o programador que caso a condição falhe o método não vai continuar. Ele não precisa analisar todo o código para chegar a essa conclusão, o que por si só é ótimo.

Finalmente, não vejo onde pode haver ganhos em terminar o método só no final. Performance? Não. Clareza? Certamente não. Purismo? Desde quando purismo por purismo é um ganho?

Argumentos do pessoal que requer que haja apenas um ponto de saída:

  • Se precisar pôr um “log” indicando a saída é necessário pô-lo apenas em um ponto
    (Esse argumento não vale se você usar algum recurso de AOP).
  • Se houver apenas um ponto de saída o tratamento de erros etc. fica centralizado em apenas um ponto
    (Para que serve o “finally”, então?)
    Acho que haver apenas um ponto de saída deixa seu código desajeitado. Quando requerem que eu faça isso meu código fica infestado de “labeled blocks”, normalmente usados como:
saida: {
   if (blablabla) break saida;
   blablabla;
   if (blablabla) break saida;
   blebleble;
   etc...
}

CV, não estou preocupado. Só trocando idéia, experiência.

Do seu comentário, pude pensar em duas coisas:

  1. O importante mesmo é ter métodos curtos, ou seja bem definidos;
    ou,
  2. se você tiver métodos curtos, acabará por ter apenas um ponto de saída.

Qual das duas coisas você quis dizer?

Paulo, acho até que o PMD é "bonzinho". Por padrão, ele só começa a reclamar depois que o método tiver mais de 100 linhas. E em 100 linhas é possível ter pelo menos uns 30 pontos de saída!!!

Um código aqui para exemplo, assim a conversa fica melhor:

/*
         * Retorna o nome do ícone,
         * de acordo com a sua função no menu do sistema.
         *
         * @param value O nó para o qual se quer descobrir o seu ícone.
         */
        private String getIconName(final Object value, final boolean expanded) {
            
            if (value instanceof DefaultMutableTreeNode) {
                final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                
                if (node.isLeaf()) {
                    return "leaf.png";
                }
                
                if(node.getLevel() &gt 1) {
                    
                    if(expanded) {
                        return "tree_opened.png";                        
                    }

                    return "tree_closed.png";
                }
                
                for (Modulo modulo : Modulo.getModulos()) {
                    
                    if (node.toString().equals(modulo.getModulo())) {
                        return modulo.getIcone22();
                    }
                }
            }
            
            // Nunca deve chegar nesse ponto.
            return "setad.png";
        }

Tratá-se de um método de uma classe TreeCellRenderer, cujo o objetivo é verificar qual a função de um nó na árvore (na verdade um menu de funções) e retornar o nome do ícone adeqüado.

Se fosse aplicar a regra de apenas um ponto de saída, ficaria assim:

private String getIconName(final Object value, final boolean expanded) {
            
            String iconName = "setad.png";
            boolean isLeaf  = false;
            
            if (value instanceof DefaultMutableTreeNode) {
                final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                
                // É folha (opção)?
                if (node.isLeaf()) {
                    iconName = "leaf.png";
                    isLeaf = true;
                }
                
                if((node.getLevel() &gt 1) && (!isLeaf)) {
                    
                    if(expanded) {
                        iconName = "tree_opened.png";
                        
                    }
                    iconName = "tree_closed.png";
                }
                
                for (Modulo modulo : Modulo.getModulos()) {
                    
                    if ((node.toString().equals(modulo.getModulo())) && (!isLeaf)){
                        iconName = modulo.getIcone22();
                        break;
                    }
                }
            }
            
            assert iconName != "setad.png";
            return iconName;            
        }

Um assert cai bem melhor no fim do método do que um comentário tosco do tipo "// Não deve nunca chegar aqui".

Mesmo porque acabei descobrindo que o método retornava setad.png. :-o :lol:

Só que o código ficou poluído…

ViniGodoy, thingol, vou ter que concordar com vocês.

Enquanto vocês respondiam, estava caçando um método para mexer e analisar as mudanças e o que vocês escreveram aconteceu mesmo.

Apareceu um break, uma variável, uma flag e os pontos de decisões ficaram mais complexos tanto de ler, quanto de escrever.

               if((node.getLevel() &gt 1) && (!isLeaf)) {
                     
                     if(expanded) {
                         iconName = "tree_opened.png";
                         
                     }
                     iconName = "tree_closed.png";
                 }
                    if ((node.toString().equals(modulo.getModulo())) && (!isLeaf)){
                         iconName = modulo.getIcone22();
                         break;
                     }

Mas foi graças a essa brincadeira que eu pude colocar o assert no código, e de quebra descobrir que o método retornava "setad.png"

Putz, eu já discuti muito isso, e o que eu penso está no meu blog:

http://felipecoury.wordpress.com/2006/08/15/coding-best-practices-thinking-about-it/

Abraços!!!

[quote=cv]Hmm… pensando aqui, se vc forcar a regra de que metodos tem que ser curtos e fazer apenas uma coisa (e faze-las bem)… vc realmente precisa se preocupar com isso?
[/quote]

Acho que realmente não. Pois dificilmente ele terá mais que um ponto de saída.

Colocando algo mais na resposta do CV, se vc tiver métodos, “mais do que curtos”, com responsábilidades bem definidas! Agora sim vc dificilmente tera mais que um ponto de saída no seu método!

Mas ainda podemos pensar:

Poxa! Mais eu posso ter um método curto e com uma responsábilidade bem definida, só que dentro deste método eu posso ter um IF ou SWITCH, neste caso faço para eliminar os vários pontos de saída ou flags para controlar a saída? Neste casos temos que invocar todos os espiritos O.O e tentar trocar esses IFs e SWITCHs por “POLIMOSFISMO”. Acho eu que ai estaremos proximos da O.O!!

Aguardo as opiniões de vcs!!!

[]s

Detalhe, métodos curtos não implicam necessariamente em um único ponto de saída. Eu costumo escrever métodos de 6 ou 7 linhas que tem uns dois ou três pontos de saída. Em geral algumas guard clauses no começo e um só retorno “normal”.

O maior argumento que os defensores de um só ponto de saída fazem é que fica mais fácil de se entender (reason about) um método. Por isso, IMO, que métodos curtos aliviam o problema. Um método de 5 linhas com uma responsabilidade bem definida (chamando outros métodos também com responsabilidade bem definida) tende a ser tão legível que é irrelevante se tem 3 returns.

Queria saber de onde veio a idéia de que um só ponto de saída deixa o código mais legível. Talvez, num método longuíssimo… mas ainda assim, tenho minhas dúvidas.

Quando você enxerga um ponto de saída no código, não precisa entender mais nada depois dele. Você tem absoluta certeza que se aquela condição for satisfeita, o código debaixo do ponto de saída não vai rodar.

Agora, se simplesmente uma condição é testada, você tem as seguintes consequencias:

Você tem um if, que testa a condição e todo código que não deve rodar estará endentado, gerando poluição visual. Se você tem 3 ou 4 condições de saída, terá 3 ou 4 níveis de endentação. Em segundo lugar, você é obrigado a conferir o código até o final. Pois nada garante que após o if, não haverá um ou outro comando a ser executado. Por final, tem a dor de cabeça de fechar todas as chaves dos ifs que você abriu.

Em muitas situações, o código com esse flag ainda fica mais difícil de ser refatorado.

Finalmente, há ainda o problema de longo prazo. Alguém (muitas vezes você mesmo dali a alguns meses) inadvertidamente pode colocar código fora do if. Isso quebra totalmente sua condição e pode gerar um objeto com um estado inválido. No caso dos códigos de guarda, esse problema simplesmente não ocorre, a menos que o if do código de guarda seja alterado.

Ontem estava revendo um projeto e devido a herança da cultura C++, “eu acho”, o projeto estava cheio de construtores por cópia. Eu então cheguei a conclusão que este poderia ser um mal cheiro que me indica este obj deve ser clonavel.

Então hoje pela manha eu cheio de curiosidade vim até meu livro de refatoração “Fowler” e olha o que eu encontro: “Justamente sobre pontos únicos de retornos”

Não tenho certeza se posso citar Folwer aqui no Forum, nem se eu posso citar parte do seu livro! De qualquer forma estou fazendo, pois acho que essas citações são otimas contribuições!

Acho que originalmente a preocupação do pessoal da programação estruturada não era com a legibilidade, mas em provar formalmente a correção de programas. E é mais fácil fazer isso se você pode assumir que o controle flui do começo da rotina até o fim dela.

De qualquer maneira, você pode transformar mecanicamente um código com vários pontos de saída assim:

Original

     ...
     return x;
     ...
     return y;
     ...
     return z;
     ...

Transformação Mecânica:

    MyReturnType ret = null;
    exit: {
        ret = x;
        break exit; 
        ...
        ret = y;
        break exit; 
        ...
        ret = z;
        break exit; 
        ...
    }
    return ret;

Assim eu tenho os benefícios de poder sair sem pôr um monte de flags e ifs, e os benefícios de ter apenas um ponto de saída no método. O único problema é se o zé que for manter o código não souber o que é um “labeled statement”…

Também não gosto de labeled statements, via de regra procuro evita-los.

Muita gente não conhece e procuro deixar meu código o mais simples possível.

Mas não deixa de ser uma técnica interessante, lembra bastante o “on error goto” do Visual Basic.

Ainda sim, prefiro o return direto. Não dá margem para alguém colocar mais código depois do return.

Não havia pensado nos labeled statements, thingol. Mas daí corre-se o risco de cair numa outra polêmica, já que alguns simplesmente abominam labels, comparando-os com os goto’s.

Está me parecendo que essa é uma questão de estilo. Pode-se ter argumentos tanto a favor de usar, quanto o de não usar só um ponto de retorno no método.

E mesmo eu preferindo não usar essa regra, pelo menos na maioria dos casos, acabei vendo vantagens em usá-la. No código que postei antes, tinha certeza que o método não chegava até o fim - e tinha um comentário para “assegurar” que não chegasse mesmo.

Mas, eu estava errado. Com o código refatorado e com o assert lá no fim, eu pude realmente assegurar que o método fazia o que estava pensando.

[quote=brunohansen]
Não tenho certeza se posso citar Folwer aqui no Forum, nem se eu posso citar parte do seu livro! De qualquer forma estou fazendo, pois acho que essas citações são otimas contribuições![/quote]

Citações são excelentes. Dá base para os argumentos, ajuda a sair do “achismo”. E claro, você se livra do plágio. 8)