Convenções para exceções ou códigos de erro

118

Ontem eu estava tendo um debate acalorado com um colega de trabalho sobre qual seria o método de relatório de erro preferido. Estamos discutindo principalmente o uso de exceções ou códigos de erro para relatar erros entre camadas ou módulos de aplicativos.

Que regras você usa para decidir se lança exceções ou retorna códigos de erro para relatórios de erros?

Jorge Ferreira
fonte

Respostas:

81

Em coisas de alto nível, exceções; em coisas de baixo nível, códigos de erro.

O comportamento padrão de uma exceção é desenrolar a pilha e parar o programa, se estou escrevendo um script e procuro uma chave que não está em um dicionário, provavelmente é um erro e quero que o programa pare e deixe-me sabe tudo sobre isso.

Se, entretanto, estou escrevendo um trecho de código cujo comportamento devo conhecer em todas as situações possíveis, então quero códigos de erro. Caso contrário, preciso saber cada exceção que pode ser lançada por cada linha em minha função para saber o que ela fará (leia A exceção que aterrou uma companhia aérea para ter uma ideia de como isso é complicado). É entediante e difícil escrever um código que reaja adequadamente a todas as situações (incluindo as infelizes), mas isso porque escrever código sem erros é tedioso e difícil, não porque você está passando códigos de erro.

Tanto Raymond Chen quanto Joel apresentaram alguns argumentos eloqüentes contra o uso de exceções para tudo.

Tom Dunham
fonte
5
1 por apontar que o contexto tem algo a ver com a estratégia de tratamento de erros.
alx9r
2
@Tom, pontos positivos, mas exceções com certeza serão detectadas. Como podemos garantir que os códigos de erro sejam detectados e não ignorados silenciosamente devido a erros?
Pacerier
13
Portanto, seu único argumento contra as exceções é que algo ruim pode acontecer quando você se esquece de capturar uma exceção, mas não considera a situação tão provável de se esquecer de verificar se há erros no valor retornado. Sem mencionar que você obtém um rastreamento de pilha quando se esquece de capturar uma exceção, enquanto não obtém nada quando se esquece de verificar um código de erro.
Esailija
3
C ++ 17 apresenta o nodiscardatributo que fornecerá um aviso do compilador se o valor de retorno de uma função não for armazenado. Ajuda um pouco a detectar a verificação de código de erro esquecido. Exemplo: godbolt.org/g/6i6E0B
Zitrax
5
O comentário de @Esailija está exatamente no ponto aqui. O argumento contra exceções aqui leva uma API baseada em código de erro hipotético onde todos os códigos de erro são documentados e um programador hipotético que lê a documentação, identifica corretamente todos os casos de erro que são logicamente possíveis em seu aplicativo e escreve o código para lidar com cada um deles , então compara esse cenário a uma API baseada em exceção hipotética e a um programador em que, por algum motivo, uma dessas etapas dá errado ... embora seja igualmente fácil (provavelmente mais fácil ) acertar todas essas etapas em uma API baseada em exceção.
Mark Amery
62

Eu normalmente prefiro exceções, porque elas têm mais informações contextuais e podem transmitir (quando usadas corretamente) o erro para o programador de uma maneira mais clara.

Por outro lado, os códigos de erro são mais leves do que as exceções, mas são mais difíceis de manter. A verificação de erros pode ser omitida inadvertidamente. Os códigos de erro são mais difíceis de manter porque você precisa manter um catálogo com todos os códigos de erro e, em seguida, ativar o resultado para ver qual erro foi gerado. Os intervalos de erro podem ajudar aqui, porque se a única coisa em que estamos interessados ​​é se estamos na presença de um erro ou não, é mais simples verificar (por exemplo, um código de erro HRESULT maior ou igual a 0 é sucesso e menos que zero é falha). Eles podem ser omitidos inadvertidamente porque não há nenhuma força programática de que o desenvolvedor irá verificar os códigos de erro. Por outro lado, você não pode ignorar exceções.

Para resumir, prefiro exceções aos códigos de erro em quase todas as situações.

