2010-08-10 5 views
5

Как мысленный эксперимент над проектом хобби, я думал так, чтобы гарантировать, что такого рода тонкие ошибки/опечатки не бывает:Сильно типизированных целые

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! 
} 

Эта ошибка будет трудно найти, потому что ошибки компиляции не существует, и вы не обязательно получите исключение во время выполнения. Вы просто получите «неожиданные результаты».

Чтобы решить эту проблему простым способом, я экспериментировал с использованием пустых определений перечислений. Эффективно делает идентификатор пользователя типа данных (не заходя так далеко, как класс или структуру):

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!! 
} 

Что ты думаешь?

+1

Почему? Вы добавили значительную сложность для того, чтобы предотвратить опечатки? Я думаю, что немного переборщить, чтобы закодировать кого-то, используя неправильную переменную. – cthom06

+0

После прочтения вашего вопроса во второй раз, я понимаю, кажется, вы пытаетесь ограничить имя arg. Это чрезмерное убийство. Почему каждый хотел бы это сделать? Разве это не тот тип, который должен выполнять эту работу? –

+0

Это мысленный эксперимент. Я подумывал применить его специально к значениям первичного ключа в моей модели. Я не предлагаю, чтобы мы все начали писать такой код, у меня появилась идея и подумал, что думают люди. –

ответ

1

Я лично считаю, что не нужно быть честным.
. Разработчик должен правильно реализовать логику, и вы не можете полагаться на ошибки времени компиляции для таких ошибок.

+0

Даже когда компилятор может проверить эту часть логики, и даже если это своего рода точка сильной печати? Почти -1. –

+0

... что имеет смысл, если вы используете динамический язык. Если у вас есть головная боль из статически типизированного языка, почему бы не позволить ему ловить ошибки для вас? – kyoryu

0

Я бы предпочел, чтобы проверить аргумент в MyMethod и поднять соответствующее исключение в случае ошибки-состояния.

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

Конечно, однако, как проверить, что UserID был передан вместо UseCaseID, когда они оба являются «int»? То есть как выглядел бы ваш 'IsValidUseCaseId()'? –

+0

прочитал мой комментарий к вашему вопросу. –

+0

Почему бы не использовать тип и уловить ошибку во время компиляции, а не во время выполнения? Разве это не одно из основных преимуществ строго типизированных языков? – kyoryu

6

Если вы собираетесь в проблему создания новых типов для хранения UserId и UseCaseId, вы могли бы почти так же легко превратить их в простые классы и использовать оператор неявного преобразования из int дать вам синтаксис вы хотите:

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; 
    } 
} 

Таким образом, вы получаете безопасность типа, не заставляя свой код бросать.

+0

+1. Это решение имеет для меня больше смысла, чем код OP. Хотя код OP очень короткий, он будет путать для пользователя класса, берущего эти параметры. –

+0

Я думаю, что неявное преобразование поражает идею позволить компилятору проверить код. –

+0

Я не рекомендую совершенно новый тип для просто проверки аргументов. В вашем решении, если это может занять 2, это может иметь значение. Обращаем ли мы внимание на правильную проблему? –

2

Если бы это было Haskell, и я хотел сделать это, я мог бы сделать это нравится:

data UserId = UserId Int 
data UseCaseId = UseCaseId Int 

Таким образом, функции будут принимать UserId вместо Int, а также создание UserId всегда явно, что-то например:

doSomething (UserId 12) (UseCaseId 15) 

Это похоже на решение Niall C. по созданию типа для обертывания вокруг Int. Однако было бы неплохо, если бы не было 10 строк для каждого типа.

1

Поздно к игре, но FWIW ... this project on codeplex определил несколько «сильно типизированных» скаляров, таких как Angle, Azimuth, Distance, Latitude, Longitude, Radian и т. Д. На самом деле каждая из них представляет собой структуру с одним скаляром член и несколько методов/свойств/конструкторов, чтобы манипулировать им «правильно». Не очень сильно отличается от того, чтобы сделать каждый из этих классов, кроме того, что это типы значений, а не ссылочные типы. Хотя я не использовал рамки, я вижу ценность, возможно, сделать эти типы первоклассными гражданами.

Не знаю, является ли это в конечном счете, хорошая идея или нет, но это, кажется, полезно, чтобы иметь возможность писать код, как это (аналогично исходному примеру) с типобезопасности (и безопасности значение) обеспечивается:

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

или

Angle a = (Angle)45.0; 

или

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

Похоже, что эта модель будет наиболее USEF ul, если ваш домен имеет множество скалярных «типов» со значениями семантики и потенциально многими экземплярами этих типов. Моделирование каждого из них как структуры с помощью одного скаляра дает очень компактное представление (сравнивая создание каждого полномасштабного класса). Хотя для реализации этого уровня абстракции (а не просто использования «голых» скаляров для представления значений домена) и дискретности может быть немного больно, получившийся API кажется, что было бы гораздо проще использовать «правильно».

2

Я хотел сделать что-то похожее на какое-то время, и ваше сообщение побудило меня попробовать следующее:

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); 
    } 
} 

, который можно использовать следующим образом

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!! 
} 

Я думаю, проходящее этот тип Объект Id лучше, чем передача всего объекта домена, поскольку он делает контракт более явным между вызывающим и вызываемым. Если вы передаете только идентификатор, вы знаете, что вызывающий абонент не имеет доступа к другому свойству объекта.

Смежные вопросы