DEV Community

Cover image for YARD for Ruby Beginners: A Documentation Guide
swastik009
swastik009

Posted on • Originally published at Medium

YARD for Ruby Beginners: A Documentation Guide

Documentation is the unsung hero of software development, ensuring that developers, users, and stakeholders can navigate, use, and maintain software with ease and precision. Among the various styles of documentation, I've developed a particular fondness for YARD(Yay! A Ruby Documentation Tool)-style documentation when working in Ruby. It's not just Ruby though, JavaScript has JSdoc, and Python boasts Google style or NumPy style; each having the clarity and structure of YARD documentation.

I've been using YARD documentation for the past 3-4 months, and I've found it to be incredibly intuitive and easy to understand. This experience has only deepened my appreciation for well-crafted documentation, making it a joy to both write and read.

What is yard-style documentation?
According to yardoc.org
YARD is the only Ruby documentation tool that supports storing metadata alongside your documentation.

YARD documentation is robust and provides a well-structured format for writing documentation. It supports extensions for unique Ruby constructs, such as unique class-level definitions, and enables users to provide consistent, easily navigable documentation that can be exported to various formats. Let's dive into installing and getting Yarddoc in action without further delay.

Let's first install the yard.

gem install yard
Enter fullscreen mode Exit fullscreen mode

If you want to add it to your Gemfile then just,

Gemfile

gem 'yard' 

# Or if you want to encapsulate it just inside development env

group :development do 
  gem 'yard'
end
Enter fullscreen mode Exit fullscreen mode

let's implement YARD-style documentation in your code.
Suppose you have a method that takes an array of numbers as a parameter and returns a single accumulated integer value.

# Sums up all elements in the provided array after converting them to integers.
#
# @param numbers [Array<String, Integer>] an array of numbers or numeric strings. Defaults to an empty array.
# @return [Integer] the sum of the converted integers in the array.
#
# @example Summing an array of numeric strings
#   add(numbers: ["1", "2", "3"]) # => 6
#
# @example Summing an array of integers
#   add(numbers: [1, 2, 3]) # => 6
#
# @example Handling non-numeric values
#   add(numbers: ["a", "1", "2"]) # => 3
#
# @example Default empty array
#   add(numbers: []) # => 0
def add(numbers: [])
  numbers.map(&:to_i).inject(0) { |sum, x| sum + x }
end
Enter fullscreen mode Exit fullscreen mode

This might seem overwhelming, 3 lines of code just turned into multiple lines of code but it is fairly straightforward. Let me break it down.

YARD uses special @tags to highlight metadata in documentation, with common ones being @param, @return, and @example. Personally, I avoid adding multiple examples to prevent cluttering the codebase. Instead, I provide clear definitions for the parameters and return types, trusting that readers can understand the method based on the concise, well-written documentation.

Sometimes, a method can have multiple return types. For instance, if you remove the inject(0) in the above code and simply use inject it will return nil for an empty array. This introduces two possible return types: a numeric value when the array has elements or nil when the array is empty. It’s important to reflect such scenarios in the documentation for clarity. Let me provide an example.

# Sums up all elements in the provided array after converting them to integers.
#
# @param numbers [Array<String, Integer>] an array of numbers or numeric strings. Defaults to an empty array.
# @return [Integer, nil] the sum of the converted integers in the array, or `nil` if the array is empty.
#
# @example Summing an array of integers
#   add(numbers: [1, 2, 3]) # => 6
#
# @example Default empty array
#   add(numbers: []) # => nil
def add(numbers: [])
  numbers.map(&:to_i).inject { |sum, x| sum + x }
end
Enter fullscreen mode Exit fullscreen mode

The @return tag can specify multiple possible data types, such as Integer or nil in this case. I’ve also updated the example for the default empty array scenario to reflect these changes. Similarly, you can define multiple data types for parameters as well. This approach works seamlessly even for vector values.

How to document error handling?

Let’s take the above example for the reference

def add(numbers: [])
  numbers.map(&:to_i).inject { |sum, x| sum + x }
end
Enter fullscreen mode Exit fullscreen mode

Let’s add a few exceptions handling for the above example:

def add(numbers: [])
  numbers.map(&:to_i).inject(0, :+)
rescue StandardError => e
  raise TypeError, "Failed to convert elements to integers: #{e.message}"
end
Enter fullscreen mode Exit fullscreen mode
Now how would you document the above method using yard-style doc?

# Sums up all elements in the provided array after converting them to integers.
#
# @param numbers [Array<String, Integer>] an array of numbers or numeric strings. Defaults to an empty array.
# @return [Integer] the sum of the converted integers in the array, or `nil` if the array is empty.
#
# @raise [TypeError] If any element cannot be converted to an integer or if unexpected errors occur during processing.
# @example Summing an array of integers
#   add(numbers: [1, 2, 3]) # => 6
#
# @example Default empty array
#   add(numbers: []) # => 0
def add(numbers: [])
  numbers.map(&:to_i).inject(0, :+)
