SlideShare una empresa de Scribd logo
1 de 110
Code Quality Analysis
       Marty Andrews
      @martinjandrews

                        .com.au
static analysis
                  =
software analysis without execution
no app runtime required!
feedback!
design improvement


improve testability


              manage complexity
Whole
                               Team




           Collective                            Coding
           Ownership                            Standard
                          Test-Driven
                          Development

Customer            Pair                    Design                      Planning
  Tests         Programming              Improvement                     Game

                               Simple
                               Design
           Continuous                          Sustainable
           Integration                            Pace

                              Metaphor


                               Small
                              Releases

                                                 circa 2001. duplicated from xprogramming.com
Collective                                       Coding
Ownership                                       Standard
               Test-Driven
               Development

         Pair                  Design
     Programming            Improvement

                   Simple
                   Design
Continuous                                   Sustainable
Integration                                     Pace
                             circa 2001. duplicated from xprogramming.com
smell the
  code
“A code smell is a surface indication
that usually corresponds to a deeper
       problem in the system.”

         -- Martin Fowler,
      paraphrasing Kent Beck
“If it stinks, change it.”

     -- Grandma Beck
Tools
code samples from


 runwayapp.com
rake stats
Summary statistics from your Rails app
Look for a good code to test ratio
$   sudo gem install rails
$   rails my_app
$   cd my_app
$   rake stats
~/Data/runway $ rake stats
(in /Users/marty/Data/runway)
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |   333 |    275 |       12 |      45 |   3|     4|
| Helpers              |   113 |    104 |        0|       10 |   0|     8|
| Models               |   828 |    681 |       12 |      96 |   8|     5|
| Libraries            |   874 |    750 |       24 |     113 |   4|     4|
| Model specs          | 1662 | 1395 |           0|        0|    0|     0|
| Controller specs     |   544 |    434 |        0|        5|    0|    84 |
| Helper specs         |   114 |     95 |        0|        0|    0|     0|
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 4468 | 3734 |          48 |     269 |   5|    11 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 1810     Test LOC: 1924       Code to Test Ratio: 1:1.1
681 |       12 |      96 |   8|     5
 750 |       24 |     113 |   4|     4
1395 |        0|        0|    0|     0
 434 |        0|        5|    0|    84
  95 |        0|        0|    0|     0
-----+---------+---------+-----+-------
3734 |       48 |     269 |   5|    11
-----+---------+---------+-----+-------
4      Code to Test Ratio: 1:1.1
use rake stats for regular
     human review
reek
http://wiki.github.com/kevinrutherford/reek
Searches for code smells
Control Couple   Long Parameter List
Duplication      Nested Iterators
Feature Envy     Uncommunicative
                 Name
Large Class
                 Utility Function
Long Method
Look for warnings that you haven’t
         thought about
$ sudo gem install reek
$ reek [file ...]
~/Data/runway $ find app -name quot;*.rbquot; | xargs reek

quot;app/controllers/actions_controller.rbquot; -- 3 warnings:
ActionsController#action calls current_user.actions multiple times
(Duplication)
ActionsController#action calls params[id] multiple times (Duplication)
ActionsController#actions calls current_user.actions multiple times
(Duplication)

quot;app/controllers/application.rbquot; -- 1 warnings:
ApplicationController#site_version doesn't depend on instance state
(Utility Function)

quot;app/helpers/home_helper.rbquot; -- 1 warnings:
HomeHelper::folder_tab has approx 6 statements (Long Method)

quot;app/models/token.rbquot; -- 21 warnings:
Token#parse_incubation_day_of_the_month refers to base more than self
(Feature Envy)
ActionsController#action calls current_user.actions multiple times
(Duplication)
ActionsController#action calls params[id] multiple times (Duplication)
ActionsController#actions calls current_user.actions multiple times
(Duplication)

quot;app/controllers/application.rbquot; -- 1 warnings:
ApplicationController#site_version doesn't depend on instance state
(Utility Function)

quot;app/helpers/home_helper.rbquot; -- 1 warnings:
HomeHelper::folder_tab has approx 6 statements (Long Method)

quot;app/models/token.rbquot; -- 21 warnings:
Token#parse_incubation_day_of_the_month refers to base more than self
(Feature Envy)
[Feature Envy] Element#update_from refers to
response more than self




  def parse_incubation_day_of_the_month
    if @remainder =~ /^(d+)(th|st|nd|rd)$/i
      day = $1.to_i
      base = day <= @today.day ? @today >> 1 : @today
      day -=1 until Date.valid_civil?(base.year, base.month, day)
      Date.new(base.year, base.month, day)
    end
  end
What about Rails?
10,645 warnings
use reek for regular human
          review
Flog
http://ruby.sadi.st/Flog.html
Complexity checking of Ruby code
“ABC” like algorithm

    Assignments
     Branches
    Conditionals
Look for high scores (> 40)
$ sudo gem install flog
$ flog [dir ...]
~/Data/runway $ flog app

  1426.9: flog total
     7.9: flog/method average

    89.7:   ActionFormat#format
    81.7:   ActionParser#parse
    60.4:   Token#parse_incubation_date
    42.3:   Token#parse_incubation_days
    35.2:   Token#tokenize
    29.8:   Token#parse
    27.8:   Token#parse_period
    27.6:   Action#apply_defaults_from_name
    25.3:   Action#none
    24.9:   RankingsController#update
    24.2:   ActionsHelper#none
    22.9:   ActionsHelper#action_contexts
    21.9:   ActionsHelper#action_tags
    20.9:   Token#parse_incubation_month
    20.2:   ActionParser#attributes

    [More content removed...]
~/Data/runway $ flog app

  1426.9: flog total
     7.9: flog/method average

    89.7:   ActionFormat#format
    81.7:   ActionParser#parse
    60.4:   Token#parse_incubation_date
    42.3:   Token#parse_incubation_days
    35.2:   Token#tokenize
    29.8:   Token#parse
    27.8:   Token#parse_period
89.7: ActionFormat#format


def format
  @tokens = []
  @semi = false

  append(quot;Waiting forquot;) if @action.waiting?
  append(@action.name) unless @action.name.blank?

  append_with_semi(@action.due_at.strftime(quot;<%d/%m/%Y-%H:%Mquot;)) if @action.solid?
  if @action.incubating?
    append_with_semi(@action.effective_at.strftime(quot;+%d/%m/%Y-%H:%Mquot;))
  elsif [quot;laterquot;, quot;donequot;].include?(@action.status)
    append_with_semi(quot;+#{@action.status}quot;)
  end

  append_with_semi(@action.contexts.sort.map(&:shorthand)) unless @action.contexts.blank?
  unless @action.waiting?
    append_with_semi(@action.time.shorthand) unless @action.time.blank?
    append_with_semi(@action.energy.shorthand) unless @action.energy.blank?
  end

  append_with_semi(@action.tags.sort.map(&:shorthand)) unless @action.tags.blank?

  append(quot;!quot;) if @action.today?

  @tokens.flatten.join(quot; quot;)
end
What about Rails?
~/Data/rails $ flog .

201079.7: flog total
    14.1: flog/method average

  3841.5:   main#none
   866.1:   Parser#none
   709.7:   timezone#Europe/London
   707.5:   timezone#America/St_Johns
   695.0:   timezone#America/Chicago
   693.3:   timezone#America/New_York
   674.3:   timezone#Europe/Dublin
   670.9:   timezone#America/Halifax
   662.2:   JoinAssociation#association_join
   661.2:   timezone#Atlantic/Azores
   656.7:   timezone#Europe/Lisbon
   556.3:   timezone#Europe/Brussels

   [More content removed...]
841.5:   main#none
866.1:   Parser#none
709.7:   timezone#Europe/London
707.5:   timezone#America/St_Johns
695.0:   timezone#America/Chicago
693.3:   timezone#America/New_York
674.3:   timezone#Europe/Dublin
670.9:   timezone#America/Halifax
662.2:   JoinAssociation#association_join
661.2:   timezone#Atlantic/Azores
656.7:   timezone#Europe/Lisbon
556.3:   timezone#Europe/Brussels

[More content removed...]
def association_join
  connection = reflection.active_record.connection
  join = case reflection.macro
    when :has_and_belongs_to_many
      quot; #{join_type} %s ON %s.%s = %s.%s quot; % [
         table_alias_for(options[:join_table], aliased_join_table_name),
         connection.quote_table_name(aliased_join_table_name),
         options[:foreign_key] || reflection.active_record.to_s.foreign_key,
         connection.quote_table_name(parent.aliased_table_name),
         reflection.active_record.primary_key] +
      quot; #{join_type} %s ON %s.%s = %s.%s quot; % [
         table_name_and_alias,
         connection.quote_table_name(aliased_table_name),
         klass.primary_key,
         connection.quote_table_name(aliased_join_table_name),
         options[:association_foreign_key] || klass.to_s.foreign_key
         ]
    when :has_many, :has_one
      case
        when reflection.options[:through]
           through_conditions = through_reflection.options[:conditions] ? quot;AND #{interpolate_s

          jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
          first_key = second_key = as_extra = nil

          if through_reflection.options[:as] # has_many :through against a polymorphic join
            jt_foreign_key = through_reflection.options[:as].to_s + '_id'
            jt_as_extra = quot; AND %s.%s = %squot; % [
               connection.quote_table_name(aliased_join_table_name),
               connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
               klass.quote_value(parent.active_record.base_class.name)
            ]
          else
            jt_foreign_key = through_reflection.primary_key_name
]
else
  jt_foreign_key = through_reflection.primary_key_name
