Usando la verificación de tipos estática para protegerse contra errores de negocios

13

Soy un gran fan de la comprobación de tipos estáticos. Te impide cometer errores estúpidos como este:

// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too

Pero no te impide cometer errores estúpidos como este:

Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues

El problema surge cuando está utilizando el mismo tipo para representar tipos de información obviamente diferentes. Estaba pensando que una buena solución sería extender la clase Integer , solo para evitar errores de lógica de negocios, pero no para agregar funcionalidad. Por ejemplo:

class Age extends Integer{};
class Pounds extends Integer{};

class Adult{
    ...
    public void setAge(Age age){..}
    public void setWeight(Pounds pounds){...}
}

Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));

¿Se considera esto una buena práctica? ¿O hay problemas de ingeniería imprevistos en el futuro con un diseño tan restrictivo?

    
pregunta J-bob 02.07.2018 - 23:06

3 respuestas

12

Básicamente, está pidiendo un sistema de unidades (no, no pruebas de unidades, "unidad" como en "unidad física", como medidores, voltios, etc.).

En tu código, Age representa el tiempo y Pounds representa la masa. Esto lleva a cosas como conversión de unidades, unidades base, precisión, etc.

Hubo / hay intentos de introducir tal cosa en Java, por ejemplo:

Los dos últimos parecen estar viviendo en esta cosa de github: enlace

C ++ tiene unidades a través de Boost

LabView incluye un grupo de unidades .

Hay otros ejemplos en otros idiomas. (Las ediciones son bienvenidas)

  

¿Se considera esto una buena práctica?

Como puede ver arriba, cuanto más probable sea que se use un idioma para manejar valores con unidades, más nativamente es compatible con unidades. LabView se utiliza a menudo para interactuar con dispositivos de medición. Como tal, tiene sentido tener una característica de este tipo en el idioma y su uso sería sin duda una buena práctica.

Pero en cualquier lenguaje de alto nivel de propósito general, donde la demanda es baja por tal rigor, es probable que sea inesperado.

  

¿O hay problemas de ingeniería imprevistos en el futuro con un diseño tan restrictivo?

Mi conjetura sería: rendimiento / memoria. Si maneja muchos valores, la sobrecarga de un objeto por valor podría convertirse en un problema. Pero como siempre: La optimización prematura es la raíz de todos los males .

Creo que el "problema" más grande es acostumbrar a la gente a él, ya que la unidad generalmente se define implícitamente de esta manera:

class Adult
{
    ...
    public void setAge(int ageInYears){..}

La gente se confundirá cuando tenga que pasar un objeto como un valor para algo que aparentemente podría describirse con un simple int , cuando no están familiarizados con los sistemas de unidades.

    
respondido por el null 03.07.2018 - 00:31
6

A diferencia de la respuesta de null, definir un tipo para una "unidad" puede ser beneficioso si un entero no es suficiente para describir la medición. Por ejemplo, el peso a menudo se mide en unidades múltiples dentro del mismo sistema de medición. Piense en "libras" y "onzas" o "kilogramos" y "gramos".

Si necesita un nivel de medición más granular, es beneficioso definir un tipo para la unidad:

public struct Weight {
    private int pounds;
    private int ounces;

    public Weight(int pounds, int ounces) {
        // Value range checks go here
        // Throw exception if ounces is greater than 16?
    }

    // Getters go here
}

Para algo como "age", recomiendo calcular eso en el tiempo de ejecución según la fecha de nacimiento de la persona:

public class Adult {
    private Date birthDate;

    public Interval getCurrentAge() {
        return calculateAge(Date.now());
    }

    public Interval calculateAge(Date date) {
        // Return interval between birthDate and date
    }
}
    
respondido por el Greg Burghardt 03.07.2018 - 15:41
2

Lo que parece estar buscando es conocido como tipos etiquetados . Son una forma de decir "este es un número entero que representa la edad", mientras que "este es también un número entero pero representa el peso" y "no se puede asignar uno a otro". Tenga en cuenta que esto va más allá de las unidades físicas, como metros o kilogramos: es posible que tenga en mi programa "alturas de personas" y "distancias entre puntos en un mapa", ambos medidos en metros, pero no compatibles entre sí ya que asignan uno a el otro no tiene sentido desde la perspectiva de la lógica empresarial.

Algunos idiomas, como Scala, admiten tipos etiquetados con bastante facilidad (consulte el enlace anterior). En otros, puede crear sus propias clases de envoltorio, pero esto es menos conveniente.

Validación, por ejemplo, verificar que la altura de una persona sea "razonable" es otro problema. Puede colocar dicho código en su clase Adult (constructor o definidores), o dentro de sus clases etiquetadas de tipo / envoltorio. En cierto modo, las clases incorporadas como URL o UUID cumplen ese rol (entre otras, por ejemplo, proporcionar métodos de utilidad).

Si el uso de tipos etiquetados o clases de envoltura realmente ayudará a mejorar su código, dependerá de varios factores. Si sus objetos son simples y tienen pocos campos, el riesgo de asignarlos mal es bajo y el código adicional necesario para usar tipos etiquetados puede no valer la pena. En sistemas complejos con estructuras complejas y muchos campos (especialmente si muchos de ellos comparten el mismo tipo primitivo), puede ser útil.

En el código que escribo, a menudo creo clases de envoltorio si paso los mapas. Los tipos como Map<String, String> son muy opacos por sí mismos, por lo que envolverlos en clases con nombres significativos como NameToAddress ayuda mucho. Por supuesto, con los tipos etiquetados, podría escribir Map<Name, Address> y no necesitar la envoltura para todo el mapa.

Sin embargo, para tipos simples como cadenas o enteros, he encontrado que las clases de envoltorio (en Java) son demasiado molestas. La lógica de negocios regular no era tan mala, pero surgieron varios problemas al serializar estos tipos a JSON, asignándolos a objetos DB, etc. Puede escribir asignadores y enlaces para todos los grandes marcos (por ejemplo, Jackson y Spring Data), pero el trabajo adicional y el mantenimiento relacionado con este código compensarán lo que gane con el uso de estos envoltorios. Por supuesto, YMMV y en otro sistema, el balance puede ser diferente.

    
respondido por el Michał Kosmulski 04.07.2018 - 21:00

Lea otras preguntas en las etiquetas