Jorge Ferreira
fonte
4
"códigos de erro são mais leves do que exceções" depende do que você mede e de como você mede. É muito fácil criar testes que mostram que as APIs baseadas em exceção podem ser muito mais rápidas.
Mooing Duck
1
@smink, pontos positivos, mas como tratamos a sobrecarga de exceções? Os códigos de erro não são apenas leves, mas basicamente não têm peso ; as exceções não são apenas de peso médio, são objetos de peso pesado contendo informações de pilha e coisas diversas que não usamos de qualquer maneira.
Pacerier
3
@Pacerier: Você está considerando apenas testes em que exceções são lançadas. Você está 100% certo de que lançar uma exceção C ++ é significativamente mais lento do que retornar um código de erro. Mãos para baixo, sem debate. Onde nós não diferem, é o outro 99,999% do código. Com exceções, não precisamos verificar os retornos do código de erro entre cada instrução, tornando esse código de 1 a 50% mais rápido (ou não, dependendo do seu compilador). O que significa que o código completo pode ser mais rápido ou mais lento, dependendo inteiramente de como o código é escrito e da frequência com que as exceções são lançadas.
Mooing Duck
1
@Pacerier: Com esse teste artificial que acabei de escrever, o código baseado em exceção é tão rápido quanto os códigos de erro no MSVC e no Clang, embora não no GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (tempos na parte inferior). Parece que os sinalizadores GCC que usei geraram exceções excepcionalmente lentas em comparação com os outros compiladores. Sou tendencioso, então critique meu teste e sinta-se à vontade para tentar outra variante.
Mooing Duck
4
@Mooing Duck, se você testou os códigos de erro e as exceções ao mesmo tempo, deve ter ativado as exceções. Se for esse o caso, seus resultados sugerem que usar códigos de erro com sobrecarga de tratamento de exceção não é mais lento do que usar apenas exceções. Os testes de código de erro precisariam ser executados com exceções desativadas para obter resultados significativos.
Mika Haarahiltunen
24

Eu prefiro exceções porque

  • eles interrompem o fluxo da lógica
  • eles se beneficiam da hierarquia de classes, que oferece mais recursos / funcionalidades
  • quando usado corretamente pode representar uma ampla gama de erros (por exemplo, um InvalidMethodCallException também é um LogicException, pois ambos ocorrem quando há um bug em seu código que deve ser detectado antes do tempo de execução), e
  • eles podem ser usados ​​para melhorar o erro (ou seja, uma definição de classe FileReadException pode conter código para verificar se o arquivo existe ou está bloqueado, etc)
JamShady
fonte
2
Seu quarto ponto não é justo: um status de erro quando convertido em um objeto, também pode conter código para verificar se o arquivo existe ou está bloqueado, etc. É simplesmente uma variação de stackoverflow.com/a/3157182/632951
Pacerier
1
"eles interrompem o fluxo da lógica". As exceções têm mais ou menos os efeitos degoto
peterchaula
22

Os códigos de erro podem ser ignorados (e geralmente são!) Pelos chamadores de suas funções. As exceções pelo menos os forçam a lidar com o erro de alguma forma. Mesmo que a versão deles para lidar com isso seja ter um manipulador de captura vazio (suspiro).

Maxam
fonte
17

Exceções sobre códigos de erro, sem dúvida. Você obtém muitos dos mesmos benefícios das exceções que os códigos de erro, mas também muito mais, sem as deficiências dos códigos de erro. A única crítica às exceções é que é um pouco mais indireto; mas nos dias de hoje, essa sobrecarga deve ser considerada insignificante para quase todos os aplicativos.

Aqui estão alguns artigos discutindo, comparando e contrastando as duas técnicas:

Há alguns bons links que podem fornecer mais leituras.

Pistos
fonte
16

Eu nunca misturaria os dois modelos ... é muito difícil converter de um para o outro conforme você se move de uma parte da pilha que está usando códigos de erro para uma parte superior que está usando exceções.

