Con nuestro SDK público, tendemos a querer dar mensajes muy informativos sobre por qué ocurre una excepción. Por ejemplo:
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);
}
Sin embargo, esto tiende a saturar el flujo del código, ya que tiende a centrarse mucho en los mensajes de error en lugar de en lo que está haciendo el código.
Un colega comenzó a refactorizar algunas de las excepciones lanzando algo como esto:
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);
}
Lo que hace que la lógica del código sea más fácil de entender, pero agrega muchos métodos adicionales para manejar los errores.
¿Cuáles son otras formas de evitar el problema de "lógica de desorden de mensajes largos de excepción"? Principalmente estoy preguntando acerca de C # / .NET idiomático, pero también es útil cómo lo manejan otros lenguajes.
[Editar]
Sería bueno tener los pros y los contras de cada enfoque también.
fuente
Exception.Data
propiedad, la captura de excepciones "selectivas", la captura del código de llamada y la adición de su propio contexto, junto con la pila de llamadas capturadas, contribuyen con información que debería permitir mensajes mucho menos detallados. FinalmenteSystem.Reflection.MethodBase
parece prometedor para proporcionar detalles para pasar a su método de "construcción de excepción".Exception.Data
. El énfasis debe ser capturar la telemetría. Refactorizar aquí está bien, pero pierde el problema.Respuestas:
¿Por qué no tener clases de excepción especializadas?
Eso empuja el formato y los detalles a la excepción en sí, y deja la clase principal despejada.
fuente
Microsoft parece (al mirar la fuente .NET) a veces usa cadenas de recursos / entorno. Por ejemplo
ParseDecimal
:Pros:
Contras:
fuente
Para el escenario de SDK público, consideraría usar contratos de código de Microsoft, ya que proporcionan errores informativos, verificaciones estáticas y también puede generar documentación para agregar a documentos XML y archivos de ayuda generados por Sandcastle . Es compatible con todas las versiones pagas de Visual Studio.
Una ventaja adicional es que si sus clientes están usando C #, pueden aprovechar sus ensambles de referencia de contrato de código para detectar posibles problemas incluso antes de ejecutar su código.
La documentación completa de los contratos de código está aquí .
fuente
La técnica que utilizo es combinar y externalizar la validación y el lanzamiento a una función de utilidad.
El beneficio más importante es que se reduce a una sola línea en la lógica empresarial .
Apuesto a que no puede hacerlo mejor a menos que pueda reducirlo aún más: eliminar todas las validaciones de argumentos y guardias de estado de objeto de la lógica comercial, manteniendo solo las condiciones operativas excepcionales.
Por supuesto, hay formas de hacerlo: lenguaje fuertemente tipado, diseño "no se permiten objetos no válidos en cualquier momento", diseño por contrato , etc.
Ejemplo:
fuente
[nota] Copié esto de la pregunta en una respuesta en caso de que haya comentarios al respecto.
Mueva cada excepción a un método de la clase, tomando cualquier argumento que necesite el formato.
Incluya todos los métodos de excepción en la región y colóquelos al final de la clase.
Pros:
Contras:
fuente
#region #endregion
(lo que hace que estén ocultos del IDE de forma predeterminada), y si son aplicables en diferentes clases, colóquelos en unainternal static ValidationUtility
clase. Por cierto, nunca te quejes de los nombres de identificadores largos frente a un programador de C #.Si puede salirse con la suya con errores un poco más generales, podría escribir una función de conversión genérica estática pública para usted, que infiere el tipo de fuente:
Hay variaciones posibles (piense
SdkHelper.RequireNotNull()
), que solo verifican los requisitos en las entradas y las arrojan si fallan, pero en este ejemplo, combinar el reparto con producir el resultado es autodocumentado y compacto.Si está en .net 4.5, hay formas de hacer que el compilador inserte el nombre del método / archivo actual como parámetro de método (consulte CallerMemberAttibute ). Pero para un SDK, probablemente no pueda exigir a sus clientes que cambien a 4.5.
fuente
Lo que nos gusta hacer para los errores de lógica de negocios (no necesariamente errores de argumento, etc.) es tener una enumeración única que defina todos los tipos potenciales de errores:
Los
[Display(Name = "...")]
atributos definen la clave en los archivos de recursos que se utilizarán para traducir los mensajes de error.Además, este archivo se puede usar como punto de partida para encontrar todas las ocurrencias en las que se genera un cierto tipo de error en su código.
La verificación de las Reglas comerciales se puede delegar a clases especializadas de Validator que generan listas de Reglas comerciales violadas.
Luego usamos un tipo de excepción personalizado para transportar las reglas violadas:
Las llamadas de servicio backend están envueltas en un código genérico de manejo de errores, que traduce la regla de negocio violada en un mensaje de error legible por el usuario:
Aquí
ToTranslatedString()
hay un método de extensión paraenum
que pueda leer las claves de recursos de los[Display]
atributos y usar elResourceManager
para traducir estas claves. El valor de la clave de recurso respectiva puede contener marcadores de posición parastring.Format
, que coinciden con los proporcionadosMessageParameters
. Ejemplo de una entrada en el archivo resx:Ejemplo de uso:
Con este enfoque, puede desacoplar la generación del mensaje de error a partir de la generación del error, sin la necesidad de introducir una nueva clase de excepción para cada nuevo tipo de error. Útil si diferentes frontends deberían mostrar diferentes mensajes, si el mensaje mostrado debería depender del idioma del usuario y / o función, etc.
fuente
Usaría cláusulas de guardia como en este ejemplo para que las verificaciones de parámetros puedan reutilizarse.
Además, puede usar el patrón de construcción para que se pueda encadenar más de una validación. Se verá un poco como afirmaciones fluidas .
fuente