He pasado mucho tiempo leyendo diferentes libros sobre "buen diseño", "patrones de diseño", etc. Soy un gran admirador del enfoque SÓLIDO y cada vez que necesito escribir un código simple, pienso en el futuro. Entonces, si la implementación de una nueva función o una corrección de errores solo requiere agregar tres líneas de código como esta:
if(xxx) {
doSomething();
}
No significa que lo haga de esta manera. Si siento que es probable que este código se vuelva más grande en el futuro cercano, pensaré en agregar abstracciones, mover esta funcionalidad a otro lugar, etc. El objetivo que persigo es mantener la complejidad promedio igual que antes de mis cambios.
Creo que, desde el punto de vista del código, es una buena idea: mi código nunca es lo suficientemente largo y es bastante fácil entender los significados de diferentes entidades, como clases, métodos y relaciones entre clases y objetos.
El problema es que lleva demasiado tiempo, y a menudo siento que sería mejor si simplemente implementara esa característica "tal cual". Se trata de "tres líneas de código" versus "nueva interfaz + dos clases para implementar esa interfaz".
Desde el punto de vista del producto (cuando hablamos del resultado ), las cosas que hago no tienen sentido. Sé que si vamos a trabajar en la próxima versión, tener un buen código es realmente genial. Pero, por otro lado, el tiempo que ha dedicado a hacer que su código sea "bueno" puede haberse dedicado a implementar un par de funciones útiles.
A menudo me siento muy insatisfecho con mis resultados: un código bueno que solo puede hacer A es peor que un código malo que puede hacer A, B, C y D.
¿Es probable que este enfoque genere una ganancia neta positiva para un proyecto de software, o es una pérdida de tiempo?
fuente
Respuestas:
Esto me huele a generalidad especulativa . Sin saber (o al menos estar razonablemente seguro) de que sus clientes necesitarán las funciones B, C y D, simplemente está complicando innecesariamente su diseño. El código más complejo es más difícil de entender y mantener a largo plazo. La complejidad adicional se justifica solo por características adicionales útiles . Pero generalmente somos muy malos para predecir el futuro. La mayoría de las funciones que creemos que pueden ser necesarias en el futuro nunca serán solicitadas en la vida real.
Entonces:
Un código bueno que solo puede hacer A (pero está haciendo eso de manera simple y limpia) es MEJOR que un código malo que puede hacer A, B, C, D (algunos de los cuales podrían ser necesarios en algún momento en el futuro).
fuente
Tiempo de anécdota:
He tenido dos desarrolladores trabajando para mí que se inclinaron hacia la sobre ingeniería de esta manera.
Para uno de ellos, esto básicamente detuvo su productividad, especialmente al iniciar un nuevo proyecto. Más especialmente si el proyecto era, por su naturaleza, bastante simple. En definitiva, un software que funciona ahora es lo que necesitamos. Esto se puso tan mal que tuve que dejarlo ir.
El otro desarrollador que era propenso a la ingeniería excesiva lo compensó siendo extremadamente productivo. Como tal, a pesar de todo el código extraño, todavía entregó más rápido que la mayoría. Sin embargo, ahora que ha avanzado, a menudo me siento irritado por la cantidad de trabajo adicional que se necesita para agregar funcionalidad, ya que tiene que modificar (completamente innecesarias) capas de abstracción, etc.
Entonces, la moraleja es que el exceso de ingeniería consumirá tiempo extra que podría gastarse mejor en hacer cosas útiles. Y no solo su propio tiempo, sino también el de aquellos que tienen que trabajar con su código.
Entonces no lo hagas.
Desea que su código sea lo más simple posible (y no más simple). El manejo de 'maybes' no está simplificando las cosas, si adivinas mal, habrás hecho que el código sea más complejo sin una ganancia real que mostrar.
fuente
Los principios SOLID y KISS / YAGNI son opuestos casi polares. Uno le dirá que si doSomething () no puede justificarse como parte integral del trabajo que está haciendo la clase que lo llama, debería estar en una clase diferente que esté acoplada e inyectada libremente. El otro le dirá que si este es el único lugar donde se utiliza doSthingthing, incluso extraerlo en un método puede haber sido excesivo.
Esto es lo que hace que los buenos programadores valgan su peso en oro. La estructura "adecuada" es una base de caso por caso, que requiere el conocimiento de la base de código actual, la ruta futura del programa y las necesidades de la empresa detrás del programa.
Me gusta seguir esta metodología simple de tres pasos.
Básicamente, así es como combinas KISS con SOLID. Cuando escribe por primera vez una línea de código, por lo que sabe, será única; simplemente tiene que funcionar y a nadie le importa cómo, así que no te preocupes. La segunda vez que coloca el cursor en esa línea de código, ha refutado su hipótesis original; Al volver a visitar este código, es probable que lo extienda o lo conecte desde otro lugar. Ahora, debes limpiarlo un poco; extraer métodos para tidbits de uso común, reducir o eliminar la codificación de copiar / pegar, agregar algunos comentarios, etc. La tercera vez que vuelve a este código, ahora es una intersección importante para las rutas de ejecución de su programa. Ahora debe tratarlo como una parte central de su programa y aplicar las reglas SÓLIDAS siempre que sea posible.
Ejemplo: debe escribir una línea de texto simple en la consola. La primera vez que esto sucede, Console.WriteLine () está bien. Luego vuelve a este código después de que los nuevos requisitos también dicten escribir la misma línea en un registro de salida. En este simple ejemplo, puede que no haya muchos códigos repetitivos de "copiar / pegar" (o tal vez lo hay), pero aún puede hacer una limpieza básica, tal vez extraer un método o dos para evitar incluir las operaciones de E / S en una lógica comercial más profunda . Luego, regresa cuando el cliente desea la misma salida de texto a una tubería con nombre para un servidor de monitoreo. Ahora, esta rutina de salida es un gran problema; estás transmitiendo el mismo texto de tres maneras. Este es el ejemplo de libro de texto de un algoritmo que se beneficiaría de un patrón compuesto; desarrollar una interfaz simple de IWriter con un método Write (), implemente esa interfaz para crear las clases ConsoleWriter, FileWriter y NamedPipeWriter, y una vez más para crear una clase compuesta "MultiWriter", luego exponga una dependencia IWriter en su clase, configure el compuesto MultiWriter con los tres escritores reales y conéctelo. Ahora, es bastante SÓLIDO; a partir de este momento, siempre que los requisitos indiquen que la salida debe ir a cualquier parte nueva, simplemente cree un nuevo IWriter y conéctelo al MultiWriter, sin necesidad de tocar ningún código de trabajo existente.
fuente
1) Tener un código que solo haga lo que se supone que debe hacer.
2) Si planea su código para hacer A, B, C y D, el cliente finalmente le pedirá E.
Su código debe hacer lo que se supone que debe hacer, no debe pensar ahora en implementaciones futuras, porque nunca terminará de cambiar su código de forma continua y, lo que es más importante, sobre-diseñará su código. Debería refactorizar su código tan pronto como sienta que lo necesita debido a las características actuales, sin luchar por prepararlo para algo que aún no va a hacer, a menos que, por supuesto, lo haya planeado como parte de la arquitectura del proyecto.
Le sugiero que lea un buen libro: El programador pragmático . Le abrirá la mente y le enseñará lo que debe hacer y lo que no debe hacer.
Además, Code Complete es un gran recurso lleno de información útil que todo desarrollador (no solo el programador) debería leer.
fuente
Quizás aquí está el problema.
En las primeras etapas, no tienes idea de cuál sería el producto final. O si lo tienes, está mal. Sin lugar a duda. Es como un niño de 14 años que preguntó hace unos días a Programmers.SE si debe, para su futura carrera, elegir entre aplicaciones web y no recuerdo qué más: es bastante obvio que en unos años, las cosas le gustará cambiará, le interesarán otros dominios, etc.
Si, para escribir tres líneas de código, crea una nueva interfaz y dos clases, está realizando una ingeniería excesiva. Obtendrá un código que será difícil de mantener y difícil de leer , solo porque para cada línea de código útil, tiene dos líneas de código que no necesita. Sin contar documentación XML, pruebas unitarias, etc.
Piénselo: si quiero saber cómo se implementa una función en su código, ¿sería más fácil para mí leer veinte líneas de código, o sería más rápido y fácil tener que abrir docenas de clases semivacías y interfaces para descubrir cuál usa cuál, cómo se relacionan, etc.
Recuerde: una base de código más grande significa más mantenimiento. No escriba más código cuando pueda evitarlo.
Su enfoque también es perjudicial en otros lados:
Si necesita eliminar una función, ¿no es más fácil descubrir dónde se utiliza un método específico de veinte líneas, que perder el tiempo para comprender las dependencias entre docenas de clases?
Al depurar, ¿no es más fácil tener un pequeño rastro de pila, o prefiere leer docenas de líneas para descubrir qué se llama por qué?
Para concluir, parece similar a la optimización prematura . Estás tratando de resolver el problema sin siquiera estar seguro de si hay un problema en primer lugar y dónde está. Cuando trabaje en la versión 1 de su producto, implemente las características que necesita implementar en este momento; no pienses en algo que esperas implementar en dos años en la versión 14.
fuente
Escribir un montón de código que (probablemente) nunca se usará es una muy buena manera de obtener un P45 . No tiene una bola de cristal y no tiene idea de la dirección final que tomará el desarrollo, por lo que pasar tiempo en estas funciones solo cuesta dinero sin devolución.
fuente
Tratar de predecir lo que se puede necesitar del código en el futuro a menudo termina siendo una sobre ingeniería innecesaria (un hábito que actualmente estoy tratando de eliminar). Yo diría que solo haga las tres líneas. Cuando surja la necesidad (y no antes), refactorice. De esa manera, su código siempre hace lo que necesita sin ser demasiado complicado y evoluciona una buena estructura de forma natural a través de la refactorización.
fuente
A menudo digo que la codificación es como el lado claro / lado oscuro de la Fuerza: el lado "claro" requiere más esfuerzo pero produce mejores resultados. El lado "oscuro" es rápido y fácil y brinda mayores beneficios de inmediato, pero lo corrompe en el futuro. Una vez que comience por el camino oscuro, dominará para siempre su destino.
Me encuentro con esto todo el tiempo, en cada trabajo que he tenido, es como una maldición de la que no puedo escapar. La cultura de la empresa siempre es el camino del lado oscuro, y los trucos / arreglos rápidos para sacar nuevas funciones, y mis súplicas y gritos de refactorización y escritura de código caen correctamente en oídos sordos, si no conducen a mi terminación por " tratando de cambiar las cosas "(no es broma, eso me ha pasado muchas veces, porque quería introducir patrones de diseño y alejarme de los ataques rápidos).
La triste verdad es que, a menudo, el lado estúpido / oscuro es la forma en que debes pisar, solo debes asegurarte de hacerlo con suavidad. Me he dado cuenta lenta y tristemente de que los programadores que entienden la forma correcta de escribir software, es decir, seguir SOLID, usar patrones de diseño, obedecer SoC, etc., son mucho menos comunes que los piratas despistados que escribirán una
if
declaración para corregir un error, y Cuando surgen más errores, simplemente agregue a esa declaración en lugar de pensar "Tal vez hay una mejor manera de hacer esto" y refactorizar el código para que sea extensible correctamente.fuente
if
es mucho más fácil de mantener queIAbstractBugFixer
de unIAbstractBugFixerFactory
. Cuando, y, SI, puedes agregar un segundoif
, entonces es hora de refactorizar. Los patrones de diseño y SOLID son muy importantes durante la fase de arquitectura, pero no cuando el producto ya se está ejecutando y está escrito en un estilo que todos los miembros del equipo han acordado.Ser consciente de lo que podría suceder (futuro) NUNCA es malo. Pensar en lo que podría ser una mejor manera de hacer algo es parte de lo que te hace bueno en tu trabajo. La parte más difícil es determinar si el tiempo empleado: la relación de pago está justificada. Todos hemos visto situaciones en las que las personas hacen el "fácil si" para detener el sangrado inmediato (y / o gritos), y que a medida que se suman, obtienes un código confuso. Muchos de nosotros también hemos experimentado una abstracción exagerada que es un misterio una vez que el codificador original se mueve, lo que también produce un código confuso.
Observaría su situación y haría estas preguntas:
¿Es este código material de misión crítica, y será significativamente más estable si vuelvo a codificar? En cirugía, ¿es esto refactorizante para salvar vidas, o es simplemente electivo y cosmético?
¿Estoy considerando refactorizar el código que vamos a reemplazar en 6 meses?
¿Estoy dispuesto a tomar tanto tiempo para documentar el diseño y mi razonamiento para futuros desarrolladores como lo estoy pasando para refactorizar?
Con respecto a mi elegante diseño para agregar funciones futuras, ¿es este el código al que los usuarios solicitan cambios cada semana, o es la primera vez que lo toco este año?
Hay momentos en que YAGNI y KISS ganan el día, pero hay días en los que un cambio fundamental te llevará de la espiral descendente a la basura. Mientras sea realista en su evaluación de no solo lo que quiere, sino también de lo que otros tendrán que hacer para mantener su trabajo, podrá determinar mejor cuál es la situación. Ah, y no olvides escribir lo que hiciste y por qué. Salvará a los que te siguen, pero también a ti mismo cuando tengas que volver más tarde.
fuente
En la segunda edición de Stroustrups 'El lenguaje de programación C ++' (no tengo la página disponible), leí
y me fue bien siguiendo los consejos. Por supuesto, hay compensaciones, y debes encontrar un equilibrio, pero los fragmentos cortos son mejor comprobables que un gran desorden de espagueti.
A menudo experimenté que al diferenciar de un caso a dos casos, si piensa en 2 como n-casos, abre una puerta a muchas posibilidades nuevas, en las que podría no haber pensado.
Pero luego tienes que hacer la pregunta de YAGNI: ¿Vale la pena? ¿Será realmente útil? Ser experimentado significa que rara vez se equivocará y, como principiante, más a menudo se equivocará.
Debe ser lo suficientemente crítico como para reconocer un patrón y detectar, si su código es difícil de mantener, debido a demasiada abstracción, o difícil de mantener, porque todo está resuelto en su lugar.
La solución no es esto o aquello, sino pensarlo. :)
fuente
"Un código bueno que solo puede hacer A es peor que un código malo que puede hacer A, B, C y D."
Esto puede tener algún sentido en el desarrollo de productos; Pero la mayoría de los profesionales de TI trabajan en "proyectos" en lugar de desarrollar productos.
En los 'proyectos de TI', si programa un buen componente, funcionará sin problemas durante la vida útil del proyecto, que puede no durar más de 5 o 10 años para entonces, el escenario comercial puede ser obsoleto y un nuevo proyecto / ERP El producto podría haberlo reemplazado. Durante este período de vida de 5/10 años, a menos que haya defectos en su código, ¡nadie notará su existencia y los méritos de sus mejores pensamientos pasarán desapercibidos! (¡a menos que seas bueno tocando fuerte tu propia trompeta!)
¡No muchos tienen la oportunidad de programar el 'Windows Ctl + Alt + Del' y esos pocos que tienen esa oportunidad pueden no darse cuenta del potencial futuro de su código!
fuente
Muchos libros sobre desarrollo ágil y / o ágil ayudarán a reforzar este enfoque: haga lo necesario en este momento. Si sabe que está creando un marco, agregue abstracciones. De lo contrario, no agregue complejidad hasta que lo necesite. Recomiendo Lean Software Development , que introducirá muchos otros conceptos que pueden marcar una diferencia sustancial en la productividad.
fuente
Es curioso cómo la gente habla sobre la manera correcta / incorrecta de hacer las cosas. Al mismo tiempo, la tarea de programar aún es dolorosamente difícil y no ofrece buenas soluciones para escribir grandes sistemas complejos.
Puede ser que algún día nosotros, los programadores, finalmente descubramos cómo escribir software complejo. Hasta entonces, sugiero que siempre comience con la implementación de prototipos "estúpidos" primero, y luego pase el tiempo suficiente en la refactorización para que sus universidades puedan seguir su código.
fuente
Habiendo visto diseños tan prematuramente generalizados que no se ajustaban en absoluto a los requisitos reales que vinieron después, ideé una regla para mí:
Para requisitos hipotéticos solo escriba código hipotético.
Es decir: le recomendamos que piense en los cambios que podrían ocurrir más adelante. Pero solo use estas ideas para elegir un diseño para el código que se pueda cambiar y refactorizar fácilmente si esos requisitos realmente surgen. Incluso puede escribir algún código en su cabeza (código hipotético) que desee escribir en ese caso, ¡pero no escriba ningún código real!
fuente
Creo que la mentalidad que lo ayudará es buscar siempre soluciones concretas a los problemas de codificación en lugar de soluciones abstractas. Las abstracciones solo deben agregarse cuando realmente ayudan a simplificar la base del código (cuando, por ejemplo, le permiten reducir la base del código).
Muchos programadores han descubierto que las abstracciones surgen casi por sí solas, cuando SECAN su código. Los patrones de diseño y las mejores prácticas lo ayudan a encontrar oportunidades para hacerlo, pero no son en sí mismos objetivos que valga la pena perseguir.
fuente
Creo que la sobre ingeniería a menudo parece por la inseguridad sobre la escritura de código. Todos los principios y patrones abstractos deben tratarse como herramientas para ayudarlo. Lo que sucede a menudo es que son tratados como estándares, uno debe ajustarse a ellos.
Creo que un programador siempre está en una mejor posición para decidir cómo abstraer que un axioma.
El resto ya lo ha dicho KeithS
fuente
Pregúntese cuáles son las ventajas de un buen diseño:
Ahora, pregúntese si agregar todas esas capas de abstracciones realmente se suman a alguno de los puntos mencionados anteriormente. Si no es así, lo estás haciendo mal .
Si puede agregar nuevas características agregando 3 líneas de código como esta:
Entonces, por favor, hazlo. Esto indica que su diseño anterior era bueno y fácil de adaptar. Solo una vez que sus clases comiencen a crecer más allá de cierta extensión, use la refactorización para dividir funciones y posiblemente extraer nuevas clases.
Mi regla general es que las nuevas funciones deben implementarse de la manera más minimalista posible, solo cuando algo es demasiado grande para comprender por adelantado (digamos, lleva más de 1 día o medio día implementarlo) puede agregar un diseño global aproximado. A partir de ahí, solo agregue capas de abstracción cuando aumente el tamaño del código. ¡Luego refactorizas después! Después de un tiempo, incluso debería ser natural para ti cuando necesites diseñar un pedazo un poco más, o ir por el camino rápido. Otra indicación de que algún código puede usar alguna limpieza es cuando lo reutiliza. Cada vez que agrega una nueva función, o llama al código antiguo en un lugar nuevo, es un buen momento para mirar el código antiguo y ver si puede mejorarlo un poco antes de agregarlo nuevamente. De esta manera, el código activo se volverá más limpio lentamente, mientras que las partes poco interesantes se pudrirán lentamente y no tomarán nada de su valioso tiempo.
Si trabajas así, nunca sobre-diseñarás nada. Puede llevar algo de disciplina volver al código anterior cuando desee agregar algo nuevo, o dejar el código nuevo un poco más feo de lo que desea, pero trabajará para lograr algo productivo en lugar de estar sobredimensionado.
fuente