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. Building a Ruby Library
    The Parts No One Talks About

    View Slide

  2. Mitchell Hashimoto
    @mitchellh

    View Slide

  3. View Slide

  4. And a thanks to my employer, Kiip,
    for being supportive of my open
    source endeavors.
    We’re hiring!
    http:/
    /kiip.me

    View Slide

  5. Big shout out to Engine Yard for
    sponsoring my open source work
    and supporting me in helping the
    Ruby community.

    View Slide

  6. 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.

    View Slide

  7. 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.

    View Slide

  8. ✓ Intuitive API
    Clearly, API matters. Most devs
    jump straight to code samples for
    libraries, and a clean API will be the
    first sell.

    View Slide

  9. 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.

    View Slide

  10. ✓ 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.

    View Slide

  11. ✓ 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.

    View Slide

  12. ✓ 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.

    View Slide

  13. ✓ Documentation
    The biggest problem with open
    source.
    A library needs documentation.

    View Slide

  14. ✓ Support
    Users of your library need a way to
    get support if they need it.

    View Slide

  15. The Boring Parts
    So these are the boring parts. No one
    talks about them.
    No one talks about them.

    View Slide

  16. A Good
    Ruby Library.
    All of these things put together
    make a FANTASTIC library.
    Whole is greater than the sum
    of its parts.

    View Slide

  17. 1. Intuitive API

    View Slide

  18. 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.

    View Slide

  19. 2. Configurable

    View Slide

  20. 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.

    View Slide

  21. 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.

    View Slide

  22. 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

    View Slide

  23. 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.

    View Slide

  24. 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

    View Slide

  25. 3. Logging

    View Slide

  26. Ruby’s stdlib
    Logger is GARBAGE.
    * Lack of namespacing.
    * Only goes to IO objects
    Basically renders logging
    useless in my opinion.

    View Slide

  27. Use log4r
    * Namespacing
    * Yeah it’s a dependency
    for your gem, but well
    worth it.
    http://log4r.rubyforge.org/

    View Slide

  28. 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.

    View Slide

  29. 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.

    View Slide

  30. 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.

    View Slide

  31. 4. Exceptions

    View Slide

  32. Without Exceptions:
    Abstraction Leakage
    * Lack of properly
    handling exceptions
    causes abstraction
    leakage.
    * Goal: Catch any possible
    exceptions, wrap and
    expose using your own.

    View Slide

  33. 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.

    View Slide

  34. Bad: Result
    ! ruby app.rb
    app.rb:in `initialize': unknown socket domain:
    twitter.com (SocketError)
    ! from app.rb:in `new'
    ! from app.rb:in `'
    This is the API that you would use
    as a developer of a library. Simple
    enough.

    View Slide

  35. 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.

    View Slide

  36. 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.

    View Slide

  37. 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.

    View Slide

  38. 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 `'
    With descriptive, specific error
    classes, its easy to see what is
    going on and to catch that specific
    example.

    View Slide

  39. 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.

    View Slide

  40. 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.

    View Slide

  41. 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.

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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!

    View Slide

  45. 6. Support

    View Slide

  46. 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.

    View Slide

  47. 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.

    View Slide

  48. Users just
    need something.
    Support just needs to be
    SOMETHING.
    IRC, mailing list, issues, etc.

    View Slide

  49. 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.

    View Slide

  50. Thanks!

    View Slide