Quando e por que você deve usar o void (em vez de, por exemplo, bool / int)

30

Ocasionalmente, encontro métodos onde um desenvolvedor optou por retornar algo que não é crítico para a função. Quero dizer, ao olhar para o código, aparentemente funciona tão bem quanto um voide depois de um momento de reflexão, pergunto "Por quê?" Isso soa familiar?

Às vezes, eu concordo que na maioria das vezes é melhor retornar algo como a boolou int, em vez disso, basta fazer a void. Não tenho certeza, no geral, sobre os prós e contras.

Dependendo da situação, retornar um intpode informar o chamador da quantidade de linhas ou objetos afetados pelo método (por exemplo, 5 registros salvos no MSSQL). Se um método como "InsertSomething" retornar um valor booleano, posso ter o método projetado para retornar truese for bem-sucedido false. O chamador pode optar por agir ou não nessas informações.

Por outro lado,

  • Isso pode levar a um objetivo menos claro de uma chamada de método? A má codificação geralmente me obriga a verificar novamente o conteúdo do método. Se retornar algo, informará que o método é do tipo que você precisa fazer com o resultado retornado.
  • Outra questão seria, se a implementação do método é desconhecida para você, o que o desenvolvedor decidiu retornar que não é essencial para a função? Claro que você pode comentar.
  • O valor de retorno deve ser processado, quando o processamento puder ser finalizado no colchete de fechamento do método.
  • O que acontece sob o capô? O método chamado foi causado falsepor um erro gerado? Ou retornou falso devido ao resultado avaliado?

Quais são suas experiências com isso? Como você agiria sobre isso?

Independente
fonte
1
Uma função que retorna nula não é tecnicamente uma função. É apenas um método / procedimento. Retornar voidpelo menos permite que o desenvolvedor saiba que o valor de retorno do método é inconseqüente; faz uma ação, em vez de calcular um valor.
KChaloux

Respostas:

32

No caso de um valor de retorno booleano para indicar o sucesso ou falha de um método, prefiro o Tryparadigma -prefix usado em vários métodos .NET.

Por exemplo, um void InsertRow()método pode lançar uma exceção se já existir uma linha com a mesma chave. A questão é: é razoável supor que o chamador garanta que sua linha seja exclusiva antes de chamar InsertRow? Se a resposta for não, também forneceria um bool TryInsertRow()que retornará false se a linha já existir. Em outros casos, como erros de conectividade db, o TryInsertRow ainda pode gerar uma exceção, supondo que a manutenção da conectividade db seja de responsabilidade do chamador.

Plástico bolha
fonte
4
+1 por fazer a distinção entre lidar com falhas esperadas e inesperadas - minha própria resposta não enfatizou isso o suficiente.
Péter Török
Absolutamente um +1 para tentar inventar um princípio para os métodos TryXX, que aparentemente tornam o método try e o método principal mais fáceis de manter e mais fáceis de entender sua finalidade.
independente
+1 Bem dito. A chave para a melhor resposta para essa pergunta é que às vezes você deseja dar ao chamador a opção de um método assertivo que gerará uma exceção. Nesses casos, porém, a exceção não deve ser capturada e manipulada de forma falsa pelo método assertivo. O ponto é que o chamador é responsabilizado. Por outro lado, um paradigma Try (ou Mônada) deve capturar e manipular exceções, depois repassa um bool ou um Enum descritivo, se mais detalhes forem necessários. Observe que um chamador espera que um Try / Monad NUNCA gere uma exceção. É como um ponto de entrada.
Suamere 10/07/2015
Portanto, como é esperado que uma exceção nunca ocorra a partir de uma Try / Mônada, um bool não é descritivo o suficiente para informar ao chamador que uma conexão db falhou ou que uma solicitação http possui um dos muitos valores de retorno necessários para agir de maneira diferente, você pode usar um Enum em vez de um bool para o Try / Monad.
Suamere 10/07/2015
Se você tiver o TryInsertRow - retornando um bool - se, por exemplo, houver mais de uma restrição exclusiva no banco de dados - como você sabe exatamente qual foi o erro - e comunicar isso ao usuário? Um tipo de retorno mais rico deve ser usado contendo a mensagem de erro / código & sucesso bool?
Niico 13/08/16
28

