3 ms·
Type-checking a Dynamic Language
- mcantor 16y agoLately I've been following an approach that I suppose you could call "strict duck-checking in danger zones." I define a very lightweight "contract checker" and drop it into the beginning of methods that make a lot of assumptions about their arguments. For example, in a ruby app that wants certain keys in the dictionary you give to it: class MethodContractException < StandardError end class Expecter def initialize(suspect) @suspect = suspect end def has_all_keys(keys) missing_keys = keys.reject { |key| @suspect.has_key? key } if !missing_keys.empty? raise MethodContractException, "Method contract violated: "\ "#{@suspect.keys.inspect} should contain "\ "#{missing_keys.inspect}." end true end end def expect(suspect) Expecter.new(suspect) end class Widget REQUIRED_SPECS = [:splines, :cogs, :thingamabobs] def reticulate(dct_spec) expect(dct_spec).has_all_keys(REQUIRED_SPECS) @dct_spec = dct_spec process(dct_spec) end end Though, it may be better to encapsulate this kind of contract by creating a WidgetSpec class that demands each spec in the form of arguments to its constructor, and simply checking that the argument supplied to Widget::reticulate() is an instance of WidgetSpec. That is probably one of the few instances where instance checking is sensible; in other situations, using respond_to?() is preferable, because we are only concerned as to whether the object we are given can :quack, not that it is an instance of a Duck. For everything else, I try to follow Joel Spolsky's advice on making wrong code look wrong: http://www.joelonsoftware.com/articles/Wrong.html http://www.joelonsoftware.com/articles/Wrong.html