Simplify this method

Rubocop complains about Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, and Metrics/PerceivedComplexity

I am wondering how this method can be simplified (or split into multiple methods, elegantly?) to satisfy Rubocop. I do not want to make it too terribly terse at the expense of readability.

It needs to split the given multi-line string into the “Things” part and the non Things part:

// this comment goes into things
Things blah blah blah {
part of things
// this comment goes into things
}

// this comment goes into items
Item1 blah blah
Item2 blah blah
  # Splits the given multi-line string into things and items
  def self.split_things_items(src)
    things_nest_level = 0
    things = []
    items = []
    comments = []

    src.lines.map(&:rstrip).reject(&:empty?).each do |line|
      case line
      when %r{^\s*//} then comments << line
      when /^\s*(Thing|Bridge)\s+/
        things.concat comments.slice!(0..), [line]
        things_nest_level += 1 if line[-1] == '{'
      else
        if things_nest_level.positive?
          things.concat comments.slice!(0..), [line]
          things_nest_level -= 1 if line[-1] == '}'
        else
          items.concat comments.slice!(0..), [line]
        end
      end
    end

    [things.join("\n"), items.join("\n")]
  end

There is a lot that can be done there as refactoring.

The simplest thing to start with is to extract the code of each branch of the case statement into its own tiny well-named method. Afterwards, you can extract the entire case statement into its own method. When you do so, it would be much simpler to share variables via instance variables. I do not know the full purpose of your method, so I probably did not come up with the best names for the submethods. I am sure you can come up with much better names.

Here is an example of a fully refactored version that is OK for rubocop (I verified it):

# frozen_string_literal: true

# Splits the given multi-line string into things and items
module Splitter
  def self.split_things_items(src)
    @things_nest_level = 0
    @things = []
    @items = []
    @comments = []
    process_src_lines(src)
    [@things.join("\n"), @items.join("\n")]
  end

  def self.process_src_lines(src)
    src.lines.map(&:rstrip).reject(&:empty?).each do |line|
      case line
      when %r{^\s*//}
        process_string(line)
      when /^\s*(Thing|Bridge)\s+/
        process_thing_bridge(line)
      else
        process_else(line)
      end
    end
  end

  def self.process_string(line)
    @comments << line
  end

  def self.process_thing_bridge(line)
    @things.concat @comments.slice!(0..), [line]
    @things_nest_level += 1 if line[-1] == '{'
  end

  def self.process_else(line)
    if @things_nest_level.positive?
      @things.concat @comments.slice!(0..), [line]
      @things_nest_level -= 1 if line[-1] == '}'
    else
      @items.concat @comments.slice!(0..), [line]
    end
  end
end
1 Like