Java中的方法签名:
public ListgetFilesIn(List directories)
类似的红宝石
def get_files_in(directories)
对于Java,类型系统为我提供了有关该方法所期望和提供的信息.在Ruby的情况下,我不知道我应该传递什么,或者我期望收到什么.
在Java中,该对象必须正式实现该接口.在Ruby中,传入的对象必须响应此处定义的方法中调用的任何方法.
这似乎很成问题:
即使有100%准确,最新的文档,Ruby代码也必须暴露其实现,打破封装.除了"OO纯度",这似乎是一个维护噩梦.
Ruby代码让我不知道返回的是什么; 我必须基本上进行实验,或者阅读代码以找出返回对象将响应的方法.
不打算讨论静态打字与鸭子打字,而是希望了解如何维护一个几乎没有能力按合同设计的生产系统.
没有人真正通过这种方法所需的文档来解决方法内部实现的暴露问题.由于没有接口,如果我不期望某个特定类型,我不必逐条列出我可能调用的每个方法,以便调用者知道可以传入什么内容吗?或者这只是一个没有真正出现的边缘情况?
它归结为Ruby中的get_files_in
一个坏名字- 让我解释一下.
在java/C#/ C++中,特别是在目标C中,函数参数是名称的一部分.在红宝石中,它们不是.
这个奇特的术语是方法重载,它由编译器强制执行.
用这些术语来思考它,你只是定义了一个被调用的方法,get_files_in
而你实际上并没有说它应该把文件放到什么位置.这些参数不是名称的一部分,所以你不能依赖它们来识别它.
它应该在目录中获取文件吗?开车?网络共享?这开启了它在所有上述情况下工作的可能性.
如果要将其限制为目录,则要考虑此信息,应调用该方法get_files_in_directory
.或者你可以把它作为Directory
类的一个方法,Ruby已经为你做了.
至于返回类型,它暗示get_files
你返回一个文件数组.你不必担心它是一个List
或一个ArrayList
如果你只是想获得一个文件,你会称它为get_file
或get_first_file
或等等.如果你正在做一些更复杂的事情,比如返回FileWrapper
对象而不仅仅是字符串,那么有一个非常好的解决方案:
# returns a list of FileWrapper objects def get_files_in_directory( dir ) end
好歹.你不能像在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
在我看来,这种语言无法阻止你按合同进行设计.相反,在我看来,这取决于开发人员.
(顺便说一句,"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
一旦你理解了这种推理,方法签名就没有意义,因为你可以动态地在函数中测试它们.(这部分是由于无法进行基于签名匹配的功能调度,但这更灵活,因为您可以定义无限的签名组合)
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 )
如果您想要更加可靠的测试,可以尝试RSpec进行行为驱动的开发.