Eliminar valores codificados y diseño defensivo frente a YAGNI

10

Primero un poco de historia. Estoy codificando una búsqueda de Age -> Rate. Hay 7 corchetes de edad, por lo que la tabla de búsqueda es de 3 columnas (de | a | tasa) con 7 filas. Los valores rara vez cambian: son tasas legisladas (primera y tercera columnas) que han permanecido igual durante 3 años. Pensé que la forma más fácil de almacenar esta tabla sin codificarla es en la base de datos en una tabla de configuración global, como un único valor de texto que contiene un CSV (así que "65,69,0.05,70,74,0.06" es cómo se almacenarían los niveles 65-69 y 70-74). Relativamente fácil de analizar y luego usar.

Luego me di cuenta de que para implementar esto, tendría que crear una nueva tabla, un repositorio para envolverlo, pruebas de capa de datos para el repositorio, pruebas unitarias alrededor del código que desencadena el CSV en la tabla y pruebas alrededor de la búsqueda en sí. El único beneficio de todo este trabajo es evitar codificar la tabla de búsqueda.

Cuando se habla con los usuarios (que actualmente usan la tabla de búsqueda directamente, mirando una copia impresa), la opinión es más o menos que "las tasas nunca cambian". Obviamente, eso no es realmente correcto: las tasas solo se crearon hace tres años y en el pasado las cosas que "nunca cambian" han tenido la costumbre de cambiar, por lo que para programar defensivamente esto definitivamente no debería almacenar la tabla de búsqueda en la aplicación.

Excepto cuando pienso en YAGNI . La función que estoy implementando no especifica que las tarifas cambiarán. Si las tarifas cambian, todavía cambiarán tan raramente que el mantenimiento ni siquiera es una consideración, y la función no es lo suficientemente crítica como para que algo se vea afectado si hubiera un retraso entre el cambio de la tarifa y la aplicación actualizada.

Casi he decidido que no perderé nada de valor si codifico la búsqueda, y no estoy demasiado preocupado por mi enfoque de esta característica en particular. Mi pregunta es, como profesional, ¿he justificado adecuadamente esa decisión? La codificación de valores es un mal diseño, pero ir a la molestia de eliminar los valores de la aplicación parece violar el principio YAGNI.

EDITAR Para aclarar la pregunta, no me preocupa la implementación real. Me preocupa que pueda hacer algo rápido y malo y justificarlo diciendo YAGNI, o puedo adoptar un enfoque más defensivo y de alto esfuerzo, que incluso en el mejor de los casos tiene beneficios bajos. Como programador profesional, ¿mi decisión de implementar un diseño que sé que es defectuoso simplemente se reduce a un análisis de costo / beneficio?

EDITAR Si bien todas las respuestas fueron muy interesantes, ya que creo que esto se reduce a las opciones de diseño de un individuo, creo que las mejores respuestas fueron @ Corbin y @EZ Hart, ya que traen a colación cosas que no había considerado en la pregunta:

  • la falsa dicotomía de "eliminar correctamente los valores codificados" moviéndolo a la base de datos versus "aplicar eficientemente YAGNI" utilizando codificación rígida. Había una tercera opción de poner la tabla de búsqueda en la configuración de la aplicación, que no incurre en la sobrecarga de la manera correcta y sin la eficiencia de YAGNI. Por lo general, no estamos limitados a ninguna de las decisiones, y luego se reduce a una decisión de costo / beneficio.
  • la generación de código puede reducir la sobrecarga de mover los valores codificados a la base de datos, y de una manera que también elimina mi decisión de ingeniería excesiva de procesar un CSV en la tabla. Potencialmente, esto también agrega un problema de mantenimiento a largo plazo con el código generado si los requisitos básicos cambian para el método de búsqueda. Todo esto solo afecta el análisis de costo / beneficio, y es probable que si hubiera tenido esa automatización disponible, ni siquiera hubiera considerado codificar algo como esto.

Estoy marcando la respuesta de @ Corbin como correcta porque cambia mis supuestos del costo de desarrollo, y probablemente agregaré algunas herramientas de generación de código a mi arsenal en el futuro cercano.

Rebecca Scott
fuente
No olvide que si las tarifas cambian y todo lo que tiene son valores codificados, podría alterar los cálculos de los registros históricos cuando las tarifas cambien (y lo harán, independientemente de lo que su cliente le esté diciendo).
Andy

Respuestas:

6

