Home > Back-end >  How to get the cyclomatic complexity of a ruby function?
How to get the cyclomatic complexity of a ruby function?

Time:12-01

I am looking for a function or gem that gives me the cyclomatic complexity of a function.

For example, using rubocop, If I write

def my_func(foo)
  foo.details['errors'].each do |attr, message|
    case attr
    when 1 then foo.errors.add(:err1, :format)
    when 2 then foo.errors.add(:err3, :format)
    when 3 then foo.errors.add(:err5, :format)
    when 4 then foo.errors.add(:err7, :format)
    when 5 then foo.errors.add(:err9, :format)
    when 6 then foo.errors.add(:err11, :format)
    when 7 then foo.errors.add(:err13, :format)
    else foo.errors.add(:base, message)
    end
  end
  return foo
end

When I run rubocop, then I got the error:

Metrics/CyclomaticComplexity: Cyclomatic complexity for my_func is too high. [9/7]                                                                                                    
    def my_func(foo) ..."

And I know my cylcomatic complexity is [9/7].

If I change my function and no error is raised by rubocop, how to get the function cyclomatic complexity ? A code snippet with an example would be great ! (I am not looking for a manual computation).

Bonus: Provide a solution for JavaScript functions also.

Thanks!

CodePudding user response:

Have you tried looking at cycromatic?

gem page, github repo.

CodePudding user response:

From Rubocop::Cop::Metrics::CyclomaticComplexity itself;

# Checks that the cyclomatic complexity of methods is not higher
# than the configured maximum. The cyclomatic complexity is the number of
# linearly independent paths through a method. The algorithm counts
# decision points and adds one.
#
# An if statement (or unless or ?:) increases the complexity by one. An
# else branch does not, since it doesn't add a decision point. The &&
# operator (or keyword and) can be converted to a nested if statement,
# and ||/or is shorthand for a sequence of ifs, so they also add one.
# Loops can be said to have an exit condition, so they add one.
# Blocks that are calls to builtin iteration methods
# (e.g. `ary.map{...}) also add one, others are ignored.
#
#   def each_child_node(*types)               # count begins: 1
#     unless block_given?                     # unless:  1
#       return to_enum(__method__, *types)
#
#     children.each do |child|                # each{}:  1
#       next unless child.is_a?(Node)         # unless:  1
#
#       yield child if types.empty? ||        # if:  1, ||:  1
#                      types.include?(child.type)
#     end
#
#     self
#   end                                       # total: 6

So, going part by part in your code, you get an initial count of 1, plus 1 for the each loop and plus 1 for each when branch;

def my_func(foo)                                   # count begins: 1
  foo.details['errors'].each do |attr, message|    # each{}:  1
    case attr
    when 1 then foo.errors.add(:err1, :format)     # when:  1
    when 2 then foo.errors.add(:err3, :format)     # when:  1
    when 3 then foo.errors.add(:err5, :format)     # when:  1
    when 4 then foo.errors.add(:err7, :format)     # when:  1
    when 5 then foo.errors.add(:err9, :format)     # when:  1
    when 6 then foo.errors.add(:err11, :format)    # when:  1
    when 7 then foo.errors.add(:err13, :format)    # when:  1
    else foo.errors.add(:base, message)
    end
  end
  return foo
end                                                # total: 9

You could check the class examples to get a better idea as well.

  • Related