dav*_*000 19 ruby java oop design-by-contract interface
Java中的方法签名:
public List<String> getFilesIn(List<File> directories)
Run Code Online (Sandbox Code Playgroud)
类似的红宝石
def get_files_in(directories)
Run Code Online (Sandbox Code Playgroud)
对于Java,类型系统为我提供了有关该方法所期望和提供的信息.在Ruby的情况下,我不知道我应该传递什么,或者我期望收到什么.
在Java中,该对象必须正式实现该接口.在Ruby中,传入的对象必须响应此处定义的方法中调用的任何方法.
这似乎很成问题:
不打算讨论静态打字与鸭子打字,而是希望了解如何维护一个几乎没有能力按合同设计的生产系统.
没有人真正通过这种方法所需的文档来解决方法内部实现的暴露问题.由于没有接口,如果我不期望某个特定类型,我不必逐条列出我可能调用的每个方法,以便调用者知道可以传入什么内容吗?或者这只是一个没有真正出现的边缘情况?
Ori*_*rds 30
它归结为Ruby中的get_files_in
一个坏名字- 让我解释一下.
在java/C#/ C++中,特别是在目标C中,函数参数是名称的一部分.在红宝石中,它们不是.
这个奇特的术语是方法重载,它由编译器强制执行.
用这些术语来思考它,你只是定义了一个被调用的方法,get_files_in
而你实际上并没有说它应该把文件放到什么位置.这些参数不是名称的一部分,所以你不能依赖它们来识别它.
它应该在目录中获取文件吗?开车?网络共享?这开启了它在所有上述情况下工作的可能性.
如果要将其限制为目录,则要考虑此信息,应调用该方法get_files_in_directory
.或者你可以把它作为Directory
类的一个方法,Ruby已经为你做了.
至于返回类型,它暗示get_files
你返回一个文件数组.你不必担心它是一个List<File>
或一个ArrayList<File
>,或者等等,因为每个人都只使用数组(如果他们编写了一个自定义的数组,他们会把它写成继承自内置数组).
如果你只是想获得一个文件,你会称它为get_file
或get_first_file
或等等.如果你正在做一些更复杂的事情,比如返回FileWrapper
对象而不仅仅是字符串,那么有一个非常好的解决方案:
# returns a list of FileWrapper objects
def get_files_in_directory( dir )
end
Run Code Online (Sandbox Code Playgroud)
好歹.你不能像在java中那样强制执行ruby中的契约,但这是更广泛的一部分,即你不能像在java中那样在ruby中强制执行任何操作.由于ruby的语法更具表现力,你可以更清楚地编写类似英语的代码,告诉其他人你的合同是什么(其中为你节省了几千个尖括号).
我相信这是一场净胜利.您可以使用新发现的空闲时间编写一些规格和测试,并在一天结束时提供更好的产品.
我认为虽然Java方法为您提供了更多信息,但它并没有为您提供足够的信息来轻松编程.
例如,字符串列表只是文件名还是完全限定路径?
鉴于此,您的Ruby没有提供足够信息的论点也适用于Java.
您仍然依赖于阅读文档,查看源代码,或调用方法并查看其输出(当然还有不错的测试).
虽然我在编写Java代码时喜欢静态类型,但是没有理由不能坚持Ruby代码(或任何类型的代码)的深思熟虑的先决条件.当我真的需要坚持方法参数的前提条件(在Ruby中)时,我很乐意编写一个可能引发运行时异常的条件来警告程序员错误.我甚至通过写作给自己一个静态类型的外观:
def get_files_in(directories)
unless File.directory? directories
raise ArgumentError, "directories should be a file directory, you bozo :)"
end
# rest of my block
end
Run Code Online (Sandbox Code Playgroud)
在我看来,这种语言无法阻止你按合同进行设计.相反,在我看来,这取决于开发人员.
(顺便说一句,"bozo"真的是指你的:)
通过duck-typing验证方法:
i = {}
=> {}
i.methods.sort
=> ["==", "===", "=~", "[]", "[]=", "__id__", "__send__", "all?", "any?", "class", "clear", "clone", "collect", "default", "default=", "default_proc", "delete", "delete_if", "detect", "display", "dup", "each", "each_key", "each_pair", "each_value", "each_with_index", "empty?", "entries", "eql?", "equal?", "extend", "fetch", "find", "find_all", "freeze", "frozen?", "gem", "grep", "has_key?", "has_value?", "hash", "id", "include?", "index", "indexes", "indices", "inject", "inspect", "instance_eval", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "invert", "is_a?", "key?", "keys", "kind_of?", "length", "map", "max", "member?", "merge", "merge!", "method", "methods", "min", "nil?", "object_id", "partition", "private_methods", "protected_methods", "public_methods", "rehash", "reject", "reject!", "replace", "require", "respond_to?", "select", "send", "shift", "singleton_methods", "size", "sort", "sort_by", "store", "taint", "tainted?", "to_a", "to_hash", "to_s", "type", "untaint", "update", "value?", "values", "values_at", "zip"]
i.respond_to?('keys')
=> true
i.respond_to?('get_files_in')
=> false
Run Code Online (Sandbox Code Playgroud)
一旦你理解了这种推理,方法签名就没有意义,因为你可以动态地在函数中测试它们.(这部分是由于无法进行基于签名匹配的功能调度,但这更灵活,因为您可以定义无限的签名组合)
def get_files_in(directories)
fail "Not a List" unless directories.instance_of?('List')
end
def example2( *params )
lists = params.map{|x| (x.instance_of?(List))?x:nil }.compact
fail "No list" unless lists.length > 0
p lists[0]
end
x = List.new
get_files_in(x)
example2( 'this', 'should', 'still' , 1,2,3,4,5,'work' , x )
Run Code Online (Sandbox Code Playgroud)
如果您想要更加可靠的测试,可以尝试RSpec进行行为驱动的开发.
这绝不是维护噩梦,只是另一种工作方式,需要 API 和良好文档的一致性。
您的担忧似乎与以下事实有关:任何动态语言都是危险的工具,无法强制执行 API 输入/输出合同。事实是,虽然选择静态可能看起来更安全,但在这两个方面你可以做的更好的事情是保留一组良好的测试,这些测试不仅验证返回数据的类型(这是 Java 编译器可以验证和验证的唯一内容)强制执行),还有它的正确性和内部工作原理(黑盒/白盒测试)。
附带说明一下,我不了解 Ruby,但在 PHP 中,您可以使用 @phpdoc 标签来提示 IDE (Eclipse PDT) 有关某个方法返回的数据类型。