Encontraste una falla en tu proceso de desarrollo. Cuando es difícil hacer lo correcto (crear la tabla, el repositorio, las pruebas de repositorio, las pruebas de aplanamiento ...), los desarrolladores encontrarán una forma de evitarlo. Esto generalmente implica hacer lo incorrecto. En este caso, es tentador tratar los datos de la aplicación como lógica de la aplicación. No lo hagas En cambio, agregue automatizaciones útiles a su proceso de desarrollo. Usamos CodeSmith para generar el código aburrido y repetitivo que nadie quiere escribir. Después de crear una tabla, ejecutamos CodeSmith y genera los DAO, DTO y elimina las pruebas unitarias para cada uno.

Dependiendo de las tecnologías que esté utilizando, debería tener opciones similares. Muchas herramientas ORM generarán modelos a partir de un esquema existente. Las migraciones de rieles funcionan en la dirección opuesta: tablas de modelos. La metaprogramación en lenguajes dinámicos es particularmente fuerte para eliminar el código repetitivo. Tendrá que trabajar un poco más para generar todo lo que necesita si tiene una aplicación compleja de varios niveles, pero vale la pena. No dejes que la sensación, "wow, esto es un dolor en el cuello" te impida hacer lo correcto.

Ah, y no almacene sus datos en un formato que requiera procesamiento adicional (CSV). Eso solo agrega pasos adicionales que requieren su atención y pruebas.

Corbin March
fuente
No diría que una migración de Rails crea tablas a partir de modelos ... Las migraciones de Rails describen cambios en las tablas. La ejecución de una migración cambia la base de datos. Las propiedades del modelo que coinciden con la estructura de la tabla se crean en tiempo de ejecución.
Kevin Cline
Esto me interesa, no había considerado automatizar partes del esfuerzo inicial a ese nivel. Y tener esa automatización significaría que podría configurar una tabla completa en lugar de usar el CSV para ahorrar tiempo. Lo que me preocupa es el mantenimiento a largo plazo del código generado. Al generarlo por adelantado, supuse que el método de búsqueda nunca cambiará. Supongo que si esa es una posibilidad, debería tomar eso en consideración como parte del costo / beneficio, dando el costo reducido de la placa repetitiva. +1
Rebecca Scott
La pregunta era: "¿debo codificar los valores cuando mi cliente dice que no cambiarán?" y su respuesta es "no, y de hecho debería lanzar un ORM al problema". Estoy en desacuerdo. Hay opciones más simples que permitirían al OP evitar la codificación rígida. Hacer grandes adiciones a la arquitectura para soportar cosas explícitamente designadas como innecesarias no es ético. Es lamentable que muchos desarrolladores estén predispuestos a hacer tales cosas. Y debo decir que no estoy de acuerdo al 100% con su sugerencia de no "dejar que la sensación, 'wow, esto es un dolor en el cuello' te impida hacer lo correcto". ¡Esos sentimientos son importantes!
user1172763
10

Para desconectar y ampliar la respuesta de @ Thorbjørn Ravn Andersen: Mantener el cálculo / búsqueda en un lugar es un buen comienzo.

Su proceso de pensamiento de defensa frente a YAGNI es un problema común. En este caso, sugeriría que sea informado por dos cosas más.

En primer lugar, ¿cómo se presentaron los requisitos del usuario? ¿Especificaron la editabilidad de las tarifas? Si no es así, ¿la complejidad adicional es parte de algo por lo que puede facturar o no? (o si está en el personal, ¿puede pasar su tiempo haciendo otro trabajo en su lugar?) Si es así, definitivamente continúe y entregue lo que razonablemente se le pidió.

En segundo lugar, y quizás lo más importante, es improbable que la simple editabilidad cumpla un requisito real ante un cambio legislativo. Si las tarifas cambian, es probable que haya una fecha límite y algún procesamiento antes y después que tenga que suceder. Además, si esa misma interfaz tiene que hacer algún tipo de procesamiento de reclamos con fecha anterior, entonces la tasa correcta debería buscarse en función de la fecha efectiva de la entrada, en lugar de la fecha real.

En resumen, estoy señalando que un requisito editable real puede ser bastante complejo, por lo que, a menos o hasta que se desarrolle, lo simple probablemente sea mejor.

Buena suerte

sdg
fuente
1
+1 para tu segundo punto. No trato de expresar lo que los cuerpos legislativos podrían hacer en el futuro.
David Thornley
Hazlo en una función de biblioteca. Está bien tener un solo usuario. Cuando y si los requisitos cambian, tiene un lugar para cambiar. (Es posible que deba agregar una función para realizar búsquedas del valor a partir de una fecha en particular.)
BillThor
La mejor y más completa respuesta, +1
Andy
7