end

case source_reflection.macro
when :has_many
  if source_reflection.options[:as]
    first_key    = quot;#{source_reflection.options[:as]}_idquot;
    second_key = options[:foreign_key] || primary_key
    as_extra     = quot; AND %s.%s = %squot; % [
       connection.quote_table_name(aliased_table_name),
       connection.quote_column_name(quot;#{source_reflection.options[:as]}_typequot;),
       klass.quote_value(source_reflection.active_record.base_class.name)
    ]
  else
    first_key    = through_reflection.klass.base_class.to_s.foreign_key
    second_key = options[:foreign_key] || primary_key
  end

  unless through_reflection.klass.descends_from_active_record?
    jt_sti_extra = quot; AND %s.%s = %squot; % [
      connection.quote_table_name(aliased_join_table_name),
      connection.quote_column_name(through_reflection.active_record.inheritance_col
      through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
  end
when :belongs_to
  first_key = primary_key
  if reflection.options[:source_type]
    second_key = source_reflection.association_foreign_key
    jt_source_extra = quot; AND %s.%s = %squot; % [
      connection.quote_table_name(aliased_join_table_name),
      connection.quote_column_name(reflection.source_reflection.options[:foreign_ty
      klass.quote_value(reflection.options[:source_type])
    ]
connection.quote_column_name(reflection.source_reflection.options[:foreign_ty
        klass.quote_value(reflection.options[:source_type])
      ]
    else
      second_key = source_reflection.primary_key_name
    end
  end

  quot; #{join_type} %s ON (%s.%s = %s.%s%s%s%s) quot; % [
    table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
    connection.quote_table_name(parent.aliased_table_name),
    connection.quote_column_name(parent.primary_key),
    connection.quote_table_name(aliased_join_table_name),
    connection.quote_column_name(jt_foreign_key),
    jt_as_extra, jt_source_extra, jt_sti_extra
  ]+
  quot; #{join_type} %s ON (%s.%s = %s.%s%s) quot; % [
    table_name_and_alias,
    connection.quote_table_name(aliased_table_name),
    connection.quote_column_name(first_key),
    connection.quote_table_name(aliased_join_table_name),
    connection.quote_column_name(second_key),
    as_extra
  ]

when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
  quot; #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %squot; % [
    table_name_and_alias,
    connection.quote_table_name(aliased_table_name),
    quot;#{reflection.options[:as]}_idquot;,
    connection.quote_table_name(parent.aliased_table_name),
    parent.primary_key,
    connection.quote_table_name(aliased_table_name),
    quot;#{reflection.options[:as]}_typequot;,
    klass.quote_value(parent.active_record.base_class.name)
connection.quote_table_name(aliased_table_name),
            quot;#{reflection.options[:as]}_typequot;,
            klass.quote_value(parent.active_record.base_class.name)
            ]
         else
            foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
            quot; #{join_type} %s ON %s.%s = %s.%s quot; % [
              table_name_and_alias,
              aliased_table_name,
              foreign_key,
              parent.aliased_table_name,
              reflection.options[:primary_key] || parent.primary_key
            ]
      end
    when :belongs_to
      quot; #{join_type} %s ON %s.%s = %s.%s quot; % [
           table_name_and_alias,
           connection.quote_table_name(aliased_table_name),
           reflection.klass.primary_key,
           connection.quote_table_name(parent.aliased_table_name),
           options[:foreign_key] || reflection.primary_key_name
         ]
    else
      quot;quot;
  end || ''
  join << %(AND %s) % [
    klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record

  [through_reflection, reflection].each do |ref|
    join << quot;AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name)
  end

  join
end
automation
lib/tasks/quality.rake


require 'flog'

desc quot;Analyze for code complexityquot;
task :flog do
  flog = Flog.new
  flog.flog_files ['app']
  threshold = 30

  bad_methods = flog.totals.select do |name, score|
    score > threshold
  end
  bad_methods.sort { |a,b| a[1] <=> b[1] }.each do |name, score|
    puts quot;%8.1f: %squot; % [score, name]
  end

  raise quot;#{bad_methods.size} methods have a flog complexity >
#{threshold}quot; unless bad_methods.empty?
end
~/Data/runway $ rake flog
(in /Users/marty/Data/runway)
    35.2: Token#tokenize
    42.3: Token#parse_incubation_days
    60.4: Token#parse_incubation_date
    81.7: ActionParser#parse
    89.7: ActionFormat#format
rake aborted!
5 methods have a flog complexity > 30
use flog in your CI build
Flay
http://ruby.sadi.st/Flay.html
Structural similarity checking of Sexp
       nodes (syntax tokens)
Fine grained duplication checking
Look for high scores (> 30)
$ sudo gem install flay
$ flay [dir ...]
~/Data/runway $ flay app

[Processing files...]

Total score (lower is better) = 286


1) Similar code found in :defn (mass = 60)
  app/controllers/signups_controller.rb:5
  app/controllers/user_sessions_controller.rb:6

2) Similar code found in :if (mass = 54)
  app/controllers/forgot_passwords_controller.rb:15
  app/controllers/signups_controller.rb:16

3) Similar code found in :defn (mass = 50)
  app/helpers/actions_helper.rb:43
  app/helpers/actions_helper.rb:49

[More content removed...]
~/Data/runway $ flay app

[Processing files...]

Total score (lower is better) = 286


1) Similar code found in :defn (mass = 60)
  app/controllers/signups_controller.rb:5
  app/controllers/user_sessions_controller.rb:6

2) Similar code found in :if (mass = 54)
  app/controllers/forgot_passwords_controller.rb:15
  app/controllers/signups_controller.rb:16

3) Similar code found in :defn (mass = 50)
  app/helpers/actions_helper.rb:43
  app/helpers/actions_helper.rb:49

[More content removed...]
1) Similar code found in :defn (mass = 60)
  app/controllers/signups_controller.rb:5
  app/controllers/user_sessions_controller.rb:6

  def create
    user = User.new(params[:signup])
    if user.save
      head(:status => :created, :location => home_path)
    else
      head(:status => :unprocessable_entity)
    end
  end



  def create
    user_session = UserSession.new(params[:user_session])
    if user_session.save
      head(:status => :created, :location => home_path)
    else
      head(:status => :unprocessable_entity)
    end
  end
What about Rails?
1132 duplicates
actionmailer/install.rb
require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by way of Log4r

$sitedir = CONFIG[quot;sitelibdirquot;]
unless $sitedir
  version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;]
  $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, quot;site_rubyquot;)
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the actual gruntwork
Dir.chdir(quot;libquot;)

