Função retornando verdadeiro / falso vs. nulo ao obter êxito e lançando uma exceção ao falhar


22

Estou construindo uma API, uma função que carrega um arquivo. Esta função não retornará nada / nulo se o arquivo tiver sido carregado corretamente e lança uma exceção quando houver algum problema.

Por que uma exceção e não apenas falsa? Como dentro de uma exceção, posso especificar o motivo da falha (sem conexão, nome do arquivo ausente, senha incorreta, descrição do arquivo ausente, etc.). Eu queria criar uma exceção personalizada (com alguma enumeração para ajudar o usuário da API a lidar com todos os erros).

Essa é uma boa prática ou é melhor retornar um objeto (com um booleano interno, uma mensagem de erro opcional e a enumeração para erros)?



59
Verdadeiro e falso vão bem juntos. Vazio e exceção vão bem juntos. Verdadeiro e exceção andam juntos como gatos e impostos. Algum dia eu posso estar lendo seu código. Por favor, não faça isso comigo.
Candied_orange 12/09/16

4
Eu gosto bastante de padrões em que um objeto é retornado que encapsula sucesso ou falha. Por exemplo, Rust possui Result<T, E>onde Testá o resultado se for bem-sucedido e Eo erro se não for bem-sucedido. Lançar uma exceção (pode ser - não é tão certo em Java) pode ser caro, pois envolve desenrolar a pilha de chamadas, mas a criação de um objeto Exception é barata. Obviamente, atenha-se aos padrões estabelecidos do idioma que você está usando, mas não combine booleanos e exceções como essa.
Dan Pantry

4
Tenha cuidado aqui. Você pode estar descendo uma toca de coelho. Quando seu programa é capaz de lidar com um problema, geralmente é mais fácil apenas detectá-lo e lidar com ele, verificando as pré-condições. Você raramente capturava uma exceção, examinava seus dados no código e tentava novamente depois de corrigir o problema. Como tal, raramente é valioso ter muitos tipos de exceção. Se você está tentando registrar problemas, isso faz muito mais sentido, mas não espere escrever um monte de blocos catch com quantidades significativas de código.
Jpmc26 12/09/16

4
@DanPantry Em Java, a pilha de chamadas é verificada ao criar a exceção, não ao lançá-la. fillInStackTrace();é chamado no super construtor, na classeThrowable
Simon Forsberg 12/12

Respostas:


35

Lançar uma exceção é simplesmente uma maneira adicional de fazer um método retornar um valor. O chamador pode verificar um valor de retorno tão facilmente quanto capturar uma exceção e verificar isso. Portanto, decidir entre throwe returnrequer outros critérios.

Lançar exceções geralmente deve ser evitado se colocar em risco a eficiência do seu programa (construir um objeto de exceção e desenrolar a pilha de chamadas é muito mais trabalhoso para o computador do que apenas colocar um valor nele). Mas se o objetivo do seu método é fazer upload de um arquivo, o gargalo sempre será a E / S da rede e do sistema de arquivos, portanto, é inútil otimizar o método de retorno.

Também é uma péssima idéia lançar exceções para o que deve ser um fluxo de controle simples (por exemplo, quando uma pesquisa é bem-sucedida ao encontrar seu valor), porque isso viola as expectativas dos usuários da API. Mas um método que não cumpre seu objetivo é um caso excepcional (ou pelo menos deveria ser), portanto não vejo razão para não lançar uma exceção. E se você fizer isso, poderá torná-lo uma exceção personalizada e mais informativa (mas é uma boa ideia transformá-lo em uma subclasse de uma exceção padrão e mais geral, como IOException).


7
Se for um resultado esperado para uma solicitação de upload de arquivo falhar, eu ficaria surpreso com uma exceção lançada na minha cara (especialmente se houver um valor de retorno na assinatura do método).
Hoffmale # /

1
Observe que o motivo para estender uma exceção padrão é que muitos (provavelmente até a maioria) dos chamadores não escrevem código para tentar solucionar o problema. Como resultado, a maioria dos chamadores deseja um simples "capturar esse grande grupo de exceções", sem precisar listá-las explicitamente.
precisa saber é o seguinte

4
@gbjbaanb É por isso que as exceções devem ser usadas quando houver realmente uma situação não corrigível. Quando você está tentando abrir um arquivo e o arquivo não pode ser lido, este é um bom caso para uma exceção. Quando você verifica a existência do arquivo, é necessário usar um booleano, pois é esperado que o arquivo não esteja lá.
Malcolm

21
Kilian está dizendo, no slogan recursivo "exceções são para situações excepcionais", situações excepcionais são quando a função não pode cumprir seu objetivo. Se a função deve ler um arquivo, e o arquivo não pode ser lido, isso é excepcional . Se a função deve informar se existe ou não um arquivo (não importa o TOCTOU em potencial), o arquivo não existente não é excepcional, porque a função ainda pode fazer o que deveria. Se a função deve afirmar que existe um arquivo, ela não existe é excepcional, porque é isso que "afirmar" significa.
Steve Jessop