rescue StandardError => e
  raise TypeError, "Failed to convert elements to integers: #{e.message}"
end
Enter fullscreen mode Exit fullscreen mode

You can see I have written yard-tag @raise after @param and @return that documents the exceptions raised from the given methods — You can document multiple exceptions using multiple @raise tags if your method raises different types of errors.

Now another great benefit is that you can document dynamic or custom Ruby constructs, for example: in readme.md for yard. You can see an example:

class List
 # Sets the publisher name for the list.
 cattr_accessor :publisher
end
Enter fullscreen mode Exit fullscreen mode

Now how do you document the above line of code?

# A list of items with a configurable publisher.
class List
  # The publisher name for the list.
  #
  # @!attribute [rw] publisher
  #   @return [String] the publisher name for this list.
  cattr_accessor :publisher
end
Enter fullscreen mode Exit fullscreen mode

In the code, we start by defining the class, List, and its attribute publisher using cattr_accessor. The first part is simply the method definition, but the key here is the use of @!attribute instead of @param. This special tag tells YARD that we're dealing with a dynamically defined attribute. This [rw] indicates that the attribute is both readable and writable, meaning it can be accessed and modified. Finally, we specify the @return type, which describes the type of value this attribute will hold.

For example:

List.publisher = "Medium"
List.publisher #=> "Medium"
Enter fullscreen mode Exit fullscreen mode

This demonstrates how publisher acts as a class-level getter and setter. There are other dynamic ways to document cattr_accessor in YARD, but that’s a topic for another article.

Now, since you have written all of these documents, what’s the use of it if you cannot have interactive documents right? So, don’t worry Yard provides a functionality to generate static HTML with support for a live server.
Since you have already installed a yard; it bundles with everything mentioned above. Let's see with an example.
This would be the final code that we want to visualize with proper documentation.

# array_adder.rb

# A utility class for array operations.
class ArrayAdder
    # Sums up all elements in the provided array after converting them to integers.
    #
    # This method converts each element of the given array into an integer
    # and returns their sum. If any element cannot be converted, a TypeError is raised.
    #
    # @param numbers [Array<String, Integer>] an array of numbers or numeric strings. Defaults to an empty array.
    # @return [Integer] the sum of the converted integers in the array. Returns 0 if the array is empty.
    #
    # @raise [TypeError] If any element cannot be converted to an integer or if an unexpected error occurs.
    #
    # @example Summing an array of integers
    #   ArrayAdder.add(numbers: [1, 2, 3]) # => 6
    #
    # @example Including numeric strings
    #   ArrayAdder.add(numbers: ["1", "2", "3"]) # => 6
    #
    # @example Default empty array
    #   ArrayAdder.add(numbers: []) # => 0
    #
    # @example Invalid elements
    #   ArrayAdder.add(numbers: ["a", 2]) # => Raises TypeError: Failed to convert elements to integers
    def self.add(numbers: [])
        numbers.map(&:to_i).inject(0, :+)
    rescue StandardError => e
        raise TypeError, "Failed to convert elements to integers: #{e.message}"
    end
end
Enter fullscreen mode Exit fullscreen mode

Generating documentation is pretty straightforward. Type below code in your terminal

yard doc your/path/to/file.rb
Enter fullscreen mode Exit fullscreen mode

for the above example is

yard doc array_adder.rb
Enter fullscreen mode Exit fullscreen mode
Files:           1
Modules:         0 (    0 undocumented)
Classes:         1 (    0 undocumented)
Constants:       0 (    0 undocumented)
Attributes:      0 (    0 undocumented)
Methods:         1 (    0 undocumented)
 100.00% documented
Enter fullscreen mode Exit fullscreen mode

You’ll notice something like the above in your terminal after executing the command with a ‘doc’ folder appearing in your project directory. Next, start the server within your project directory to view the documentation.

yard server
You will see something like below:

Image description

A proper interactive yard documentation — NOICE.

In summary, documentation can change how you and others work with your code. It lets you create easy-to-understand guides and examples for each piece of code, making everything clearer for everyone involved. Yard is a tool to create systematic documentation but there are tons of other tools or methods, You can pick any one of them as per your liking.

To finish off, motivate yourself and your team to see documentation as a key part of coding. Remember, documentation isn’t just about explaining what your code does; it’s about making it accessible and maintainable for others (and your future self). Happy coding, and may your documentation be as clear as your intentions!

References and Further Reading:

Official YARD Documentation: yardoc.org: The primary source for all things YARD, including installation, usage, and contributing guidelines.

YARD GitHub Repository: github.com/lsegal/yard: For those interested in the codebase, bug reports, or contributing to YARD.

YARD Tags Reference: rubydoc.info/gems/yard/file/docs/Tags.md: Detailed documentation on YARD tags for advanced documentation practices.

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay