Java 中的方法签名:
public List<String> getFilesIn(List<File> directories)
ruby 中的类似方法签名
def get_files_in(directories)
在 Java 中,类型系统为我提供有关该方法期望和交付什么的信息。 就 Ruby 而言,我不知道我应该传递什么,或者我期望收到什么。
在Java中,对象必须正式实现接口。 在 Ruby 中,传入的对象必须响应此处定义的方法中调用的任何方法。
这似乎存在很大的问题:
- 即使拥有 100% 准确的最新文档,Ruby 代码也必须从本质上暴露其实现,从而破坏封装。 抛开“面向对象的纯度”不谈,这似乎是一场维护噩梦。
- Ruby 代码让我不知道返回了什么; 我必须进行本质上的实验,或者阅读代码来找出返回的对象将响应哪些方法。
不想争论静态类型与鸭子类型,而是想了解如何维护一个几乎没有能力通过合同进行设计的生产系统。
更新
没有人真正通过该方法所需的文档来解决方法内部实现的暴露问题。 由于没有接口,如果我不期望特定类型,我是否必须逐项列出我可能调用的每个方法,以便调用者知道可以传入什么? 或者这只是一个没有真正出现的边缘情况?
Method signature in Java:
public List<String> getFilesIn(List<File> directories)
similar one in ruby
def get_files_in(directories)
In the case of Java, the type system gives me information about what the method expects and delivers. In Ruby's case, I have no clue what I'm supposed to pass in, or what I'll expect to receive.
In Java, the object must formally implement the interface. In Ruby, the object being passed in must respond to whatever methods are called in the method defined here.
This seems highly problematic:
- Even with 100% accurate, up-to-date documentation, the Ruby code has to essentially expose its implementation, breaking encapsulation. "OO purity" aside, this would seem to be a maintenance nightmare.
- The Ruby code gives me no clue what's being returned; I would have to essentially experiment, or read the code to find out what methods the returned object would respond to.
Not looking to debate static typing vs duck typing, but looking to understand how you maintain a production system where you have almost no ability to design by contract.
Update
No one has really addressed the exposure of a method's internal implementation via documentation that this approach requires. Since there are no interfaces, if I'm not expecting a particular type, don't I have to itemize every method I might call so that the caller knows what can be passed in? Or is this just an edge case that doesn't really come up?
发布评论
评论(8)
归根结底,
get_files_in
在 Ruby 中是一个不好的名字 - 让我解释一下。在 java/C#/C++ 中,尤其是在 Objective C 中,函数参数是名称的一部分。 在红宝石中则不然。
对此的奇特术语是方法重载,它是由编译器强制执行的。
从这些角度考虑,您只是定义了一个名为
get_files_in
的方法,而实际上并没有说明它应该获取文件的内容。参数不是的一部分名称,这样您就不能依赖他们来识别它。它应该获取目录中的文件吗? 一个驱动器? 网络共享? 这为其在所有上述情况下工作提供了可能性。
如果您想将其限制为目录,则要考虑此信息,您应该调用方法
get_files_in_directory
。 或者,您可以将其设为Directory
类上的方法,Ruby 已经这样做了你。至于返回类型,
get_files
暗示您正在返回一个文件数组。 您不必担心它是List
或ArrayList> 等,因为每个人都只使用数组(如果他们已经编写了一个自定义数组,他们会将其编写为从内置数组继承)。
如果您只想获取一个文件,则可以将其称为
get_file
或get_first_file
等。 如果您正在做一些更复杂的事情,例如返回 FileWrapper 对象而不仅仅是字符串,那么有一个非常好的解决方案:无论如何。 您无法像在 java 中那样在 ruby 中强制执行契约,但这是更广泛的观点的一个子集,即您不能像在 java 中一样在 ruby 中强制执行任何。 由于 ruby 的语法更具表现力,您可以更清楚地编写类似于英语的代码,告诉其他人您的合同是什么(从而节省了数千个尖括号)。
我个人认为这是一场净胜利。 您可以利用新发现的业余时间编写一些规范和测试,并在一天结束时推出更好的产品。
What it comes down to is that
get_files_in
is a bad name in Ruby - let me explain.In java/C#/C++, and especially in objective C, the function arguments are part of the name. In ruby they are not.
The fancy term for this is Method Overloading, and it's enforced by the compiler.
Thinking of it in those terms, you're just defining a method called
get_files_in
and you're not actually saying what it should get files in. The arguments are not part of the name so you can't rely on them to identify it.Should it get files in a directory? a drive? a network share? This opens up the possibility for it to work in all of the above situations.
If you wanted to limit it to a directory, then to take this information into account, you should call the method
get_files_in_directory
. Alternatively you could make it a method on theDirectory
class, which Ruby already does for you.As for the return type, it's implied from
get_files
that you are returning an array of files. You don't have to worry about it being aList<File>
or anArrayList<File
>, or so on, because everyone just uses arrays (and if they've written a custom one, they'll write it to inherit from the built in array).If you only wanted to get one file, you'd call it
get_file
orget_first_file
or so on. If you are doing something more complex such as returningFileWrapper
objects rather than just strings, then there is a really good solution:At any rate. You can't enforce contracts in ruby like you can in java, but this is a subset of the wider point, which is that you can't enforce anything in ruby like you can in java. Because of ruby's more expressive syntax, you instead get to more clearly write english-like code which tells other people what your contract is (therein saving you several thousand angle brackets).
I for one believe that this is a net win. You can use your newfound spare time to write some specs and tests and come out with a much better product at the end of the day.
我认为,尽管 Java 方法为您提供了更多信息,但它并没有为您提供足够信息来轻松地进行编程。
例如,字符串列表只是文件名还是完全限定的路径?
鉴于此,您关于 Ruby 没有为您提供足够信息的论点也适用于 Java。
您仍然依赖于阅读文档、查看源代码或调用该方法并查看其输出(当然还有适当的测试)。
I would argue that although the Java method gives you more information, it doesn't give you enough information to comfortably program against.
For example, is that List of Strings just filenames or fully-qualified paths?
Given that, your argument that Ruby doesn't give you enough information also applies to Java.
You're still relying on reading documentation, looking at the source code, or calling the method and looking at its output (and decent testing of course).
虽然我在编写 Java 代码时喜欢静态类型,但您没有理由不能在 Ruby 代码(或任何类型的代码)中坚持深思熟虑的前提条件。 当我确实需要坚持方法参数的前提条件(在 Ruby 中)时,我很乐意编写一个可以抛出运行时异常以警告程序员错误的条件。 我什至通过写给自己一个静态类型的外观:
在我看来,该语言并没有阻止你进行契约设计。 相反,在我看来,这取决于开发人员。
(顺便说一句,“bozo”真正指的是你的:)
While I love static typing when I'm writing Java code, there's no reason that you can't insist upon thoughtful preconditions in Ruby code (or any kind of code for that matter). When I really need to insist upon preconditions for method params (in Ruby), I'm happy to write a condition that could throw a runtime exception to warn of programmer errors. I even give myself a semblance of static typing by writing:
It doesn't seem to me that the language prevents you from doing design-by-contract. Rather, it seems to me that this is up to the developers.
(BTW, "bozo" refers to yours truly :)
通过鸭子类型进行方法验证:
一旦你明白了这个推理,方法签名就没有实际意义了,因为你可以在函数中动态地测试它们。 (这部分是由于无法执行基于签名匹配的函数调度,但这更灵活,因为您可以定义无限的签名组合)
如果您想要更可靠的测试,您可以尝试 RSpec 用于行为驱动开发。
Method Validation via duck-typing:
Once you've got that reasoning down, method signatures are moot because you can test them in the function dynamically. ( this is partially due to not being able do do signature-match-based-function-dispatch, but this is more flexible because you can define unlimited combinations of signatures )
If you want a more assurable test, you can try RSpec for Behaviour driven developement.
简短回答:自动化单元测试和良好的命名实践。
方法的正确命名至关重要。 通过为方法指定名称
get_files_in(directory)
,您还可以向用户提示该方法期望获得什么以及将返回什么。 例如,我不希望从get_files_in()
中产生一个Potato
对象 - 它只是没有意义。 只有从该方法获取文件名列表或更准确地说是 File 实例列表才有意义。 至于List的具体类型,取决于你想要做什么,返回的List的实际类型并不重要。 重要的是您可以以某种方式枚举该列表中的项目。最后,您可以通过针对该方法编写单元测试来明确这一点 - 展示有关它应该如何工作的示例。 因此,如果 get_files_in 突然返回一个 Potato,测试将引发错误,您就会知道最初的假设现在是错误的。
Short answer: Automated unit tests and good naming practices.
The proper naming of methods is essential. By giving the name
get_files_in(directory)
to a method, you are also giving a hint to the users on what the method expects to get and what it will give back in return. For example, I would not expect aPotato
object coming out ofget_files_in()
- it just doesn't make sense. It only makes sense to get a list of filenames or more appropriately, a list of File instances from that method. As for the concrete type of the list, depending on what you wanted to do, the actual type of List returned is not really important. What's important is that you can somehow enumerate the items on that list.Finally, you make that explicit by writing unit tests against that method - showing examples on how it should work. So that if
get_files_in
suddenly returns a Potato, the test will raise an error and you'll know that the initial assumptions are now wrong.按契约设计是一个比仅仅指定参数类型和返回类型更微妙的原则。 这里的其他答案主要集中在良好的命名上,这很重要。 我可以继续讨论名称
get_files_in
含糊不清的多种方式。 但良好的命名只是拥有良好契约并由其设计的更深层次原则的外在结果。 名字总是有点模棱两可,良好的语用语言学是良好思维的产物。您可以将契约视为设计原则,但以抽象形式表述它们通常是困难且无聊的。 无类型语言要求程序员真正考虑契约,她对它们的理解比仅仅作为类型约束更深层次。 如果有团队,团队成员必须都遵守并遵守相同的合同。 他们必须是专注的思考者,必须花时间一起讨论具体例子,以便建立对合同的共同理解。
同样的要求也适用于 API 用户:用户必须首先记住文档,然后才能逐渐理解合约,如果合约设计得经过深思熟虑,就会开始喜欢 API(反之则讨厌 API)。
这与鸭子打字有关。 无论方法输入的类型如何,合约都必须提供有关发生情况的线索。 因此必须以更深入、更普遍的方式来理解合同。 这个答案本身可能看起来有点不具体,甚至傲慢,对此我深表歉意。 我只是想说鸭子不是谎言,鸭子的意思是人们在更高的抽象层次上思考自己的问题。 设计师、程序员、数学家是所有不同的名称相同的能力,数学家知道数学能力有很多级别,较高级别的数学家可以轻松解决较低级别的数学家难以解决的问题。 鸭子意味着你的编程必须是良好的数学,它限制了成功的开发人员和用户只能是那些谁能够这样做。
Design by contract is a much subtler principle than just specifying the argument type an return type. Other answers here concentrate much on good naming, which is important. I could go on an on about the many ways in which the name
get_files_in
is ambiguous. But good naming is just an outward consequence of a deeper principle of having good contracts and designing by them. Names are always a bit ambiguous, and good pragmatic linguistics is a product of good thinking.You can consider contracts the design principles, and they are frequently hard and boring to state in an abstract form. An untyped language requires that the programmer thinks about contracts for real, that she understands them a deeper level than just as type constraints. If there is a team, the team members must all mean and abide by the same contracts. They must be dedicated thinkers and must spend time together discussing concrete examples in order to establish shared understanding of contracts.
The same requirements apply to the API user: The user must first memorize the documentation, and then she is able to gradually understand the contracts, and start loving the API if the contracts are thoughtfully crafted (or hating it if otherwise).
This is connected to duck typing. A contract must give clue as to what happens regardless of the type of the method inputs. So the contract must be understood in a deeper, more generalized way. This answer itself might seem a bit inconcrete, or even haughty, for which I apologize. I am simply trying to say that the duck is not a lie, the duck means that one thinks about one's problem on a higher level of abstraction. The designers, the programmers, the mathematicians are all different names for the same capability, and mathematicians know that there are many levels of aptitude in mathematics, where mathematicians on a next higher level easily solve problems which those on lower levels find too hard to solve. The duck means that your programming has to be good mathematics, and it restricts the successful developers and users to only those, who are able to do so.
这绝不是维护噩梦,只是另一种工作方式,需要 API 和良好文档的一致性。
您的担忧似乎与以下事实有关:任何动态语言都是危险的工具,无法强制执行 API 输入/输出合同。 事实是,虽然选择静态可能看起来更安全,但在这两个领域中您可以做的更好的事情是保留一组良好的测试,这些测试不仅验证返回数据的类型(这是 Java 编译器可以验证和验证的唯一内容)强制执行),还有它的正确性和内部工作原理(黑盒/白盒测试)。
附带说明一下,我不了解 Ruby,但在 PHP 中,您可以使用 @phpdoc 标签来提示 IDE (Eclipse PDT) 有关某个方法返回的数据类型。
It's by no means a maintenance nightmare, just another way of working, that calls for consistence in the API and good documentation.
Your concern seems related to the fact that any dynamic language is a dangerous tool, that cannot enforce API input/output contracts. The fact is, while chosing static may seem safer, the better thing you can do in both worlds is to keep a good set of tests that verify not only the type of the data returned (which is the only thing the Java compiler can verify and enforce), but also it's correctness and inner workings(Black box/white box testing).
As a side note, I don't know about Ruby, but in PHP you can use @phpdoc tags to hint the IDE (Eclipse PDT) about the data types returned by a certain method.
几年前,我对 Ruby 的 dbc 之类的东西进行了半生不熟的尝试,可能会给人们一些关于如何推进更全面的解决方案的想法:
I made a half-baked attempt at something like dbc for Ruby a few years ago, may give folks some ideas about how to move forward with a more comprehensive solution:
https://github.com/justinwiley/higher-expectations