2009-03-09 10 views
7

Estaba cavando en MSDN y encontré this article que tenía un consejo interesante: No tiene miembros públicos que pueden lanzar o no lanzar excepciones basadas en alguna opción.Lanzar/no-lanzar una excepción basada en un parámetro: ¿por qué no es una buena idea?

Por ejemplo:

Uri ParseUri(string uriValue, bool throwOnError) 

Ahora, por supuesto que puedo ver que en el 99% de los casos esto sería horrible, pero se justifica su uso ocasional?

Un caso que he visto que se utiliza es con un parámetro "AllowEmpty" al acceder a los datos en la base de datos o un archivo de configuración. Por ejemplo:

object LoadConfigSetting(string key, bool allowEmpty); 

En este caso, la alternativa sería devolver nulo. Pero entonces el código de llamada estaría lleno de verificación de referencias nulas. (Y el método también excluiría la posibilidad de permitir realmente nulo como un valor específicamente configurable, si así lo desea).

¿Cuáles son sus pensamientos? ¿Por qué sería esto un gran problema?

Respuesta

9

Creo que es definitivamente una mala idea tener una decisión de tiro/no lanzamiento basada en un booleano. A saber, porque requiere que los desarrolladores que buscan un fragmento de código tengan un conocimiento funcional de la API para determinar qué significa el booleano. Esto es malo por sí mismo, pero cuando cambia el manejo del error subyacente puede hacer que sea muy fácil para los desarrolladores cometer errores al leer el código.

Sería mucho mejor y más legible tener 2 API en este caso.

Uri ParseUriOrThrow(string value); 

bool TryParseUri(string value, out Uri uri); 

En este caso, es 100% claro lo que hacen estas API.

artículo sobre por qué son malas booleanos como parámetros: http://blogs.msdn.com/jaredpar/archive/2007/01/23/boolean-parameters.aspx

3

Por lo general, es mejor elegir un mecanismo de manejo de errores y mantenerlo de manera consistente. Permitir este tipo de código de flip-flop realmente no puede mejorar la vida de los desarrolladores.

En el ejemplo anterior, ¿qué ocurre si el análisis falla y throwOnError es falso? Ahora el usuario tiene que adivinar si NULL se va a devolver, o si Dios sabe ...

Es cierto que hay un debate continuo entre las excepciones y los valores de retorno como el mejor método de manejo de errores, pero estoy bastante seguro de que hay un consenso acerca de ser consistente y apegarse a cualquier elección que haga. La API no puede sorprender a sus usuarios y el manejo de errores debe ser parte de la interfaz, y debe estar tan claramente definido como la interfaz.

1

Es un poco desagradable desde el punto de vista readabilty. Los desarrolladores tienden a esperar que todos los métodos arrojen una excepción, y si quieren ignorar la excepción, la atraparán ellos mismos. Con el enfoque de 'bandera booleana', cada método debe implementar esta semántica inhibidora de excepciones.

Sin embargo, creo que el artículo de MSDN se refiere estrictamente a los indicadores 'throwOnError'. En estos casos, el error se ignora dentro del método mismo (malo, ya que está oculto) o se devuelve algún tipo de objeto nulo/erróneo (incorrecto, porque no está utilizando excepciones para manejar el error, que es inconsistente y se equivoca) -propenso).

Considerando que su ejemplo me parece bien. Una excepción indica una falla del método para cumplir con su deber, no hay un valor de retorno. Sin embargo, el indicador 'allowEmpty' cambia la semántica del método, por lo que ahora se esperaba y es legal lo que habría sido una excepción ('Valor vacío'). Además, si tenía lanzada una excepción, no podría fácilmente devolver los datos de configuración. Entonces parece estar bien en este caso.

0

Otro ejemplo de acuerdo con esto podría ser un conjunto de métodos TryParse en algunos de los tipos de valor

bool DateTime.TryParse(string text, out DateTime)

1

En cualquier API pública es realmente una mala idea tener dos formas de comprobar una defectuosa condición porque entonces no es obvio lo que sucederá si ocurre el error. Solo mirando el código no ayudará. Debe comprender la semántica del parámetro de indicador (y nada le impide ser una expresión).

Si la comprobación de nulo no es una opción, y si necesito recuperarme de esta falla específica, prefiero crear una excepción específica para poder capturarla más tarde y manejarla adecuadamente. En cualquier otro caso arrojo una excepción general.

0

Tener un parámetro donTThrowException vence el objetivo de las excepciones (en cualquier idioma). Si el código de llamada quiere tener:

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

están invitados a (C# incluso no ha marcado excepciones). En Java lo mismo se logra con:

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

De cualquier manera, es el trabajo de la persona que llama para decidir cómo manejar (o no) la excepción, no la parte llamada de.

0

En el artículo vinculado hay una nota que indica que las Excepciones no se deben usar para el flujo de control, lo que parece estar implícito en las misiones de ejemplo. Las excepciones deben reflejar la falla del nivel del método. Tener una firma de que está bien lanzar un error parece que el diseño no está pensado.

Jeffrey Richters libro CLR través de C# señala: "debe lanzar una excepción cuando el método no puede completar su tarea como lo indica su nombre".

Su libro también señaló un error muy común. Las personas tienden a escribir código para captar todo (sus palabras "Un error omnipresente de los desarrolladores que no han recibido la capacitación adecuada sobre el uso adecuado de las excepciones tiende a utilizar bloqueos de captura con demasiada frecuencia e incorrectamente. Cuando se detecta una excepción, se indica que esperabas esta excepción, entiendes por qué ocurrió y sabes cómo manejarla. ")

Eso me ha hecho tratar de codificar las excepciones que puedo esperar y puedo manejar en mi lógica; de lo contrario, debería ser una error.

Valide sus argumentos y evite las excepciones, y solo capte lo que puede manejar.

+0

Creo que han entendido mal el scen ario. Tenemos la opción de arrojar una excepción (que detendrá el programa o lo que sea), o no lanzar una excepción (es decir, devolver nulo o no hacer nada). En ninguno de los casos se lanzan excepciones y luego son atrapadas. – cbp

0

Me atrevería a decir que a menudo es útil tener un parámetro que indique si una falla debe causar una excepción o simplemente devolver una indicación de error, ya que dichos parámetros se pueden pasar fácilmente de una rutina externa a una interna.Considerar algo como:

byte [] ReadPacket (bool DontThrowIfNone) // documentada como devolver un valor nulo si ninguno { int len ​​= ReadByte (DontThrowIfNone); // documentada como devolver -1 si nada si (len

Si algo así como un TimeoutException durante la lectura de los datos debe provocar una excepción, tal excepción debe ser tirado dentro del ReadByte() o (ReadMultiBytesbytes). Sin embargo, si tales la falta de datos debe considerarse normal, luego la rutina ReadByte() o ReadMultiBytesbytes() no debe arrojar una excepción. Si uno simplemente usara el patrón do/try, las rutinas ReadPacket y TryReadPacket necesitarían tener un código casi idéntico, pero con uno utilizando la lectura * métodos y los otros utilizando métodos TryRead *. Icky.

puede ser mejor usar una enumeración en lugar de un valor lógico.

Cuestiones relacionadas