2010-08-10 13 views
5

Como un experimento mental en un proyecto de pasatiempo, he estado pensando en una manera de asegurar que este tipo de error sutil/errata no sucede:enteros de tipo fuerte

public void MyMethod(int useCaseId) 
{ 
    // Do something with the useCaseId 
} 

public void SomeOtherMethod() 
{ 
    int userId = 12; 
    int useCaseId = 15; 
    MyMethod(userId); // Ooops! Used the wrong value! 
} 

Este error sería es difícil de encontrar porque no hay un error de tiempo de compilación, y no necesariamente obtendrás una excepción en el tiempo de ejecución. Acabas de obtener "resultados inesperados".

Para resolver esto de una manera simple, he experimentado con el uso de definiciones enum vacías. efectivamente haciendo un identificador de usuario de un tipo de datos (sin ir tan lejos como clase o una estructura):

public enum UseCaseId { // Empty… } 

public enum UserId { // Empty… } 

public void MyMethod(UseCaseId useCaseId) 
{ 
    // Do something with the useCaseId 
} 

public void SomeOtherMethod() 
{ 
    UserId userId = (UserId)12; 
    UseCaseId useCaseId = (UseCaseId)15; 
    MyMethod(userId); // Compile error!! 
} 

¿Qué te parece?

+1

¿Por qué? ¿Ha agregado una cantidad significativa de complejidad para qué? ¿Previene errores tipográficos? Creo que es un poco excesivo codificar a alguien que usa la variable incorrecta. – cthom06

+0

Después de leer su pregunta por segunda vez, me doy cuenta, parece que está tratando de poner una restricción a arg-name. Esto es una muerte excesiva. ¿Por qué alguien querría hacer esto alguna vez? ¿No se supone que el tipo hace el trabajo? –

+0

Es un experimento mental. Estaba pensando en aplicarlo específicamente a los valores clave principales en mi modelo. No estoy proponiendo que todos comencemos a escribir código como este, solo tuve la idea y me pregunté qué pensaría la gente. –

Respuesta

1

Personalmente, creo que no es necesario ser sincero.
Depende del desarrollador implementar la lógica correctamente y no puede confiar en los errores de tiempo de compilación para dichos errores.

+0

Incluso cuando el compilador puede verificar esta parte de la lógica, e incluso cuando este es el tipo de tipeo fuerte. Casi -1. –

+0

... que tiene sentido si usa un lenguaje dinámico. Si tiene el dolor de cabeza de un lenguaje estático, ¿por qué no dejar que capte errores? – kyoryu

0

prefiero prefiero validar el argumento en MiMetodo y elevar excepción adecuada en caso de error condición.

public void MyMethod(int useCaseId) 
{ 
    if(!IsValidUseCaseId(useCaseId)) 
    { 
     throw new ArgumentException("Invalid useCaseId."); 
    } 
    // Do something with the useCaseId 
} 

public bool IsValidUseCaseId(int useCaseId) 
{ 
    //perform validation here, and return True or False based on your condition. 
} 

public void SomeOtherMethod() 
{ 
    int userId = 12; 
    int useCaseId = 15; 
    MyMethod(userId); // Ooops! Used the wrong value! 
} 
+2

Por supuesto, ¿cómo validar que se pasó un UserID en vez de un UseCaseID, cuando ambos son 'int'? Es decir. ¿Cómo sería tu 'IsValidUseCaseId()'? –

+0

lea mi comentario sobre su pregunta. –

+0

¿Por qué no utilizar un tipo y detectar el error en tiempo de compilación en lugar de en tiempo de ejecución? ¿No es ese uno de los principales beneficios de los lenguajes fuertemente tipados? – kyoryu

6

Si vas a la molestia de crear nuevos tipos de mantener UserId y UseCaseId, se podría casi tan fácilmente convertirlos en clases simples y utilizar un operador de conversión implícita de int para darle la sintaxis usted quiere:

public class UserId 
{ 
    public int Id { get; private set; } 