10
Observe que a única diferença entre uma função que informa se existe um arquivo e uma função que afirma que existe um arquivo é se o caso do arquivo não existente é ou não excepcional. Às vezes, as pessoas falam como se fosse uma propriedade da condição de erro "seja excepcional" ou não. Não é uma propriedade da condição de erro, é uma propriedade combinada da condição de erro e o objetivo da função em que ocorre. Caso contrário, nunca capturaríamos exceções.
Steve Jessop

31

Não há absolutamente nenhuma razão para retornar truecom sucesso, se você não retornar false. Como deve ser o código do cliente?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

Nesse caso, o chamador precisa de um bloco try-catch de qualquer maneira, mas pode escrever melhor:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Portanto, o truevalor de retorno é completamente irrelevante para o chamador. Então, apenas mantenha o método void.


Em geral, existem três maneiras de projetar modos de falha.

  1. Retornar verdadeiro / falso
  2. Usar void, lançar (marcada) exceção
  3. retornar um objeto de resultado intermediário .

Retorno true/false

Isso é usado em algumas APIs mais antigas, principalmente no estilo c. As desvantagens são óbvias, você não tem idéia do que deu errado. O PHP faz isso com bastante frequência, levando a códigos como este:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

Em contextos multithread, isso é ainda pior.

Lançar exceções (marcadas)

Essa é uma boa maneira de fazer isso. Em alguns pontos, você pode esperar falhas. Java faz isso com soquetes. A suposição básica é que uma chamada deve ser bem-sucedida, mas todos sabem que certas operações podem falhar. Conexões de soquete estão entre elas. Portanto, o chamador é forçado a lidar com a falha. é um design agradável, porque garante que o chamador realmente lide com a falha e oferece ao chamador uma maneira elegante de lidar com a falha.

Retornar objeto de resultado

Essa é outra ótima maneira de lidar com isso. É frequentemente usado para analisar ou simplesmente coisas que precisam ser validadas.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Lógica limpa e agradável para o chamador também.

Há um pouco de debate sobre quando usar o segundo caso e quando usar o terceiro. Algumas pessoas acreditam que as exceções devem ser excepcionais e que você não deve projetar com a possibilidade de exceções em mente, e sempre usará as terceiras opções. Essa multa. Como verificamos exceções em Java, não vejo razão para não usá-las. Uso expetions verificadas quando a suposição básica é que a chamada deve ser bem- sucedida (como usar um soquete), mas a falha é possível e uso a terceira opção quando não está claro se a chamada deve ser bem-sucedida (como validar dados). Mas existem opiniões diferentes sobre isso.

No seu caso, eu usaria void+ Exception. Você espera que o upload do arquivo seja bem-sucedido e, quando isso não acontecer, será excepcional. Mas o chamador é forçado a lidar com esse modo de falha e você pode retornar uma exceção que descreva corretamente que tipo de erro ocorreu.


5

Isso realmente se resume a se uma falha é excepcional ou esperada .

Se um erro não é algo que você geralmente espera, é razoavelmente provável que o usuário da API chame seu método sem nenhum tratamento especial. Lançar uma exceção permite que a bolha borbulhe para um local onde é notada.

Se, por outro lado, os erros são comuns, você deve otimizar para o desenvolvedor que os está verificando e as try-catchcláusulas são um pouco mais complicadas do que uma série if/elifou switch.

Sempre crie uma API na mente de alguém que a esteja usando.


1
No meu caso, como alguém apontou, deve ser uma falha excepcional que o usuário da API não possa carregar o arquivo.
Accollativo 13/09/16

4

Não retorne um valor booleano se você nunca pretende retornar false. Basta fazer o método voide documentar que ele lançará uma exceção ( IOExceptioné adequado) em caso de falha.

A razão para isso é que, se você retornar um booleano, o usuário da sua API poderá concluir que pode fazer isso:

if (fileUpload(file) == false) {
    // Handle failure.
}

Isso não vai funcionar, é claro; isto é, há uma incongruência entre o contrato do seu método e o comportamento dele. Se você lançar uma exceção verificada, o usuário do método precisará lidar com a falha:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}

3

Se seu método tiver um valor de retorno, lançar uma exceção poderá surpreender seus usuários. Se eu ver um método retornar um valor booleano, estou bastante convencido de que retornará true se for bem-sucedido e false se não for, e estruturarei meu código usando uma cláusula if-else. Se a sua exceção for baseada em uma enumeração, você também pode retornar um valor de enumeração, semelhante ao windows HResults, e manter um valor de enumeração para quando o método for bem-sucedido.

Também é irritante ter um método lançar exceção quando não é a etapa final de um procedimento. Ter que escrever um fluxo de controle try-catch e desviar para o bloco catch é uma boa receita para espaguete e deve ser evitado.

Se você estiver avançando com exceções, tente retornar nulo, os usuários descobrirão que terão êxito se nenhuma exceção for lançada e nenhum tentará usar uma cláusula if-else para desviar o fluxo de controle.


1
A enumeração foi apenas uma ideia para ajudar o usuário da API a lidar com diferentes tipos de erros sem analisar a mensagem de erro. Então, do seu ponto de vista, no meu caso, é melhor retornar a enumeração e adicionar uma enumeração para "OK".
Accollativo 12/09/16
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.