Friday morning at the conference was opened by Jim Weirich, author of Rake, with a talk called Playing it Safe. This was about good and bad ways to extend Ruby libraries.
He started with a couple of questions from mailing lists – “Are Ruby’s Open Classes1 bad for large projects?”. He then gave a fairly well know example email to a mailing list entitled the “Chainsaw Infanticide Logger Maneuver”. This was an example of someone who couldn’t figure out why logging wasn’t working correctly in his application, but after several hours of search discovered that one of the libraries he was using had modified the Logger class to behave differently.
This is a bad thing. Jim illustrated the point he was trying to make by showing the famous Leeroy Jenkins video. Sometimes individuals can’t do just what they want; they have to stick to what is good for the group.
Jim then moved on to a specific list of tips to guard against problems from extending Ruby libraries.
- ‘Guard against Murphy, not Machiavelli’ – the tips are only to help prevent accidents, they won’t stop people who are intent on causing problems.
- Use namespaces (i.e.
Module::ClassName) – and check for existing libraries so you can choose a unique name. He gave an example of searching forNodeclasses, of which there were many. - Avoid top level functions and constants. His Rake violates this rule, but the functions form part of the DSL that Rake creates, so it is somewhat justified.
- Avoid modifying existing classes, like in the Logger email earlier.
- Prefer adding over modifying, and if you add methods to existing classes, use a namespace type prefix so the methods don’t clash.
- The code that adds a new method should test whether a method of that name already exists, and warn.
- When adding public methods, ask first. (There may be a new feature in Ruby 2.0 called Selector Namespaces, which would allow users to choose their preferred implementation of some features).
“Whenever someone says they have ‘a cool trick’, take them outside and slap them up.”
- There are some ‘cool tricks’ done with Ruby’s
*_missingmethods2, but you have to be careful not to break stuff. The general pattern for these is to alias the existing method, implement your functionality, and then chain to the existing version. To avoid doing damage, limit the scope and only handle your specific cases. - An example Database library modified
NilClassto makemethod_missingjust returnnilwith no other effect – this was very bad for anything else trying to run as it would not warn about nil problems and made them hard to find. This was fixed by having the library return its ownNullobject instead, which limited the scope.
- A different example is Whiny Nils in Rails. This also changes the
method_missinginNilClass, but because it does so by adding details it does not modify the ‘essential behaviour’ of the method, so it is ok.
Jim moved into some explanation of ‘design by contract’. Ruby doesn’t explicitly provide this, but we can use the idea as guidance. Methods have ‘preconditions’ – what the state must be before the method can be called, and ‘postconditions’ – what the result of the method must be.
When making extensions to behaviour, the new precondition must be the same or weaker than the original – so any input that was acceptable to the original will be accepted by the new version. But the new postcondition must be the same or stronger than the original.
Finally, Jim summed the guidelines up as a couple of rules of thumb:
- Be polite with global resources.
- Preserver essential behaviour.
1 For those not familiar with Ruby: it allows you to ‘reopen’ classes in various ways, and redefine existing methods or add new ones. Since there are no class-level permissions, anyone can do this even to core classes.
2 For example, method_missing allows a class to intercept calls to a method on that class that doesn’t exist, and provide some alternative handling. Active Record in Rails uses this to provide finder and attribute methods.