Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a Ruby Library: The Parts No One Talks About

Building a Ruby Library: The Parts No One Talks About

Mitchell Hashimoto

June 02, 2012
Tweet

More Decks by Mitchell Hashimoto

Other Decks in Programming

Transcript

  1. And a thanks to my employer, Kiip, for being supportive

    of my open source endeavors. We’re hiring! http:/ /kiip.me
  2. Big shout out to Engine Yard for sponsoring my open

    source work and supporting me in helping the Ruby community.
  3. WARNING! OPINIONS AHEAD This talk has many opinions throughout. I

    try to back up my opinions with some evidence but of course there will be differing thoughts. My opinions are based on over two years of maintaining relatively popular Ruby libraries, as well as being a full-time Ruby dev for over five years.
  4. A Good Ruby Library? What makes a good Ruby library?

    Most people think “easy to use” or “simple” or “clean API.” But there is a lot more going on that is equally important.
  5. ✓ Intuitive API Clearly, API matters. Most devs jump straight

    to code samples for libraries, and a clean API will be the first sell.
  6. But... The Other Parts Intuitive API everyone always gets. But

    there are a LOT of other parts to a good Ruby library. Let’s go over them quickly, then in more detail later.
  7. ✓ Configurable Almost all libraries have tunable elements. Exposing those

    tunable elements in a predictable way is important to give your library the most power, while retaining its simplicity.
  8. ✓ Logged Easily overlooked, when a library DOESNT work, users

    of a library would like to know whats going on without diving straight into the source code. Properly structured log messages are a godsend.
  9. ✓ Exceptions If I’m using an HTTP library and make

    a request, I don’t want to see a SocketError pop up. I didn’t make a socket, how did I error? Libraries should catch their exceptions, wrap them in their own, and expose them in a documented way. More on this later.
  10. The Boring Parts So these are the boring parts. No

    one talks about them. No one talks about them.
  11. A Good Ruby Library. All of these things put together

    make a FANTASTIC library. Whole is greater than the sum of its parts.
  12. Idiomatic Ruby * Don’t do anything crazy, just stick with

    the norms. * Ruby has been around for a long time now, we’ve got our conventions. * Helps people pick it up faster. * Gives users that “it just feels right” feeling. Don’t try to be cool. I’m not going to spend much time on this section because this is generally the part that people get right, because they’re used to working with Ruby already and this is what they focus on. Plus, during the talk I’m super crunched for time, so I had to move on.
  13. Plain old Ruby * Don’t try something fancy, configuration should

    be non-magical and straightforward. * Use plain Ruby (APIs), or maybe JSON/YAML for tools. Config should have NO MAGIC.
  14. Central configuration * Don’t try something fancy, configuration should be

    non-magical and straightforward. * Use plain Ruby (APIs), or maybe JSON/YAML for tools. Keep it in one place.
  15. 1 require "mylib" 2 3 MyLib.configure do |config| 4 config.setting

    = "value" 5 config.enable_something! 6 end * Central configuration * Plain old ruby object (`config`) Central Configuration
  16. Instance configuration via initializers and option hashes * Don’t try

    something fancy, configuration should be non-magical and straightforward. * Use plain Ruby (APIs), or maybe JSON/YAML for tools. Idiomatic, well understood.
  17. 1 require "mylib" 2 3 obj = MyLib.new(:setting => "value")

    4 obj.setting = "value2" * Use normal constructors and option hashes for instance-level configuration. * Very idiomatic, very clear. Instance Configuration
  18. Ruby’s stdlib Logger is GARBAGE. * Lack of namespacing. *

    Only goes to IO objects Basically renders logging useless in my opinion.
  19. Use log4r * Namespacing * Yeah it’s a dependency for

    your gem, but well worth it. http://log4r.rubyforge.org/
  20. Usage: Your API 1 log = Log4r::Logger.new("mylib::connection") 2 log.debug("debug!") 3

    log.info("information") This is the API that you would use as a developer of a library. Simple enough.
  21. Usage: User API 1 log = Log4r::Logger.new("mylib") 2 log.outputters =

    3 Log4r::Outputter.stderr 4 log.level = Log4r::INFO This is what the user of your library would have to do to enable logging for your library. Super easy, predictable.
  22. Don’t: • Print to stdout/stderr • Rely on “debug” config

    option • Use something custom * stdout/stderr: Tempting, but extremely annoying and can cause crashes if pipe is closed. * “debug” option is fine, but don’t require it. Sometimes I want debug logging, but not other debug behavior. * log4r may not be the best, but the last thing we need is more fragmentation.
  23. Without Exceptions: Abstraction Leakage * Lack of properly handling exceptions

    causes abstraction leakage. * Goal: Catch any possible exceptions, wrap and expose using your own.
  24. Abstraction Leakage 1 require "twitter" 2 3 Twitter.socialize! Example: Let’s

    say you’re doing something with Twitter. It doesn’t matter what, it’s just important to know that there is network activity going on.
  25. Bad: Result ! ruby app.rb app.rb:in `initialize': unknown socket domain:

    twitter.com (SocketError) ! from app.rb:in `new' ! from app.rb:in `<main>' This is the API that you would use as a developer of a library. Simple enough.
  26. Single Parent Exception Class * Allows a “catch all” for

    a specific library very easily. * Can find all children classes easily. * Inherit from StandardError, so that catch-alls still work on that.
  27. Single Parent Class 1 require "twitter" 2 3 begin 4

    Twitter.socialize! 5 rescue Twitter::Error 6 # Twitter is always down so 7 # I stopped caring. 8 end A single parent class makes it easy to catch-all in a specific library, such as this case.
  28. Many Subclasses Be descriptive! * Don’t be afraid, make a

    lot! The user can always “catch all” * Make the name descriptive, makes it a lot easier to read code.
  29. Descriptive Subclasses ! ruby app.rb app.rb:in `initialize': Failed to connect

    to twitter (Twitter::ConnectionError) ! from app.rb:in `new' ! from app.rb:in `<main>' With descriptive, specific error classes, its easy to see what is going on and to catch that specific example.
  30. Use Standard Exceptions When it makes sense. * Don’t reinvent

    the wheel: FooNotEnoughArguments, just use ArgumentError There aren’t many available, but the basics are good.
  31. 5. Documentation * Two kinds: API docs (libraries) and usage

    docs (tools) * Both should be kept in the same repo as the code, ideally. * Write it AS YOU CODE. Same as tests.
  32. Write it from the beginning. * Its easy to push

    off docs until the end so your lib isn’t changing much anymore, but documentation is SO BORING that you’ll end up hating yourself. * Write it as you go (like tests). If the library changes, then you’ll have to change docs, so there is more work, but at least you’ll have the docs at the end. * Example: Vagrant 0.1 docs pushed off until the end, took me 2 weeks to write them. Really burned me out. More work, but less pain.
  33. Keep it with the code. * Docs should be in

    the same repo as the code. * Use a branch, won’t ever be merged into master, so clean it and just keep it going. * Makes it easy to contribute. (Pull requests!) * Can see “point-in-time” documentation for specific versions
  34. API: Use YARD * API docs, use YARD * Similar

    to RDoc/JavaDoc, so its familiar. * Generates pretty HTML, PDFs. * You automatically get generated docs from rubydoc.info http://yardoc.org
  35. Other: Markdown + Jekyll * For other docs, use plain

    old markdown, but try to fit it into a Jekyll-powered site. * Two birds, one stone: - Readable from the source. - Able to generate a pretty website. Readable + Website!
  36. Unsupported code is not “open source” * If I don’t

    see support, I either don’t use the project or I use the code as reference to implement something myself. It’s just reference material.
  37. Support doesn’t have to equal work Providing support doesn’t mean

    you need to work much harder. Its simply more important to provide the opportunity for support.
  38. A Great Ruby Library. With all these things put together,

    you don’t get a good Ruby library, you get a _great_ Ruby library. From learning about your library to developing with it to maintaining it, everyone will thank you.