Haga que la búsqueda real sea una función de biblioteca. Luego puede ver todo el código usando esa búsqueda en su repositorio de origen, para que sepa qué programas deben actualizarse cuando cambian las tarifas.


fuente
La búsqueda solo se implementará en un solo lugar (algo así como una PensionRateLookupclase tal vez) que luego se usa globalmente. Lo haría independientemente de si está almacenado fuera de la aplicación o codificado, de esa manera solo se PensionRateLookupnecesita mantener la implementación de la clase. Mi problema es cómo he usado YAGNI para llegar a la conclusión de que codificar la tabla de búsqueda es aceptable.
Rebecca Scott
Gracias por tu respuesta sin embargo. Mantener la implementación de búsqueda en un solo lugar es definitivamente un buen diseño.
Rebecca Scott
YAGNI solo es válido SI puede enviar nuevos binarios cuando cambian las tarifas. Si no puede, pero puede alterar la configuración, hágalo un valor de configuración leído al inicio con la representación textual actual como predeterminada.
Envío una nueva versión generalmente por semana, por lo que actualizar la búsqueda no es un problema. Ponerlo en la configuración del cliente es realmente peor ya que no puedo cambiar la configuración del cliente con un nuevo binario. La alternativa como la veo es ponerla en la base de datos, lo que significa mucho esfuerzo.
Rebecca Scott
¿Entonces cuál es el problema? Cuando las tarifas cambian, ¿actualiza el código y lo envía a todos los clientes?
2

Déjame ver si tengo tu pregunta correcta. Tiene 2 opciones para implementar una función: o codifica un valor y su función es fácil de implementar (pero no le gusta la parte del código) o tiene un gran esfuerzo para "rehacer" muchas cosas que se hacen para que pueda desarrollar su función de manera limpia. ¿Es eso correcto?

Lo primero que se me ocurre es "Lo más importante acerca de conocer las buenas prácticas es saber cuándo estás mejor sin ellas".

En este caso, el esfuerzo es muy alto, por lo que puede hacerlo de manera limpia, las posibilidades de efecto colateral de este cambio son grandes y el rendimiento que obtendría es pequeño (como explicó, es probable que no lo haga) cambio).

Usaría el enfoque de código duro (pero prepárelo para que sea flexible en el futuro) y, en caso de cambio de esta tasa en el futuro, aproveche la oportunidad para refactorizar toda esta sección de mal diseño del código. Por lo tanto, el tiempo y el costo podrían estimarse correctamente y el costo de cambiar su valor codificado sería mínimo.

Este sería mi enfoque :)

JSBach
fuente
Gracias @ Oscar. La parte técnica de eso es def. correcto. Y estoy de acuerdo con cómo abordaría el problema, refactorizando solo cuando sea necesario. Entonces, ¿estás diciendo que, como profesional, podemos y debemos elegir nuestros principios de diseño, basados ​​únicamente en el costo / beneficio? Tiene sentido.
Rebecca Scott
@Ben Scott: De nada :). Sí, en mi punto de vista, los principios de diseño se crearon / catalogaron no porque se vean hermosos, sino porque aportan beneficios como la "limpieza" del código, la flexibilidad, la solidez, etc. Pero siempre hay 2 preguntas: 1- ¿Necesito eso? ? 2- ¿Mis restricciones (tiempo, técnico, etc.) me permiten implementarlo? Esto suele ser lo que me lleva a elegir mi principio de diseño. El exceso de ingeniería también es malo;) pd: si cree que es una respuesta interesante, vote por ella para que otras personas también la lean con mayor probabilidad. ¡Gracias! :)
JSBach
upvoted ;-)
Rebecca Scott
2

Este es un elemento que "no cambiará" hasta que lo haga. Es inevitable que cambie, pero ese tiempo puede estar un poco lejos.

Tu justificación es correcta. En este momento, el cliente no solicitó la capacidad de cambiar fácilmente esas tarifas. Como tal, YAGNI.

Sin embargo, lo que no desea es el código que accede a sus tarifas e interpreta los resultados dispersos por toda la base de código. Un buen diseño de OO le permitiría encapsular la representación interna de sus tarifas en una clase, y solo exponer uno o dos métodos necesarios para usar los datos.

