Esta es una P relacionada: ¿Es el uso de la cláusula finalmente para hacer el trabajo después de devolver un mal estilo / peligroso?
En la Q referenciada, el código finalmente está relacionado con la estructura utilizada y la necesidad de buscar previamente. Mi pregunta es un poco diferente, y creo que es pertinente para el público en general. Mi ejemplo particular es una aplicación winform de C #, pero esto también se aplicaría al uso de C ++ / Java.
Noto bastantes bloques try-catch-finally donde hay mucho código no relacionado con excepciones y manejo / limpieza de excepciones enterrado dentro del bloque. Y admitiré mi predisposición a tener bloques try-catch-finally muy ajustados con el código estrechamente relacionado con la excepción y el manejo. Aquí hay algunos ejemplos de lo que estoy viendo.
Los bloques de prueba tendrán muchas llamadas preliminares y variables que se establecerán antes del código que podrían arrojarse. La información de registro también se configurará y ejecutará en el bloque de prueba.
Finalmente, los bloques tendrán llamadas de formateo de formato / módulo / control (a pesar de que la aplicación está a punto de finalizar, como se expresa en el bloque catch), así como la creación de nuevos objetos como paneles.
Aproximadamente:
methodName (...) { tratar { // Mucho código para el método ... // código que podría arrojar ... // Mucho más código para el método y un retorno ... } atrapar (algo) {// manejar excepción} finalmente { // alguna limpieza debido a una excepción, cerrando cosas // más código para las cosas que se crearon (ignorando que podrían haberse producido excepciones) ... // tal vez cree algunos objetos más } }
El código funciona, por lo que tiene algún valor. No está bien encapsulado y la lógica es un poco complicada. Estoy (dolorosamente) familiarizado con los riesgos de cambiar el código, así como la refactorización, por lo que mi pregunta se reduce a querer conocer la experiencia de los demás con un código estructurado de manera similar.
¿El mal estilo justifica hacer los cambios? ¿Alguien ha sido gravemente quemado por una situación similar? ¿Te gustaría compartir los detalles de esa mala experiencia? ¿Dejarlo porque estoy exagerando y no es tan mal estilo? ¿Obtener los beneficios de mantenimiento de ordenar las cosas?
fuente
finally
. Todos los buenos usos están cubiertos por RAII / RRID / SBRM (el acrónimo que desee).finally
hace en C #). ¿Cuál es el equivalente de C ++? En lo que estoy pensando es en el código después decatch
, y eso aplica lo mismo para el código después de C #finally
.Environment.FailFast()
; es posible que no se ejecute si tiene una excepción no detectada. Y se vuelve aún más complicado si tiene un bloque iterador con elfinally
que itera manualmente.Respuestas:
He pasado por una situación muy similar cuando tuve que lidiar con un terrible código heredado de Windows Forms escrito por desarrolladores que claramente no sabía lo que estaban haciendo.
En primer lugar, no estás exagerando. Este es un mal código. Como dijiste, el bloqueo debería ser sobre abortar y prepararte para parar. No es hora de crear objetos (especialmente paneles). Ni siquiera puedo empezar a explicar por qué esto es malo.
Habiendo dicho eso...
Mi primer consejo es: si no está roto, ¡no lo toques!
Si su trabajo es mantener el código, debe hacer todo lo posible para no romperlo. Sé que es doloroso (he estado allí) pero tienes que hacer todo lo posible para no romper lo que ya está funcionando.
Mi segundo consejo es: si tiene que agregar más funciones, intente mantener la estructura de código existente tanto como sea posible para no romper el código.
Ejemplo: si hay una declaración de caso de cambio horrible que cree que podría ser reemplazada por una herencia adecuada, debe tener cuidado y pensar dos veces antes de decidir comenzar a mover las cosas.
Definitivamente encontrará situaciones en las que una refactorización es el enfoque correcto, pero tenga cuidado: es más probable que la refactorización de código introduzca errores . Debe tomar esa decisión desde la perspectiva de los propietarios de la aplicación, no desde la perspectiva del desarrollador. Por lo tanto, debe pensar si vale la pena refactorizar el esfuerzo (dinero) necesario para solucionar el problema. He visto muchas veces que un desarrollador pasa varios días arreglando algo que no está realmente roto solo porque piensa que "el código es feo".
Mi tercer consejo es: te quemarás si rompes el código, no importa si es tu culpa o no.
Si ha sido contratado para dar mantenimiento, realmente no importa si la aplicación se está desmoronando porque alguien más tomó malas decisiones. Desde la perspectiva del usuario, estaba funcionando antes y ahora lo rompió. ¡Lo rompiste!
Joel pone muy bien en su artículo explicando varias razones por las que no debe reescribir el código heredado.
http://www.joelonsoftware.com/articles/fog0000000069.html
Así que deberías sentirte realmente mal por ese tipo de código (y nunca deberías escribir algo así), pero mantenerlo es un monstruo completamente diferente.
Acerca de mi experiencia: tuve que mantener el código durante aproximadamente 1 año y, finalmente, pude reescribirlo desde cero, pero no todo de una vez.
Lo que sucedió es que el código era tan malo que las nuevas características eran imposibles de implementar. La aplicación existente tenía serios problemas de rendimiento y usabilidad. Finalmente, me pidieron que realizara un cambio que me llevaría de 3 a 4 meses (principalmente porque trabajar con ese código me llevó mucho más tiempo de lo habitual). Pensé que podría reescribir toda esa pieza (incluida la implementación de la nueva función deseada) en unos 5-6 meses. Llevé esta propuesta a las partes interesadas y acuerdan reescribirla (por suerte para mí).
Después de reescribir esta pieza, entendieron que podía entregar cosas mucho mejores de lo que ya tenían. Así que pude reescribir toda la aplicación.
Mi enfoque fue reescribirlo pieza por pieza. Primero reemplacé toda la interfaz de usuario (formularios Windows Forms), luego comencé a reemplazar la capa de comunicación (llamadas al servicio web) y, por último, reemplacé toda la implementación del servidor (era una aplicación de tipo cliente / servidor).
Un par de años después y esta aplicación se ha convertido en una hermosa herramienta clave utilizada por toda la empresa. Estoy 100% seguro de que nunca hubiera sido posible si no hubiera reescrito todo el asunto.
Aunque pude hacerlo, la parte importante es que las partes interesadas lo aprobaron y pude convencerlos de que valía la pena el dinero. Entonces, si bien debe mantener la aplicación existente, haga todo lo posible para no romper nada y si puede convencer a los propietarios sobre el beneficio de reescribirla, intente hacerlo como Jack the Ripper: por partes.
fuente
Si no hay necesidad de cambiar el código, déjelo como está. Pero si tiene que cambiar el código porque tiene que corregir algún error o cambiar alguna funcionalidad, tendrá que cambiarlo, le guste o no. En mi humilde opinión, a menudo es más seguro y menos propenso a errores, si primero lo refactoriza en piezas más pequeñas, mejor comprensibles y luego agrega la funcionalidad. Cuando tenga a mano herramientas de refactorización automáticas, esto se puede hacer de manera relativamente segura, con un riesgo muy pequeño de introducir nuevos errores. Y le recomiendo que obtenga una copia del libro de Michael Feathers
http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
lo que le dará sugerencias valiosas sobre cómo hacer que el código sea más comprobable, agregar pruebas unitarias y evitar romper el código al agregar nuevas funciones.
Si lo hace bien, es de esperar que su método sea lo suficientemente simple como el try-catch-finally ya no contendrá ningún código no relacionado en los lugares incorrectos.
fuente
Suponga que tiene seis métodos para llamar, cuatro de los cuales solo deberían llamarse si el primero se completa sin errores. Considere dos alternativas:
vs
En el segundo código, está tratando excepciones como códigos de retorno. Conceptualmente, esto es idéntico a:
Si vamos a ajustar todos los métodos que pueden arrojar una excepción en un bloque try / catch, ¿cuál es el punto de tener excepciones en una característica del lenguaje? No hay beneficio y hay más mecanografía.
La razón por la que tenemos excepciones en los idiomas en primer lugar es que en los viejos tiempos de los códigos de retorno, a menudo tuvimos situaciones anteriores en las que teníamos múltiples métodos que podían devolver errores, y cada error significaba abortar todos los métodos por completo. Al comienzo de mi carrera, en los viejos días de C, vi un código como este por todas partes:
Este fue un patrón increíblemente común, y uno que las excepciones se resolvieron de manera muy clara al permitirle volver al primer ejemplo anterior. Una regla de estilo que dice que cada método tiene su propio bloque de excepción lo arroja completamente por la ventana. ¿Por qué molestarse, entonces?
Siempre debe tratar de hacer que el bloque try sobrepase el conjunto de código que conceptualmente es una unidad. Debe rodear el código para el cual si hay algún error en la unidad de código, entonces no tiene sentido ejecutar ningún otro código en la unidad de código.
Volviendo al propósito original de las excepciones: recuerde que fueron creadas porque a menudo el código no puede manejar fácilmente los errores justo cuando realmente ocurren. El punto de excepciones es permitirle lidiar con errores significativamente más adelante en el código, o más arriba en la pila. La gente olvida eso y, en muchos casos, los captura localmente cuando estarían mejor simplemente documentando que el método en cuestión puede lanzar esta excepción. Hay demasiado código en el mundo que se ve así:
Aquí, solo está agregando capas, y las capas agregan confusión. Solo haz esto:
Además de eso, creo que si sigue esa regla y se encuentra con más de un par de bloques try / catch en el método, debe preguntarse si el método en sí es demasiado complejo y debe dividirse en varios métodos. .
La conclusión: un bloque de prueba debe contener el conjunto de código que debe abortarse si se produce un error en cualquier parte del bloque. Si este conjunto de código es una línea o mil líneas, no importa. (Aunque si tienes mil líneas en un método, tienes otros problemas).
fuente
method0
que casi se puede esperar que se produzca una excepción, y aquellos en los que no. Por ejemplo, supongamosmethod1
que a veces arroja unInvalidArgumentException
, pero si no lo hace, pondrá un objeto en un estado temporalmente no válido que será corregido pormethod2
, lo que se espera que siempre vuelva a poner el objeto en un estado válido. Si el método 2 arroja unInvalidArgumentException
código que espera capturar tal excepción,method1
lo capturará, aunque ...