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

这个错误很难找到,因为没有编译时错误,你甚至不一定会在运行时遇到异常。你只会得到“意想不到的结果”。

为了以简单的方式解决这个问题,我尝试使用空枚举定义。有效地使用户 id 成为数据类型(而不是类或结构):

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

你怎么看?

4

6 回答 6

6

如果您要麻烦地创建新类型来保存UserIdand UseCaseId,您几乎可以轻松地将它们变成简单的类,并使用隐式转换运算符 fromint为您提供所需的语法:

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

这样,您就可以获得类型安全,而不必在代码中乱扔垃圾。

于 2010-08-10T11:56:18.807 回答
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 对象比传递整个域对象要好,因为它使调用者和被调用者之间的契约更加明确。如果您只传递 id,您就知道被调用者没有访问对象上的任何其他属性。

于 2011-09-24T00:57:24.097 回答
2

如果是 Haskell 并且我想这样做,我可能会这样做:

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

这样,函数将接受 aUserId而不是 Int,并且创建 aUserId始终是显式的,例如:

doSomething (UserId 12) (UseCaseId 15)

这类似于 Niall C. 的创建类型以环绕 Int 的解决方案。但是,如果每个类型不需要 10 行来实现,那就太好了。

于 2010-08-12T02:43:18.607 回答
1

游戏迟到了,但是 FWIW ......这个关于 codeplex的项目定义了几个“强类型”标量,如角度、方位角、距离、纬度、经度、弧度等。实际上,每个都是具有单个标量成员的结构和几个方法/属性/构造函数来“正确”地操作它。除了这些是值类型而不是引用类型这一事实之外,与将它们中的每一个都设为类并没有太大区别。虽然我没有使用过该框架,但我可以看到可能使这些类型成为一等公民的价值。

不知道这最终是否是一个好主意,但能够编写这样的代码(类似于您的原始示例)并确保类型安全(和值安全)似乎很有用:

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;

如果您的域具有许多具有值语义的标量“类型”以及这些类型的许多实例,那么这种模式似乎最有用。将每个建模为具有单个标量的结构可以提供非常紧凑的表示(与使每个都成为完整的类相比)。虽然实现这种抽象级别(而不是仅仅使用“裸”标量来表示域值)和离散性可能有些痛苦,但生成的 API 似乎更容易“正确”使用。

于 2010-12-13T18:51:28.667 回答
1

我个人认为没有必要说实话。
正确实现逻辑取决于开发人员,您不能依赖编译时错误来解决此类错误。

于 2010-08-10T11:49:58.190 回答
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!
}
于 2010-08-10T11:56:05.327 回答