¿Cuándo no es un código la obsesión primitiva?

13

Recientemente he leído muchos artículos que describen Obsesión Primitiva como un olor de código.

Hay dos beneficios de evitar la obsesión primitiva:

  1. Hace que el modelo de dominio sea más explícito. Por ejemplo, puedo hablar con un analista de negocios sobre un Código postal en lugar de una cadena que contiene un código postal.

  2. Toda la validación está en un solo lugar en lugar de en toda la aplicación.

Hay muchos artículos por ahí que describen cuando se trata de un olor a código. Por ejemplo, puedo ver el beneficio de eliminar la obsesión primitiva por un código postal como este:

public class Address
{
    public ZipCode ZipCode { get; set; }
}

Aquí está el constructor del código postal:

public ZipCode(string value)
    {
        // perform regex matching to verify XXXXX or XXXXX-XXXX format
        _value = value;
    }

Estaría rompiendo el principio DRY poniendo esa lógica de validación en todos los lugares donde se usa un código postal.

Sin embargo, ¿qué pasa con los siguientes objetos:

  1. Fecha de nacimiento: marque esa fecha mayor que la de mindate y menor que la de hoy.

  2. Salario: verifique que sea mayor o igual a cero.

¿Crearía un objeto DateOfBirth y un objeto Salario? El beneficio es que puede hablar sobre ellos al describir el modelo de dominio. Sin embargo, este es un caso de ingeniería excesiva, ya que no hay mucha validación. ¿Existe alguna regla que describa cuándo y cuándo no eliminar la obsesión primitiva o siempre debe hacerlo si es posible?

Actualizar Supongo que podría crear un alias de tipo en lugar de una clase, lo que ayudaría con el punto uno arriba.

    
pregunta w0051977 31.01.2018 - 11:14

6 respuestas

8
  

La obsesión primitiva está usando tipos de datos primitivos para representar ideas de dominio.

Lo contrario sería "modelado de dominios", o quizás "sobre ingeniería".

  

¿Crearías un objeto DateOfBirth y un objeto Salario?

La introducción de un objeto Salario puede ser una buena idea por la siguiente razón: los números rara vez son independientes en el modelo de dominio, casi siempre tienen una dimensión y una unidad. Normalmente no modelamos nada útil si agregamos una longitud a un tiempo o una masa, y rara vez obtenemos buenos resultados cuando mezclamos metros y pies.

En cuanto a DateOfBirth, probablemente, hay dos cuestiones a considerar. Primero, crear una Fecha no primitiva te da un lugar para centrar todas las preocupaciones extrañas en torno a las matemáticas de fechas. Muchos idiomas proporcionan uno de la caja; DateTime , java.util.Date . Estas son implementaciones de fechas independientes del dominio, pero son primitivas no .

Segundo, DateOfBirth no es realmente una fecha y hora; Aquí en los EE. UU., "fecha de nacimiento" es una construcción cultural / ficción legal. Tendemos a medir la fecha de nacimiento a partir de la fecha local del nacimiento de una persona; Bob, nacido en California, podría tener una fecha de nacimiento "más temprana" que Alice, nacida en Nueva York, a pesar de que es el más joven de los dos.

  

¿Hay alguna regla que describa cuándo y cuándo no eliminar la obsesión primitiva o siempre debes hacerlo si es posible?

Ciertamente no siempre; en las fronteras, las aplicaciones no están orientadas a objetos . Es bastante común ver primitivas utilizadas para describir los comportamientos en tests .

    
respondido por el VoiceOfUnreason 31.01.2018 - 13:25
6

Para ser honesto: depende.

Siempre existe el riesgo de sobre-diseñar su código. ¿Hasta qué punto se utilizarán DateOfBirth y Salary? ¿Solo los usará en tres clases estrechamente acopladas, o se usarán en toda la aplicación? ¿Podría "simplemente" encapsularlos en su propio Tipo / Clase para imponer esa única restricción, o puede pensar en más restricciones / funciones que realmente pertenecen allí?

Tomemos Salario por ejemplo: ¿Tiene alguna operación con "Salario" (por ejemplo, manejo de diferentes monedas, o quizás una función toString ())? Considere lo que Salario es / hace cuando no lo ve como una simple primitiva, y hay una buena posibilidad de que Salario sea su propia clase.

    
respondido por el CharonX 31.01.2018 - 11:51
5

Una posible regla de oro puede depender de la capa del programa. Para el Dominio (DDD), también conocido como Entities Layer (Martin, 2018), esto también podría ser "para evitar primitivas para cualquier cosa que represente un concepto de dominio / negocio". Las justificaciones son las establecidas por el OP: un modelo de dominio más expresivo, la validación de reglas de negocios, que hace explícitos los conceptos implícitos (Evans, 2004).