Find.find(quot;action_mailerquot;, quot;action_mailer.rbquot;) { |f|
  if f[-3..-1] == quot;.rbquot;
    File::install(f, File.join($sitedir, *f.split(///)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(///)))
  end
}
activesupport/install.rb
require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by ways of Log4r

$sitedir = CONFIG[quot;sitelibdirquot;]
unless $sitedir
  version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;]
  $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, quot;site_rubyquot;)
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the actual gruntwork
Dir.chdir(quot;libquot;)

Find.find(quot;active_supportquot;, quot;active_support.rbquot;) { |f|
  if f[-3..-1] == quot;.rbquot;
    File::install(f, File.join($sitedir, *f.split(///)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(///)))
  end
}
activerecord/install.rb
require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by ways of Log4r

$sitedir = CONFIG[quot;sitelibdirquot;]
unless $sitedir
  version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;]
  $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, quot;site_rubyquot;)
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the actual gruntwork
Dir.chdir(quot;libquot;)

Find.find(quot;active_recordquot;, quot;active_record.rbquot;) { |f|
  if f[-3..-1] == quot;.rbquot;
    File::install(f, File.join($sitedir, *f.split(///)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(///)))
  end
}
actionpack/install.rb
require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by way of Log4r

$sitedir = CONFIG[quot;sitelibdirquot;]
unless $sitedir
  version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;]
  $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, quot;site_rubyquot;)
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the actual gruntwork
Dir.chdir(quot;libquot;)

Find.find(quot;action_controllerquot;, quot;action_controller.rbquot;, quot;action_viewquot;, quot;action_view.rbquot;) { |f|
  if f[-3..-1] == quot;.rbquot;
    File::install(f, File.join($sitedir, *f.split(///)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(///)))
  end
}
automation
lib/tasks/quality.rake


require 'flay'

desc quot;Analyze for code duplicationquot;
task :flay do
  threshold = 25
  flay = Flay.new({:fuzzy => false, :verbose => false, :mass =>
threshold})
  flay.process(*Flay.expand_dirs_to_files(['app']))

  flay.report

  raise quot;#{flay.masses.size} chunks of code have a duplicate mass >
#{threshold}quot; unless flay.masses.empty?
end
~/Data/runway $ rake flay
(in /Users/marty/Data/runway)
Total score (lower is better) = 164

1) Similar code found in :defn (mass = 60)
  app/controllers/signups_controller.rb:5
  app/controllers/user_sessions_controller.rb:6

2) Similar code found in :if (mass = 54)
  app/controllers/forgot_passwords_controller.rb:15
  app/controllers/signups_controller.rb:16

3) Similar code found in :defn (mass = 50)
  app/helpers/actions_helper.rb:43
  app/helpers/actions_helper.rb:49
rake aborted!
3 chunks of code have a duplicate mass > 25
use flay in your CI build
roodi
http://roodi.rubyforge.org/
Checks for design problems
Class Name          Block Cyclomatic Complexity

Method Name         Method Cyclomatic
                    Complexity
Module Name


                    Assignment In Conditional
Class Line Count
                    Case Missing Else
Method Line Count
                    Empty Rescue Body
Module Line Count
                    For Loop Check

                    Parameter Number
Look for any errors
$ sudo gem install roodi
$ roodi [pattern ...]
~/Data/runway $ roodi quot;app/**/*.rbquot;

app/models/action_format.rb:10

 Method name quot;formatquot; has a cyclomatic complexity is 12.

 It should be 8 or less.

app/models/action_parser.rb:50

 Block cyclomatic complexity is 11.

 It should be 4 or less.

app/models/action.rb:107

 Case statement is missing an else clause.

app/models/action.rb:103

 Method quot;apply_defaults_from_namequot; has 23 lines.

 It should have 20 or less.

app/controllers/application.rb:52

 Found = in conditional.

 It should probably be an ==
Method name quot;formatquot; has a cyclomatic complexit

   It should be 8 or less.

app/models/action_parser.rb:50

 Block cyclomatic complexity is 11.

 It should be 4 or less.

app/models/action.rb:107

 Case statement is missing an else clause.

app/models/action.rb:103

 Method quot;apply_defaults_from_namequot; has 23 lines.

 It should have 20 or less.

app/controllers/application.rb:52

 Found = in conditional.

 It should probably be an ==
app/models/action_parser.rb:50

 Block cyclomatic complexity is 11.

 It should be 4 or less.

   meta.each do |token|
     case token.klass
     when :context
       unless token.value.blank?
         @contexts ||= []
         @contexts << token.value
       end
     when :time
       @time = token.value
     when :energy
       @energy = token.value
     when :tag
       @tags << token.value unless token.value.blank?
     when :semi_colon
       # Ignore
     when :available, :today, :later, :done
       @status = token.klass.to_s
       @effective_at = token.value
     when :due_at
       @due_at = token.value
     end
   end
app/models/action_parser.rb:50

 Block cyclomatic complexity is 11.

 It should be 4 or less.

1        meta.each do |token|
           case token.klass
    2      when :context

          3 unless token.value.blank?
               @contexts ||= [] 4
               @contexts << token.value
             end
    5      when :time
             @time = token.value
    6      when :energy
             @energy = token.value
    7                                    8
           when :tag
             @tags << token.value unless token.value.blank?
    9      when :semi_colon
             # Ignore
    10     when :available, :today, :later, :done
             @status = token.klass.to_s
             @effective_at = token.value
    11     when :due_at
             @due_at = token.value
           end
         end
app/controllers/application.rb:52

 Found = in conditional.

 It should probably be an ==




  def extract_authenticity_token
    if token = request.headers[quot;HTTP_X_RUNWAY_AUTHENTICITY_TOKENquot;]
      params[request_forgery_protection_token] = token
    end
  end
What about Rails?
1996 errors
./actionpack/lib/action_controller/vendor/html-
scanner/html/node.rb:417
  Method name quot;matchquot;
  cyclomatic complexity is 47.
  It should be 8 or less.
def match(conditions)
  conditions = validate_conditions(conditions)
  # check content of child nodes
  if conditions[:content]
    if children.empty?
      return false unless match_condition(quot;quot;, conditions[:content])
    else
      return false unless children.find { |child| child.match(conditions[:content]) }
    end
  end

  # test the name
  return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]

  # test attributes
  (conditions[:attributes] || {}).each do |key, value|
    return false unless match_condition(self[key], value)
  end

  # test parent
  return false unless parent.match(conditions[:parent]) if conditions[:parent]

  # test children
  return false unless children.find { |child| child.match(conditions[:child]) } if conditio

  # test ancestors
  if conditions[:ancestor]
    return false unless catch :found do
      p = self
      throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
    end
  end

  # test descendants
# test descendants
if conditions[:descendant]
  return false unless children.find do |child|
    # test the child
    child.match(conditions[:descendant]) ||
    # test the child's descendants
    child.match(:descendant => conditions[:descendant])
  end
end

# count children
if opts = conditions[:children]
  matches = children.select do |c|
    (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
  end

  matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
  opts.each do |key, value|
    next if key == :only
    case key
      when :count
        if Integer === value
          return false if matches.length != value
        else
          return false unless value.include?(matches.length)
        end
      when :less_than
        return false unless matches.length < value
      when :greater_than
        return false unless matches.length > value
      else raise quot;unknown count condition #{key}quot;
    end
  end
end
else raise quot;unknown count condition #{key}quot;
      end
    end
  end

  # test siblings
  if conditions[:sibling] || conditions[:before] || conditions[:after]
    siblings = parent ? parent.children : []
    self_index = siblings.index(self)

    if conditions[:sibling]
      return false unless siblings.detect do |s|
        s != self && s.match(conditions[:sibling])
      end
    end

    if conditions[:before]
      return false unless siblings[self_index+1..-1].detect do |s|
        s != self && s.match(conditions[:before])
      end
    end

    if conditions[:after]
      return false unless siblings[0,self_index].detect do |s|
        s != self && s.match(conditions[:after])
      end
    end
  end

  true
end
automation
lib/tasks/quality.rake




require 'roodi'
require 'roodi_task'

RoodiTask.new 'roodi', ['app/**/*.rb', 'lib/**/*.rb'], 'roodi.yml'
roodi.yml

# AssignmentInConditionalCheck:        {}
# CaseMissingElseCheck:                {}
ClassLineCountCheck:               {   line_count: 300 }
ClassNameCheck:                    {   pattern: !ruby/regexp /^[A-Z][a-zA-Z
# ClassVariableCheck:                  {}
CyclomaticComplexityBlockCheck:    {   complexity: 8 }
CyclomaticComplexityMethodCheck:   {   complexity: 10 }
EmptyRescueBodyCheck:              {   }
ForLoopCheck:                      {   }
MethodLineCountCheck:              {   line_count: 20 }
MethodNameCheck:                   {   pattern: !ruby/regexp /^[_a-z<>=[|+
ModuleLineCountCheck:              {   line_count: 300 }
ModuleNameCheck:                   {   pattern: !ruby/regexp /^[A-Z][a-zA-Z
ParameterNumberCheck:              {   parameter_count: 5 }
~/Data/runway $ rake roodi
(in /Users/marty/Data/runway)
app/models/action_format.rb:10 - Method name quot;formatquot; cyclomatic
complexity is 12. It should be 10 or less.
app/models/action_parser.rb:11 - Method name quot;attributesquot; cyclomatic
complexity is 12. It should be 10 or less.
app/models/action_parser.rb:30 - Method name quot;parsequot; cyclomatic
complexity is 14. It should be 10 or less.
app/models/token.rb:173 - Method name quot;parse_incubation_datequot;
cyclomatic complexity is 11. It should be 10 or less.
app/models/token.rb:226 - Method name quot;parse_incubation_daysquot;
cyclomatic complexity is 12. It should be 10 or less.
app/models/action_parser.rb:50 - Block cyclomatic complexity is 11.
It should be 8 or less.
app/models/token.rb:11 - Block cyclomatic complexity is 10. It should
be 8 or less.
app/models/action.rb:103 - Method quot;apply_defaults_from_namequot; has 23
lines. It should have 20 or less.
app/models/action_parser.rb:30 - Method quot;parsequot; has 23 lines. It
should have 20 or less.
rake aborted!
Found 9 errors.
use roodi in your CI build
metric_fu
 http://metric-fu.rubyforge.org/
reports on everything you’ve
         seen today

        plus more!
$ sudo gem sources -a http://gems.github.com
$ sudo gem install jscruggs-metric_fu
automation
lib/tasks/quality.rake




require 'metric_fu'
$ rake metrics:all
use metrics_fu for regular
     human review
Summary
lib/tasks/quality.rake (part 1)


require 'flog'

desc quot;Analyze for code complexityquot;
task :flog do
  flog = Flog.new
  flog.flog_files ['app']
  threshold = 40

  bad_methods = flog.totals.select do |name, score|
    score > threshold
  end
  bad_methods.sort { |a,b| a[1] <=> b[1] }.each do |name, score|
    puts quot;%8.1f: %squot; % [score, name]
  end

  raise quot;#{bad_methods.size} methods have a flog complexity >
#{threshold}quot; unless bad_methods.empty?
end
lib/tasks/quality.rake (part 2)


require 'flay'

desc quot;Analyze for code duplicationquot;
task :flay do
  threshold = 25
  flay = Flay.new({:fuzzy => false, :verbose => false, :mass =>
threshold})
  flay.process(*Flay.expand_dirs_to_files(['app']))

  flay.report

  raise quot;#{flay.masses.size} chunks of code have a duplicate mass >
#{threshold}quot; unless flay.masses.empty?
end
lib/tasks/quality.rake (part 3)




require 'roodi'
require 'roodi_task'
require 'metric_fu'

RoodiTask.new 'roodi', ['app/**/*.rb', 'lib/**/*.rb'], 'roodi.yml'

task :quality => [:flog, :flay, :roodi, 'metrics:all']
continuous integration
      flog         flay       roodi


   regular human review
             metric_fu

rake stats       reek    and more
Resources
me
 
 
 
 
 
 @martinjandrews
  

 
 
 
 
 
 
 marty@cogentconsulting.com.au

code samples from
                 
   runwayapp.com

reek
 
 
    


     wiki.github.com/kevinrutherford/reek
flog
 
 
     


     ruby.sadi.st/Flog.html
flay
 
 
     


     ruby.sadi.st/Flay.html
roodi

 
    


     roodi.rubyforge.org
metric_fu
   


     metric-fu.rubyforge.org
Code Quality Analysis

Más contenido relacionado

Destacado

Agile code quality metrics
Agile code quality metricsAgile code quality metrics
Agile code quality metricsGil Nahmias
 
SonarQube - The leading platform for Continuous Code Quality
SonarQube - The leading platform for Continuous Code QualitySonarQube - The leading platform for Continuous Code Quality
SonarQube - The leading platform for Continuous Code QualityLarry Nung
 
Code quality
Code qualityCode quality
Code qualityProvectus
 
Python - code quality and production monitoring
Python - code quality and production monitoringPython - code quality and production monitoring
Python - code quality and production monitoringDavid Melamed
 
Measure and Improve code quality. Using automation.
Measure and Improve code quality.  Using automation.Measure and Improve code quality.  Using automation.
Measure and Improve code quality. Using automation.Vladimir Korolev
 

Destacado (6)

Agile code quality metrics
Agile code quality metricsAgile code quality metrics
Agile code quality metrics
 
SonarQube - The leading platform for Continuous Code Quality
SonarQube - The leading platform for Continuous Code QualitySonarQube - The leading platform for Continuous Code Quality
SonarQube - The leading platform for Continuous Code Quality
 
Code quality
Code qualityCode quality
Code quality
 
Python - code quality and production monitoring
Python - code quality and production monitoringPython - code quality and production monitoring
Python - code quality and production monitoring
 
Measure and Improve code quality. Using automation.
Measure and Improve code quality.  Using automation.Measure and Improve code quality.  Using automation.
Measure and Improve code quality. Using automation.
 
Aspects Of Code Quality meetup
Aspects Of Code Quality   meetupAspects Of Code Quality   meetup
Aspects Of Code Quality meetup
 

Similar a Code Quality Analysis

The Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicThe Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicDavid Solivan
 
Parasoft .TEST, Write better C# Code Using Data Flow Analysis
Parasoft .TEST, Write better C# Code Using  Data Flow Analysis Parasoft .TEST, Write better C# Code Using  Data Flow Analysis
Parasoft .TEST, Write better C# Code Using Data Flow Analysis Engineering Software Lab
 
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016Ramesh Krishnamurthy, CTO at World DevOps Summit 2016
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016Indium Software
 
Improving Code Quality Through Effective Review Process
Improving Code Quality Through Effective  Review ProcessImproving Code Quality Through Effective  Review Process
Improving Code Quality Through Effective Review ProcessDr. Syed Hassan Amin
 
Measuring Your Code
Measuring Your CodeMeasuring Your Code
Measuring Your CodeNate Abele
 
Measuring Your Code 2.0
Measuring Your Code 2.0Measuring Your Code 2.0
Measuring Your Code 2.0Nate Abele
 
Developing a Culture of Quality Code (Midwest PHP 2020)
Developing a Culture of Quality Code (Midwest PHP 2020)Developing a Culture of Quality Code (Midwest PHP 2020)
Developing a Culture of Quality Code (Midwest PHP 2020)Scott Keck-Warren
 
Useful practices of creation automatic tests by using cucumber jvm
Useful practices of creation automatic tests by using cucumber jvmUseful practices of creation automatic tests by using cucumber jvm
Useful practices of creation automatic tests by using cucumber jvmAnton Shapin
 
Track c how do we break - jasper
Track c   how do we break - jasperTrack c   how do we break - jasper
Track c how do we break - jasperchiportal
 
Neotys PAC 2018 - Tingting Zong
Neotys PAC 2018 - Tingting ZongNeotys PAC 2018 - Tingting Zong
Neotys PAC 2018 - Tingting ZongNeotys_Partner
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperfNew Relic
 
Surekha_haoop_exp
Surekha_haoop_expSurekha_haoop_exp
Surekha_haoop_expsurekhakadi
 
The "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsThe "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsErik Osterman
 
Graph-Based Source Code Analysis of JavaScript Repositories
Graph-Based Source Code Analysis of JavaScript Repositories Graph-Based Source Code Analysis of JavaScript Repositories
Graph-Based Source Code Analysis of JavaScript Repositories Dániel Stein
 
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)Neotys_Partner
 
20141210 rakuten techtalk
20141210 rakuten techtalk20141210 rakuten techtalk
20141210 rakuten techtalkHiroshi SHIBATA
 
Pragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptPragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptdavejohnson
 
Compliance as Code Everywhere
Compliance as Code EverywhereCompliance as Code Everywhere
Compliance as Code EverywhereMatt Ray
 
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...Perfecto by Perforce
 

Similar a Code Quality Analysis (20)

The Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs PublicThe Magic Of Application Lifecycle Management In Vs Public
The Magic Of Application Lifecycle Management In Vs Public
 
Parasoft .TEST, Write better C# Code Using Data Flow Analysis
Parasoft .TEST, Write better C# Code Using  Data Flow Analysis Parasoft .TEST, Write better C# Code Using  Data Flow Analysis
Parasoft .TEST, Write better C# Code Using Data Flow Analysis
 
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016Ramesh Krishnamurthy, CTO at World DevOps Summit 2016
Ramesh Krishnamurthy, CTO at World DevOps Summit 2016
 
Improving Code Quality Through Effective Review Process
Improving Code Quality Through Effective  Review ProcessImproving Code Quality Through Effective  Review Process
Improving Code Quality Through Effective Review Process
 
Measuring Your Code
Measuring Your CodeMeasuring Your Code
Measuring Your Code
 
Measuring Your Code 2.0
Measuring Your Code 2.0Measuring Your Code 2.0
Measuring Your Code 2.0
 
Developing a Culture of Quality Code (Midwest PHP 2020)
Developing a Culture of Quality Code (Midwest PHP 2020)Developing a Culture of Quality Code (Midwest PHP 2020)
Developing a Culture of Quality Code (Midwest PHP 2020)
 
Useful practices of creation automatic tests by using cucumber jvm
Useful practices of creation automatic tests by using cucumber jvmUseful practices of creation automatic tests by using cucumber jvm
Useful practices of creation automatic tests by using cucumber jvm
 
Track c how do we break - jasper
Track c   how do we break - jasperTrack c   how do we break - jasper
Track c how do we break - jasper
 
Neotys PAC 2018 - Tingting Zong
Neotys PAC 2018 - Tingting ZongNeotys PAC 2018 - Tingting Zong
Neotys PAC 2018 - Tingting Zong
 
Intro to-rails-webperf
Intro to-rails-webperfIntro to-rails-webperf
Intro to-rails-webperf
 
Surekha_haoop_exp
Surekha_haoop_expSurekha_haoop_exp
Surekha_haoop_exp
 
The "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/OpsThe "Holy Grail" of Dev/Ops
The "Holy Grail" of Dev/Ops
 
Graph-Based Source Code Analysis of JavaScript Repositories
Graph-Based Source Code Analysis of JavaScript Repositories Graph-Based Source Code Analysis of JavaScript Repositories
Graph-Based Source Code Analysis of JavaScript Repositories
 
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)
Jonathon Wright - Intelligent Performance Cognitive Learning (AIOps)
 
20141210 rakuten techtalk
20141210 rakuten techtalk20141210 rakuten techtalk
20141210 rakuten techtalk
 
10 Ways To Improve Your Code
10 Ways To Improve Your Code10 Ways To Improve Your Code
10 Ways To Improve Your Code
 
Pragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScriptPragmatic Parallels: Java and JavaScript
Pragmatic Parallels: Java and JavaScript
 
Compliance as Code Everywhere
Compliance as Code EverywhereCompliance as Code Everywhere
Compliance as Code Everywhere
 
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...
Enhancing Your Test Automation Scenario Coverage with Selenium - QA or the Hi...
 

Último

How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 

Último (20)

How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 

Code Quality Analysis

  • 1. Code Quality Analysis Marty Andrews @martinjandrews .com.au
  • 2.
  • 3.
  • 4. static analysis = software analysis without execution
  • 5. no app runtime required!
  • 6.
  • 9. Whole Team Collective Coding Ownership Standard Test-Driven Development Customer Pair Design Planning Tests Programming Improvement Game Simple Design Continuous Sustainable Integration Pace Metaphor Small Releases circa 2001. duplicated from xprogramming.com
  • 10. Collective Coding Ownership Standard Test-Driven Development Pair Design Programming Improvement Simple Design Continuous Sustainable Integration Pace circa 2001. duplicated from xprogramming.com
  • 11.
  • 12. smell the code
  • 13. “A code smell is a surface indication that usually corresponds to a deeper problem in the system.” -- Martin Fowler, paraphrasing Kent Beck
  • 14. “If it stinks, change it.” -- Grandma Beck
  • 15. Tools
  • 16. code samples from runwayapp.com
  • 18. Summary statistics from your Rails app
  • 19. Look for a good code to test ratio
  • 20. $ sudo gem install rails $ rails my_app $ cd my_app $ rake stats
  • 21. ~/Data/runway $ rake stats (in /Users/marty/Data/runway) +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Controllers | 333 | 275 | 12 | 45 | 3| 4| | Helpers | 113 | 104 | 0| 10 | 0| 8| | Models | 828 | 681 | 12 | 96 | 8| 5| | Libraries | 874 | 750 | 24 | 113 | 4| 4| | Model specs | 1662 | 1395 | 0| 0| 0| 0| | Controller specs | 544 | 434 | 0| 5| 0| 84 | | Helper specs | 114 | 95 | 0| 0| 0| 0| +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 4468 | 3734 | 48 | 269 | 5| 11 | +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 1810 Test LOC: 1924 Code to Test Ratio: 1:1.1
  • 22. 681 | 12 | 96 | 8| 5 750 | 24 | 113 | 4| 4 1395 | 0| 0| 0| 0 434 | 0| 5| 0| 84 95 | 0| 0| 0| 0 -----+---------+---------+-----+------- 3734 | 48 | 269 | 5| 11 -----+---------+---------+-----+------- 4 Code to Test Ratio: 1:1.1
  • 23. use rake stats for regular human review
  • 26. Control Couple Long Parameter List Duplication Nested Iterators Feature Envy Uncommunicative Name Large Class Utility Function Long Method
  • 27. Look for warnings that you haven’t thought about
  • 28. $ sudo gem install reek $ reek [file ...]
  • 29. ~/Data/runway $ find app -name quot;*.rbquot; | xargs reek quot;app/controllers/actions_controller.rbquot; -- 3 warnings: ActionsController#action calls current_user.actions multiple times (Duplication) ActionsController#action calls params[id] multiple times (Duplication) ActionsController#actions calls current_user.actions multiple times (Duplication) quot;app/controllers/application.rbquot; -- 1 warnings: ApplicationController#site_version doesn't depend on instance state (Utility Function) quot;app/helpers/home_helper.rbquot; -- 1 warnings: HomeHelper::folder_tab has approx 6 statements (Long Method) quot;app/models/token.rbquot; -- 21 warnings: Token#parse_incubation_day_of_the_month refers to base more than self (Feature Envy)
  • 30. ActionsController#action calls current_user.actions multiple times (Duplication) ActionsController#action calls params[id] multiple times (Duplication) ActionsController#actions calls current_user.actions multiple times (Duplication) quot;app/controllers/application.rbquot; -- 1 warnings: ApplicationController#site_version doesn't depend on instance state (Utility Function) quot;app/helpers/home_helper.rbquot; -- 1 warnings: HomeHelper::folder_tab has approx 6 statements (Long Method) quot;app/models/token.rbquot; -- 21 warnings: Token#parse_incubation_day_of_the_month refers to base more than self (Feature Envy)
  • 31. [Feature Envy] Element#update_from refers to response more than self def parse_incubation_day_of_the_month if @remainder =~ /^(d+)(th|st|nd|rd)$/i day = $1.to_i base = day <= @today.day ? @today >> 1 : @today day -=1 until Date.valid_civil?(base.year, base.month, day) Date.new(base.year, base.month, day) end end
  • 34. use reek for regular human review
  • 37. “ABC” like algorithm Assignments Branches Conditionals
  • 38. Look for high scores (> 40)
  • 39. $ sudo gem install flog $ flog [dir ...]
  • 40. ~/Data/runway $ flog app 1426.9: flog total 7.9: flog/method average 89.7: ActionFormat#format 81.7: ActionParser#parse 60.4: Token#parse_incubation_date 42.3: Token#parse_incubation_days 35.2: Token#tokenize 29.8: Token#parse 27.8: Token#parse_period 27.6: Action#apply_defaults_from_name 25.3: Action#none 24.9: RankingsController#update 24.2: ActionsHelper#none 22.9: ActionsHelper#action_contexts 21.9: ActionsHelper#action_tags 20.9: Token#parse_incubation_month 20.2: ActionParser#attributes [More content removed...]
  • 41. ~/Data/runway $ flog app 1426.9: flog total 7.9: flog/method average 89.7: ActionFormat#format 81.7: ActionParser#parse 60.4: Token#parse_incubation_date 42.3: Token#parse_incubation_days 35.2: Token#tokenize 29.8: Token#parse 27.8: Token#parse_period
  • 42. 89.7: ActionFormat#format def format @tokens = [] @semi = false append(quot;Waiting forquot;) if @action.waiting? append(@action.name) unless @action.name.blank? append_with_semi(@action.due_at.strftime(quot;<%d/%m/%Y-%H:%Mquot;)) if @action.solid? if @action.incubating? append_with_semi(@action.effective_at.strftime(quot;+%d/%m/%Y-%H:%Mquot;)) elsif [quot;laterquot;, quot;donequot;].include?(@action.status) append_with_semi(quot;+#{@action.status}quot;) end append_with_semi(@action.contexts.sort.map(&:shorthand)) unless @action.contexts.blank? unless @action.waiting? append_with_semi(@action.time.shorthand) unless @action.time.blank? append_with_semi(@action.energy.shorthand) unless @action.energy.blank? end append_with_semi(@action.tags.sort.map(&:shorthand)) unless @action.tags.blank? append(quot;!quot;) if @action.today? @tokens.flatten.join(quot; quot;) end
  • 44. ~/Data/rails $ flog . 201079.7: flog total 14.1: flog/method average 3841.5: main#none 866.1: Parser#none 709.7: timezone#Europe/London 707.5: timezone#America/St_Johns 695.0: timezone#America/Chicago 693.3: timezone#America/New_York 674.3: timezone#Europe/Dublin 670.9: timezone#America/Halifax 662.2: JoinAssociation#association_join 661.2: timezone#Atlantic/Azores 656.7: timezone#Europe/Lisbon 556.3: timezone#Europe/Brussels [More content removed...]
  • 45. 841.5: main#none 866.1: Parser#none 709.7: timezone#Europe/London 707.5: timezone#America/St_Johns 695.0: timezone#America/Chicago 693.3: timezone#America/New_York 674.3: timezone#Europe/Dublin 670.9: timezone#America/Halifax 662.2: JoinAssociation#association_join 661.2: timezone#Atlantic/Azores 656.7: timezone#Europe/Lisbon 556.3: timezone#Europe/Brussels [More content removed...]
  • 46. def association_join connection = reflection.active_record.connection join = case reflection.macro when :has_and_belongs_to_many quot; #{join_type} %s ON %s.%s = %s.%s quot; % [ table_alias_for(options[:join_table], aliased_join_table_name), connection.quote_table_name(aliased_join_table_name), options[:foreign_key] || reflection.active_record.to_s.foreign_key, connection.quote_table_name(parent.aliased_table_name), reflection.active_record.primary_key] + quot; #{join_type} %s ON %s.%s = %s.%s quot; % [ table_name_and_alias, connection.quote_table_name(aliased_table_name), klass.primary_key, connection.quote_table_name(aliased_join_table_name), options[:association_foreign_key] || klass.to_s.foreign_key ] when :has_many, :has_one case when reflection.options[:through] through_conditions = through_reflection.options[:conditions] ? quot;AND #{interpolate_s jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil first_key = second_key = as_extra = nil if through_reflection.options[:as] # has_many :through against a polymorphic join jt_foreign_key = through_reflection.options[:as].to_s + '_id' jt_as_extra = quot; AND %s.%s = %squot; % [ connection.quote_table_name(aliased_join_table_name), connection.quote_column_name(through_reflection.options[:as].to_s + '_type'), klass.quote_value(parent.active_record.base_class.name) ] else jt_foreign_key = through_reflection.primary_key_name
  • 47. ] else jt_foreign_key = through_reflection.primary_key_name end case source_reflection.macro when :has_many if source_reflection.options[:as] first_key = quot;#{source_reflection.options[:as]}_idquot; second_key = options[:foreign_key] || primary_key as_extra = quot; AND %s.%s = %squot; % [ connection.quote_table_name(aliased_table_name), connection.quote_column_name(quot;#{source_reflection.options[:as]}_typequot;), klass.quote_value(source_reflection.active_record.base_class.name) ] else first_key = through_reflection.klass.base_class.to_s.foreign_key second_key = options[:foreign_key] || primary_key end unless through_reflection.klass.descends_from_active_record? jt_sti_extra = quot; AND %s.%s = %squot; % [ connection.quote_table_name(aliased_join_table_name), connection.quote_column_name(through_reflection.active_record.inheritance_col through_reflection.klass.quote_value(through_reflection.klass.sti_name)] end when :belongs_to first_key = primary_key if reflection.options[:source_type] second_key = source_reflection.association_foreign_key jt_source_extra = quot; AND %s.%s = %squot; % [ connection.quote_table_name(aliased_join_table_name), connection.quote_column_name(reflection.source_reflection.options[:foreign_ty klass.quote_value(reflection.options[:source_type]) ]
  • 48. connection.quote_column_name(reflection.source_reflection.options[:foreign_ty klass.quote_value(reflection.options[:source_type]) ] else second_key = source_reflection.primary_key_name end end quot; #{join_type} %s ON (%s.%s = %s.%s%s%s%s) quot; % [ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), connection.quote_table_name(parent.aliased_table_name), connection.quote_column_name(parent.primary_key), connection.quote_table_name(aliased_join_table_name), connection.quote_column_name(jt_foreign_key), jt_as_extra, jt_source_extra, jt_sti_extra ]+ quot; #{join_type} %s ON (%s.%s = %s.%s%s) quot; % [ table_name_and_alias, connection.quote_table_name(aliased_table_name), connection.quote_column_name(first_key), connection.quote_table_name(aliased_join_table_name), connection.quote_column_name(second_key), as_extra ] when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro) quot; #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %squot; % [ table_name_and_alias, connection.quote_table_name(aliased_table_name), quot;#{reflection.options[:as]}_idquot;, connection.quote_table_name(parent.aliased_table_name), parent.primary_key, connection.quote_table_name(aliased_table_name), quot;#{reflection.options[:as]}_typequot;, klass.quote_value(parent.active_record.base_class.name)
  • 49. connection.quote_table_name(aliased_table_name), quot;#{reflection.options[:as]}_typequot;, klass.quote_value(parent.active_record.base_class.name) ] else foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key quot; #{join_type} %s ON %s.%s = %s.%s quot; % [ table_name_and_alias, aliased_table_name, foreign_key, parent.aliased_table_name, reflection.options[:primary_key] || parent.primary_key ] end when :belongs_to quot; #{join_type} %s ON %s.%s = %s.%s quot; % [ table_name_and_alias, connection.quote_table_name(aliased_table_name), reflection.klass.primary_key, connection.quote_table_name(parent.aliased_table_name), options[:foreign_key] || reflection.primary_key_name ] else quot;quot; end || '' join << %(AND %s) % [ klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record [through_reflection, reflection].each do |ref| join << quot;AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name) end join end
  • 51. lib/tasks/quality.rake require 'flog' desc quot;Analyze for code complexityquot; task :flog do flog = Flog.new flog.flog_files ['app'] threshold = 30 bad_methods = flog.totals.select do |name, score| score > threshold end bad_methods.sort { |a,b| a[1] <=> b[1] }.each do |name, score| puts quot;%8.1f: %squot; % [score, name] end raise quot;#{bad_methods.size} methods have a flog complexity > #{threshold}quot; unless bad_methods.empty? end
  • 52. ~/Data/runway $ rake flog (in /Users/marty/Data/runway) 35.2: Token#tokenize 42.3: Token#parse_incubation_days 60.4: Token#parse_incubation_date 81.7: ActionParser#parse 89.7: ActionFormat#format rake aborted! 5 methods have a flog complexity > 30
  • 53. use flog in your CI build
  • 55. Structural similarity checking of Sexp nodes (syntax tokens)
  • 57. Look for high scores (> 30)
  • 58. $ sudo gem install flay $ flay [dir ...]
  • 59. ~/Data/runway $ flay app [Processing files...] Total score (lower is better) = 286 1) Similar code found in :defn (mass = 60) app/controllers/signups_controller.rb:5 app/controllers/user_sessions_controller.rb:6 2) Similar code found in :if (mass = 54) app/controllers/forgot_passwords_controller.rb:15 app/controllers/signups_controller.rb:16 3) Similar code found in :defn (mass = 50) app/helpers/actions_helper.rb:43 app/helpers/actions_helper.rb:49 [More content removed...]
  • 60. ~/Data/runway $ flay app [Processing files...] Total score (lower is better) = 286 1) Similar code found in :defn (mass = 60) app/controllers/signups_controller.rb:5 app/controllers/user_sessions_controller.rb:6 2) Similar code found in :if (mass = 54) app/controllers/forgot_passwords_controller.rb:15 app/controllers/signups_controller.rb:16 3) Similar code found in :defn (mass = 50) app/helpers/actions_helper.rb:43 app/helpers/actions_helper.rb:49 [More content removed...]
  • 61. 1) Similar code found in :defn (mass = 60) app/controllers/signups_controller.rb:5 app/controllers/user_sessions_controller.rb:6 def create user = User.new(params[:signup]) if user.save head(:status => :created, :location => home_path) else head(:status => :unprocessable_entity) end end def create user_session = UserSession.new(params[:user_session]) if user_session.save head(:status => :created, :location => home_path) else head(:status => :unprocessable_entity) end end
  • 64. actionmailer/install.rb require 'rbconfig' require 'find' require 'ftools' include Config # this was adapted from rdoc's install.rb by way of Log4r $sitedir = CONFIG[quot;sitelibdirquot;] unless $sitedir version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;] $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version) $sitedir = $:.find {|x| x =~ /site_ruby/ } if !$sitedir $sitedir = File.join($libdir, quot;site_rubyquot;) elsif $sitedir !~ Regexp.quote(version) $sitedir = File.join($sitedir, version) end end # the actual gruntwork Dir.chdir(quot;libquot;) Find.find(quot;action_mailerquot;, quot;action_mailer.rbquot;) { |f| if f[-3..-1] == quot;.rbquot; File::install(f, File.join($sitedir, *f.split(///)), 0644, true) else File::makedirs(File.join($sitedir, *f.split(///))) end }
  • 65. activesupport/install.rb require 'rbconfig' require 'find' require 'ftools' include Config # this was adapted from rdoc's install.rb by ways of Log4r $sitedir = CONFIG[quot;sitelibdirquot;] unless $sitedir version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;] $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version) $sitedir = $:.find {|x| x =~ /site_ruby/ } if !$sitedir $sitedir = File.join($libdir, quot;site_rubyquot;) elsif $sitedir !~ Regexp.quote(version) $sitedir = File.join($sitedir, version) end end # the actual gruntwork Dir.chdir(quot;libquot;) Find.find(quot;active_supportquot;, quot;active_support.rbquot;) { |f| if f[-3..-1] == quot;.rbquot; File::install(f, File.join($sitedir, *f.split(///)), 0644, true) else File::makedirs(File.join($sitedir, *f.split(///))) end }
  • 66. activerecord/install.rb require 'rbconfig' require 'find' require 'ftools' include Config # this was adapted from rdoc's install.rb by ways of Log4r $sitedir = CONFIG[quot;sitelibdirquot;] unless $sitedir version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;] $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version) $sitedir = $:.find {|x| x =~ /site_ruby/ } if !$sitedir $sitedir = File.join($libdir, quot;site_rubyquot;) elsif $sitedir !~ Regexp.quote(version) $sitedir = File.join($sitedir, version) end end # the actual gruntwork Dir.chdir(quot;libquot;) Find.find(quot;active_recordquot;, quot;active_record.rbquot;) { |f| if f[-3..-1] == quot;.rbquot; File::install(f, File.join($sitedir, *f.split(///)), 0644, true) else File::makedirs(File.join($sitedir, *f.split(///))) end }
  • 67. actionpack/install.rb require 'rbconfig' require 'find' require 'ftools' include Config # this was adapted from rdoc's install.rb by way of Log4r $sitedir = CONFIG[quot;sitelibdirquot;] unless $sitedir version = CONFIG[quot;MAJORquot;] + quot;.quot; + CONFIG[quot;MINORquot;] $libdir = File.join(CONFIG[quot;libdirquot;], quot;rubyquot;, version) $sitedir = $:.find {|x| x =~ /site_ruby/ } if !$sitedir $sitedir = File.join($libdir, quot;site_rubyquot;) elsif $sitedir !~ Regexp.quote(version) $sitedir = File.join($sitedir, version) end end # the actual gruntwork Dir.chdir(quot;libquot;) Find.find(quot;action_controllerquot;, quot;action_controller.rbquot;, quot;action_viewquot;, quot;action_view.rbquot;) { |f| if f[-3..-1] == quot;.rbquot; File::install(f, File.join($sitedir, *f.split(///)), 0644, true) else File::makedirs(File.join($sitedir, *f.split(///))) end }
  • 69. lib/tasks/quality.rake require 'flay' desc quot;Analyze for code duplicationquot; task :flay do threshold = 25 flay = Flay.new({:fuzzy => false, :verbose => false, :mass => threshold}) flay.process(*Flay.expand_dirs_to_files(['app'])) flay.report raise quot;#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}quot; unless flay.masses.empty? end
  • 70. ~/Data/runway $ rake flay (in /Users/marty/Data/runway) Total score (lower is better) = 164 1) Similar code found in :defn (mass = 60) app/controllers/signups_controller.rb:5 app/controllers/user_sessions_controller.rb:6 2) Similar code found in :if (mass = 54) app/controllers/forgot_passwords_controller.rb:15 app/controllers/signups_controller.rb:16 3) Similar code found in :defn (mass = 50) app/helpers/actions_helper.rb:43 app/helpers/actions_helper.rb:49 rake aborted! 3 chunks of code have a duplicate mass > 25
  • 71. use flay in your CI build
  • 73. Checks for design problems
  • 74. Class Name Block Cyclomatic Complexity Method Name Method Cyclomatic Complexity Module Name Assignment In Conditional Class Line Count Case Missing Else Method Line Count Empty Rescue Body Module Line Count For Loop Check Parameter Number
  • 75. Look for any errors
  • 76. $ sudo gem install roodi $ roodi [pattern ...]
  • 77. ~/Data/runway $ roodi quot;app/**/*.rbquot; app/models/action_format.rb:10 Method name quot;formatquot; has a cyclomatic complexity is 12. It should be 8 or less. app/models/action_parser.rb:50 Block cyclomatic complexity is 11. It should be 4 or less. app/models/action.rb:107 Case statement is missing an else clause. app/models/action.rb:103 Method quot;apply_defaults_from_namequot; has 23 lines. It should have 20 or less. app/controllers/application.rb:52 Found = in conditional. It should probably be an ==
  • 78. Method name quot;formatquot; has a cyclomatic complexit It should be 8 or less. app/models/action_parser.rb:50 Block cyclomatic complexity is 11. It should be 4 or less. app/models/action.rb:107 Case statement is missing an else clause. app/models/action.rb:103 Method quot;apply_defaults_from_namequot; has 23 lines. It should have 20 or less. app/controllers/application.rb:52 Found = in conditional. It should probably be an ==
  • 79. app/models/action_parser.rb:50 Block cyclomatic complexity is 11. It should be 4 or less. meta.each do |token| case token.klass when :context unless token.value.blank? @contexts ||= [] @contexts << token.value end when :time @time = token.value when :energy @energy = token.value when :tag @tags << token.value unless token.value.blank? when :semi_colon # Ignore when :available, :today, :later, :done @status = token.klass.to_s @effective_at = token.value when :due_at @due_at = token.value end end
  • 80. app/models/action_parser.rb:50 Block cyclomatic complexity is 11. It should be 4 or less. 1 meta.each do |token| case token.klass 2 when :context 3 unless token.value.blank? @contexts ||= [] 4 @contexts << token.value end 5 when :time @time = token.value 6 when :energy @energy = token.value 7 8 when :tag @tags << token.value unless token.value.blank? 9 when :semi_colon # Ignore 10 when :available, :today, :later, :done @status = token.klass.to_s @effective_at = token.value 11 when :due_at @due_at = token.value end end
  • 81. app/controllers/application.rb:52 Found = in conditional. It should probably be an == def extract_authenticity_token if token = request.headers[quot;HTTP_X_RUNWAY_AUTHENTICITY_TOKENquot;] params[request_forgery_protection_token] = token end end
  • 84. ./actionpack/lib/action_controller/vendor/html- scanner/html/node.rb:417 Method name quot;matchquot; cyclomatic complexity is 47. It should be 8 or less.
  • 85. def match(conditions) conditions = validate_conditions(conditions) # check content of child nodes if conditions[:content] if children.empty? return false unless match_condition(quot;quot;, conditions[:content]) else return false unless children.find { |child| child.match(conditions[:content]) } end end # test the name return false unless match_condition(@name, conditions[:tag]) if conditions[:tag] # test attributes (conditions[:attributes] || {}).each do |key, value| return false unless match_condition(self[key], value) end # test parent return false unless parent.match(conditions[:parent]) if conditions[:parent] # test children return false unless children.find { |child| child.match(conditions[:child]) } if conditio # test ancestors if conditions[:ancestor] return false unless catch :found do p = self throw :found, true if p.match(conditions[:ancestor]) while p = p.parent end end # test descendants
  • 86. # test descendants if conditions[:descendant] return false unless children.find do |child| # test the child child.match(conditions[:descendant]) || # test the child's descendants child.match(:descendant => conditions[:descendant]) end end # count children if opts = conditions[:children] matches = children.select do |c| (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?)) end matches = matches.select { |c| c.match(opts[:only]) } if opts[:only] opts.each do |key, value| next if key == :only case key when :count if Integer === value return false if matches.length != value else return false unless value.include?(matches.length) end when :less_than return false unless matches.length < value when :greater_than return false unless matches.length > value else raise quot;unknown count condition #{key}quot; end end end
  • 87. else raise quot;unknown count condition #{key}quot; end end end # test siblings if conditions[:sibling] || conditions[:before] || conditions[:after] siblings = parent ? parent.children : [] self_index = siblings.index(self) if conditions[:sibling] return false unless siblings.detect do |s| s != self && s.match(conditions[:sibling]) end end if conditions[:before] return false unless siblings[self_index+1..-1].detect do |s| s != self && s.match(conditions[:before]) end end if conditions[:after] return false unless siblings[0,self_index].detect do |s| s != self && s.match(conditions[:after]) end end end true end
  • 89. lib/tasks/quality.rake require 'roodi' require 'roodi_task' RoodiTask.new 'roodi', ['app/**/*.rb', 'lib/**/*.rb'], 'roodi.yml'
  • 90. roodi.yml # AssignmentInConditionalCheck: {} # CaseMissingElseCheck: {} ClassLineCountCheck: { line_count: 300 } ClassNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z # ClassVariableCheck: {} CyclomaticComplexityBlockCheck: { complexity: 8 } CyclomaticComplexityMethodCheck: { complexity: 10 } EmptyRescueBodyCheck: { } ForLoopCheck: { } MethodLineCountCheck: { line_count: 20 } MethodNameCheck: { pattern: !ruby/regexp /^[_a-z<>=[|+ ModuleLineCountCheck: { line_count: 300 } ModuleNameCheck: { pattern: !ruby/regexp /^[A-Z][a-zA-Z ParameterNumberCheck: { parameter_count: 5 }
  • 91. ~/Data/runway $ rake roodi (in /Users/marty/Data/runway) app/models/action_format.rb:10 - Method name quot;formatquot; cyclomatic complexity is 12. It should be 10 or less. app/models/action_parser.rb:11 - Method name quot;attributesquot; cyclomatic complexity is 12. It should be 10 or less. app/models/action_parser.rb:30 - Method name quot;parsequot; cyclomatic complexity is 14. It should be 10 or less. app/models/token.rb:173 - Method name quot;parse_incubation_datequot; cyclomatic complexity is 11. It should be 10 or less. app/models/token.rb:226 - Method name quot;parse_incubation_daysquot; cyclomatic complexity is 12. It should be 10 or less. app/models/action_parser.rb:50 - Block cyclomatic complexity is 11. It should be 8 or less. app/models/token.rb:11 - Block cyclomatic complexity is 10. It should be 8 or less. app/models/action.rb:103 - Method quot;apply_defaults_from_namequot; has 23 lines. It should have 20 or less. app/models/action_parser.rb:30 - Method quot;parsequot; has 23 lines. It should have 20 or less. rake aborted! Found 9 errors.
  • 92. use roodi in your CI build
  • 94. reports on everything you’ve seen today plus more!
  • 95. $ sudo gem sources -a http://gems.github.com $ sudo gem install jscruggs-metric_fu
  • 99.
  • 100.
  • 101.
  • 102.
  • 103. use metrics_fu for regular human review
  • 105. lib/tasks/quality.rake (part 1) require 'flog' desc quot;Analyze for code complexityquot; task :flog do flog = Flog.new flog.flog_files ['app'] threshold = 40 bad_methods = flog.totals.select do |name, score| score > threshold end bad_methods.sort { |a,b| a[1] <=> b[1] }.each do |name, score| puts quot;%8.1f: %squot; % [score, name] end raise quot;#{bad_methods.size} methods have a flog complexity > #{threshold}quot; unless bad_methods.empty? end
  • 106. lib/tasks/quality.rake (part 2) require 'flay' desc quot;Analyze for code duplicationquot; task :flay do threshold = 25 flay = Flay.new({:fuzzy => false, :verbose => false, :mass => threshold}) flay.process(*Flay.expand_dirs_to_files(['app'])) flay.report raise quot;#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}quot; unless flay.masses.empty? end
  • 107. lib/tasks/quality.rake (part 3) require 'roodi' require 'roodi_task' require 'metric_fu' RoodiTask.new 'roodi', ['app/**/*.rb', 'lib/**/*.rb'], 'roodi.yml' task :quality => [:flog, :flay, :roodi, 'metrics:all']
  • 108. continuous integration flog flay roodi regular human review metric_fu rake stats reek and more
  • 109. Resources me @martinjandrews marty@cogentconsulting.com.au code samples from runwayapp.com reek wiki.github.com/kevinrutherford/reek flog ruby.sadi.st/Flog.html flay ruby.sadi.st/Flay.html roodi roodi.rubyforge.org metric_fu metric-fu.rubyforge.org

Notas del editor