Necesitará esa encapsulación para evitar errores de copiar y pegar, o tendrá que realizar una refactorización en todo el código que usa las tasas cuando necesite hacer un cambio en la representación interna. Cuando tomas esa precaución inicial, el enfoque más complicado puede ser un simple intercambio y reemplazo por la versión más destacada.

Además, llame al cliente la limitación del diseño actual. Dígales que para mantener el cronograma codificó los valores, lo que requiere un cambio de codificación para actualizarlos. De esa manera, cuando se dan cuenta de los cambios en las tasas pendientes debido a la nueva legislación, pueden elegir simplemente actualizar la tabla de búsqueda o realizar el cambio más complicado en ese momento. Pero pon esa decisión en su regazo.

Berin Loritsch
fuente
Gracias @berin, no mencioné SRP en la pregunta pero estaba en el plan. Un buen punto es devolverle la propiedad del problema al cliente.
Rebecca Scott
Asignar la culpa de cuando las cosas salgan mal en el futuro no me parece profesional.
Andy
@Andy, ¿qué parte de eso es culpar? Presentar las limitaciones de diseño al cliente les da la oportunidad de priorizar el trabajo complicado ahora y sacar otras cosas de la mesa, retrasar la fecha límite o aceptar el diseño limitado ahora, ya que otras cosas en la mesa son más importantes para ellos. Estás capacitando a tu cliente con la opción sobre su producto. Hacer que su cliente sea consciente del riesgo / recompensa de las elecciones que desea tomar en su interés hará que el proyecto funcione sin problemas.
Berin Loritsch
@BerinLoritsch Pero esta decisión de diseño en particular es similar al uso de piezas de calidad inferior que dicen "Puedo usar masilla de plomería para tapar esta tubería con fugas, ¡esa es su opción más barata!" Las tarifas cambiarán. Es un hecho, y es irresponsable que un profesional construya un sistema que no lo permita. Y los ahorros son probablemente insignificantes en el gran esquema del costo del proyecto. Poner los datos en una tabla; el código que obtiene los datos de búsqueda es ligeramente diferente, la lógica para determinar qué velocidad usar es la misma en ambos sentidos. La única otra decisión es cómo manejar los datos históricos después de ...
Andy
cambios de tarifas, que generalmente se manejan simplemente copiando la tarifa en el registro relevante. No se necesita una pantalla de administrador en este momento, el sistema puede manejar cuándo cambian las tasas y será simple script cambiarlas. Nada de esto debería durar más que unas pocas horas, pero evitará que el sótano se inunde el próximo año cuando la masilla falle.
Andy
2

Divida la diferencia y coloque los datos de velocidad en una configuración. Puede usar el formato CSV que ya tiene, evitar la sobrecarga innecesaria de la base de datos, y si el cambio es necesario, será un cambio que el cliente debería poder hacer sin tener que recompilar / reinstalar y sin desordenar en la base de datos: solo pueden editar el archivo de configuración.

Por lo general, cuando se trata de elegir entre dos extremos (violar YAGNI frente a los datos dinámicos de codificación rígida), la mejor respuesta está en algún punto intermedio. Cuidado con las falsas dicotomías.

Esto supone que toda su configuración está en archivos; Si es un lugar difícil como el registro, probablemente deberías ignorar este consejo :)

EZ Hart
fuente
Pensé en tenerlo en una configuración, pero lo dejé a un lado porque los usuarios no son muy técnicos en cuanto a editar una cadena CSV, por lo que yo sería el que actualizaría la configuración para N usuarios (ya que hago todo el soporte de TI en la organización también), en términos de esfuerzo, sería más fácil hacer el trabajo inicial para ingresarlo en la base de datos que puedo administrar desde un solo lugar. Supongo que si eso no fuera una consideración (si los usuarios fueran capaces de administrar su configuración individual) no tendría este problema. Así que un muy buen punto, gracias.
Rebecca Scott
Si combinó esto con las otras sugerencias de lógica centralizada, entonces es una gran respuesta. Es pura maldad hacer una búsqueda realmente intensa en lugar de ponerla de una manera que permita que se cambie a través de la configuración. Siempre que cualquier otro desarrollador lo encuentre, lo odiarán por ello. Incluso una tienda de desarrollo unipersonal debería considerar qué sucede si son atropellados por un autobús y debe facilitar que el próximo desarrollador cambie los valores sin una versión completa del software. Solo si odias al cliente y odias a todos los demás desarrolladores deberías hacerlo hardcore. Así que básicamente nunca.
simbo1905
2