As exceções são para "qualquer coisa que interrompa ou iniba o método ou sub-rotina de fazer o que você pediu" ... NÃO para passar mensagens de volta sobre irregularidades ou circunstâncias incomuns, ou o estado do sistema, etc. Use valores de retorno ou ref (ou fora) parâmetros para isso.

As exceções permitem que métodos sejam escritos (e utilizados) com semânticas que dependem da função do método, ou seja, um método que retorna um objeto Employee ou List of Employees pode ser digitado para fazer exatamente isso e você pode utilizá-lo chamando.

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Com códigos de erro, todos os métodos retornam um código de erro, então, para aqueles que precisam retornar algo a ser usado pelo código de chamada, você deve passar uma variável de referência a ser preenchida com esses dados e testar o valor de retorno para o código de erro, e lidar com ele, em cada chamada de função ou método.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Se você codificar de forma que cada método faça uma e apenas uma coisa simples, deverá lançar uma exceção sempre que o método não conseguir cumprir o objetivo desejado. As exceções são muito mais ricas e fáceis de usar dessa forma do que os códigos de erro. Seu código é muito mais limpo - O fluxo padrão do caminho do código "normal" pode ser dedicado estritamente ao caso em que o método É capaz de realizar o que você deseja ... E então o código para limpar ou lidar com o Circunstâncias "excepcionais" quando algo de ruim acontece que impede o método de ser concluído com êxito podem ser isoladas do código normal. Além disso, se você não pode lidar com a exceção onde ela ocorreu e deve passá-la para cima na pilha para uma IU (ou pior, através da conexão de um componente de camada intermediária para uma IU), então, com o modelo de exceção,

Charles Bretana
fonte
Ótima resposta! Solução fora da caixa na hora certa!
Cristian E.
11

No passado, entrei no campo do código de erro (fazia muita programação C). Mas agora eu vi a luz.

Sim, as exceções são um pouco onerosas para o sistema. Mas eles simplificam o código, reduzindo o número de erros (e WTF's).

Portanto, use a exceção, mas com sabedoria. E eles serão seus amigos.

Como uma nota rodapé. Aprendi a documentar qual exceção pode ser lançada por qual método. Infelizmente, isso não é exigido pela maioria dos idiomas. Mas aumenta a chance de lidar com as exceções certas no nível certo.

Toon Krijthe
fonte
1
yap C deixa alguns hábitos em todos nós;)
Jorge Ferreira
11

Pode haver algumas situações em que usar exceções de uma maneira limpa, clara e correta seja complicado, mas na grande maioria das vezes as exceções são a escolha óbvia. O maior benefício que o tratamento de exceções tem sobre os códigos de erro é que muda o fluxo de execução, o que é importante por dois motivos.

Quando ocorre uma exceção, o aplicativo não está mais seguindo seu caminho de execução 'normal'. A primeira razão pela qual isso é tão importante é que, a menos que o autor do código saia bem e verdadeiramente de seu caminho para ser ruim, o programa irá parar e não continuará fazendo coisas imprevisíveis. Se um código de erro não for verificado e as ações apropriadas não forem tomadas em resposta a um código de erro ruim, o programa continuará fazendo o que está fazendo e quem sabe qual será o resultado dessa ação. Existem muitas situações em que fazer com que o programa faça "qualquer coisa" pode acabar saindo muito caro. Considere um programa que recupera informações de desempenho de vários instrumentos financeiros que uma empresa vende e entrega essas informações a corretores / atacadistas. Se algo der errado e o programa continuar, ele poderia enviar dados de desempenho errôneos aos corretores e atacadistas. Não sei de mais ninguém, mas não quero ser aquele sentado no escritório de um VP explicando por que meu código fez com que a empresa recebesse multas regulatórias no valor de 7 dígitos. Entregar uma mensagem de erro aos clientes é geralmente preferível a entregar dados errados que podem parecer 'reais', e a última situação é muito mais fácil de enfrentar com uma abordagem muito menos agressiva, como códigos de erro.