O IMHO retornando um "código de status" decorre dos tempos históricos, antes que as exceções se tornassem comuns em idiomas de nível médio a superior, como o C #. Hoje em dia é melhor lançar uma exceção se algum erro inesperado impedir o êxito do seu método. Dessa forma, é certo que o erro não passa despercebido e o chamador pode lidar com isso conforme apropriado. Portanto, nesses casos, é perfeitamente bom que um método não retorne nada (ou seja void), em vez de um sinalizador de status booleano ou um código de status inteiro. (Obviamente, deve-se documentar quais exceções o método pode gerar e quando.)

Por outro lado, se for uma função no sentido estrito, isto é, realiza algum cálculo com base nos parâmetros de entrada, e espera-se retornar o resultado desse cálculo, o tipo de retorno é óbvio.

Há uma área cinza entre os dois, quando se pode decidir retornar algumas informações "extras" do método, se isso for considerado útil para os chamadores. Como seu exemplo sobre o número de linhas afetadas em uma inserção. Ou, para um método para colocar um elemento em uma coleção associativa, pode ser útil retornar o valor anterior associado a essa chave, se houver algum. Esses usos podem ser identificados analisando cuidadosamente os cenários de uso (conhecidos e previstos) de uma API.

Péter Török
fonte
4
+1 para exceções sobre o código de status. A exceção (trocadilho mal intencionado) a isso é o padrão TryXX bem usado.
21811 Andy Lowry
Éter Estou com você lá. TcF e não silenciar erros! Provavelmente, porém, a zona cinzenta. Sempre pode ser útil retornar algo. Linhas afetadas, o último ID inserido, um PID de execução de software, mas ainda acho que é mais fácil pensar "diabos, eu devolvo isso" durante o desenvolvimento. Então, um ano depois, ao estender, "o quê? Isso faz um 'int someThing = RunProcess (x)' o que acontece se não puder ser executado?"
independente
+1 de informações extras é o motivo pelo qual codifico métodos como este em linguagens de nível superior como C #. Isso facilita o uso de informações extras quando você precisar.
precisa saber é o seguinte
2
-1 Uau, suas próprias palavras são tão contraditórias e erradas. Você nunca deve usar exceções para controlar o fluxo ou a comunicação. Eles são infinitamente mais caros do que condicionais. Quando é o tempo "antes que as exceções se tornem comuns"? ... você quer dizer antes que os blocos try / catch se tornem comuns? Exceções são comuns desde sempre. "Lance uma exceção se ocorrer algum erro inesperado ..." Como você executa algo inesperado? Por que você capturou uma exceção e a lançou novamente? Isso é muito caro. Por que você diz "erro"? Erros e exceções são coisas diferentes.
Suamere 10/07/2015
1
E quem pode dizer que uma exceção lançada não passará despercebida? Nada força alguém a efetuar logon em um bloco catch, por que não fazer logon em um bloco "then"? Por que "documentar quais exceções o método pode lançar e quando"? Que tal se escrevermos código de auto-documentação e um método simplesmente retornar uma enumeração com os estados finais esperados desse método? Se uma possível exceção for evitável ao verificar o estado, elas deverão ser capturadas e manipuladas sem a emissão.
Suamere 10/07/2015
14

A resposta de Pedro cobre bem as exceções. Outra consideração aqui envolve a Separação de Consulta de Comando

O princípio do CQS diz que um método deve ser um comando ou uma consulta e não ambos. Os comandos nunca devem retornar um valor, modificar apenas o estado e as consultas devem retornar e não modificar o estado. Isso mantém a semântica muito clara e ajuda a tornar o código mais legível e sustentável.