Pensé que la forma más fácil de almacenar esta tabla sin codificarla es en la base de datos en una tabla de configuración global, como un único valor de texto que contiene un CSV (entonces "65,69,0.05,70,74,0.06" es cómo los niveles 65-69 y 70-74 serían almacenados.

Acabo de crear una tabla DB. (¿Pensabas en almacenar una mesa entera en una, te has vuelto loco?)

La tabla necesita 2 campos NO 3. Edad y tasa. ¡La siguiente fila contiene el valor superior! ¡Estás desnormalizando sin siquiera saberlo!

Aquí está el Sql para obtener la tarifa para alguien de edad que tiene 67 años.

Select * from RateTable where Age in (Select max(age) from RateTable where age <=67) 

No se moleste en hacer una pantalla de mantenimiento ya que está fuera del alcance. Si lo solicitan más tarde, emita una solicitud de cambio y hágalo.

EDITAR: Como otros han dicho, mantenga el código para obtener la tasa centralizada, en caso de que cambie toda la estructura de la Tarifa.

Imbéciles
fuente
Hola @Morons, gracias por tu respuesta. La tabla realmente necesitaría 3 columnas. Es una tasa única por rango de edades, edad mínima a edad máxima (65 a 69 años tienen un 5%). Y solo estoy almacenando una pequeña cantidad de datos para un propósito limitado, entonces, ¿qué tiene de malo suponer la estructura y elegir un CSV en lugar de una tabla dedicada completa? Probablemente agregaría un delimitador de fila, y lo dividiría por fila, luego por columna, y extraería las columnas en los campos obligatorios. Esto siempre se leerá mucho más de lo que se escribirá, así que no estoy demasiado preocupado por una tabla completa.
Rebecca Scott
La siguiente fila contiene el valor superior del rango ... Esto es duplicación de datos. Decir <69 y> 7 son lo mismo. La única razón para tener ambos valores es si el rango puede tener agujeros. ... Estoy diciendo que use una tabla porque es más fácil (y un mejor diseño). No entiendo por qué crees que almacenar un csv en una tabla te ahorrará tiempo o esfuerzo. Todavía necesitas una llamada de DB solo para obtener esa cadena, luego tienes que analizarla. Como le mostré anteriormente, puede obtener la tarifa con una sola llamada de DB.
Morons
Ah cierto. Mantuve la tabla en sí similar al material de origen ... y me perdí que la edad es el límite superior en su respuesta porque tenía los tristes que les di.
Rebecca Scott
1
"¡Estás desmoralizando sin siquiera saberlo!" - Al principio pensé que esto era un error tipográfico y que querías decir 'desnormalizar', pero cuanto más lo pensaba, más parecía que tenía razón en ambos sentidos :)
EZ Hart
@ez Hart LOL cierto.
Rebecca Scott
1

Estoy de acuerdo con la mayoría de las respuestas dadas. También agregaría que la consistencia con el resto de la aplicación es importante. Si este es el único lugar en el código que tiene valores codificados, probablemente sorprenderá a los encargados del mantenimiento. Esto es especialmente cierto si se trata de una base de código grande y estable. Si es un programa pequeño mantenido por usted, la decisión es menos importante.

Tengo un recuerdo lejano de leer a un conocido tipo ágil / OOP (como Dave Thomas o Kent Beck o alguien) que dice que su regla general para la duplicación de código fue dos y solo dos veces: no refactorice la primera vez que duplica algo desde es posible que solo lo escriba dos veces en su vida. Pero la tercera vez ...

Eso no responde exactamente a la pregunta, ya que estás cuestionando a YAGNI, pero creo que habla de la flexibilidad general de las reglas ágiles. El punto ágil es adaptarse a la situación y avanzar.

Dave
fuente
Gracias @Dave, eso es útil. Soy el único responsable, desde el inicio, pero es una base de código grande y relativamente estable (miles de archivos, más de 100 tablas, etc.) y todavía estoy constantemente sorprendido (y consternado). La coherencia es definitivamente uno de mis objetivos en el futuro.
Rebecca Scott
0

Código duro en una función.

  • Cuando los valores cambian, puede facturar al cliente nuevamente
  • Cuando los valores cambian, es probable que el formato de la tabla tenga que cambiar, hacer CSV perderá tiempo
  • Más fácil de implementar, mayores posibilidades de estar dentro del presupuesto con el contrato actual
  • Puede encontrar fácilmente cuándo necesitará actualizarse
earlNameless
fuente
Permítanme instalar este valor deficiente para que, cuando se rompa en un año, me repitan los negocios. Eso suena sombrío, porque lo es.
Andy