作为一个爱好项目的思想实验,我一直在想办法确保不会发生这种微妙的错误/错字:
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!
}
这个bug很难找到,因为没有编译时错误,你甚至不一定会在运行时获得异常。你只会得到“意想不到的结果”。
为了以简单的方式解决这个问题,我尝试使用空的枚举定义。有效地使用户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!!
}
你觉得怎么样?
答案 0 :(得分:6)
如果您遇到创建新类型以保留UserId
和UseCaseId
的麻烦,您几乎可以轻松地将它们变为简单类并使用来自{{1}的隐式转换运算符给你想要的语法:
int
这样,您就可以获得类型安全,而无需使用强制转换来丢弃您的代码。
答案 1 :(得分:2)
如果是Haskell并且我想这样做,我可能会这样做:
data UserId = UserId Int
data UseCaseId = UseCaseId Int
这样,函数将接受UserId
而不是Int,并且创建UserId
始终是显式的,如:
doSomething (UserId 12) (UseCaseId 15)
这类似于Niall C.创建包裹Int的类型的解决方案。但是,如果每种类型不需要10行就可以了。
答案 2 :(得分: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,则知道被调用者没有访问该对象的任何其他属性。
答案 3 :(得分:1)
我个人认为没必要诚实 由开发人员正确实现逻辑,你不能依赖编译时错误来解决这些错误。
答案 4 :(得分:1)
游戏后期,但FWIW ...... this project on 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似乎更容易“正确”使用。
答案 5 :(得分: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!
}