The letter A styled as Alchemists logo. lchemists
Published April 18, 2020 Updated September 24, 2021
Cover
Barewords Pattern

I first discovered the Barewords Pattern via Ruby Tapas - Episode 4 (Barewords) by Avdi Grimm many years back. Prior to learning of this pattern, I had used a combination of instance variables, methods, constants, etc. to implement my designs, which accomplished what I needed but made the code unmaintainable. Since then, I’ve found barewords are valuable because they simplify terminology and limit scope within scripts, objects, and/or methods. In this article, I’m sharing why the Barewords Pattern is valuable and how best to use it in your own code.

Advantages

There are several advantages to the Barewords Pattern, including:

  • The ability to allow greater flexibility when enhancing or refactoring code by limiting scope and reducing complexity. For example, in the code snippet at the start of this article, the Name implementation doesn’t have to concern itself with where the values come from, how they should be interpolated, etc. Name, instead, has only the sole responsibility of concatenating a string. By using a consistent syntax, we have a single way to send the messages we want.

  • An immediate exception when making a typo. Instead of using first or @first and you use frst instead, you’ll get an exception stating that no local variable exists: undefined local variable or method `frst'. Whereas, had you used @frst instead of @first, you’ll end up with a nil which is much harder to debug. Having an exception thrown when a typo is introduced provides immediate feedback for quickly fixing the problem before deploying code to production and having to track down where a silent nil was introduced into the code.

  • Takes less key strokes and finger traversal to type first instead of @first.

Disadvanges

A slight disadvantage is the extra lines of code — the private attr_reader — required to generate private methods for the instance variables.

Guidelines

Barewords originated from Perl but is applicable in other languages, too. For instance, in this article, I’ll use Ruby to demonstrate the requirements of a bareword:

  • No surrounding quotes.

  • No special sigil as a prefix (i.e. $, @, @@, etc).

  • No preceding method call syntax.

  • No ending parenthesis.

  • No uppercase (i.e. EXAMPLE), snakecase (i.e. ExAmPle), or any character that would not be used in a typical Ruby method.

Usage

Returning to the Ruby snippet, at the start of this article, let’s dissect how the code adheres to the Barewords Pattern:

  1. The @first, @middle, and @last instance variables are initialized via the constructor and never referenced again. In truth, construction should be the only place instance variables are used.

  2. The public API of the Name object is locked down using the private attr_reader macro to ensure @first, @middle, and @last are bareword methods and are read-only to prevent mutability.

  3. The #to_s instance method is scoped to only using the bareword methods without having to know the specifics of using globals, instance variables, constants, etc.

For example, here is the proper use of a bareword:

example

On the flip side, the following should be avoided with caveats pointed out later:

$example  # Global variable
EXAMPLE   # Constant variable
@@example # Class variable
@example  # Instance variable

In fact, global and class variables should be avoided in general, not just when using barewords. They are a code smell and lead to hard to maintain code due to their broad and far reaching scopes. For example, here is a global variable use case that should be avoided:

$middle = "Xavier"

class Name
  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{$middle} #{last}"

  private

  attr_reader :first, :last
end

The above is a code smell due to the following reasons:

  • Introduces a Global Variable Antipattern.

  • Use of $middle means the global variable can be mutated and referenced anywhere in the entire program.

  • Having a hard coded reference to the $middle global variable means specifically searching for $middle instead of middle when refactoring.

  • Breaks encapsulation because $middle is not scoped to the name Name object.

Same goes for class variables:

class Name
  @@middle = "Xavier"

  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{@@middle} #{last}"

  private

  attr_reader :first, :last
end

Unlike the $middle global variable use case, @@middle is scoped to Name. Unfortunately, if we subclassed Name and mutated @@middle, both Name (superclass) and the corresponding subclass would be updated to share the same value. Again, do yourself a favor and avoid the Class Variable Antipattern.

Constants, on the other hand, are acceptable as long as they are scoped to a module and/or class. Even then, though, constants should be injected into the object being initialized for maximum benefit. For example, avoid the following:

class Name
  MIDDLE = "Xavier"

  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{MIDDLE} #{last}"

  private

  attr_reader :first, :last
end

While the above limits the scope of MIDDLE to the Name object, use of the constant still breaks encapsulation as first shown with the $middle global variable example earlier. Here’s a better solution:

class Name
  DEFAULT_MIDDLE = "Xavier"

  def initialize first, last, middle: DEFAULT_MIDDLE
    @first = first
    @last = last
    @middle = middle
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

The above is successful because it accomplishes the following:

  • Leverages DEFAULT_MIDDLE as a clearly defined default constant.

  • Initializes Name with middle being a keyword argument defaulting to the DEFAULT_MIDDLE constant as the value. This also allows the middle keyword argument to be customized should someone need to construct the object with default value other than DEFAULT_MIDDLE.

  • Allows middle to be a bareword via attr_reader so the implementation remains locally scoped and properly encapsulated.

Finally, instance variables are the most common use case. What you want to avoid, and often used, is the following:

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{@first} #{@middle} #{@last}"
end

With the above, the object’s API access to @first, @middle, and @last remains private. Unfortunately, we are not using barewords in the #to_s method. This brings us back to the screenshot at the start of this article. What we want is the following:

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

Granted, the above adds a few more lines of code, which some people might grumble about, but I’d say the payoff for flexible/maintainable code is worth the effort in the end.

Performance

In terms of performance, there is a tiny hit to performance since the instance variables are wrapped in accessor methods. Use the following script to see for yourself:

#! /usr/bin/env ruby
# frozen_string_literal: true

# Save as `snippet.rb` and run as `ruby snippet.rb`

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"
  gem "debug"
  gem "benchmark-ips", require: "benchmark/ips"
end

class NameBasic
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{@first} #{@middle} #{@last}"
end

class NameBare
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

basic = NameBasic.new "Zoe", "Alleyne", "Washburne"
bare = NameBare.new "Zoe", "Alleyne", "Washburne"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report "With Barewords" do
    bare.to_s
  end

  benchmark.report "Without Barewords" do
    basic.to_s
  end

  benchmark.compare!
end

The output of the above script should look similar to these results:

Warming up --------------------------------------
      With Barewords   518.316k i/100ms
   Without Barewords   580.698k i/100ms
Calculating -------------------------------------
      With Barewords      5.267M (± 2.5%) i/s -     26.434M in   5.021731s
   Without Barewords      5.749M (± 3.7%) i/s -     29.035M in   5.057604s

Comparison:
   Without Barewords:  5749389.7 i/s
      With Barewords:  5267326.8 i/s - 1.09x  (± 0.00) slower

As you can see, there is a performance hit but it’s small.

Tooling

The Barewords Pattern is so useful, I use a Sublime Text Snippet to quickly craft new objects. To execute a snippet, I type initb which is short for initialize body to yield the following template:

def initialize $1
  $2
end

private

attr_reader :$3

I can then use the $1, $2, and $3 tab stops to quickly fill out the rest of the template with my implementation while still adhering to the Barewords Pattern.

Conclusion

Hopefully, you have a better sense of the Barewords Pattern and how to use it in your own code. May your code be easier to read, maintain, and more enjoyable to work with!