首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用静态类型检查防止业务错误

使用静态类型检查防止业务错误
EN

Software Engineering用户
提问于 2018-07-02 21:06:33
回答 3查看 465关注 0票数 13

我非常喜欢静态类型检查。它能防止你犯这样愚蠢的错误:

代码语言:javascript
复制
// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too

但这并不能阻止你犯这样愚蠢的错误:

代码语言:javascript
复制
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

当您使用相同的类型来表示明显不同类型的信息时,就会出现这个问题。我认为一个很好的解决方案是扩展Integer类,只是为了防止业务逻辑错误,而不是添加功能。例如:

代码语言:javascript
复制
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));

这被认为是好做法吗?或者,在这样一个限制性设计的道路上,是否存在不可预见的工程问题?

EN

回答 3

Software Engineering用户

回答已采纳

发布于 2018-07-02 22:31:15

你本质上要求一个单位系统(不,不是单元测试,“单位”,如“物理单位”,如米,伏特等)。

在您的代码中,Age代表时间,Pounds代表大量。这导致了诸如单位转换,基本单位,精度等。

曾经有/正在尝试将这样的东西导入Java,例如:

后两者似乎生活在github中:https://github.com/unitsofmeasurement

C++通过助推拥有单位

LabView和一堆单位一起来了。

还有其他语言的例子。(欢迎编辑)

这被认为是好做法吗?

正如您在上面看到的,语言越可能使用单元来处理值,它就越支持单元。LabView经常用于与测量设备交互。因此,在语言中有这样一个特性是有意义的,使用它肯定会被认为是一种良好的做法。

但是在任何通用的高级语言中,对于如此严格的语言的需求都很低,这可能是出乎意料的。

或者,在这样一个限制性设计的道路上,是否存在不可预见的工程问题?

我猜是:性能/内存。如果处理大量的值,则每个值的对象开销可能会成为一个问题。但和往常一样:过早的优化是万恶之源。

我认为更大的“问题”是让人们习惯它,因为这个单位通常是这样含蓄地定义的:

代码语言:javascript
复制
class Adult
{
    ...
    public void setAge(int ageInYears){..}

当人们不得不将一个对象作为一个值传递给一个看似可以用简单的int来描述的东西时,当他们不熟悉单元系统时,人们会感到困惑。

票数 12
EN

Software Engineering用户

发布于 2018-07-03 13:41:44

与null的答案相反,如果一个整数不足以描述度量,那么为一个“单元”定义一个类型可能是有益的。例如,在同一个测量系统中,重量通常是用多个单位测量的。想想“磅”、“盎司”、“公斤”和“克”。

如果您需要更细粒度的度量级别,那么为单元定义类型是有益的:

代码语言:javascript
复制
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
}

对于像“年龄”这样的东西,我建议在运行时根据人的出生日期计算:

代码语言:javascript
复制
public class Adult {
    private Date birthDate;

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

    public Interval calculateAge(Date date) {
        // Return interval between birthDate and date
    }
}
票数 6
EN

Software Engineering用户

发布于 2018-07-04 19:00:13

您似乎正在寻找的是所谓的标记类型。它们是一种表示“这是一个代表年龄的整数”的方式,而“这也是一个整数,但它代表的是权重”和“您不能将其中一个分配给另一个”。请注意,这比物理单位(如米或公斤)更远:我的程序中可能有“人的高度”和“地图上各点之间的距离”,两者都是以米为单位测量的,但由于将其中一个分配给另一个没有意义,所以从业务逻辑的角度来看是不兼容的。

有些语言,比如Scala很容易支持标记类型(参见上面的链接)。在其他情况下,您可以创建自己的包装类,但这不太方便。

验证,例如,检查一个人的身高是否“合理”是另一个问题。您可以将这些代码放在Adult类(构造函数或setter)中,也可以放在标记的类型/包装器类中。在某种程度上,内置类(如URLUUID )可以实现这一角色(例如提供实用方法)。

是否使用标记类型或包装类实际上将有助于您的代码更好,将取决于以下几个因素。如果您的对象很简单且字段很少,那么错误分配它们的风险很低,使用标记类型所需的额外代码可能不值得付出努力。在具有复杂结构和许多领域的复杂系统中(特别是如果它们中的许多具有相同的原始类型),它实际上可能会有所帮助。

在我编写的代码中,我经常在传递映射时创建包装类。像Map<String, String>这样的类型本身是非常不透明的,所以用有意义的名称(如NameToAddress )将它们封装在类中非常有用。当然,使用标记类型,您可以编写Map<Name, Address>,而不需要整个映射的包装器。

然而,对于简单的类型,如String或整数,我发现包装类(在Java中)太麻烦了。常规业务逻辑并不糟糕,但是在将这些类型序列化为JSON、将它们映射到DB对象等方面存在许多问题。您可以为所有大型框架(例如Jackson和Spring数据)编写映射器和挂钩,但是与此代码相关的额外工作和维护将抵消从使用这些包装器获得的任何好处。当然,YMMV和另一个系统的平衡可能是不同的。

票数 2
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/373487

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档