分享
 
 
 

用标记区别对象类型会导致误贴标签

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

当使用字段中非凡的标记来区别对象类型时,会产生标记对相关数据误贴标签的错误 ? 被称为 Impostor Type 错误模式。在诊断 Java 代码的这一部分中,Eric Allen 对这个错误的症状和起因进行了分析,具体说明了预防错误产生的方法,并讨论了一种吸引人的混合实现方法,这种方法不使用 impostor type,但最后,还是有很多相同的缺点产生。请在讨论论坛与作者和其他读者分享您对本文的看法。

程序中除了最无关紧要的部分外都要对某些数据类型进行操作。静态类型系统提供了一种方法,它能够确保程序不会对给定类型的数据进行不当的操作。Java 语言的优点之一是严格的区分类型,所以在程序运行前已消除了类型错误。作为开发人员,我们可以使用这个类型系统提供更健壮且没有错误的代码。然而,我们却经常没有让类型系统发挥出最大的潜力。

Impostor Type 错误模式

很多程序可以更多地使用静态类型系统,但它们没有这样做,而是依靠包含区别数据类型标记的非凡字段。

代码快速跟踪

清单 1. 用 impostor type 实现几何外形

一个基本示例,演示了引入这类错误是如何的轻而易举。

清单 2. 构造新的 form

引入了错误。

清单 3. 用实际类型实现 form

这种新奇的方法可以在运行时报错。

清单 4. 一种混合的实现方式

不使用 impostor type,但易受性一样。

依靠这些非凡字段区别数据类型,这样的程序放弃了类型系统专门提供给它们的保护措施。当这些标记中的一个对它的数据误贴了标签,就会产生我称之为 Impostor Type 的错误。

症状

impostor type 错误的一种常见症状是很多概念上不同类型的数据都被同样(并且错误)的方式处理。另一常见症状是数据与任何指定的类型都不匹配。

首要规则是,只要当概念上的数据类型和它被程序处理的方法不匹配,就可以怀疑是否发生了这个模式的错误。

为说明引入这种模式的错误是多么的轻而易举,让我们来考虑一个简单的示例。假设我们需要处理各种各样的欧几里得几何学外形,如圆形、正方形等等。这些几何外形没有坐标,但含有一个 scale 变量,所以可以计算它们的面积。

清单 1. 用 imposter type 实现各种几何外形

public class Form {

String shape;

double scale;

public Form(String _shape, double _scale) {

this.shape = _shape;

this.scale = _scale;

}

public double getArea() {

if (shape.equals("square")) {

return scale * scale;

}

else if (shape.equals("circle")) {

return Math.PI * scale * scale;

}

else { // shape.equals("triangle"), an equilateral triangle

return scale * (scale * Math.sqrt(3) / 4);

}

}

}

尽管您会发现人们经常这么做,但用这种方法实现几何外形还是存在严重缺点。

最显著的缺点之一是这个方法不能真正的扩展。假如要为我们的 form 引入一个新的几何外形(比如,“五边形”),我们必须进入并修改 getArea() 方法的源代码。不过可扩展性是个独立的考虑因素;在本文中,我们把重点放在实现几何外形所造成的错误的易受性上。我会在以后的文章中回到关于可扩展性的问题上来。

假如我们在程序其它部分构造了一个新的 Form 对象,如下所示,请考虑将会发生什么情况:

清单 2. 构造一个新的 form

Form f = new Form("sqaure", 2);

当然,“square”被拼错了,但是编译器认为,这是完全合法的代码。

现在考虑一下,当我们试图对新的 Form 对象调用,比如说 getArea() 方法时发生什么情况。因为 Form 对象中的几何外形与 if-then-else 代码块中的任一测试的几何外形都不匹配,它的面积将在 else 分句中被计算,似乎它是个三角形似的!

这里将不会报错。事实上,在很多情况下,返回值看起来都好象是完全合理的数字。即使我们插入些冗余代码,检查 else 分句中的隐含条件是否包含(比如说,断言),也要到代码执行时才能发现错误。

很多其它相似的错误也可能在上述代码中产生。if-then-else 代码块可能会偶然遗漏一句分句,导致类型与那句分句相对应的所有 Form 都被错误地处理了。此外,因为 impostor type 在字段中只是一个 String,所以它可能会被意外或恶意地修改。

无论用哪一种方法,这样的修改会带来各种各样的损害。

治疗和预防措施

