From: Tim Hunter Date: 2002-08-22T05:53:41+09:00 Subject: Re: What makes a "good" Ruby extension? So I'm reading Hal's 42-item list and in my head it sounds like this: "Uh-huh. Did that. Okay. Oops. Got it covered. Oops. Oops. Okay. Oops..." With all this good advice I'll be able to polish my extension to a high gloss! Thanks, everybody! P.S. In the "real" (a.k.a. corporate) world, this kind of advice would cost $2,000/day, take two days to present, and come bound in a leather folder with my name embossed in gold on the cover. On Tue, 20 Aug 2002 22:26:34 -0400, Hal E. Fulton wrote: > ----- Original Message ----- > From: "Tim Hunter" Newsgroups: comp.lang.ruby To: > "ruby-talk ML" Sent: Tuesday, August 20, 2002 > 5:31 PM Subject: What makes a "good" Ruby extension? > > > An interesting question. I'll offer an opinion or two. > > An API should take advantage of the things Ruby does well -- the things > that make it Ruby -- WHEN this is appropriate. > > Most of my examples here will deal with only the API that is exposed to > the user... if we got into coding style, I could probably come up with > even more. > > Examples: > > 1. Use class variables and methods in the API as needed. 2. If you have > a collection or list, consider giving it an iterator. Consider more than > one kind, e.g. with_index. 3. If you do an open/close or start/stop, > consider using an "non-iterating" iterator for this. Examples are > File.open, Mutex#synchronize, and Dir.chdir (the new one). 4. If a > method takes an object, consider whether it might be meaningful for it > to take a block instead or in addition. 5. Make use of nil where > appropriate. 6. Make use of the existing modules, especially Enumerable > and Comparable, where appropriate. > 7. Use symbols if/when appropriate. Sometimes they make a good > substitute for string constants or arbitrary integer values. 8. Create > meaningful exception classes as needed. 9. Provide objects with to_a, > to_s, to_i, etc., as needed. 10. Give objects good 'inspect' methods. > 11. Remember that often it matters less what class a thing is than what > methods it responds to. One of my favorite examples: Suppose your method > appends each thing in a collection onto an object supplied by the caller > (obj << item). This will work whether the 'obj' passed in is a string, > array, or file (since you can meaningfully append onto any of them with > a << operation). > 12. As regards #9, we also have to_ary, to_str, and (I think?) to_int. > 13. If you define some kind of numeric class, make sure it can be > coerced as needed. > 14. Subclass as needed. This is just good OO besides good Ruby. Subclass > your own classes and/or the predefined ones. 15. Use hashes when > convenient/appropriate. They're powerful. 16. As regards #11 above, > remember to hide implementation details to de-couple with the caller. If > a hash has numeric keys, it resembles an array (if you only use [] and > []=). Or it could even be your own special data structure. 17. Use > readers/writers/accessors when appropriate. 18. Don't be afraid to add > to existing classes/modules when it makes sense to do so. That's why > they're open. 19. An occasional alias can help an API. (Some people > don't like this, but I do.) In the core, indexes and indices are the > same for Arrays. For Strings, size and length are the same. Etc. 20. > When an object needs to act "almost" like another object, minus some > functionality, consider delegation (delegate.rb). 21. When appropriate, > make use of the ? and ! suffixes in the standard way. 22. When > appropriate, use default values for parameters. 23. Use Struct when it > will help you. This will often prevent indexing into arrays with > arbitrary indices. 24. Be platform independent when you can. There are > things like File::SEPARATOR (?) that help with this. 25. Consider > whether it makes sense to marshal an object. If so, try to write the > class so that it remains marshallable. 26. Overload operators as needed. > That's why we have the capability. 27. In your API, try to use > terminology consistent with the rest of the Ruby world. (Sigh. I don't > always do this either.) 28. It's a good idea (though I don't practice > what I preach) to try to support non-US and/or non-ASCII users to the > extent you can. 29. Use singleton methods for objects where it makes > sense. For example: In a GUI, you might allow the user to attach a > method directly to a Button object. > 30. Worry about performance last, and only when needed. (Some may flame > me for this.) If you really need great performance, write a C extension. > Otherwise stay in Ruby. Conceivably you might even offer options for > some pieces. > 31. Allow users to represent their data in creative ways. If your "foo" > method takes an array, see if it makes sense for it to take a hash also > (or any object that knows [] and []=). If it takes a string, see if a > regex makes sense. Et cetera... 32. Make use of method_missing where > appropriate. At the very least, catch undefined methods that may be > generated/called at runtime. Hmm, unneeded? Ruby does it for you... 33. > If you add to a class or module, go to the highest meaningful level of > generality. Don't add to File if it belongs in IO. Don't add to a dozen > classes if it belongs in Enumerable. 34. Consider making objects > thread-safe when appropriate, since Ruby has thread capabilities. 35. > Consider a "hybrid iterator" (if I may call it that) which returns a > list when called without a block. 36. Don't forget that Ruby is dynamic. > I'm not sure what I mean by saying this, but don't forget it. :) 37. > Avoid camelCase in your API. Some like it, some use it, but it isn't > done in the core, I think. Likewise avoid using capitalized method names > (unless perhaps they perform data conversion, like Integer). Underscores > are generally accepted (i.e., used in the core). 38. Don't force users > to use global variables or the Perl-like "swearing" variables ($!, $_, > etc.). > 39. Don't forget there is such a thing as a Range class. Allow users to > pass in ranges where it makes sense. 40. Allow arbitrary numbers of > parameters where it makes sense. 41. Make error messages and exceptions > as meaningful as possible. You can include data in them when it helps. > 42. Use Rubylike ways of dealing with data. For example, don't represent > a time as an array of numbers or a string, but as a Time object. If you > need to convert to/from the other representations, provide a way to do > so. > > Oh, yeah. And wear sunscreen. > > Hal Fulton