Lance / fazer-não-lançar uma exceção com base em um parâmetro - por que isso não é uma boa ideia?

StackOverflow https://stackoverflow.com/questions/624647

Pergunta

Eu estava cavando ao redor no MSDN e encontrou este artigo , que teve um pouco interessante do conselho:. não tem membros públicos que pode jogar ou não jogar exceções com base em alguma opção

Por exemplo:

Uri ParseUri(string uriValue, bool throwOnError)

Agora é claro que eu posso ver que em 99% dos casos, isso seria horrível, mas é o seu uso ocasional justificado?

Um caso de eu tê-lo visto utilizado é com um parâmetro "AllowEmpty" ao acessar dados no banco de dados ou um arquivo de configuração. Por exemplo:

object LoadConfigSetting(string key, bool allowEmpty);

Neste caso, a alternativa seria nulo retorno. Mas, em seguida, o código de chamada seria repleta de verificação de referências nulas. (E o método também impediria a capacidade de realmente permitir nulo como um valor especificamente configurável, se você fosse tão inclinado).

Quais são seus pensamentos? Por que isso seria um grande problema?

Foi útil?

Solução

Eu acho que é definitivamente uma má idéia ter um lance / nenhuma decisão lance ser baseado fora de um booleano. Nomeadamente porque requer que os desenvolvedores a olhar para um pedaço de código para ter um conhecimento funcional do API para determinar o que os meios de boolean. Isso é ruim, por si própria, mas quando se muda o erro subjacente manuseá-lo pode tornar muito fácil para os desenvolvedores a cometer erros durante a leitura de código.

Seria muito melhor e mais legível para ter 2 APIs neste caso.

Uri ParseUriOrThrow(string value);

bool TryParseUri(string value, out Uri uri);

Neste caso é 100% claro o que essas APIs fazer.

Artigo sobre o porquê booleans são ruins como parâmetros: http : //blogs.msdn.com/jaredpar/archive/2007/01/23/boolean-parameters.aspx

Outras dicas

Geralmente é melhor para escolher mecanismo de manipulação de um erro e ficar com ela de forma consistente. Permitindo que este tipo de código flip-flop não pode realmente melhorar a vida dos desenvolvedores.

No exemplo acima, o que acontece se a análise falha e throwOnError é falsa? Agora o usuário tem que adivinhar se NULL se vai ser devolvido, ou Deus sabe ...

É verdade que há um debate em curso entre as excepções e valores de retorno como o melhor erro método de manipulação, mas estou certo de que há um consenso sobre ser consistente e furando com qualquer escolha que você faz. A API não pode surpreender seus usuários e tratamento de erros deve ser parte da interface, e ser tão claramente definida como a interface.

É uma espécie de desagradável do ponto de vista readabilty. Os desenvolvedores tendem a esperar todos os métodos para lançar uma exceção, e se eles querem ignorar a exceção, eles vão pegá-lo eles mesmos. Com a abordagem 'boolean flag', cada necessidades método para implementar esta inibidora exceção semântica.

No entanto, acho que o artigo do MSDN é estritamente referindo-se a bandeiras 'throwOnError'. Nestes casos, quer o erro é ignorado dentro do próprio (mau, como é oculto) método ou algum tipo de objeto nulo / erro é retornado (ruim, porque você não está usando exceções para lidar com o erro, o que é inconsistente e erro em si -prone).

Considerando que o seu exemplo parece-me muito bem. Uma exceção indica uma falha do método para executar o seu dever - não há nenhum valor de retorno. No entanto, o flag 'allowEmpty' altera a semântica do método - então o que teria sido uma exceção ( 'valor vazio') agora é esperado e legal. Além disso, se você estiveste uma exceção, você não iria facilmente ser capaz de retornar os dados de configuração. Assim, parece OK neste caso.

Em qualquer API pública é realmente uma má idéia ter duas maneiras de verificar para uma condição de falha, porque então ela se torna não-óbvio o que vai acontecer se o erro ocorre. Basta olhar para o código não vai ajudar. Você tem que entender a semântica do parâmetro flag (e impede nada que ele seja uma expressão).

Se a verificação de nulo não é uma opção, e se eu precisar para recuperar essa falha específica, eu prefiro criar uma exceção específica para que eu possa pegá-lo mais tarde e manipulá-lo adequadamente. Em qualquer outro caso, eu lançar uma exceção geral.

Outro exemplo em linha com isso poderia ser definido de métodos TryParse em alguns dos tipos de valor

bool DateTime.TryParse(string text, out DateTime)

Ter um parâmetro donTThrowException derrota o ponto de exceções (em qualquer idioma). Se o código de chamada quer ter:

public static void Main()
{
        FileStream myFile = File.Open("NonExistent.txt", FileMode.Open, FileAccess.Read);
}

Eles são bem-vindos para (C # nem sequer ter verificado exceções). Em Java a mesma coisa que ser feito com:

public static void main(String[] args) throws FileNotFoundException
{
        FileInputStream fs = new FileInputStream("NonExistent.txt");
}

De qualquer maneira, é trabalho do chamador para decidir como lidar com (ou não) a exceção, não a do chamado.

No ligada ao artigo há uma nota que exceções não devem ser usados ??para o fluxo de controle - que parece estar implícito nas exemplo questsions. Exceções devem refletir fracasso nível de método. Para se ter uma assinatura que é OK para lançar um erro parece que o design não é pensado.

Jeffrey Richter livro CLR via C # pontos fora - "você deve lançar uma exceção quando o método não pode completar sua tarefa como indicado pelo seu nome".

Seu livro também apontou um erro muito comum. As pessoas tendem a escrever código para pegar tudo (suas palavras "um erro onipresente de desenvolvedores que não foram devidamente treinados sobre o uso adequado de exceções tendem a usar blocos catch muitas vezes e de forma inadequada. Quando você capturar uma exceção, você está afirmando que você esperava essa exceção, você entender por que ela ocorreu, e você sabe como lidar com ele. ")

Isso me fez tentar código para exceções que posso esperar e pode lidar na minha lógica caso contrário ele deve ser um erro.

validar os seus argumentos e evitar que as exceções, e só pegar o que você pode manipular.

Eu afirmam que muitas vezes é útil ter um parâmetro que indica se um fracasso deve causar uma exceção ou simplesmente retornar uma indicação de erro, uma vez que tais parâmetros um pode ser facilmente transmitida de uma rotina externa para um um interior. Considere algo como:

Byte [] ReadPacket (bool DontThrowIfNone) // documentado como retornando null se nenhum { int len ??= ReadByte (DontThrowIfNone); // documentada como retornando -1 se nada if (len

Se algo como um TimeoutException durante a leitura dos dados deve causar uma exceção, tal exceção deve ser jogado dentro do ReadByte () ou ReadMultiBytesbytes (). Se, no entanto, uma tal falta de dados deve ser considerado normal, então a rotina ReadByte () ou ReadMultiBytesbytes () não deve jogar uma excepção. Se alguém simplesmente usado o padrão não / tentar, uma das rotinas ReadPacket e TryReadPacket precisaria ter código quase idêntico, mas com um usando Ler * métodos e outro usando TryRead * métodos. Icky.

Pode ser melhor usar uma enumeração em vez de um booleano.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top