正如您可能设想过的那样,我建议用类型系统在静态检查期间将它们清除,从而避免这种类型的错误。请考虑这种新奇的实现方法:

清单 3. 用实际类型实现 form

public abstract class Form {

double scale;

public Form(double _scale) {

this.scale = _scale;

}

public abstract double getArea();

}

class Square extends Form {

public Square(double _scale) {

super(_scale);

}

public double getArea() {

return scale * scale;

}

}

class Circle extends Form {

public Circle(double _scale) {

super(_scale);

}

public double getArea() {

return Math.PI * scale * scale;

}

}

class Triangle extends Form {

public Triangle(double _scale) {

super(_scale);

}

public double getArea() {

return scale * (scale * Math.sqrt(3) / 4);

}

}

现在考虑一下,在创建一个新 Form 时,假如误输入了“Sqaure”,会发生什么情况。编译器将会报错,告诉我们类 Sqaure 找不到。代码将连运行的机会也没有。

同样地,编译器将不会答应我们忘记为我们的任意子类定义 getArea() 方法。当然,任何对象要改变 Form 的类型是不可能的。

最后说明

在离开这个主题之前,我还想讨论另一种可能的实现,一种我曾经讨论过的两种实现方法的混合。

在这种情况下,不使用 impostor type,但代码包含很多相同的易受性,似乎它们以前就有。实际上,这种实现方法比对每个类型单独实现 getArea() 方法更差。

清单 4. 一种混合的实现方式

public abstract class Form {

double scale;

public Form(double _scale) {

this.scale = _scale;

}

public double getArea() {

if (this instanceof Square) {

return scale * scale;

}

else if (this instanceof Circle) {

return Math.PI * scale * scale;

}

else { // this instanceof Triangle

return scale * (scale * Math.sqrt(3) / 4);

}

}

}

class Square extends Form {

public Square(double _scale) {

super(_scale);

}

}

class Circle extends Form {

public Circle(double _scale) {

super(_scale);

}

}

class Triangle extends Form {

public Triangle(double _scale) {

super(_scale);

}

}

尽管编译器仍然会捕捉类型的拼写错误,且对象类型是无法改变的,我们又一次使用了 if-then-else 代码块调度适当的类型。这样,我们又要面临 if-then-else 代码块中 instanceof 检查与我们所操作的那组类型不匹配的情况。

还必须提出,像第一种实现方法那样,这个实现方法的扩展性不如第二种。

总结

那么,简而言之,这就是我们最近的错误模式:

模式:Impostor Type

症状:一种程序,它用同样的方式处理概念上不同类型的数据,或者无法识别某种类型的数据。

起因:程序针对各种类型的数据使用带标记的字段,而不是独立的类。

治疗和预防措施:尽可能将概念上不同的数据类型分成几个独立的类。

重点在于,这种语言为您提供了避免这类错误的最好资源 ? 只是要记得使用它们。

参考资料

请参与本文的讨论论坛。

JUnit 主页提供了很多有趣文章的链接,这些文章讨论了程序测试的方法,还有最新的 JUnit 版本。

假如您喜欢 JUnit,请查看整套 xUnit 测试工具,有多种不同语言版本。

我必须提一下工具中的 xUnit 套件是设计用来和极端编程一起使用的,这是一种新型的功能强大并快速开发干净、健壮软件的方法。

“框架体系结构的 UML 概要文件”(PDF 幻灯片放映)突出显示具体的 JUnit 个案研究。

尽管与这个讨论没有直接的联系,我还是推荐您参阅 Martin Fowler 的文章,其中讨论了 UML 的角色和极端编程中的设计。

采用 “Java 调试”教程(developerWorks,2001 年 2 月),从而获得一般调试技术的帮助。

不熟悉 Java 开发或希望重温 Java 编程技术吗?请采用这篇全面的教程,“ Java 语言的基础知识”。

请阅读 Eric 的所有诊断 Java 代码的文章,许多篇着重讨论错误模式。

请在 developerWorks Java 技术专区中查阅更多 Java 参考资料。

关于作者

Eric Allen 毕业于 Cornell 大学,曾获得计算机系和数学系的学士学位。他还是 Rice 大学 Java 编程语言小组的博士研究生。它的研究涉及到开发用于 Java 语言的语义模型和静态分析工具,两者都是源代码和字节码级别的。目前,他正在为 NextGen 编程语言实现一种从源代码到字节码的编译器,这也是 Java 语言的泛型运行时类型的一种扩展。请通过 eallen@cs.rice.edu 与他联系。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有