Existem alguns casos em que violar o princípio CQS é uma boa ideia. Geralmente, isso envolve desempenho ou segurança de thread.

Andy Lowry
fonte
1
-1 Errado e Errado. Em primeiro lugar, o ponto principal do CQS está no Q. Um chamador deve ser capaz de obter cálculos ou chamadas externas através de algum serviço com estado, sem medo de alterar o estado desse serviço. Além disso, embora o CQS seja um princípio útil a ser seguido livremente, ele é seguido apenas estritamente em projetos específicos. Por fim, mesmo no CQS estrito, nada diz que um comando não pode retornar um valor; ele diz que não deve calcular ou chamar cálculos externos e retornar dados . C ou Q podem se sentir à vontade para retornar seu estado final, se isso for útil para o chamador.
Suamere 10/07/2015
Ah, mas o número de linhas afetadas por um comando facilita a criação de novos comandos a partir dos antigos. Fonte: anos e anos de experiência.
Joshua Joshua
@ Joshua: Da mesma forma, "adicione um novo registro e retorne um ID (para algumas estruturas, um número de linha) que foi gerado durante o add" pode ser usado para fins que, de outra forma, não poderiam ser alcançados com eficiência.
Supercat
Você não precisa retornar as linhas afetadas. O comando deve saber quantos serão afetados. Se for qualquer coisa menos esse #, deve reverter e lançar uma exceção, pois algo estranho aconteceu. Portanto, essas são as informações que o chamador realmente não se importa com elas (a menos que o usuário precise saber o número verdadeiro e você não possa saber dos dados fornecidos). Ainda existem áreas cinzentas como IDs gerados pelo banco de dados, pilhas / filas nas quais a obtenção de dados altera seu estado, mas eu gosto de criar um "CommandQuery" nesses cenários para que ninguém tente fazer algo "estranho".
Daniel Lorenz
3

Seja qual for o caminho, você vai caminhar com isso. Não há valores de retorno para uso futuro (por exemplo, as funções sempre retornam verdadeiras), é um terrível desperdício de esforço e não torna o código mais claro de se ler. Quando você tem um valor de retorno, deve sempre usá-lo e, para poder usá-lo, deve ter pelo menos 2 valores de retorno possíveis.

refro
fonte
+1 Isso não responde à pergunta, mas é definitivamente uma informação correta. Ao tomar uma decisão, a decisão deve ser baseada em uma necessidade. Se os chamadores não acharem os dados de retorno úteis, isso não deve ser feito. E ninguém encontra utilidade em retornar 100% do tempo verdadeiro para "provas futuras". É verdade que, se o "futuro" for como ... daqui a alguns dias e houver uma tarefa para isso, é uma história diferente, lol.
Suamere 10/07/2015
1

Eu costumava escrever muitas funções nulas. Mas desde que eu iniciei todo o método de encadeamento de crack, comecei a retornar isso em vez de nulo - é melhor deixar alguém tirar proveito do retorno, porque você não pode agachar com nulo. E se eles não querem fazer nada com isso, eles podem simplesmente ignorá-lo.

Wyatt Barnett
fonte
1
O encadeamento de métodos pode tornar a herança complicada e difícil. Eu não faria encadeamento de métodos, a menos que você tenha um bom motivo para fazê-lo, além de não querer "anular" seus métodos.
KallDrexx 13/04
Verdade. Mas a herança pode ser complicada e difícil de usar.
Wyatt Barnett
Olhando para um código desconhecido que o retorno seja qual for (mencionado todo o objeto) presta muita atenção apenas verificar o interior do fluxo e métodos fora ..
Independent
Suponho que você não entrou em Rubyalgum momento? Foi isso que me apresentou ao encadeamento de métodos.
KChaloux
Um problema que tenho com o encadeamento de métodos é que fica menos claro se uma instrução como return Thing.Change1().Change2();está esperando alterar Thinge retornar uma referência ao original ou se deve retornar uma referência a uma coisa nova que difere da original, deixando o original intocado.
Supercat 23/08