A segunda razão pela qual gosto de exceções e sua quebra da execução normal é que isso torna muito, muito mais fácil manter a lógica de 'coisas normais estão acontecendo' separada da lógica de 'algo deu errado'. Para mim, este:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... é preferível a isto:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Existem outras pequenas coisas sobre as exceções que são boas também. Ter um monte de lógica condicional para controlar se algum dos métodos sendo chamados em uma função teve um código de erro retornado e retornar esse código de erro mais acima é um monte de boiler plate. Na verdade, é um monte de boiler plate que pode dar errado. Tenho muito mais fé no sistema de exceção da maioria das línguas do que em um ninho de ratos de declarações if-else-if-else que Fred escreveu "recém-saído da faculdade", e tenho coisas muito melhores para fazer com o meu tempo do que o código revisando o dito ninho de rato.

Cães
fonte
8

Você deve usar ambos. A questão é decidir quando usar cada um .

Existem alguns cenários em que as exceções são a escolha óbvia :

  1. Em algumas situações você não pode fazer nada com o código de erro , e você só precisa tratá-lo em um nível superior na pilha de chamadas , geralmente apenas registrar o erro, exibir algo para o usuário ou fechar o programa. Nesses casos, os códigos de erro exigiriam que você borbulhasse os códigos de erro manualmente nível por nível, o que é obviamente muito mais fácil de fazer com exceções. A questão é que isso é para situações inesperadas e intratáveis .

  2. Ainda assim, sobre a situação 1 (onde algo inesperado e impossível de manusear acontece, você simplesmente não quer registrar), as exceções podem ser úteis porque você pode adicionar informações contextuais . Por exemplo, se eu obtiver uma SqlException em meus auxiliares de dados de nível inferior, vou querer capturar esse erro no nível inferior (onde conheço o comando SQL que causou o erro) para que possa capturar essas informações e relançar com informações adicionais . Observe a palavra mágica aqui: relançar e não engolir . A primeira regra de tratamento de exceções: não engula exceções . Além disso, observe que minha captura interna não precisa registrar nada porque a captura externa terá o rastreamento de pilha inteiro e pode registrá-lo.

  3. Em algumas situações, você tem uma sequência de comandos, e se algum deles falhar, você deve limpar / descartar recursos (*), seja esta uma situação irrecuperável (que deve ser lançada) ou uma situação recuperável (neste caso, você pode lidar localmente ou no código do chamador, mas você não precisa de exceções). Obviamente, é muito mais fácil colocar todos esses comandos em uma única tentativa, em vez de testar os códigos de erro após cada método e limpar / descartar no bloco finally. Por favor, note que se você quiser que o erro borbulhe (que é provavelmente o que você quer), você nem precisa detectá-lo - você apenas usa o finally para limpar / descartar - você só deve usar catch / retrow se quiser para adicionar informações contextuais (consulte o marcador 2).

    Um exemplo seria uma sequência de instruções SQL dentro de um bloco de transação. Novamente, esta também é uma situação "intratável", mesmo se você decidir pegá-la cedo (trate-a localmente em vez de borbulhar até o topo), ainda é uma situação fatal em que o melhor resultado é abortar tudo ou pelo menos abortar um grande parte do processo.
    (*) É como o on error gotoque usávamos no antigo Visual Basic

  4. Em construtores, você só pode lançar exceções.

Dito isso, em todas as outras situações em que você está retornando alguma informação sobre a qual o chamador PODE / DEVE fazer alguma coisa , usar códigos de retorno é provavelmente uma alternativa melhor. Isso inclui todos os "erros" esperados , porque provavelmente eles devem ser tratados pelo chamador imediato, e dificilmente precisarão subir muitos níveis na pilha.

É claro que sempre é possível tratar os erros esperados como exceções e capturar imediatamente um nível acima, e também é possível incluir cada linha de código em um try catch e executar ações para cada erro possível. IMO, este é um design ruim, não apenas porque é muito mais prolixo, mas especialmente porque as possíveis exceções que podem ser lançadas não são óbvias sem a leitura do código-fonte - e exceções podem ser lançadas de qualquer método profundo, criando gotos invisíveis . Eles quebram a estrutura do código criando vários pontos de saída invisíveis que tornam o código difícil de ler e inspecionar. Em outras palavras, você nunca deve usar exceções como controle de fluxo, porque isso seria difícil para outras pessoas entenderem e manterem. Pode ficar ainda mais difícil entender todos os fluxos de código possíveis para teste.
Novamente: para uma limpeza / descarte corretos, você pode usar try-finally sem pegar nada .

