¿Qué es realmente "Soft Coding"?

87

En este artículo por 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.

Cito:

  

Si todas las constantes de reglas de negocios se almacenaran 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 se compartieron uno, archivo grande (o, a la inversa, una gran cantidad de archivos de configuración pequeños); la implementación de cambios en las reglas de negocios no requiere un nuevo código, sino el cambio manual de 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 resumido. 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 ruptura 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, debajo de la cual se aplica una regla y encima de la otra.

¿Cómo se refiere a él desde un archivo de configuración, o incluso un #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 simple cadena de constantes. 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 estoy diciendo que el artículo esté mal; 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 constante, definición o variable de configuración, no solo no es necesario, sino que complica las cosas, pero 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 eso?

    
pregunta K. Gkinis 08.04.2016 - 11:14

8 respuestas

100

El autor está advirtiendo contra la abstracción prematura.

La línea if (ledgerAmt > 500000) parece ser el tipo de regla comercial que usted esperaría ver para grandes sistemas de negocios complejos cuyos requisitos son increíblemente complejos pero precisos y están bien documentados.

Normalmente, este tipo de requisitos son casos excepcionales / de borde en lugar de una lógica reutilizable. Esos requisitos generalmente son propiedad y son mantenidos por analistas de negocios y expertos en la materia, en lugar de por ingenieros

(Tenga en cuenta que la "propiedad" de los requisitos por parte de los analistas de negocios / expertos en estos casos suele ocurrir cuando los desarrolladores que trabajan en campos especializados no tienen suficiente experiencia en el dominio. los expertos del dominio para proteger contra requisitos ambiguos o mal escritos.)

Cuando se mantienen sistemas cuyos requisitos están llenos de casos de borde y lógica altamente compleja, generalmente no hay manera de abstraer esa lógica o hacerla más mantenible; los intentos de tratar de construir abstracciones pueden ser contraproducentes fácilmente, no solo resultando en una pérdida de tiempo, sino también en un código menos mantenible.

  

¿Cómo se está refiriendo 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 realice otra elección, quedará atornillado (porque cuando cambia, nada le garantiza que cambiará en ambos archivos). Eso es claramente peor para la depuración.

Este tipo de código tiende a estar protegido por el hecho de que el propio código probablemente tenga un mapeo uno a uno con los requisitos; es decir, cuando un desarrollador sabe que la cifra 500000 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 mayor de que alguien que cambie el valor de const no se dé cuenta de que 500000 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 no se dieron cuenta de que habían cambiado.

Este escenario ocurre mucho en software legal / financiero a medida (por ejemplo, lógica de cotización de seguros): las personas que escriben tales documentos no son ingenieros, y no tienen problemas para copiar + pegar fragmentos completos 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 similar a los requisitos (incluida la codificación de todos los datos) como sea posible.

La realidad de estos requisitos es que generalmente no se mantienen copia + pegado por mucho tiempo, y los valores a veces cambian de forma regular, pero a menudo no cambian en conjunto, por lo que se trata de racionalizar o abstraer esos requisitos. o simplifíquelos de cualquier manera termina creando más de un dolor de cabeza de mantenimiento que simplemente traduciendo los requisitos literalmente en código.

    
respondido por el Ben Cottrell 08.04.2016 - 11:58
44

El artículo tiene un buen punto. ¿Cómo puede ser una mala práctica extraer constantes a 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 como está escrito es fácil de seguir.

  

Además, mañana, el gobierno dice "A partir del 5/3/2050, debe   agregue AUTHLDG-122B en lugar de AUTHLDG-1A ".

Sí, entonces cambia el código. El punto 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 obtiene una lógica más compleja, pero el punto es que tiene que hacer una llamada de juicio, y algunas veces la solución más simple simplemente es la mejor.

  

¿Cómo sabes que no lo necesitarás más adelante? O alguien más   para el caso?

Este es el punto del principio YAGNI. No diseñe para un futuro desconocido que puede resultar completamente diferente, diseño para el presente. Es correcto 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 del código de software que sabe que podría cambiar independientemente de la lógica de la aplicación central. Nunca codificaría una cadena de conexión a una base de datos, ya que sabe que podría 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 ser cambiadas por diferentes personas.

Pero en el caso del ejemplo de código, las cadenas y números codificados son una parte integral de la lógica de la aplicación. Es posible que un archivo cambie su nombre debido a algún cambio de política fuera de su control, pero es tan concebible que necesitamos agregar una nueva comprobación if-branch para una condición diferente. Extraer los nombres de los archivos y los números realmente rompe la cohesión en este caso.

    
respondido por el JacquesB 08.04.2016 - 12:27
26

El artículo continúa hablando sobre 'Enterprise Rule Engine's', que probablemente son un mejor ejemplo de lo que está argumentando en contra.

La lógica es que puede generalizar hasta el punto en el que su configuración se vuelve tan complicada que contiene su propio lenguaje de programación.

