00:00:12.860
Thank you. Very quickly, let me introduce myself. My name is Mitchell Hashimoto. This is my Twitter handle and my gravatar that I use everywhere.
00:00:18.300
Maybe you recognize it, maybe you don’t. I wrote a piece of software called Vagrant, so if you use that, have used it, or have heard of it, you can visit vagrantup.com. But this talk isn't about that.
00:00:30.539
That’s all I'm going to say about that. I work for a company called Hashicorp, and we do mobile advertising in San Francisco. I'm an Operations Engineer, so I actually don't do Ruby all day anymore. I have in a couple of years, but I do it every day in open source.
00:00:42.360
I need to thank Engine Yard because they sponsor my open source work. They are the reason I am able to come here and talk to you about these topics, which is pretty cool.
00:01:00.120
Before I get started, I want to put up a disclaimer: this talk contains opinions. It's about things that are not written down in some rulebook. They are about community practices that the Ruby community has accepted, but they are not strict rules.
00:01:14.700
You are free to disagree with me. If you do disagree, that’s cool; just talk to me about it. I've been working with Ruby for about seven years, and this is what I've learned over that time.
00:01:41.400
So, what makes a good Ruby library, and why do you care? I asked this question to many people months ago: what makes up a good Ruby library? The most common response I received was that it has a good API.
00:02:10.260
I interpreted that to mean intuitive. People want to be able to use the library and expect it to behave in a certain way. When they need to do something differently, they want it to follow the same patterns. That’s definitely an important part of a library.
00:02:34.200
However, there are many other components involved in a library, and I’m sure many of you here are library maintainers or have thought about writing libraries at some point.
00:02:45.660
Even if you’re just using libraries, you will encounter these components. At some point, you will wonder how to implement various features and how the Ruby community accepts different approaches.
00:03:10.260
Today, I will talk about several key aspects of building a Ruby library, including configuration, logging, exceptions, file hierarchies, and testing. These are just a handful of topics, but they are essential to understand.
00:03:40.379
When you start writing libraries in any programming language, you’ll encounter questions about configuration. How do others do it? Do they use configuration files, code, or DSLs? All methods work, but it is important to know the community-accepted idiomatic way.
00:04:21.600
These components are often overlooked, yet they are equally important in defining what makes a good Ruby library. If you only have a great API but lack proper logging or handling of exceptions, you will run into problems.
00:04:38.640
So, let’s start off with the first topic, which is an intuitive API. An intuitive API is basically idiomatic Ruby. While there are exceptions, I’d say that about 99 out of 100 libraries typically follow the natural Ruby conventions.
00:04:56.400
If you were to pick up a Ruby book and learn the basics of Ruby, like class structures and how to instantiate objects, you would find that using the library feels right and familiar. A well-designed library should rely on established Ruby object-oriented principles.
00:05:30.600
The next topic is versioning, which is quite interesting. Every community has their practices regarding version numbers and what they mean for upgrades and downgrades. Ruby's community has stabilized around semantic versioning.
00:05:48.120
The semantic versioning scheme includes three parts: major, minor, and patch versions. Major version changes indicate backwards incompatible changes or significant new features. Minor versions reflect backwards compatible changes that add functionality and patch versions are reserved for bug fixes.
00:06:27.420
If the major version number is zero, it indicates instability; if it is one or greater, it means the library has reached a stable version. This pattern has become widely adopted in the Ruby community.
00:06:46.320
Pre-releases are another topic to consider—RubyGems supports this feature, allowing you to append an identifier like 'dev' or 'rc' to the version number. When a user installs the gem with the pre-release flag, it installs that specific version.
00:07:06.120
However, I recommend using pre-releases sparingly. They can complicate your release cycle and unless you have a large user base, it's better to maintain a more straightforward approach to versioning.
00:07:28.680
Now, let’s talk about dependencies. Most libraries depend on other gems to function. My first recommendation is to always include a Gemfile in your code. The Gemfile is used by Bundler to manage gem dependencies.
00:08:02.280
Having a Gemfile allows users to clone your repository, run 'bundle install' to get all dependencies, and run tests easily. It provides a standard workflow expected by Ruby developers.
00:08:21.120
Additionally, you should implement pessimistic version constraints. This ensures that your library won’t be dependent on a breaking change from a gem version increment.
00:08:47.040
The pessimistic operator lets you express a dependency range that protects against breaking changes while still accepting new features from the last minor version. This convention fits neatly into semantic versioning.
00:09:29.040
Next, let's touch on code style. While I don't focus too much on code style personally, there are community-driven Ruby style guides available that most people are comfortable with. Adhering to these is beneficial whenever you are contributing to Ruby projects.
00:09:58.500
File hierarchy is another topic worth mentioning—where to place your code files within a Ruby gem structure is important.
00:10:15.840
Typically, a Ruby gem will have directories like 'bin', 'lib', and 'test' or 'spec'. 'Bin' contains executable files, 'lib' contains the actual library code, and 'test/spec' contains your tests.
00:10:43.740
Following this convention makes it easy for other developers to understand how to contribute to your library, as they won’t have to guess where to find things.
00:11:01.080
Moving on to configuration, Ruby libraries should facilitate easy configuration that leverages plain Ruby rather than creating complex DSLs. It’s best practice to keep configuration settings centralized.
00:11:18.840
Providing a clear structure for configuration allows for easier documentation and user understanding. You want your configuration method to be simple and clear.
00:12:00.840
Logging is also a crucial part of library development. Good logging can provide insights when things go wrong and helps you maintain and debug your library efficiently.
00:12:24.840
While Ruby's standard Logger is a common yet insufficient option, there are better alternatives, such as Log4r, which offer more features and flexibility with logging.
00:12:47.760
A well-structured logging approach allows for namespace management and configuring the output. Users can tailor their logging environment based on their specific requirements.
00:13:11.580
Next up is handling exceptions. Proper exception hierarchy is essential to prevent abstraction leakage, allowing your library to manage errors gracefully without exposing implementation details to users.
00:13:30.840
By using custom exception classes, you can enhance user experience. Users should receive meaningful error messages that direct them towards a solution rather than cryptic socket error messages.
00:14:14.520
Using a single parent exception class allows you to group errors by context and provides clarity when handling them.
00:14:37.860
Moving on to testing, every Ruby developer understands the value of testing. Regardless of the framework you choose to follow, make sure it is easy to run tests. Running tests should feel straightforward.
00:15:01.860
Acceptance tests or end-to-end tests, which simulate user scenarios, can catch integration errors that unit tests often miss. While they can be slower, running them regularly can identify significant issues before release.
00:15:30.720
Continuous Integration (CI) is another incredibly useful practice. Using CI tools like Travis CI can automate your testing process, providing a safety net to catch issues before they make it to production.
00:15:49.680
Documentation is critically important. A well-written README file can guide users through installation, usage, and testing process, making your library more appealing for contributors and users alike.
00:16:08.160
Additionally, thorough and clear documentation helps mitigate confusion and improves user experience.
00:16:26.880
As you finish creating your Ruby library, remember to provide support channels for users to report issues. An unsupported library can lead to frustration and reduced usage.
00:16:43.440
Support doesn't mean you need to be available at all times; it’s simply about establishing some clear way for users to ask questions or report issues.
00:17:02.399
In conclusion, when you pull together all of these elements—configuration, logging, exception handling, testing, documentation, and community support—you will end up with a well-rounded Ruby library.
00:17:40.380
These may seem like the boring parts of library development, but they are certainly critical questions that every developer will eventually encounter.
00:18:14.520
Thank you, and I’ll take any questions you may have.
00:18:21.600
Are there any questions?
00:18:58.920
Yes, I can provide references and details on the libraries I mentioned.
00:19:08.760
About support, if your project scales in size and complexity, support becomes critical.
00:19:19.680
You can use community channels and forums to allow users to engage and report issues more effectively.
00:19:30.600
I will wrap this up, so if there are any final questions...
00:19:41.520
Thank you all for your time.