A crítica mais popular sobre os códigos de retorno é que "alguém pode ignorar os códigos de erro, mas no mesmo sentido, alguém também pode engolir exceções. O tratamento incorreto de exceções é fácil em ambos os métodos. Mas escrever um bom programa baseado em código de erro ainda é muito mais fácil do que escrever um programa baseado em exceções . E se alguém por qualquer motivo decidir ignorar todos os erros (os antigos on error resume next), você pode fazer isso facilmente com códigos de retorno e não pode fazer isso sem um monte de boilerplate try-catchs.

A segunda crítica mais popular sobre os códigos de retorno é que "é difícil aparecer" - mas isso é porque as pessoas não entendem que as exceções são para situações não recuperáveis, enquanto os códigos de erro não.

Decidir entre exceções e códigos de erro é uma área cinzenta. É até possível que você precise obter um código de erro de algum método de negócios reutilizável e, em seguida, decida agrupá-lo em uma exceção (possivelmente adicionando informações) e deixá-lo borbulhar. Mas é um erro de design presumir que TODOS os erros devem ser lançados como exceções.

Resumindo:

  • Gosto de usar exceções quando tenho uma situação inesperada, em que não há muito o que fazer e geralmente queremos abortar um grande bloco de código ou até mesmo toda a operação ou programa. Isso é como o antigo "em erro, goto".

  • Gosto de usar códigos de retorno quando espero situações em que o código do chamador pode / deve realizar alguma ação. Isso inclui a maioria dos métodos de negócios, APIs, validações e assim por diante.

Essa diferença entre exceções e códigos de erro é um dos princípios de design da linguagem GO, que usa "pânico" para situações inesperadas fatais, enquanto as situações normais esperadas são retornadas como erros.

Já sobre o GO, ele também permite múltiplos valores de retorno , o que é algo que ajuda muito no uso de códigos de retorno, já que você pode retornar simultaneamente um erro e algo mais. Em C # / Java, podemos conseguir isso sem parâmetros, Tuplas ou (meu favorito) Genéricos, que combinados com enums podem fornecer códigos de erro claros para o chamador:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

Se eu adicionar um novo retorno possível em meu método, posso até verificar todos os chamadores se eles estão cobrindo esse novo valor em uma instrução switch, por exemplo. Você realmente não pode fazer isso com exceções. Quando você usa códigos de retorno, geralmente sabe com antecedência todos os erros possíveis e os testa. Com exceções, você geralmente não sabe o que pode acontecer. Encapsular enums dentro de exceções (em vez de genéricos) é uma alternativa (desde que esteja claro o tipo de exceções que cada método lançará), mas IMO ainda é um projeto ruim.

drizin
fonte
4

Meu raciocínio seria se você estiver escrevendo um driver de baixo nível que realmente precisa de desempenho, use códigos de erro. Mas se você estiver usando esse código em um aplicativo de nível superior e ele puder lidar com um pouco de sobrecarga, envolva esse código com uma interface que verifica esses códigos de erro e levanta exceções.

Em todos os outros casos, as exceções são provavelmente o caminho a percorrer.

Claudiu
fonte
4

Posso estar sentado em cima do muro aqui, mas ...

  1. Depende do idioma.
  2. Seja qual for o modelo que você escolher, seja consistente sobre como usá-lo.

Em Python, o uso de exceções é uma prática padrão e estou muito feliz em definir minhas próprias exceções. Em C, você não tem exceções.

Em C ++ (pelo menos no STL), as exceções são normalmente lançadas apenas para erros verdadeiramente excepcionais (virtualmente nunca os vejo). Não vejo razão para fazer algo diferente em meu próprio código. Sim, é fácil ignorar os valores de retorno, mas C ++ também não o força a capturar exceções. Acho que você só precisa adquirir o hábito de fazer isso.

