Ruby Style Guide
Overview
Ruby is a dynamic, expressive programming language known for its elegant syntax and developer-friendly conventions. Following a consistent coding style is essential for writing maintainable and readable Ruby code. This article serves as a comprehensive Ruby style guide, providing a cheatsheet of best practices and conventions for various aspects of Ruby programming. By following these guidelines, we can enhance the clarity, readability, and maintainability of our Ruby code.
Whitespace
Whitespace plays an important role in maintaining the readability and clarity of Ruby code. Here are some guidelines for using whitespace effectively in Ruby:
Indentation
- Use soft-tabs with a two-space indent. This is the recommended convention in Ruby.
- Indent the when keyword as deep as the case keyword when using a case statement.
- When using a multi-line case statement with multiple conditions, align the conditions with an extra indentation.
- The function arguments must be aligned on the same line or one per line.
- In multi-line boolean expressions, indent consecutive lines to increase readability.
Inline
- Avoid trailing whitespace at the end of lines. It can be distracting and unnecessary.
- Use spaces around operators, after commas, colons, and semicolons, and around { and before } to improve readability.
- Avoid spaces after (, [ or before ], ).
NewLines
- If the conditions span many lines, add a new line after each of them. This distinguishes the conditions from the body of the if statement.
Line Length
When writing Ruby code, it's important to follow a consistent style guide to ensure readability and maintainability. One aspect of a good style guide is the line length. Keeping lines of code within a reasonable length makes it easier to read and understand the code. The commonly recommended limit is 100 characters per line, although this can vary depending on the specific guidelines of our project or team.
To adhere to the line length guideline, we can employ several techniques:
- Utilize line breaks within unclosed brackets or parentheses: When dealing with complex data structures or method chains, it's beneficial to break the code into multiple lines. We can avoid excessively long lines of code and ensure that each line focuses on a specific part of the code logic, making it easier to understand and debug if necessary.
- Use method chaining and dot ending for long chains: In the case of lengthy method chains, breaking them into multiple lines is recommended. Each line should end with a dot (.) to signify the continuation of the chain on the next line. This improves code clarity and maintainability.
- Construct long strings by concatenating with backslash-newline: When working with extended strings, consider breaking them into multiple lines by placing the strings adjacent to each other. Separate the strings using a backslash followed by a newline character. This approach enhances the readability of lengthy string literals.
- Break lengthy logical statements with line breaks after operators: For lengthy logical statements, it's advisable to break them into multiple lines for better comprehension. Introduce line breaks after logical operators such as && and ||. Ensure that the continuation lines are appropriately indented to enhance code readability.
Commenting
Commenting code is an essential practice to make the code more readable and understandable. The following guidelines provide a comprehensive overview of commenting in Ruby, including file/class-level comments, function comments, block and inline comments, punctuation, spelling, grammar, TODO comments, and avoiding commented-out code
File/class-level comments
- Every class definition should have an accompanying comment describing its purpose and usage.
- A file that contains zero classes or multiple classes should have a comment at the top explaining its contents.
For example:
In this example, we have a Calculator class with accompanying comments describing its purpose and usage. The comments provide a brief explanation of what the class does and what methods it provides (add and subtract).
Function comments
- All function declarations should contain comments that describe what the function performs and how to utilise it.
- Rather than being imperative, the function comments should be descriptive and should specify the inputs and outcomes.
For example:
In this example, the function comment immediately precedes the calculate_sum function declaration. The comment describes what the function does, which is calculating the sum of two numbers. It also mentions the inputs (parameters) of the function, num1 and num2, and specifies the return value, which is the sum of num1 and num2.
Block and inline comments
- Place comments in tricky parts of the code that require explanation during code review.
- Use a few lines of comments before complicated operations and comments at the end of the line for non-obvious operations.
- Avoid describing the code itself, assuming the reader knows the language but not the specific intention of the code.
For example:
In this example, we use block comments to explain complicated operations. The comments describe the purpose and intention of the code block, making it easier for other developers to understand the logic during code review.
Punctuation, spelling, and grammar
- Pay attention to punctuation, spelling, and grammar in comments for improved readability.
- Use proper capitalization, punctuation, and complete sentences in most cases.
- Shorter comments at the end of a line can be less formal but should maintain consistency.
TODO comments
- TODO comments are useful for temporary code, short-term solutions, or adequate but not ideal implementations.
- TODOs should begin with the word "TODO" in all capitals, followed by the complete name of the person who can offer further information about the problem.
- Include a note describing what needs to be done.
- TODOs are not promises to solve the problem, but rather a reference to the person who can offer additional details.
For example:
Commented-out code
- Avoid leaving commented-out code in the codebase, as it adds clutter and confusion.
- Remove unused code instead of commenting it out.
By following these guidelines, we can ensure that our code is well-documented, readable, and maintainable.
Methods
Method definitions
- Use def with parentheses when the method has parameters:
- Omit parentheses when the method doesn't accept any parameters:
- Avoid using default arguments in method definitions. Instead, use an options hash as a parameter.
In the good example, the method func accepts an options hash, which provides flexibility and avoids cluttering the method signature with multiple default arguments.
Method calls
- Method calls should be enclosed in brackets:
a) When the method returns a result
b) When the method's first parameter is enclosed in brackets:
- A space should never be placed between the method name and the opening parenthesis:
- Omit parentheses for method calls without arguments:
- If a method doesn't return a value or if we don't care about the return value, parentheses are optional. However, if the arguments span multiple lines, using parentheses can improve readability:
- Do not use {} during calling when a method receives an options hash as its final argument:
Conditional Expressions
Conditional keywords
- Avoid using then for multi-line if/unless statements:
When writing multi-line if/unless statements, it is preferred to omit the then keyword.
- Avoid using and, or, and not keywords. Instead, use &&, ||, and ! respectively:
It is recommended to use &&, ||, and ! instead of their respective keywords for logical operations.
- Modifier if/unless usage is acceptable when the body and condition are simple and fit on one line. Otherwise, avoid it:
While it is acceptable to use modifier if/unless statements for simple conditions and bodies, more complex situations should be written in multiple lines to improve readability.
- Avoid using unless with else. Rewrite the code with the positive case first:
It is preferred to structure the code with the positive case (if) before the negative case (else) for better readability.
- Avoid using unless with multiple conditions:
Instead of using unless with multiple conditions, it is recommended to use if with negated conditions for better clarity.
- Avoid using parentheses around the condition of an if/unless/while unless it contains an assignment:
Parentheses are unnecessary around the condition unless an assignment is present. This improves readability by reducing unnecessary syntax.
Ternary operator
- Avoid using the ternary operator (?:) except for extremely trivial expressions. Prefer if/else constructs for more complex conditionals:
The ternary operator should be used rarely and preferably for simple expressions. For more complex conditionals, if/else constructs are preferred for better readability.
- In a ternary operator, use a single expression per branch. Avoid nesting ternary operators and instead use if/else constructs:
Ternary operators should not be nested, and each branch should contain only one expression. For more complex cases, it is better to use if/else constructs.
- Avoid multi-line ternary operators. Use if/then/else/end instead:
Multi-line ternary operators can reduce readability. It is recommended to use if/then/else/end constructs for such cases.
Syntax
- Avoid using for loops: Instead of using for loops, it's recommended to use iterators like each. The for loop can add unnecessary levels of indirection and doesn't introduce a new scope. Here's an example:
Explanation
Instead of using a for loop, the recommended approach is to use the each iterator method on the array arr. By using {...}, we can pass a block of code to the iterator for each element of the array, achieving the same result as the for loop.
- Prefer {...} over do...end for single-line blocks: For single-line blocks, it's preferred to use {...} instead of do...end. However, for multi-line blocks, it's better to use do...end to improve readability. Here are some examples:
Explanation
The examples demonstrate the recommended approach for both cases.
- Avoid unnecessary use of return: If a method's last line is the desired return value, we don't need to use return. Here's an example:
Explanation
In Ruby, the last evaluated expression within a method is automatically returned, so using return is unnecessary unless we want to exit the method early. In the given example, the size of arr is the last expression, so it will be implicitly returned.
- Use parentheses around assignments: When using the return value of an assignment, it's recommended to surround the assignment with parentheses to make the intention clear. Here's an example:
Explanation
When using the return value of an assignment, it's recommended to surround the assignment with parentheses to improve clarity and avoid potential confusion. It makes the intention of using the assignment result explicit and helps prevent unintended errors.
- Use ||= to initialize variables: We can use ||= to assign a value to a variable only if it's currently nil or false. Here's an example:
-
Avoid using Perl-style special variables: In Ruby, it's discouraged to use Perl-style special variables (e.g., 1, PROGRAM_VERSION).
-
Use _ for unused block arguments: If a block argument is not used within the block, it's recommended to name it _ to indicate that it's intentionally unused. Here's an example:
- For basic method calls, use &: as a shortcut: We can use the &: shorthand when a method block accepts just one parameter and the method body involves of reading an attribute or invoking another method with no arguments. Here's an illustration:
The &: shorthand simplifies the code and makes it more concise by implicitly passing the method as a block to the iterator.
These guidelines aim to improve code readability and maintainability in Ruby. It's important to follow them to ensure consistency within a codebase and make it easier for other developers to understand and maintain the code.
Naming
Let's go through the Ruby style guide for naming conventions and provide relevant examples.
- For methods and variables, use snake_case: Snake case means using lowercase letters with underscores between words. This convention is commonly used for naming methods and variables in Ruby. For example:
- CamelCase should be used for classes and modules: Camel case means capitalizing the first letter of each word without any spaces or underscores. This convention is used for naming classes and modules in Ruby. For example:
HTTP and XML are examples of acronyms that should be capitalized completely:
- For additional constants, use SCREAMING_SNAKE_CASE: SCREAMING_SNAKE_CASE means using uppercase letters with underscores between words. This convention is used for naming constants in Ruby. For example:
- Predicate methods should be denoted with a question mark: Predicate methods are methods that return a boolean value (true or false). It is a common convention to end the names of these methods with a question mark. For example:
- For potentially "dangerous" methods, use an exclamation mark: Methods that have the potential to modify the object or have side effects are sometimes called "dangerous" methods. To indicate this, their names should end with an exclamation mark. For example:
- For throwaway variables, use _: When we don't need the value of a particular variable, we can use _ as its name. This convention indicates that the variable's value is not relevant to the current context. For example:
In this example, the calculate_sum method takes an array of numbers as input and calculates their sum. Inside the method, we iterate over the numbers array using the each method. Since we are only interested in the value of each number and not its index, we use _ as the throwaway variable.
By following these naming conventions, our code will become more consistent and easier to read and understand.
Classes
Here are some guidelines related to classes:
- Avoid the use of class variables (@@). Class variables are shared among all classes in a hierarchy, which can lead to unexpected behavior. Instead, prefer using class instance variables.
In the given example, we have a MyClass with a class variable @@class_var initialized as 'Hello', and a class method print_class_var that outputs the value of @@class_var. Then, an AnotherClass is defined, inheriting from MyClass, where the value of @@class_var is changed to 'World'. When we invoke print_class_var on MyClass, it outputs "World" instead of "Hello". This occurs because class variables are shared across the entire class hierarchy, causing modifications made in AnotherClass to affect the value accessed in MyClass.
- Use def self.method to define singleton methods. Singleton methods are methods that belong to a specific instance of a class rather than all instances. This approach makes the methods more resistant to refactoring changes. For example:
- Avoid using class << self except when necessary, such as for single accessors or aliased attributes. This construct is useful in certain scenarios, but excessive use can decrease code readability. For example:
In the improved version, we kept the necessary class << self block for defining accessors and aliasing attributes. However, other class methods (first_method and second_method_etc) are defined directly using def self.method_name syntax.
- Indent the public, protected, and private methods the same as the method definitions they apply to. Leave one blank line above them for clarity. For example:
In the above code snippet, private_method is indented the same as public_method, and a blank line is left above the private keyword to separate it visually from the previous method.
Exceptions
Exceptions are a powerful mechanism in Ruby for handling errors and exceptional situations. However, they should not be used for flow control, such as branching logic or handling expected conditions. Instead, they should be reserved for handling exceptional or unexpected errors.
Let's take a look at some examples to illustrate the recommended practices.
- Avoid the use exceptions for flow control:
In this example, an exception is used to handle the division operation when the divisor is zero. Instead, it's better to check the condition explicitly and handle it without using exceptions.
Here, we check if the divisor is zero using the zero? method and handle the case accordingly. This approach avoids the unnecessary use of exceptions for a predictable condition.
- Avoid the rescue of the Exception class:
In this example, the Exception class is used as the rescue clause, which captures all exceptions, including system-level errors. It's generally not recommended to rescue the Exception class because it may hide critical errors that should be handled differently.
Instead, it's better to rescue the StandardError class, which captures normal application-level exceptions. This approach ensures that critical system-level errors are not ignored.
Collections
- If we have a collection of elements where uniqueness is important, it's better to use a Set instead of an Array. The Set class implements a collection of unordered values with no duplicates. This allows for efficient lookup and ensures that each element appears only once. For example:
- When defining a hash, it's preferable to use symbols as keys instead of strings. Symbols are immutable and unique, making them more efficient for hash lookups. For example:
- When dealing with complex hashes, it's often better to use a multi-line format for better readability. This makes it easier to see each key-value pair and avoids excessively long lines.
Strings
Here are some guidelines related to string manipulation:
- Prefer String Interpolation: Instead of using string concatenation (+) to combine strings, it's recommended to use string interpolation (#{}) for better readability. Here's an example:
In the above example, the good approach using string interpolation makes it clear how the strings are being combined, and it eliminates the need for explicit concatenation.
- Ruby 1.9-style Interpolation: When composing cache keys or similar scenarios, Ruby 1.9 introduced a shorthand syntax for string interpolation. Let's consider an example where we want to append a string to the user's ID for a cache key:
Instead of using concatenation, we can use the shorthand interpolation syntax (%) to achieve the same result:
In the above example, %d is a placeholder for an integer value, and % @user.id substitutes the placeholder with the actual value. This approach is more concise and expressive.
- String Concatenation with <<: When we need to construct large data chunks by appending multiple strings, it's recommended to use the << operator instead of +. The << operator mutates the string instance in-place and is generally faster than +, which creates new string objects.
In the above example, the html string is built by appending strings using <<.
Regular Expressions
Here are some tips for using regular expressions effectively in Ruby code, along with relevant examples:
- Avoid using numbered backreferences: It is recommended to avoid using $1-9 as they can be hard to track and understand what they contain. Instead, use named groups to capture matched patterns. This makes the code more self-explanatory. Here's an example:
In this example, we define a regular expression pattern that captures a single word using the named group word ((?<word>\w+)). When we match the pattern against the string "Hello, World!", we can access the captured word using match[:word]. This approach provides a clear and concise way to refer to the captured value without relying on numbered backreferences.
- Be cautious with ^ and $ anchors: In Ruby regular expressions, ^ and $ match the start and end of a line, respectively, rather than the entire string. To match the whole string, use \A and \z anchors. Here's an example:
In the given example, we modify the patterns to include the /m (multiline) modifier (/^Hello$/m and /\AHello\z/m). This modifier allows ^ and $ to match the start and end of individual lines within the string. Therefore, the pattern ^Hello$ now matches the line "Hello" within the multiline string, resulting in true output. However, the pattern /\AHello\z/m doesn't match the entire string due to the newline character, resulting in false output.
- Use the x modifier for complex regexps: For complex regular expressions, it's recommended to use the x modifier to enhance readability. This allows us to include comments and whitespace, which are ignored by Ruby. Here's an example:
The given code demonstrates the use of the x modifier for complex regular expressions.
Percent Literals
- %w for Arrays: The %w notation is used to create an array of words (strings without spaces) or symbols. It allows us to create an array without using quotes or commas between elements. Here's an example:
In this example, arr is an array containing three elements: "abc", "pqr", and "xyz". The %w notation eliminates the need for quotes and commas, resulting in a cleaner syntax.
- %() for Single-Line Strings: The %() notation is used to construct single-line strings that require interpolation as well as embedded double quotes. This technique is very handy when we don't want to escape double quotes within the string. It is suggested to use heredocs instead of %() for multi-line strings. Here's an example of %() notation in effect:
In this example, we can interpolate variables within the string without the need for escaping double quotes by using %().
- %r for Regular Expressions: The %r notation is used to create regular expressions. It is recommended to use %r only for regular expressions that match more than one '/' character. Here's an example:
In the good example, the %r notation is used to create a regular expression that matches URLs starting with either http:// or https://, optionally followed by www., and ending with example.com. By using %r{...}, the regular expression is enclosed in curly braces {} instead of the usual forward slashes /. This notation is recommended when dealing with regular expressions that contain multiple forward slashes, as it helps improve readability and avoids excessive escaping.
In the bad example, the regular expression is written using the conventional forward slash notation /.../. While this is still valid and works fine for simple regular expressions, it can become more difficult to read and maintain when the expression contains multiple forward slashes that need to be escaped.
Conclusion
- Consistent coding style is vital for maintainable and readable Ruby code.
- A standardized Ruby style guide enhances code clarity and maintainability.
- Guidelines cover whitespace, indentation, line length, commenting, naming conventions, and more.
- Effective whitespace usage (indentation, alignment) improves readability.
- Keeping lines within reasonable length enhances readability and maintainability.
- Commenting (file/class-level, function, block, inline) and avoiding commented-out code aids readability.
- Methods (including parentheses and default arguments) are outlined.
- Best practices for conditional expressions and syntax (e.g., avoiding for loops) are provided.
- Following these guidelines ensures well-documented, readable, and maintainable Ruby code.