Class: Reek::SmellDetectors::ControlParameter

Inherits:
BaseDetector
  • Object
show all
Defined in:
lib/reek/smell_detectors/control_parameter.rb

Overview

Control Coupling occurs when a method or block checks the value of a parameter in order to decide which execution path to take. The offending parameter is often called a Control Couple.

A simple example would be the quoted parameter in the following method:

def write(quoted)
  if quoted
    write_quoted(@value)
  else
    puts @value
  end
end

Control Coupling is a kind of duplication, because the calling method already knows which path should be taken.

Control Coupling reduces the code’s flexibility by creating a dependency between the caller and callee: any change to the possible values of the controlling parameter must be reflected on both sides of the call.

A Control Couple also reveals a loss of simplicity: the called method probably has more than one responsibility, because it includes at least two different code paths.

One possible solution is to use the Strategy Pattern to pass into the callee what must be done. This is not considered to be control coupling because the callee will do the same thing with the strategy, whatever it happens to be. Sometimes in Ruby the strategy may actually just be a block passed in, and that remains next to where the caller invokes it in the source code.

See Control-Parameter for details.

Constant Summary

Constants inherited from BaseDetector

BaseDetector::DEFAULT_EXCLUDE_SET, BaseDetector::EXCLUDE_KEY

Instance Attribute Summary

Attributes inherited from BaseDetector

#config, #context

Instance Method Summary collapse

Methods inherited from BaseDetector

#config_for, configuration_keys, contexts, default_config, descendants, #enabled?, #exception?, #expression, inherited, #initialize, #run, #smell_type, smell_type, #smell_warning, #source_line, to_detector, todo_configuration_for, #value

Constructor Details

This class inherits a constructor from Reek::SmellDetectors::BaseDetector

Instance Method Details

#control_parametersArray<ControlParameterHelpers::Candidate> (private)



69
70
71
72
73
# File 'lib/reek/smell_detectors/control_parameter.rb', line 69

def control_parameters
  potential_parameters.
    map { |parameter| ControlParameterHelpers::Candidate.new(parameter, find_matches(parameter)) }.
    select(&:smells?)
end

#find_matches(parameter) ⇒ Array<Reek::AST::Node> (private)

Parameters:

  • parameter (Symbol)

    the name of the parameter

Returns:



86
87
88
# File 'lib/reek/smell_detectors/control_parameter.rb', line 86

def find_matches(parameter)
  ControlParameterHelpers::ControlParameterFinder.new(expression, parameter).find_matches
end

#potential_parametersArray<Symbol> (private)

Returns e.g. [:bravo, :charlie].

Returns:

  • (Array<Symbol>)

    e.g. [:bravo, :charlie]



78
79
80
# File 'lib/reek/smell_detectors/control_parameter.rb', line 78

def potential_parameters
  expression.parameter_names
end

#sniffArray<SmellWarning>

Checks whether the given method chooses its execution path by testing the value of one of its parameters.

Returns:



54
55
56
57
58
59
60
61
62
# File 'lib/reek/smell_detectors/control_parameter.rb', line 54

def sniff
  control_parameters.map do |control_parameter|
    argument = control_parameter.name.to_s
    smell_warning(
      lines: control_parameter.lines,
      message: "is controlled by argument '#{argument}'",
      parameters: { argument: argument })
  end
end