O código base em que trabalho é principalmente C ++ e usamos códigos de erro em quase todos os lugares, mas há um módulo que levanta exceções para qualquer erro, incluindo os muito comuns, e todo o código que usa esse módulo é horrível. Mas isso pode ser apenas porque misturamos exceções e códigos de erro. O código que usa consistentemente códigos de erro é muito mais fácil de trabalhar. Se nosso código usasse exceções consistentemente, talvez não fosse tão ruim. Misturar os dois não parece funcionar tão bem.

jrb
fonte
4

Como trabalho com C ++ e tenho RAII para torná-los seguros para uso, uso exceções quase que exclusivamente. Ele puxa o tratamento de erros do fluxo normal do programa e torna a intenção mais clara.

Deixo exceções para circunstâncias excepcionais. Se estou esperando que um certo erro aconteça com frequência, verificarei se a operação será bem-sucedida antes de executá-la ou chamo uma versão da função que usa códigos de erro (como TryParse())

Eclipse
fonte
3

As assinaturas do método devem comunicar a você o que o método faz. Algo como long errorCode = getErrorCode (); pode estar bem, mas long errorCode = fetchRecord (); é confuso.

Paul Croarkin
fonte
3

Minha abordagem é que podemos usar ambos, ou seja, códigos de exceções e erros ao mesmo tempo.

Estou acostumado a definir vários tipos de Exceptions (ex: DataValidationException ou ProcessInterruptExcepion) e dentro de cada exceção definir uma descrição mais detalhada de cada problema.

Um exemplo simples em Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

Dessa forma, eu uso exceções para definir erros de categoria e códigos de erro para definir informações mais detalhadas sobre o problema.

Sakana
fonte
2
erm ... throw new DataValidationException("The input is too small")? Uma das vantagens das exceções é permitir informações detalhadas.
Eva
2

As exceções são para circunstâncias excepcionais - isto é, quando não fazem parte do fluxo normal do código.

É bastante legítimo misturar exceções e códigos de erro, onde códigos de erro representam o status de algo, ao invés de um erro na execução do código em si (por exemplo, verificar o código de retorno de um processo filho).

Mas quando ocorre uma circunstância excepcional, acredito que as exceções são o modelo mais expressivo.

Há casos em que você pode preferir, ou deve, usar códigos de erro no lugar de Exceções, e eles já foram adequadamente cobertos (além de outras restrições óbvias, como suporte do compilador).

Mas indo na outra direção, usar Exceptions permite que você crie abstrações de nível ainda mais alto para o tratamento de erros, o que pode tornar seu código ainda mais expressivo e natural. Eu recomendo fortemente a leitura deste excelente, embora subestimado, artigo do especialista em C ++ Andrei Alexandrescu sobre o que ele chama de "Enforcements": http://www.ddj.com/cpp/184403864 . Embora seja um artigo C ++, os princípios são geralmente aplicáveis, e traduzi o conceito de reforços para C # com bastante sucesso.

philsquared
fonte
2

Em primeiro lugar, concordo com a resposta de Tom de que, para itens de alto nível, use exceções, e para itens de baixo nível, use códigos de erro, contanto que não seja Arquitetura Orientada a Serviços (SOA).

