En este artículo de Alex Papadimoulis, puedes ver este fragmento:
private void attachSupplementalDocuments()
{
if (stateCode == "AZ" || stateCode == "TX") {
//SR008-04X/I are always required in these states
attachDocument("SR008-04X");
attachDocument("SR008-04XI");
}
if (ledgerAmnt >= 500000) {
//Ledger of 500K or more requires AUTHLDG-1A
attachDocument("AUTHLDG-1A");
}
if (coInsuredCount >= 5 && orgStatusCode != "CORP") {
//Non-CORP orgs with 5 or more co-ins require AUTHCNS-1A
attachDocument("AUTHCNS-1A");
}
}
Realmente no entiendo este artículo.
Yo cito:
Si cada constante de la regla de negocio se almacenara en algún archivo de configuración, la vida sería mucho [más ( sic )] difícil para todos los que mantienen el software: habría muchos archivos de código que compartían uno, un archivo grande (o, por el contrario, una gran cantidad de pequeños archivos de configuración); desplegar cambios en las reglas de negocio no requiere un código nuevo, sino cambiar manualmente los archivos de configuración; y la depuración es mucho más difícil.
Este es un argumento en contra de tener el entero constante "500000" en un archivo de configuración, o el "AUTHCNS-1A" y otras constantes de cadena.
¿Cómo puede ser esto una mala práctica?
En este fragmento, "500000" no es un número. No es, por ejemplo, lo mismo que:
int doubleMe(int a) { return a * 2;}
donde 2, es un número que no necesita ser abstraído. Su uso es obvio y no representa algo que pueda reutilizarse más adelante.
Por el contrario, "500000" no es simplemente un número. Es un valor significativo, uno que representa la idea de un punto de interrupción en la funcionalidad. Este número podría usarse en más de un lugar, pero no es el número que está usando; es la idea del límite / límite, por debajo del cual se aplica una regla y por encima del cual se aplica otra.
¿Cómo es referirse a él desde un archivo de configuración, o incluso a #define
, const
o lo que sea que proporcione su idioma, peor que incluir su valor? Si más adelante el programa, o algún otro programador, también requiere ese límite, para que el software haga otra elección, estás jodido (porque cuando cambia, nada te garantiza que cambiará en ambos archivos). Eso es claramente peor para la depuración.
Además, si mañana el gobierno exige "Desde el 5/3/2050, debe agregar AUTHLDG-122B en lugar de AUTHLDG-1A", esta constante de cadena no es una constante de cadena simple. Es uno que representa una idea; es solo el valor actual de esa idea (que es "lo que agrega si el libro mayor está por encima de 500k").
Déjame aclarar. No digo que el artículo esté equivocado; Simplemente no entiendo; tal vez no esté muy bien explicado (al menos para mi pensamiento).
Entiendo que reemplazar cada posible valor literal o numérico de cadena con una variable constante, definida o de configuración, no solo no es necesario, sino que complica demasiado las cosas, sino que este ejemplo en particular no parece pertenecer a esta categoría. ¿Cómo sabes que no lo necesitarás más adelante? ¿O alguien más para el caso?
Respuestas:
El autor advierte contra la abstracción prematura.
La línea se
if (ledgerAmt > 500000)
parece al tipo de regla comercial que esperaría ver para sistemas comerciales grandes y complejos cuyos requisitos son increíblemente complejos pero precisos y bien documentados.Por lo general, ese tipo de requisitos son casos excepcionales / límite en lugar de lógica reutilizable útil. Esos requisitos suelen ser propiedad y mantenidos por analistas comerciales y expertos en la materia, en lugar de ingenieros
(Tenga en cuenta que la `` propiedad '' de los requisitos por parte de los analistas de negocios / expertos en estos casos generalmente ocurre cuando los desarrolladores que trabajan en campos especializados no tienen suficiente experiencia en el dominio; aunque todavía esperaría una comunicación / cooperación completa entre los desarrolladores y los expertos en el dominio para proteger contra requisitos ambiguos o mal escritos).
Cuando se mantienen sistemas cuyos requisitos están llenos de casos límite y lógica altamente compleja, generalmente no hay forma de abstraer esa lógica o hacerla más fácil de mantener; Los intentos de intentar crear abstracciones pueden ser contraproducentes, no solo dando como resultado una pérdida de tiempo, sino también un código menos mantenible.
Este tipo de código tiende a estar protegido por el hecho de que el código en sí mismo probablemente tenga una correspondencia uno a uno con los requisitos; es decir, cuando un desarrollador sabe que la
500000
cifra aparece dos veces en los requisitos, ese desarrollador también sabe que aparece dos veces en el código.Considere el otro escenario (igualmente probable) donde
500000
aparece en varios lugares en el documento de requisitos, pero los expertos en la materia deciden cambiar solo uno de ellos; allí tiene un riesgo aún peor de que alguien que cambia elconst
valor no se dé cuenta de que500000
se usa para significar cosas diferentes, por lo que el desarrollador lo cambia en el único lugar donde lo encuentra en el código y termina rompiendo algo que ellos no se dio cuenta de que habían cambiado.Este escenario sucede mucho en software legal / financiero a medida (por ejemplo, lógica de cotización de seguros): las personas que escriben dichos documentos no son ingenieros, y no tienen problemas para copiar + pegar fragmentos enteros de la especificación, modificando algunas palabras / números, pero dejando la mayor parte igual.
En esos escenarios, la mejor manera de lidiar con los requisitos de copiar y pegar es escribir el código de copiar y pegar, y hacer que el código se vea lo más similar posible a los requisitos (incluida la codificación de todos los datos).
La realidad de tales requisitos es que generalmente no permanecen copiando + pegando por mucho tiempo, y los valores a veces cambian de manera regular, pero a menudo no cambian en tándem, por lo que tratar de racionalizar o abstraer esos requisitos o simplificarlos De cualquier forma, terminan creando más dolores de cabeza de mantenimiento que simplemente traducir los requisitos textualmente al código.
fuente
Those requirements are typically owned and maintained by business analysts and subject matter experts, rather than by engineers
lo cual no siempre es una buena idea. A veces, el acto de convertir esos requisitos en código revelará casos de esquina donde los requisitos no están bien definidos o están definidos de tal manera que va en contra del interés comercial. Si los analistas y desarrolladores de negocios pueden cooperar para lograr un objetivo común, entonces se pueden evitar muchos problemas.El artículo tiene un buen punto. ¿Cómo puede ser una mala práctica extraer constantes en un archivo de configuración? Puede ser una mala práctica si complica el código innecesariamente. Tener un valor directamente en el código es mucho más simple que tener que leerlo desde un archivo de configuración, y el código tal como está escrito es fácil de seguir.
Sí, entonces cambias el código. El objetivo del artículo es que no es más complicado cambiar el código que cambiar un archivo de configuración.
El enfoque descrito en el artículo no se escala si obtienes una lógica más compleja, pero el punto es que tienes que hacer un juicio y, a veces, la solución más simple simplemente es la mejor.
Este es el punto del principio YAGNI. No diseñe para un futuro desconocido que pueda resultar completamente diferente, diseñe para el presente. Tiene razón en que si el valor 500000 se usa en varios lugares del programa, por supuesto, debe extraerse a una constante. Pero este no es el caso en el código en cuestión.
Softcoding es realmente una cuestión de separación de preocupaciones . La información de softcode que sabe que puede cambiar independientemente de la lógica de la aplicación principal. Nunca codificaría una cadena de conexión a una base de datos, porque sabe que puede cambiar independientemente de la lógica de la aplicación y tendrá que diferenciarla para diferentes entornos. En una aplicación web, nos gusta separar la lógica empresarial de las plantillas html y las hojas de estilo, ya que pueden cambiar de forma independiente e incluso pueden ser cambiadas por diferentes personas.
Pero en el caso de la muestra de código, las cadenas y los números codificados son una parte integral de la lógica de la aplicación. Es concebible que un archivo pueda cambiar su nombre debido a algún cambio de política fuera de su control, pero es igualmente concebible que necesitemos agregar una nueva comprobación if-branch para una condición diferente. Extraer los nombres y números de archivo en realidad rompe la cohesión en este caso.
fuente
if
se basan en una variable diferente! Si la variable que necesita no está accesible desde la configuración, debe modificar el software de todos modos.El artículo continúa hablando sobre 'Enterprise Rule Engine's, que probablemente son un mejor ejemplo de lo que está argumentando.
La lógica es que puede generalizar hasta el punto en que su configuración se vuelve tan complicada que contiene su propio lenguaje de programación.
Por ejemplo, el código de estado para la asignación de documentos en el ejemplo podría moverse a un archivo de configuración. Pero entonces necesitarías expresar una relación compleja.
¿Quizás también pondrías la cantidad del libro mayor?
Pronto descubrirá que está programando en un nuevo lenguaje que ha inventado y guardando ese código en archivos de configuración que no tienen fuente ni control de cambios.
Cabe señalar que este artículo es de 2007 cuando este tipo de cosas era un enfoque común.
Hoy en día probablemente resolveríamos el problema con la inyección de dependencia (DI). Es decir, tendrías un 'código duro'
que reemplazarías con un código rígido o más configurable
cuando la ley o los requisitos comerciales cambiaron.
fuente
if
declaraciones para dar diferentes valores para cada cliente? Eso suena como algo que debería estar en un archivo de configuración. Estar en un tipo de archivo u otro, siendo todo lo demás igual, no es una razón para no controlar / rastrear / respaldar el archivo. @ewan parece estar diciendo que el archivo de un DSL no se puede guardar como parte del proyecto por alguna razón, cuando incluso los activos que no son de código, como las imágenes y los archivos de sonido y la documentación, sí lo son .Y eso se expresa al tener (y podría argumentar que incluso el comentario es redundante):
Esto es solo repetir lo que está haciendo el código:
Tenga en cuenta que el autor supone que el significado de 500000 está vinculado a esta regla; no es un valor que se pueda reutilizar en otro lugar:
El punto principal del artículo, en mi opinión, es que a veces un número es solo un número: no tiene ningún significado adicional, aparte de lo que se transmite en el código y no es probable que se use en otro lugar. Por lo tanto, resumir torpemente lo que está haciendo el código (ahora) en un nombre de variable solo para evitar valores codificados es, en el mejor de los casos, una repetición innecesaria.
fuente
LEDGER_AMOUNT_REQUIRING_AUTHLDG1A
, ya no escribiría el comentario en el código. Los comentarios no son bien mantenidos por los programadores. Si la cantidad cambiara, laif
condición y el comentario no estarían sincronizados. Por el contrario, la constanteLEDGER_AMOUNT_REQUIRING_AUTHLDG1A
nunca se desincroniza consigo misma y explica su propósito sin el comentario innecesario.attachDocument("AUTHLDG-2B");
línea no actualice el nombre constante al mismo tiempo. En este caso, creo que el código es bastante claro sin un comentario ni una variable explicativa. (Aunque podría tener sentido tener una convención que indique la sección apropiada del documento de requisitos comerciales a través de comentarios de código. Según dicha convención, un comentario de código que haga eso sería apropiado aquí.)LEDGER_AMOUNT_REQUIRING_ADDITIONAL_DOCUMENTS
(lo que probablemente debería haber hecho en primer lugar). También tengo la costumbre de poner los ID de requisitos comerciales en los mensajes de confirmación de Git, no en el código fuente.attachIfNecessary()
método y simplemente recorrería todos ellos.Las otras respuestas son correctas y reflexivas. Pero aquí está mi respuesta corta y dulce.
Si las reglas y los valores especiales aparecen en un lugar en el código y no cambian durante el tiempo de ejecución, codifique tal como se muestra en la pregunta.
Si las reglas o los valores especiales aparecen en más de un lugar en el código, y no cambian durante el tiempo de ejecución, entonces codifique suavemente. La codificación suave de una regla podría definir una clase / método específico o utilizar el patrón Builder . Para los valores, la codificación suave puede significar definir una sola constante o enumeración para el valor que se utilizará en su código.
Si las reglas o los valores especiales pueden cambiar durante el tiempo de ejecución, debe externalizarlos. Se realiza comúnmente actualizando valores en una base de datos. O actualice los valores en la memoria manualmente por un usuario que ingresa datos. También se realiza almacenando valores en un archivo de texto (XML, JSON, texto sin formato, lo que sea) que se escanea repetidamente para modificar el cambio de fecha y hora de la modificación del archivo.
fuente
Esta es la trampa en la que caemos cuando usamos un problema de juguete y luego planteamos solo soluciones de sorpresa , cuando tratamos de ilustrar un problema real.
En el ejemplo dado, no importa si los valores dados están codificados como valores en línea, o definidos como consts.
Es el código circundante lo que haría del ejemplo un horror de mantenimiento y codificación. Si no es ningún código que rodea, a continuación, el fragmento está bien, al menos en un ambiente de refactorización constante. En un entorno donde la refactorización tiende a no ocurrir, los encargados de mantener ese código ya están muertos, por razones que pronto se harán evidentes.
Mira, si hay un código que lo rodea, entonces claramente suceden cosas malas.
Lo primero malo es que el valor 50000 se usa para otro valor en algún lugar, por ejemplo, el monto del libro mayor sobre el cual cambia la tasa impositiva en algunos estados ... luego, cuando ocurre el cambio, el mantenedor no tiene forma de saber cuándo encuentra esos dos instancias de 50000 en el código, ya sea que signifiquen el mismo 50k o 50k completamente no relacionados. ¿Y también debe buscar 49999 y 50001, en caso de que alguien los use también como constantes? Esta no es una llamada para incluir esas variables en un archivo de configuración de un servicio separado: pero codificarlas en línea también es claramente incorrecto. En cambio, deben ser constantes, definidas y definidas dentro de la clase o archivo en el que se usan. Si las dos instancias de 50k usan la misma constante, entonces probablemente representan la misma restricción legislativa; si no, probablemente no; y de cualquier manera, tendrán un nombre,
Los nombres de los archivos se pasan a una función, attachDocument (), que acepta los nombres de los archivos base como una cadena, sin ruta ni extensión. Los nombres de los archivos son, esencialmente, claves foráneas para algún sistema de archivos, o base de datos, o de donde attachDocument () obtiene los archivos. Pero las cadenas no le dicen nada sobre esto: ¿cuántos archivos hay? ¿Qué tipos de archivo son? ¿Cómo sabe, al abrir un nuevo mercado, si necesita actualizar esta función? ¿A qué tipo de cosas se pueden unir? El mantenedor se queda completamente en la oscuridad, y todo lo que tiene es una cadena, que puede aparecer varias veces en el código y significa diferentes cosas cada vez que aparece. En un lugar, "SR008-04X" es un código trampa. En otro, es un comando ordenar cuatro cohetes de refuerzo SR008. Aquí' sa nombre de archivo? ¿Están relacionados? Alguien acaba de cambiar esa función para mencionar otro archivo, "CLIENTE". Entonces, pobre mantenedor, le han dicho que el archivo "CLIENTE" debe ser renombrado como "CLIENTE". Pero la cadena "CLIENTE" aparece 937 veces en el código ... ¿dónde empiezas a buscar?
El problema del juguete es que los valores son todos inusuales y se puede garantizar razonablemente que sean únicos en el código. No "1" o "10" sino "50,000". No "cliente" o "informe" sino "SR008-04X".
El problema es que la única otra forma de abordar el problema de las constantes opacas impenetrables es colocarlas en el archivo de configuración de algún servicio no relacionado.
Juntos, pueden usar estas dos falacias para demostrar que cualquier argumento es verdadero.
fuente
if (...) { attachDocument(...); }
.Hay varios problemas en esto.
Un problema es si se debe construir un motor de reglas para que todas las reglas sean fácilmente configurables fuera del programa. La respuesta en casos similares a esto es a menudo no. Las reglas cambiarán de formas extrañas que son difíciles de predecir, lo que significa que el motor de reglas debe ampliarse siempre que haya un cambio.
Otro problema es cómo manejar estas reglas y sus cambios en el control de su versión. La mejor solución aquí es dividir las reglas en una clase para cada regla.
Eso permite que cada regla tenga su propia validez, algunas reglas cambian cada año, algunas cambian dependiendo de cuándo se ha otorgado un permiso o se emite una factura. La regla misma que contiene la verificación de la versión que tiene que aplicar.
Además, como la constante es privada, no se puede utilizar de forma incorrecta en ningún otro lugar del código.
Luego tenga una lista de todas las reglas y aplique la lista.
Otro problema es cómo manejar las constantes. 500000 puede parecer discreto, pero se debe tener mucho cuidado para asegurarse de que se convierta correctamente. Si se aplica una aritmética de coma flotante, podría convertirse a 500,000.00001, por lo que podría fallar una comparación con 500,000.00000. O peor aún, 500000 siempre funciona según lo previsto, pero de alguna manera 565000 falla cuando se convierte. Asegúrese de que la conversión sea explícita y que usted no la haya compilado adivinando. A menudo, esto se hace convirtiéndolo en BigInteger o BigDecimal antes de que se use.
fuente
Si bien no se menciona directamente en la pregunta, me gustaría señalar que lo importante es no enterrar la lógica de negocios en el código.
El código, como el ejemplo anterior, que codifica los requisitos comerciales especificados externamente realmente debería vivir en una parte distinta del árbol de origen, quizás nombrado
businesslogic
o algo similar, y se debe tener cuidado para garantizar que solo codifique los requisitos comerciales de manera simple, legible y de manera concisa como sea posible, con un mínimo de repetitivo y con comentarios claros e informativos.Debería no ser mezclado con el código de "infraestructura" que implementa la funcionalidad necesaria para llevar a cabo la lógica de negocio, tales como, por ejemplo, la puesta en práctica del
attachDocument()
método en el ejemplo, o por ejemplo, la interfaz de usuario, registro o código de base de datos en general. Si bien una forma de imponer esta separación es "codificar" toda la lógica de negocios en un archivo de configuración, esto está lejos de ser el único (o el mejor) método.Dicho código de lógica de negocios también debe escribirse con la suficiente claridad como para que, si se lo mostraras a un experto en dominios de negocios sin habilidades de codificación, podrían tener sentido. Por lo menos, si los requisitos comerciales cambian, el código que los codifica debe ser lo suficientemente claro como para que incluso un nuevo programador sin familiaridad previa con la base de código pueda localizar, revisar y actualizar fácilmente la lógica comercial, suponiendo que no se requiere ninguna funcionalidad cualitativamente nueva.
Idealmente, dicho código también se escribiría en un lenguaje específico de dominio para forzar la separación entre la lógica empresarial y la infraestructura subyacente, pero eso puede ser innecesariamente complicado para una aplicación interna básica. Dicho esto, si por ejemplo está vendiendo el software a múltiples clientes que necesitan cada uno su propio conjunto personalizado de reglas comerciales, un lenguaje de scripting específico de dominio simple (tal vez, por ejemplo, basado en un sandbox Lua ) podría ser la solución.
fuente