Por ejemplo, el código de estado para documentar la asignación en el ejemplo podría moverse a un archivo de configuración. Pero entonces necesitarías expresar una relación compleja.

<statecode id="AZ">
    <document id="SR008-04X"/>
    <document id="SR008-04XI"/>
</statecode>

¿Quizás también pondría la cantidad del libro mayor?

<statecode id="ALL">
    <document id="AUTHLDG-1A" rule="ledgerAmt >= 50000"/>
</statecode>

Pronto encontrará que está programando en un nuevo idioma que ha inventado y guardando ese código en archivos de configuración que no tienen control de fuente o cambio.

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 inyección de dependencia (DI). Es decir, tendrías un 'código duro'

InvoiceRules_America2007 : InvoiceRules

que reemplazaría con un codificado por hardware o más configurable

InvoiceRules_America2008 : InvoiceRules

cuando la ley o los requisitos comerciales cambiaron.

    
respondido por el Ewan 08.04.2016 - 13:26
17
  

Por el contrario, "500000" no es simplemente un número. Es un significativo   valor, 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á utilizando, es la idea del límite / límite, a continuación   cuál regla se aplica, y encima de cuál otra.

Y eso se expresa por tener (y podría argumentar que incluso el comentario es redundante):

 if (ledgerAmnt >= 500000) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
  }

Esto es solo repetir lo que hace el código:

LEDGER_AMOUNT_REQUIRING_AUTHLDG1A=500000
if (ledgerAmnt >= LEDGER_AMOUNT_REQUIRING_AUTHLDG1A) {
    //Ledger of 500K or more requires AUTHLDG-1A
    attachDocument("AUTHLDG-1A");
}

Tenga en cuenta que el autor asume que el significado de 500000 está vinculado a esta regla; no es un valor que sea o pueda ser reutilizado en otro lugar:

  

El único y único cambio de regla de negocio que este anterior Soft Coding   alguna vez podría explicar es un cambio en el monto del libro mayor que se requiere   una forma AUTHLDG-1A. Cualquier otro cambio de regla de negocio requeriría incluso   más trabajo: configuración, documentación, código, etc.

El punto principal del artículo, en mi opinión, es que a veces un número es solo un número: no tiene un significado adicional más que lo que se transmite en el código y no es probable que se use en otros lugares. Por lo tanto, resumir torpemente lo que el código está haciendo (ahora) en un nombre de variable por el simple hecho de evitar valores codificados es, en el mejor de los casos, una repetición innecesaria.

    
respondido por el Thanos Tintinidis 08.04.2016 - 12:22
8

Las otras respuestas son correctas y reflexivas. Pero aquí está mi respuesta corta y dulce.

  Rule/value          |      At Runtime, rule/value…
  appears in code:    |   …Is fixed          …Changes
----------------------|------------------------------------
                      |                 |
  Once                |   Hard-code     |   Externalize
                      |                 |   (soft-code)
                      |                 |
                      |------------------------------------
                      |                 |
  More than once      |   Soft-code     |   Externalize
                      |   (internal)    |   (soft-code)
                      |                 |
                      |------------------------------------

Si las reglas y los valores especiales aparecen en un lugar en el código, y no cambian durante el tiempo de ejecución, entonces codifique 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 código blando. La codificación suave para una regla podría definir una clase / método específico o utilizar el patrón de creación . Para los valores, la codificación suave puede significar la definición de una sola constante o enumeración para el valor que se utilizará en todo su código.

Si las reglas o los valores especiales pueden cambiar durante el tiempo de ejecución, debe externalizarlos. Se hace comúnmente actualizando los valores en una base de datos. O actualice los valores en la memoria manualmente por un usuario que ingresa datos. También se realiza mediante el almacenamiento de valores en un archivo de texto (XML, JSON, texto sin formato, lo que sea) que se analiza repetidamente para cambiar la fecha y hora de modificación del archivo.

    
respondido por el Basil Bourque 08.04.2016 - 21:57
7

Esta es la trampa en la que caemos cuando usamos un problema de juguetes y luego planteamos solo las soluciones de strawman , cuando intentamos ilustrar un problema real.

En el ejemplo dado, no hace una diferencia entre si los valores dados están codificados como valores en línea, o definidos como consts.

Es el código circundante el que haría del ejemplo un horror de mantenimiento y codificación. Si no hay ningún código circundante, entonces el fragmento está bien, al menos en un entorno de refactorización constante. En un entorno donde la refactorización tiende a no ocurrir, los mantenedores de ese código ya están muertos, por razones que pronto serán obvias.

Mira, si hay un código que lo rodea, entonces suceden cosas malas con claridad.

