永远不要在Java中使用字符串?[关闭]


73

我偶然发现了一个博客条目,不鼓励在Java中使用字符串来使您的代码缺乏语义,建议您改用瘦包装器类。这是该条目提供的用于说明问题的示例的前后:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

根据我阅读编程博客的经验,我得出的结论是90%都是胡说八道,但是我想知道这是否是正确的观点。不知何故,我觉得不合适,但是我无法确切指出编程风格的不对之处。



5
噢,哇,那篇博客文章使我感到身体不适
Rob

5
我读过至少一本书(可能是Code Complete的第一版),建议您对所有原始类型进行类型定义以使用问题域中的名称。相同的想法。
user16764 2012年

9
以“从不”开头的语句“总是”为假!
弗洛伦斯·塞莱

1
那家伙是首席技术官?
Hyangelo 2012年

Answers:


88

封装可以防止程序被更改。名称的表示会改变吗?如果没有,那么您就是在浪费时间,YAGNI就会适用。

编辑:我读过博客文章,他有一个基本的好主意。问题在于他的缩放比例过高。喜欢的东西String orderId 非常糟糕的,因为那时"!"£$%^&*())ADAFVF是不是有效orderId。这意味着String表示的可能值比有效orderIds 大得多。但是,对于类似的东西name,您可能无法预测可能是或不是有效名称,而any String是有效名称name

在第一种情况下,您(正确地)将可能的输入减少为仅有效的输入。在第二种情况下,您没有缩小可能的有效输入范围。

再次编辑:考虑无效输入的情况。如果您写“ Gareth Gobulcoque”作为名字,它将看起来很愚蠢,但这不会是世界末日。如果您输入了无效的OrderID,则很有可能无法正常使用。


5
使用订单ID时,我仍然可能更喜欢在接受ID的代码中添加检查并保留String。这些类应该提供用于处理数据的方法。如果某个类仅检查某些数据的有效性,然后什么也不做,那么在我看来这不合适。OOP很好,但是您不应过度使用它。
马尔科姆

13
@Malcolm:但是,除了一次又一次地验证它们之外,您无法知道哪些字符串已验证。
DeadMG 2012年

7
“ [...]您可能无法预测什么是有效名称,任何String都是有效名称。” 我猜想这种技术的优势之一在于,如果您的参数类型为Name,则您不会偶然传递另一个不相关的String值。在您期望a的情况下Name,只会Name编译a,并且不会意外接受字符串“ I owe $ 5”。(请注意,这与任何类型的名称验证都无关!)
Andres F.

6
创建特定于特定用途的类型可以增加语义丰富性,并有助于增加安全性。此外,使用IoC容器自动装配类时,创建表示特定值的类型会很有帮助-当它是为特定类注册的唯一bean时,易于解析要使用的bean。当它只是注册的许多字符串之一时,需要付出更多的努力。
丹丹

3
@DeadMG这是有争议的,不是我们可以在评论中解决的问题。我会说确实会错放基元类型的值,强化接口是改善情况的一种方法。
Andres F.

46

那太疯狂了:)


2
这也是我的意见,但事实是我记得“代码完成”中的建议。当然,那本书中的所有内容都不是无可争辩的,但至少在拒绝一个想法之前,至少让我三思而后行。
DPM 2012年

8
您刚刚提到了一些口号,没有任何实际根据。您能详细说明一下吗?例如,某些可接受的模式和语言功能可能看起来像增加了复杂性,但提供了一些有价值的回报(例如:静态键入)
Andres F.

12
常识不是常识。
卡兹龙