    public UserId(int id) 
    { 
     id_ = id; 
    } 

    public static implicit operator UserId(int id) 
    { 
     return new UserId(id); 
    } 

    public static void Main() 
    { 
     UserId id1 = new UserId(1); 
     UserId id2 = 2; 
    } 
} 

De esta manera, obtiene el tipo de seguridad sin tener que ensuciar su código con moldes.

+0

+1. Esta solución tiene más sentido para mí que el código OP. Si bien el código OP es muy corto, será confuso para un usuario de la clase que tome esos parámetros. –

+0

Creo que la conversión implícita derrota la idea de dejar que el compilador verifique el código. –

+0

No recomiendo un tipo completamente nuevo para simplemente validar los argumentos. En su solución, si pudiera tomar 2, podría tomar cualquier valor. ¿Estamos abordando el problema correcto aquí? –

2

Si se tratara de Haskell y yo quería hacer esto, yo podría hacerlo como:

data UserId = UserId Int 
data UseCaseId = UseCaseId Int 

De esta manera, las funciones aceptará un UserId en lugar de un int, y la creación de un UserId siempre es explícita, algo como:

doSomething (UserId 12) (UseCaseId 15) 

Esto es similar a la solución de Niall C. de crear un tipo para envolver un Int. Sin embargo, sería bueno que no tomara 10 líneas para implementar por tipo.

1

Tarde en el juego, pero FWIW ... this project on codeplex ha definido varios escalares "fuertemente tipados" como ángulo, acimut, distancia, latitud, longitud, radianes, etc. En realidad, cada uno de estos es una estructura con un solo escalar miembro y varios métodos/propiedades/constructores para manipularlo "correctamente". No es muy diferente a hacer de cada una de estas una clase, aparte del hecho de que estos son tipos de valores en lugar de tipos de referencia. Si bien no he usado el marco, puedo ver el valor de posiblemente convertir a estos tipos en ciudadanos de primera clase.

no saben si en última instancia es una buena idea o no, pero parece útil ser capaz de escribir código como este (similar a su original ejemplo) con la seguridad de tipos (valor y seguridad) aseguró:

Angle a = new Angle(45); //45 degrees 
SomeObject o = new SomeObject(); 
o.Rotate(a); //Ensure that only Angle can be sent to Rotate 

o

Angle a = (Angle)45.0; 

o

Radians r = Math.PI/2.0; 
Angle a = (Angle)r; 

parece que este patrón sería más útil si su dominio tiene muchos "tipos" escalares con semántica de valores y potencialmente muchas instancias de estos tipos. Modelar cada una como una estructura con un solo escalar da una representación muy compacta (comparada haciendo que cada una sea una clase completa). Si bien podría ser un poco doloroso implementar este nivel de abstracción (en lugar de simplemente usar escalares "desnudos" para representar valores de dominio) y discreción, la API resultante parece que sería mucho más fácil usar "correctamente".

2

he querido hacer algo similar por un tiempo y tu post me impulsó a tratar lo siguiente:

public class Id<T> { 
    private readonly int _Id; 

    private Id(int id) { 
     _Id = id; 
    } 

    public static implicit operator int(Id<T> id) { 
     return id._Id; 
    } 

    public static implicit operator Id<T>(int id) { 
     return new Id<T>(id); 
    } 
} 

que puedo utilizar de la siguiente manera

public void MyMethod(Id<UseCase> useCaseId) 
{ 
    // Do something with the useCaseId 
} 

public void SomeOtherMethod() 
{ 
    Id<User> userId = 12; 
    Id<UseCase> useCaseId = 15; 
    MyMethod(userId); // Compile error!! 
} 

Creo que pasa este tipo de El objeto Id es mejor que pasar el objeto de dominio completo porque hace que el contrato sea más explícito entre la persona que llama y el destinatario. Si solo pasa la identificación, usted sabe que el destinatario no está accediendo a ninguna otra propiedad en el objeto.

Cuestiones relacionadas