Lanzar / no lanzar una excepción basada en un parámetro: ¿por qué no es una buena idea?

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

Pregunta

Estaba buscando en MSDN y encontré este artículo que tenía un consejo interesante: No tengas miembros públicos que puedan o no lanzar excepciones basadas en alguna opción.

Por ejemplo:

Uri ParseUri(string uriValue, bool throwOnError)

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

Un caso que he visto que se usa es con un " AllowEmpty " parámetro al acceder a 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 referencias nulas. (Y el método también excluiría la posibilidad de permitir nulos como un valor específicamente configurable, si así lo desea).

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

¿Fue útil?

Solución

Creo que definitivamente es una mala idea que una decisión de lanzar / no lanzar esté basada en un booleano. Es decir, porque requiere que los desarrolladores que miran un fragmento de código tengan un conocimiento funcional de la API para determinar qué significa el booleano. Esto es malo por sí solo, 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, está 100% claro lo que hacen estas API.

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

Otros consejos

Por lo general, es mejor elegir un mecanismo de manejo de errores y atenerse a él constantemente. Permitir este tipo de código flip-flop realmente no puede mejorar la vida de los desarrolladores.

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

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

Es un poco desagradable desde el punto de vista de la legibilidad. Los desarrolladores tienden a esperar que cada método lance 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 que inhibe las excepciones.

Sin embargo, creo que el artículo de MSDN se refiere estrictamente a las banderas 'throwOnError'. En estos casos, o el error se ignora dentro del método en sí (incorrecto, ya que está oculto) o se devuelve algún tipo de objeto nulo / error (incorrecto, porque no está utilizando excepciones para manejar el error, que es inconsistente y el error en sí mismo propensos).

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

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

Si buscar nulo no es una opción, y si necesito recuperarme de este error específico, prefiero crear una excepción específica para poder detectarlo más tarde y manejarlo adecuadamente. En cualquier otro caso lanzo una excepción general.

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

bool DateTime.TryParse (texto de cadena, fuera de DateTime)

Tener un parámetro donTThrowException derrota todo el punto de 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);
}

son bienvenidos (C # ni siquiera tiene excepciones marcadas). En Java, lo mismo se lograría 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 decidir cómo manejar (o no) la excepción, no de la persona que llama.

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

El libro CLR de Jeffrey Richters a 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. La gente tiende a escribir código para atrapar todo (sus palabras " Un error omnipresente de los desarrolladores que no han sido capacitados adecuadamente sobre el uso adecuado de las excepciones tienden a usar bloques de captura con demasiada frecuencia e incorrectamente. Cuando detecta una excepción, está indicando que esperabas esta excepción, entiendes por qué ocurrió y sabes cómo lidiar con ella. ")

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

Valida tus argumentos y evita las excepciones, y solo captura lo que puedas manejar.

Yo afirmaría 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. Considera algo como:

  Byte [] ReadPacket (bool DontThrowIfNone) // Documentado como que devuelve nulo si ninguno   {     int len ??= ReadByte (DontThrowIfNone); // Documentado como devolviendo -1 si nada     si (len

Si algo como una TimeoutException al leer los datos debe causar una excepción, dicha excepción debe ser lanzada dentro de ReadByte () o ReadMultiBytesbytes (). Sin embargo, si dicha falta de datos se considera normal, entonces la rutina ReadByte () o ReadMultiBytesbytes () no debería generar 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 uno con los métodos Read * y el otro con los métodos TryRead *. Icky.

Puede ser mejor usar una enumeración en lugar de un booleano.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top