Em SOA, onde os métodos podem ser chamados em máquinas diferentes, as exceções não podem ser transmitidas, em vez disso, usamos respostas de sucesso / falha com uma estrutura como a abaixo (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

E use assim:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

Quando eles são usados ​​consistentemente em suas respostas de serviço, ele cria um padrão muito bom de tratamento de sucessos / falhas no aplicativo. Isso permite um tratamento mais fácil de erros em chamadas assíncronas nos serviços, bem como entre os serviços.

orad
fonte
1

Eu preferiria as exceções para todos os casos de erro, exceto quando uma falha é um resultado esperado sem erros de uma função que retorna um tipo de dados primitivo. Por exemplo, encontrar o índice de uma substring dentro de uma string maior normalmente retornaria -1 se não fosse encontrado, em vez de gerar uma NotFoundException.

Retornar ponteiros inválidos que podem ser desreferenciados (por exemplo, causando NullPointerException em Java) não é aceitável.

Usar vários códigos de erro numéricos diferentes (-1, -2) como valores de retorno para a mesma função geralmente é um estilo incorreto, pois os clientes podem fazer uma verificação "== -1" em vez de "<0".

Uma coisa a ter em mente aqui é a evolução das APIs ao longo do tempo. Uma boa API permite alterar e estender o comportamento de falha de várias maneiras sem interromper os clientes. Por exemplo, se um identificador de erro do cliente for verificado para 4 casos de erro, e você adicionar um quinto valor de erro à sua função, o manipulador do cliente pode não testar isso e quebrar. Se você lançar exceções, isso geralmente tornará mais fácil para os clientes migrarem para uma versão mais recente de uma biblioteca.

Outra coisa a se considerar é, ao trabalhar em equipe, traçar uma linha clara para que todos os desenvolvedores tomem essa decisão. Por exemplo, "Exceções para coisas de alto nível, códigos de erro para coisas de baixo nível" é muito subjetivo.

Em qualquer caso, onde mais de um tipo trivial de erro é possível, o código-fonte nunca deve usar o literal numérico para retornar um código de erro ou para tratá-lo (retornar -7, se x == -7 ...), mas sempre uma constante nomeada (retornar NO_SUCH_FOO, se x == NO_SUCH_FOO).

tkruse
fonte
1

Se você trabalha em um grande projeto, não pode usar apenas exceções ou apenas códigos de erro. Em casos diferentes, você deve usar abordagens diferentes.

Por exemplo, você decide usar apenas exceções. Mas depois de decidir usar o processamento assíncrono de eventos. É uma má ideia usar exceções para tratamento de erros nessas situações. Mas usar códigos de erro em todos os lugares do aplicativo é entediante.

Portanto, minha opinião é que é normal usar exceções e códigos de erro simultaneamente.

gomons
fonte
0

Para a maioria dos aplicativos, as exceções são melhores. A exceção é quando o software precisa se comunicar com outros dispositivos. O domínio em que trabalho é o de controles industriais. Aqui, os códigos de erro são preferidos e esperados. Portanto, minha resposta é que depende da situação.

Jim C
fonte
0

Acho que também depende se você realmente precisa de informações como rastreamento de pilha do resultado. Se sim, você definitivamente vai para Exception, que fornece objeto cheio de informações sobre o problema. No entanto, se você está interessado apenas no resultado e não se importa por que esse resultado, vá para o código de erro.

Por exemplo, quando você está processando um arquivo e enfrenta IOException, o cliente pode se interessar em saber de onde isso foi disparado, em abrir o arquivo ou analisar o arquivo, etc. Portanto, é melhor retornar IOException ou sua subclasse específica. Porém, cenário como você tem método de login e quer saber se foi bem sucedido ou não, ou você apenas retorna booleano ou para mostrar a mensagem correta, retorna o código de erro. Aqui, o Cliente não está interessado em saber qual parte da lógica causou aquele código de erro. Ele só sabe se sua credencial é inválida ou se a conta está bloqueada etc.

Outro caso de uso em que posso pensar é quando os dados trafegam na rede. Seu método remoto pode retornar apenas o código de erro em vez de Exceção para minimizar a transferência de dados.

Ashah
fonte
0

Minha regra geral é:

  • Apenas um erro pode aparecer em uma função: use o código de erro (como parâmetro da função)
  • Mais de um erro específico pode aparecer: lançar exceção
CARNE MORTA
fonte
-1

Os códigos de erro também não funcionam quando o seu método retorna qualquer coisa diferente de um valor numérico ...

Omar Kooheji
fonte
3
Hum, não. Consulte o paradigma win32 GetLastError (). Não estou defendendo isso, apenas afirmando que você está incorreto.
Tim
4
Na verdade, existem muitas outras maneiras de fazer isso. Outra maneira é retornar um objeto que contém o código de erro e o valor real de retorno. Ainda outra maneira é fazer a passagem de referência.
Pacerier