Lo primero malo es que el valor 50000 se usa para otro valor en alguna parte, por ejemplo, el monto del libro contable sobre el cual la tasa impositiva cambia en algunos estados ... luego, cuando ocurre un cambio, el mantenedor no tiene forma de saber cuándo. encuentra esas dos instancias de 50000 en el código, ya sean las mismas 50k, o las 50ks no relacionadas por completo. ¿Y debería también buscar 49999 y 50001, en caso de que alguien también los use como constantes? Esto no es una llamada a que se plonicen esas variables en un archivo de configuración de un servicio separado: pero codificarlas en línea también está mal. En su lugar, deben ser constantes, definidas y con alcance dentro de la clase o archivo en el que se utilizan. Si las dos instancias de 50k utilizan la misma constante, entonces probablemente representan la misma restricción legislativa; Si no, probablemente no lo hacen; y de cualquier manera, tendrán un nombre, que será menos opaco que un número en línea.

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, en esencia, claves foráneas para algún sistema de archivos, o base de datos, o de dondequiera que attachDocument () obtiene los archivos. Pero las cadenas no te 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 adjuntar? El mantenedor queda completamente en la oscuridad, y todo lo que tiene es una cadena, que puede aparecer varias veces en el código y significar cosas diferentes cada vez que aparece. En un lugar, "SR008-04X" es un código de trucos. En otro, es un comando para ordenar cuatro cohetes de refuerzo SR008. Aquí, ¿es un nombre de archivo? ¿Están estos relacionados? Alguien acaba de cambiar esa función para mencionar otro archivo, "CLIENTE". Entonces a usted, mal mantenedor, se le ha dicho que el archivo "CLIENTE" necesita ser renombrado a "CLIENTE". Pero la cadena "CLIENTE" aparece 937 veces en el código ... ¿dónde empiezas a buscar?

El problema del juguete es que todos los valores son inusuales y se puede garantizar razonablemente que son únicos en el código. No "1" o "10" sino "50,000". No "cliente" o "informe", sino "SR008-04X".

El strawman es que la única otra forma de abordar el problema de las constantes impenetrablemente opacas es agruparlas en el archivo de configuración de algún servicio no relacionado.

Juntos, pueden usar estas dos falacias para probar que cualquier argumento es verdadero.

    
respondido por el Dewi Morgan 10.04.2016 - 01:15
2

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 propio 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 extenderse siempre que haya un cambio.

Otro problema es cómo manejar estas reglas y sus cambios en su control de 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, algunos cambios dependen de cuándo se haya otorgado un permiso o se haya emitido una factura. La propia regla que contiene la verificación de la versión que debe aplicar.

Además, como la constante es privada, no se puede utilizar incorrectamente en ninguna otra parte del código.

Luego ten una lista de todas las reglas y aplica la lista.

Otro problema es cómo manejar las constantes. 500000 puede parecer discreto, pero hay que tener mucho cuidado para asegurarse de que se convierta correctamente. Si se aplica una aritmética de punto flotante, se puede convertir a 500,000.00001, por lo que la comparación con 500,000.00000 podría fallar. O incluso peor, 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 realizada por usted, no por la adivinación del compilador A menudo, esto se hace convirtiéndolo en algún BigInteger o BigDecimal antes de usarlo.

    
respondido por el Bent 10.04.2016 - 20:01
2

Aunque no se menciona directamente en la pregunta, me gustaría señalar que lo importante no es enterrar la lógica de negocios en el código.

Código, como en el ejemplo anterior, que codifica los requisitos de negocios especificados externamente realmente deben vivir en una parte distinta del árbol de origen, quizás llamado businesslogic o algo similar, y se debe tener cuidado de asegúrese de que solo codifique los requisitos comerciales de la manera más simple, legible y concisa posible, con un mínimo de repetición y con comentarios claros e informativos.

No se debe mezclar no con el código de "infraestructura" que implementa la funcionalidad necesaria para llevar a cabo la lógica empresarial, como, por ejemplo, la implementación de attachDocument() método en el ejemplo, o por ejemplo UI, logging 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 del método único (o el mejor).

Dicho código de lógica de negocios también debe escribirse con la suficiente claridad para que, si se lo mostrara a un experto en el dominio de negocios sin conocimientos de codificación, puedan darle sentido. Como mínimo, si los requisitos comerciales cambian, el código que los codifica debe ser lo suficientemente claro como para que incluso un programador nuevo sin familiaridad con el código base pueda localizar, revisar y actualizar fácilmente la lógica empresarial, asumiendo que no se requiere ninguna funcionalidad cualitativamente nueva.

Idealmente, dicho código también se escribiría en un lenguaje específico del dominio para imponer la separación entre la lógica de negocios y la infraestructura subyacente, pero eso puede ser innecesariamente complicado para una aplicación interna básica. Dicho esto, si eres eg. vendiendo el software a varios clientes que necesitan su propio conjunto personalizado de reglas de negocio, un lenguaje de scripting simple y específico para el dominio (quizás, por ejemplo, basado en un Lua sandbox ) podría ser la cosa adecuada.

    
respondido por el Ilmari Karonen 11.04.2016 - 00:08

Lea otras preguntas en las etiquetas