¿Evitar o adoptar construcciones de C#que rompen la edición y continúen?
debugging lambda (10)
Mi pregunta es si es una buena práctica evitar el uso de estas construcciones más avanzadas en favor de un código que es muy, muy fácil de depurar.
Yo diría que cada vez que te obligues a escribir un código que es:
- Menos expresivo
- Más
- Repetido (evitando métodos genéricos)
- No portátil (nunca debug y test 64bit ??!?!?)
Usted está agregando a su costo de mantenimiento general mucho más que la pérdida de la funcionalidad "Editar y continuar" en el depurador.
Escribiría el mejor código posible, no el código que hace que una característica de su IDE funcione.
Desarrollo y mantengo una gran aplicación de WinForms (500k + LOC) escrita en C # 2.0. Es multiusuario y actualmente se implementa en aproximadamente 15 máquinas. El desarrollo del sistema está en curso (se puede considerar como un beta perpetuo), y se hace muy poco para proteger a los usuarios de posibles errores nuevos que podrían introducirse en una compilación semanal.
Por esta razón, entre otras, me he vuelto muy dependiente de editar y continuar en el depurador. Ayuda no solo con la búsqueda y corrección de errores, sino también en algunos casos con el desarrollo continuo. Creo que es extremadamente valioso poder ejecutar código recién escrito desde el contexto de una aplicación en ejecución; no es necesario recompilar y agregar un punto de entrada específico al nuevo código (tener que agregar opciones de menú ficticias, botones, etc. a La aplicación y el hecho de recordar eliminarlos antes de la siguiente compilación de producción): todo se puede probar y probar en tiempo real sin detener el proceso.
Mantengo la función de editar y continuar con tanta consideración que escribo activamente el código para ser totalmente compatible con él. Por ejemplo, evito:
- Métodos anónimos y delegados en línea (a menos que sea completamente imposible de reescribir)
- Métodos genéricos (excepto en el código de utilidad estable, invariable)
- Dirigir proyectos a ''Cualquier CPU'' (es decir, nunca ejecutarse en 64 bits)
- Inicializando campos en el punto de declaración (la inicialización se mueve al constructor)
- Escritura de bloques de enumerador que utilizan el
yield
(excepto en el código de utilidad)
Ahora, soy plenamente consciente de que las nuevas características de lenguaje en C # 3 y 4 son en gran medida incompatibles con editar y continuar (expresiones lambda, LINQ, etc.). Esta es una de las razones por las que me he resistido a mover el proyecto a una versión más nueva de Framework.
Mi pregunta es si es una buena práctica evitar el uso de estas construcciones más avanzadas en favor de un código que sea muy, muy fácil de depurar. ¿Hay legitimidad en este tipo de desarrollo, o es un desperdicio? Además, lo que es más importante, ¿alguna de estas construcciones (expresiones lambda, métodos anónimos, etc.) incurren en sobrecargas de rendimiento / memoria que el código bien escrito, de edición y continuación compatible podría evitar? ... o ¿el funcionamiento interno del compilador de C # hace que tales construcciones avanzadas se ejecuten más rápido que el código ''expandido'' escrito manualmente?
Confiando en Editar y Cont. suena como si hubiera muy poco tiempo dedicado al diseño de nuevas funciones, y mucho menos a las pruebas unitarias. Me parece que esto es malo porque probablemente terminas haciendo una gran cantidad de depuración y corrección de errores, y algunas veces las correcciones de errores causan más errores, ¿verdad?
Sin embargo, es muy difícil juzgar si debe o no usar las características del lenguaje o no, porque esto también depende de muchos, muchos otros factores: requisitos del proyecto, plazos de lanzamiento, habilidades de equipo, costo de administración del código después de la refactorización, por nombrar algunos .
¡Espero que esto ayude!
El problema que parece tener es:
Le toma demasiado tiempo reconstruir su aplicación, volver a iniciarla y llegar al bit de IU en la que está trabajando.
Como todos han dicho, Unit Tests ayudará a reducir la cantidad de veces que tiene que ejecutar su aplicación para encontrar / corregir errores en ninguno de los códigos de UI; Sin embargo, no ayudan con problemas como el diseño de la interfaz de usuario.
En el pasado, escribí una aplicación de prueba que cargará rápidamente la interfaz de usuario en la que estoy trabajando y la rellenaré con datos ficticios, a fin de reducir el tiempo del ciclo.
La separación de ninguno de los códigos de UI en otras clases que puedan probarse con pruebas unitarias, le permitirá utilizar todas las construcciones de C # en esas clases. Entonces, solo puede limitar las construcciones en uso en el código de la interfaz de usuario.
Cuando comencé a escribir muchas pruebas unitarias, mi uso de "editar y continuar" se redujo, por lo que casi no lo uso aparte del código de UI.
Me encanta ''Editar y continuar''. Me parece que es un gran habilitador para el desarrollo / depuración interactivos y a mí también me resulta bastante molesto cuando no funciona.
Si ''Editar y continuar'' ayuda a su metodología de desarrollo, entonces, por supuesto, tome decisiones para facilitarlo, teniendo en cuenta el valor de lo que está renunciando.
Una de mis cosas favoritas es que editar cualquier cosa en una función con expresiones lambda rompe ''Editar y continuar''. Si me tropiezo lo suficiente, puedo escribir la expresión lambda. Estoy en la valla con expresiones lambda. Puedo hacer algunas cosas más rápido con ellos, pero no me ahorran tiempo si termino de escribirlas más tarde.
En mi caso, evito usar expresiones lambda cuando realmente no necesito hacerlo. Si se interponen en el camino, puedo envolverlos en una función para que pueda "Editar y Continuar" el código que los utiliza. Si son gratuitos puedo escribirlos.
Tu acercamiento no necesita ser blanco y negro.
Podrías probar Test Driven Development . Me pareció muy útil evitar el uso del depurador. Comienza con una nueva prueba (por ejemplo, prueba de unidad), y luego solo ejecuta esta prueba de unidad para verificar su desarrollo, no necesita que la aplicación se ejecute todo el tiempo. ¡Y esto significa que no necesita editar y continuar!
Sé que TDD es la palabra de moda actual, pero realmente funciona para mí. Si necesito usar el depurador, lo tomo como un fallo personal :)
Quería aclarar un poco estas cosas.
¿Es una buena práctica evitar el uso de estas construcciones más avanzadas en favor de un código que es muy, muy fácil de depurar?
Editar y continuar no es realmente depuración, se está desarrollando. Hago esta distinción porque las nuevas características de C # son muy debugable. Cada versión del lenguaje agrega soporte de depuración para nuevas características de idioma para hacerlas lo más fácil posible.
Todo se puede probar y probar en tiempo real sin detener el proceso.
Esta afirmación es engañosa. Es posible con Editar y Continuar verificar que un cambio solucione un problema muy específico. Es mucho más difícil verificar que el cambio sea correcto y que no rompa muchos otros problemas. Es decir, porque editar y continuar no modifica los archivos binarios en el disco y, por lo tanto, no permite elementos como la prueba de unidades.
En general, sí, creo que es un error evitar nuevos contratos de C # a favor de permitir la edición y continuar. Editar y continuar es una gran característica (realmente me encantó cuando la encontré por primera vez en mis días en C ++). Pero es valioso ya que un servidor de producción auxiliar no compensa las ganancias de producción de las nuevas funciones C # IMHO.
Realmente debería introducir la integración continua, que puede ayudarlo a encontrar y eliminar errores antes de implementar el software. Especialmente los proyectos grandes (creo que 500k bastante grandes) necesitan algún tipo de validación.
http://www.codinghorror.com/blog/2006/02/revisiting-edit-and-continue.html
Con respecto a la pregunta específica: no evite estas construcciones y no confíe en sus locas habilidades de depuración: intente evitar los errores (en el software implementado). Escribir pruebas unitarias en su lugar.
Si bien no hay nada intrínsecamente incorrecto en su enfoque, lo limita a la cantidad de expresividad que entiende el IDE. Su código se convierte en un reflejo de sus capacidades, no del lenguaje, y, por lo tanto, su valor general en el mundo del desarrollo disminuye porque se está absteniendo de aprender otras técnicas para mejorar la productividad. Evitar LINQ a favor de Editar y Continuar se siente como un enorme costo de oportunidad para mí personalmente, pero la paradoja es que tiene que ganar algo de experiencia con él antes de poder sentirse de esa manera.
Además, como se ha mencionado en otras respuestas, la prueba unitaria de su código elimina la necesidad de ejecutar toda la aplicación todo el tiempo y, por lo tanto, resuelve su dilema de una manera diferente. Si no puede hacer clic derecho en su IDE y probar solo las 3 líneas de código que le interesan, ya está haciendo demasiado trabajo durante el desarrollo.
Sin querer sonar trillado, es una buena práctica escribir pruebas de unidad / integración en lugar de confiar en Editar-Continuar.
De esa manera, usted gasta el esfuerzo una vez, y cada otra vez es ''gratis'' ...
Ahora no te estoy sugiriendo que escribas retrospectivamente unidades para todo tu código; más bien, cada vez que tenga que corregir un error, comience escribiendo una prueba (o, más comúnmente, varias pruebas) que demuestre la solución.
Como @Dave Swersky menciona en los comentarios, el libro de Mchael Feathers, Working Effectively with Legacy Code es un buen recurso (es legacy 5 minutos después de que lo escribiste, ¿verdad?)
Así que sí, creo que es un error evitar nuevos contratos de C # en favor de permitir la edición y continuar; PERO también creo que es un error abrazar nuevas construcciones por el simple hecho de hacerlo, y especialmente si llevan a un código más difícil de entender.
También he trabajado en grandes proyectos de beta permanente.
He utilizado métodos anónimos y delegados en línea para mantener algunos bits de lógica de uso uno relativamente cerca de su único lugar de uso.
He usado métodos y clases genéricos para reutilización y confiabilidad.
He inicializado las clases en constructores en la medida de lo posible, para mantener invariantes de clase y eliminar la posibilidad de errores causados por objetos en estados no válidos.
He usado bloques de enumerador para reducir la cantidad de código necesario para crear una clase de enumerador a unas pocas líneas.
Todos estos son útiles para mantener un proyecto grande que cambia rápidamente en un estado confiable.
Si no puedo editar y continuar, lo edito y comienzo de nuevo. Esto me cuesta unos segundos la mayoría del tiempo, un par de minutos en casos desagradables. Vale la pena por las horas que me ahorra una mayor capacidad de razonar sobre el código y una mayor confiabilidad a través de la reutilización.
Hay muchas cosas que haré para que sea más fácil encontrar errores, pero no si también será más fácil tener errores.