Com nosso SDK público, tendemos a enviar mensagens muito informativas sobre o motivo de uma exceção. Por exemplo:
if (interfaceInstance == null)
{
string errMsg = string.Format(
"Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
ParameterInfo.Name,
ParameterInfo.ParameterType,
typeof(IParameter)
);
throw new InvalidOperationException(errMsg);
}
No entanto, isso tende a atrapalhar o fluxo do código, pois tende a colocar muito foco nas mensagens de erro, e não no que o código está fazendo.
Um colega começou a refatorar algumas das exceções lançando algo assim:
if (interfaceInstance == null)
throw EmptyConstructor();
...
private Exception EmptyConstructor()
{
string errMsg = string.Format(
"Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
ParameterInfo.Name,
ParameterInfo.ParameterType,
typeof(IParameter)
);
return new InvalidOperationException(errMsg);
}
O que facilita a compreensão da lógica do código, mas adiciona muitos métodos extras para o tratamento de erros.
Quais são as outras maneiras de evitar o problema da "lógica de confusão de mensagens de exceção longa"? Estou perguntando principalmente sobre C # /. NET idiomático, mas como outros idiomas o gerenciam também são úteis.
[Editar]
Seria bom ter os prós e contras de cada abordagem também.
c#
.net
clean-code
exception-handling
FriendlyGuy
fonte
fonte
Exception.Data
propriedade, captura "exigente" de exceção, captura de código e adição de seu próprio contexto, juntamente com a pilha de chamadas capturadas, contribuem com informações que devem permitir mensagens muito menos detalhadas. Finalmente,System.Reflection.MethodBase
parece promissor fornecer detalhes para passar ao seu método de "construção de exceção".Exception.Data
. A ênfase deve ser a captura de telemetria. Refatorar aqui é bom, mas perde o problema.Respostas:
Por que não ter classes de exceção especializadas?
Isso leva a formatação e os detalhes à exceção em si e deixa a classe principal organizada.
fonte
A Microsoft parece (via olhando a fonte .NET) às vezes usar cadeias de recursos / ambiente. Por exemplo
ParseDecimal
:Prós:
Contras:
fonte
Para o cenário público do SDK, eu consideraria fortemente o uso de contratos de código da Microsoft, pois eles fornecem erros informativos, verificações estáticas e você também pode gerar documentação para adicionar aos documentos XML e aos arquivos de ajuda gerados pelo Sandcastle . É suportado em todas as versões pagas do Visual Studio.
Uma vantagem adicional é que, se seus clientes estiverem usando C #, eles poderão aproveitar seus assemblies de referência de contrato de código para detectar possíveis problemas antes mesmo de executarem seu código.
A documentação completa dos contratos de código está aqui .
fonte
A técnica que eu uso é combinar e terceirizar a validação e lançar completamente para uma função de utilidade.
O benefício mais importante é que ele é reduzido a uma linha na lógica de negócios .
Aposto que você não pode fazer melhor a menos que possa reduzi-lo ainda mais - para eliminar todas as validações de argumento e proteções de estado do objeto da lógica de negócios, mantendo apenas as condições operacionais excepcionais.
Obviamente, existem maneiras de fazer isso - linguagem fortemente tipada, "nenhum objeto inválido é permitido a qualquer momento", design por contrato , etc.
Exemplo:
fonte
[note] Copiei isso da pergunta para uma resposta, caso haja comentários sobre ela.
Mova cada exceção para um método da classe, aceitando os argumentos que precisam da formatação.
Coloque todos os métodos de exceção na região e coloque-os no final da classe.
Prós:
Contras:
fonte
#region #endregion
(o que faz com que sejam ocultos do IDE por padrão) e, se aplicáveis a diferentes classes, coloque-os em umainternal static ValidationUtility
classe. Aliás, nunca reclame nomes longos de identificadores na frente de um programador de C #.Se você conseguir se livrar de erros um pouco mais gerais, poderá escrever uma função de conversão genérica estática pública para você, que infere o tipo de fonte:
Existem variações possíveis (pense
SdkHelper.RequireNotNull()
), que apenas verificam os requisitos das entradas e lançam se elas falharem, mas neste exemplo, combinar o elenco com a produção do resultado é autodocumentado e compacto.Se você estiver no .net 4.5, há maneiras de o compilador inserir o nome do método / arquivo atual como um parâmetro do método (consulte CallerMemberAttibute ). Mas para um SDK, você provavelmente não pode exigir que seus clientes mudem para o 4.5.
fonte
O que gostamos de fazer para erros de lógica de negócios (não necessariamente erros de argumento etc.) é ter uma enumeração única que define todos os tipos possíveis de erros:
Os
[Display(Name = "...")]
atributos definem a chave nos arquivos de recursos a serem usados para converter as mensagens de erro.Além disso, esse arquivo pode ser usado como ponto de partida para encontrar todas as ocorrências em que um certo tipo de erro é gerado no seu código.
A verificação de regras de negócios pode ser delegada a classes especializadas do Validator que produzem listas de regras de negócios violadas.
Em seguida, usamos um tipo de exceção personalizado para transportar as regras violadas:
As chamadas de serviço de back-end são agrupadas no código genérico de tratamento de erros, que traduz a regra de busines violada em uma mensagem de erro legível pelo usuário:
Aqui
ToTranslatedString()
está um método de extensão paraenum
ler as chaves de recursos dos[Display]
atributos e usar oResourceManager
para converter essas chaves. O valor da respectiva chave de recurso pode conter espaços reservados parastring.Format
, que correspondem ao fornecidoMessageParameters
. Exemplo de uma entrada no arquivo resx:Exemplo de uso:
Com essa abordagem, você pode separar a geração da mensagem de erro da geração do erro, sem a necessidade de introduzir uma nova classe de exceção para cada novo tipo de erro. Útil se diferentes frontends exibirem mensagens diferentes, se a mensagem exibida depender do idioma e / ou função do usuário etc.
fonte
Eu usaria cláusulas de guarda como neste exemplo para que as verificações de parâmetros possam ser reutilizadas.
Além disso, você pode usar o padrão do construtor para que mais de uma validação possa ser encadeada. Será um pouco parecido com afirmações fluentes .
fonte