1
+1,我要补充一点,当一种语言支持命名参数并且IDE很好时(例如C#和VS2010),那么就不必用疯狂的模式来弥补该语言缺乏的功能。如果可以编写,则不需要名为X的类和名为Y的类。var p = new Point(x: 10, y:20);而且,这并不像Cinema不同于字符串那样。我会理解是否要处理物理量,例如压力,温度,能量,其中的单位不同,而某些单位不能为负。博客的作者需要尝试功能。
Job

1
+1表示“不要过度设计!”
伊万(Ivan)2012年

23

我主要同意作者的观点。如果某个字段有任何特定的行为,例如订单ID的验证,那么我将创建一个类来表示该类型。他的第二点甚至更重要:如果您有一组表示某些概念(例如地址)的字段,则为该概念创建一个类。如果您使用Java进行编程,则要为静态类型付出高昂的代价。您也可以获取所有可能的价值。


2
下选民可以发表评论吗?
凯文·克莱恩

我们为静态打字付出的高价是什么?
理查德·廷格

@RichardTingle:重新编译代码并为每次代码更改重新启动JVM的时间。进行源兼容但二进制不兼容的更改以及需要重新编译的内容所浪费的时间并没有,您会得到“ MissingMethodException”。
凯文·克莱恩

我从来没有遇到过Java应用程序的问题(对于连续编译,我在运行时通常已经编译了什么),但是对于Web WAR来说,这是一个公平的点,对于Web WAR,重新部署的速度似乎有点慢
Richard Tingle 2015年

16

不要做 它将使事情变得过于复杂,您将不需要它

...就是我两年前在这里写的答案。现在,我不确定。实际上,最近几个月我已经开始将旧代码迁移到这种格式,不是因为我没有更好的事情要做,而是因为我确实需要它来实现新功能或更改现有功能。我了解这里其他人看到的自动厌恶该代码,但是我认为这是值得认真考虑的事情。


好处

好处之一是修改和扩展代码的能力。如果您使用

class Point {
    int x,y;
    // other point operations
}

而不是只是传递几个整数-不幸的是,许多接口都这样做-后来添加另一个维度变得容易得多。或将类型更改为double。如果以后使用List<Author> authorsList<Person> authors代替List<String> authors它,向作者表示的内容添加更多信息变得容易得多。像这样写下来,感觉就像是我说的很明显,但实际上,我本人多次以这种方式使用字符串而感到内especially,尤其是在开始时不很明显的情况下,我还需要一个字符串。

我目前正在尝试重构一些在我的代码中交织在一起的字符串列表,因为我在那里需要更多信息,并且我感到很痛苦:\

除此之外,我同意博客作者的观点,即博客包含更多的语义信息,使读者更容易理解。尽管通常给参数赋予有意义的名称并获得专用的文档行,但对于字段或本地变量却常常不是这种情况。

出于明显的原因,最后的好处是类型安全,但是在我看来这是次要的事情。

缺点

写作需要更长的时间。编写小型课程既快速又轻松,但不费吹灰之力,尤其是在您需要大量此类的情况下。如果您发现自己每隔3分钟停下来写一些新的包装器类,那也可能确实对您的专心有害。但是,我想认为,这种努力状态通常只会在编写任何代码的第一阶段发生;通常,我通常可以很快了解需要涉及哪些实体。

它可能涉及许多冗余的二传手(或构造)和吸气剂。博客作者提供了new Point(x(10), y(10))代替的真正丑陋的示例new Point(10, 10),我想补充一点,用法也可能涉及诸如Math.max(p.x.get(), p.y.get())代替的内容Math.max(p.x, p.y)。长代码通常被认为更难阅读,而且这样做也是如此。但老实说,我感觉到很多代码在移动对象,只有选择方法才能创建对象,甚至更少的需要访问其详细信息(无论如何都不是OOPy)。

值得商bat的

我想说这是否有助于提高代码的可读性尚待商.。是的,更多的语义信息,但是更长的代码。是的,更容易理解每​​个本地用户的角色,但是很难理解您可以使用它做什么,除非您阅读其文档。


与大多数其他编程思想流派一样,我认为将其推向极端是不健康的。我看不到自己曾经将x和y坐标分别设为不同的类型。我认为只要满足就没有Count必要int。我不喜欢unsigned intC语言中的用法-尽管理论上不错,但它不能为您提供足够的信息,并且确实禁止稍后扩展代码以支持该神奇的-1。有时您确实需要简单。

我认为该博客文章有些偏激。但是总的来说,我从痛苦的经历中学到,其背后的基本思想是正确的东西。

我对过度设计的代码深感厌恶。我是真的 但是使用正确,我不认为这种过度设计。


5

尽管有点过分,但我经常认为我所看到的大多数东西都是工程不足。

这不仅是“安全性”,关于Java的真正好处之一是,它可以记住/弄清楚任何给定的库方法调用需要/期望的内容,从而对您有很大帮助。

我使用过的最差的Java库(到目前为止)是由一个非常喜欢Smalltalk的人编写的,并在此之后对GUI库进行了建模,使其更像Smalltalk一样工作-问题是每个方法都使用相同的基础对象,但实际上无法使用基础对象可以转换的所有内容,因此您又回到猜测要传递给方法的内容,并且不知道自己是否在运行时失败(我曾经必须每次都处理一次在C中工作)。

另一个问题-如果不带对象地传递字符串,整数,集合和数组,那么您所拥有的只是一堆毫无意义的数据。当您考虑“某些应用程序”将使用的库时,这似乎很自然,但是在设计整个应用程序时,在定义数据的地方为所有数据分配含义(代码)并只考虑它们会更有用。这些高级对象如何交互的术语。如果您传递的是基元而不是对象,那么根据定义,您将数据更改到与定义的位置不同的地方(这也是为什么我真的不喜欢Setter和Getters的原因。在不是您的数据上)。

最后,如果您为所有内容定义单独的对象,那么您总会有一个验证所有内容的好地方-例如,如果您为邮政编码创建一个对象,后来发现您必须确保邮政编码始终包含4位数字扩展名,放的完美地方。

这实际上不是一个坏主意。考虑一下,我什至不敢肯定我会说它是经过过度工程设计的,几乎可以在所有方面使用它都更容易-一个例外是微小类的泛滥,但Java类如此轻巧且容易写道这几乎不算成本(甚至可以产生)。

我真的很想看到一个写得很好的Java项目,实际上定义了太多的类(这使得编写程序更加困难),我开始认为不可能有太多的类。


3

我认为您必须从另一个角度来看这个概念。从数据库设计人员的角度看一下:在第一个示例中传递的类型并没有以唯一的方式定义参数,更不用说一种有用的方式了。

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

需要两个参数来指定预订门票的实际顾客,您可能有两部具有相同名称的电影(例如翻拍),您可能有同一部具有不同名称的电影(例如翻译)。电影院的某一个链可能有不同的分公司,所以你怎么在一个字符串,并以一致的方式来处理那些(例如,您正在使用$chain ($city)或者$chain in $city甚至是别的东西你怎么确保这是最糟糕的实际上是通过两个参数来指定您的顾客,提供名字和姓氏的事实并不能保证有效的顾客(并且您不能区分两个John Does)。

答案是声明类型,但是如我上面显示的那样,它们很少是薄包装器。它们很可能会用作您的数据存储或与某种数据库耦合。因此,一个Cinema对象可能会具有名称,位置等,这样您就可以摆脱此类歧义。如果它们是薄包装纸,那么它们是巧合。

因此,恕我直言,博客文章只是说“确保您传递正确的类型”,其作者只是做出了过于严格的选择,特别是选择了基本数据类型(这是错误的信息)。

建议的替代方法更好:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

另一方面,我认为该博客文章对所有内容都进行了包装。Count太普通了,我可以用它来数苹果或橙子,再加上它们,但仍然有种种情况,类型系统允许我进行无意义的操作。您当然可以应用与博客中相同的逻辑并定义类型CountOfOranges等,但这也很愚蠢。

为了它的价值,我实际上会写一些像

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

长话短说:您不应该传递无意义的变量;只有当您运行查询(例如public Collection<Film> findFilmsWithTitle(String title))或将概念证明放在一起时,您才实际指定一个值并不能确定实际对象的对象。保持类型系统整洁,因此不要使用过于笼统的类型(例如,以表示的电影String)或过于严格/特定/人为(例如,Count而不是int)的类型。只要有可能,就使用一种类型来唯一且明确地定义对象。

编辑:更简短的摘要。对于小型应用程序(例如概念验证):为什么要打扰复杂的设计?只需使用Stringint并继续使用它。

对于大型应用程序:您是否真的有很多类包含一个具有基本数据类型的单个字段?如果此类类很少,那么您只有“普通”对象,没有什么特别的事情。

我觉得封装字符串的想法……只是一个不完整的设计:对于小型应用程序来说过于复杂,对于大型应用程序来说不够完整。


我明白您的意思,但我认为您承担的责任超出了作者的陈述。就我们所知,他的模特只有一个顾客专用琴弦,一部电影专用琴弦等等。假设的额外功能确实是问题的症结所在,因此他要么“心不在s”以忽略在提出案情时就以为他应该提供更多的语义能力,要么是因为。再说一次,据我们所知,这就是他所想的。
DPM 2012年

@Jubbat:的确,我承担的责任远超出作者所说的。但是我的观点是,要么您拥有一个简单的应用程序,要么在任何情况下都过于复杂。对于这种规模,可维护性不是问题,语义上的区别会阻碍您的编码速度。另一方面,如果您的应用程序很大,则值得正确定义类型(但不太可能是简单的包装器)。恕我直言,他的例子并不能令人信服,或者存在严重的设计缺陷,超出了他试图提出的范围。
伊贡(Egon)2012年

2

对我而言,这与在C#中使用区域相同。通常,如果您认为使代码可读性是必需的,那么您会遇到更大的问题,应该花时间在上面。


2
+1表示症状而非原因的治疗。
Job

2

我想说这是具有强类型typedef之类功能的语言的一个好主意。

在Java中,您不需要这样做,因此为这些事情创建一个全新的类意味着成本可能超过收益。通过注意变量/参数命名,您还可以获得80%的收益。


0

如果这不是很好(如果String(结束为Integer,并且只说说String))不是最终的,那么这些类可以是某些(受限制的)具有含义的String,并且仍可以发送给某些知道如何处理的独立对象基本类型(不来回交换)。

当有例如时,它的“好处”增加。所有名称的限制。

但是在创建某个应用程序(而非库)时,始终可以对其进行重构。所以我敢于没有它开始。


0

举个例子:在我们当前开发的系统中,有许多不同的实体可以通过多种标识符(由于使用外部系统)来标识,有时甚至是同一类型的实体。所有标识符都是字符串-因此,如果有人混淆应该将哪种id作为参数传递,则不会显示编译时错误,但是程序会在运行时崩溃。这种情况经常发生。因此,我不得不说,该原则的主要目的不是为了防止更改(尽管它也可以这样做),而是为了保护自己免受错误的侵害。而且无论如何,如果有人设计了API,则它必须反映域的概念,因此定义特定于域的类在概念上很有用-一切都取决于开发人员的想法,


@downvoter:能否请您解释一下?
thSoft 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.