Un alias de tipo puede ser una alternativa ligera (Ghosh, 2017) y refactorizarse a una clase de entidad cuando sea necesario. Por ejemplo, primero podemos requerir que Salary sea >=0 , y luego decida no permitir $100.33333 y cualquier cosa por encima de $10,000,000 (lo que llevaría a la bancarrota al cliente). El uso de Nonnegative primitivo para representar Salary y otros conceptos complicaría esta refactorización.

Evitar primitivas también puede ayudar a evitar el exceso de ingeniería. Supongamos que necesitamos combinar Salario y Fecha de nacimiento en una estructura de datos: por ejemplo, tener menos parámetros de método o pasar datos entre módulos. Entonces podemos usar una tupla con el tipo (Salary, DateOfBirth) . De hecho, una tupla con primitivas, (Nonnegative, Nonnegative) , no es informativa, mientras que un poco hinchado class EmployeeData escondería los campos requeridos entre otros. La firma en, por ejemplo, calcPension(d: (Salary, DateOfBirth)) está más enfocada que en calcPension(d: EmployeeData) , lo que viola el Principio de Segregación de Interfaz. Del mismo modo, un class SalaryAndDateOfBirth especializado parece incómodo y es probablemente una exageración. Más adelante, podemos elegir definir una clase de datos; Las tuplas y los tipos de dominio elemental nos permiten aplazar tales decisiones.

En una capa externa (por ejemplo, GUI) puede tener sentido "despojar" las entidades a sus primitivas constituyentes (por ejemplo, poner en un DAO). Esto evita filtrar las abstracciones de dominio a las capas externas, como se recomienda en Martin (2018).

Referencias
E. Evans, "Diseño basado en dominio", 2004
D. Ghosh, "Modelado de dominio funcional y reactivo", 2017
R. C. Martin, "Arquitectura limpia", 2018

    
respondido por el Tupolev._ 02.02.2018 - 21:56
3

Si tuvieras una clase de salario, podría tener métodos como ApplyRaise.

Por otro lado, su clase ZipCode no tiene que tener una validación interna para evitar la duplicación de la validación en todos los lugares donde podría tener una clase ZipCodeValidator que podría inyectarse, por lo que si su sistema se ejecuta en direcciones de EE. UU. simplemente inyecte el validador correcto y cuando tenga que manejar direcciones AUS, también puede agregar un nuevo validador.

Otra preocupación es que si tiene que escribir datos en una base de datos a través de EntityFramework, entonces necesitará saber cómo manejar Salary o ZipCode.

No hay una respuesta clara sobre dónde trazar la línea entre cómo deberían ser las clases inteligentes, pero diré que tiendo a mover la lógica de negocios, como la validación, a clases de lógica de negocios en las que las clases de datos son datos puros. ya que esto parece funcionar mejor con EntityFramework.

En cuanto al uso de alias de tipo, el nombre del miembro / propiedad debe proporcionar toda la información necesaria sobre los contenidos, por lo que no usaría alias de tipo.

    
respondido por el Bent 31.01.2018 - 11:33
3

¿Es mejor sufrir por obsesión primitiva o ser un astronauta de arquitectura ?

Ambos casos son patológicos, en un caso tienes muy pocas abstracciones, lo que lleva a la repetición y confunde fácilmente una manzana con una naranja, y en el otro te olvidaste de seguir con eso y comienzas a hacer las cosas, lo que dificulta hacer cualquier cosa.

Como casi siempre, usted desea moderación, una forma intermedia, con suerte bien considerada.

Recuerde que una propiedad tiene un nombre, además de un tipo. Además, descomponer una dirección en sus partes constituyentes podría ser demasiado restrictivo si siempre se hace de la misma manera. No todo el mundo está en el centro de Nueva York.

    
respondido por el Deduplicator 02.02.2018 - 22:47
0

(Lo que probablemente sea realmente la pregunta)

¿Cuándo es el uso del tipo primitivo no un olor de código?

( Respuesta de respuesta )

Cuando el parámetro no tiene reglas, use un tipo primitivo.

Use el tipo primitivo para los gustos de:

htmlEntityEncode(string value)

Usar objeto para los gustos de:

numberOfDaysSinceUnixEpoch(SimpleDate value)

El último ejemplo tiene reglas, es decir, el objeto SimpleDate se compone de Year , Month y Day . Mediante el uso de Objeto en este caso, el concepto de SimpleDate que es válido se puede encapsular dentro del objeto.

    
respondido por el Stoked PHP Engineer 20.06.2018 - 06:09

Lea otras preguntas en las etiquetas