Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

Allow matching on number of smell instances

#33

For incremental improvement of an existing codebase, something like:

some_source.should reek(:maximum_smells => 4400)
some_source.should_not reek_much(:maximum_smells => 4400)

See Twitter conversation for motivation:

https://twitter.com/nick_evans/status/1427543378
https://twitter.com/ashleymoran/statuses/1427646807

WDYT?

Reported by Ashley Moran · March 31st, 2009 @ 11:13 PM

State: resolved
Milestone: none
Assigned to: Kevin Rutherford Kevin Rutherford

Activity

  1. nicholas a. evans
    nicholas a. evans

    agree with the second version:

    
    some_source.should_not reek_much(:maximum_smells => 4400)
    # or an alternate syntax:
    some_source.should_not reek.more_than(4400).smells
    some_source.should reek.less_than(4400).smells
    # '.smells' is just unnecessary sugar to make it read nicer.
    

    I think I'd use that to keep the overall smells from going up. But since that would still leave me with the "waterbed" or "balloon" problem (push out the smells on one area and the smells might show up somewhere else), I'm also going to quarantine certain files/directories as "smell free zones". It's very annoying to greenfield an new area, feel quite pleased with how clean it is, and then come back a few months later to a great big mess. What happened?!

    April 1st, 2009 @ 01:19 PM

  2. Kevin Rutherford
    Kevin Rutherford

    How about starting with

    
    Dir[...].should_not reek_of(:UncommunicativeName)
    

    for some smallish Dir down the tree?

    April 1st, 2009 @ 01:26 PM

  3. nicholas a. evans
    nicholas a. evans

    That's also a good ratchet-like approach. Unfortunately, our codebase seems to collect all of the smells. ;-) I think I'll use that approach for some of them... but:

    
     $ reek app/**/*.rb lib/**/*.rb | tee TODO.reek | perl -pe 's/^(\[[^\]]*\])?.*/\1/' | sort | uniq -c
    (string):335: warning: useless use of == in void context
    (string):370: warning: useless use of a variable in void context
    (string):43: warning: ambiguous first argument; put parentheses or even spaces
    (string):253: warning: unused literal ignored
    (string):33: warning: (...) interpreted as grouped expression
    (string):140: warning: useless use of a variable in void context
    (string):14: warning: `*' interpreted as argument prefix
    (string):41: warning: `*' interpreted as argument prefix
    (string):42: warning: (...) interpreted as grouped expression
        544
        142 [Control Couple]
       1843 [Duplication]
        590 [Feature Envy]
          1 [Large Class]
        649 [Long Method]
         89 [Long Parameter List]
          1 [Long Yield List]
        191 [Nested Iterators]
         79 [Uncommunicative Name]
        313 [Utility Function]
    

    (are those $stderr warnings coming from reek itself? I think they are.)

    So to handle this code, I'd put an hour or two into cleanup, and then I could use something like:

    
    files = Dir['app/**/*.rb', 'lib/**/*.rb']
    files.should_not reek_of(:UncommunicativeName, :LongYieldList, :LargeClass)
    files.should_not reek_of(:UtilityFunction).more_than(313).smells
    files.should_not reek_of(:NestedIterators).more_than(191).smells
    

    And so on. Basically, the more places I can use to ratchet up the code quality, draw a line and say "this far, no farther!", the better. :-)

    April 1st, 2009 @ 01:59 PM

  4. nicholas a. evans
    nicholas a. evans

    And yes, like you said... I'll probably start with smaller directories down the tree, rather than all of app and lib (as in my example).

    April 1st, 2009 @ 02:01 PM

  5. Kevin Rutherford
    Kevin Rutherford

    The $stderr warnings are from the underlying Ruby parser. (So they're smells too :).

    I'd say that UncommunicativeName and Duplication are the easiest to remove -- not least because Reek's Duplication check right now is very simple. My guess is you could eliminate UncommunicativeNames everywhere for not much effort? And then work through a Dir at a time cleaning up the Duplication reports. I'd expect to see some of the other smells go away as a result of doing just that...

    April 1st, 2009 @ 02:07 PM

  6. Kevin Rutherford
    Kevin Rutherford

    I'm quite reluctant to add numerical theshholds for smells. But here's an alternative thought:

    
    require 'my_class'
    
    describe MyClass do
      it 'reports no code smells' do
        MyClass.should_not reek
      end
    end
    

    Would that help you clean up incrementally?

    April 2nd, 2009 @ 02:04 PM

  7. nicholas a. evans
    nicholas a. evans

    Absolutely. I like that a lot. I'm envisioning something like the following:

    
    # specific smells we've cleaned up *everywhere*
    describe "our codebase" do
      [
        :UncommunicativeName,
        :Duplication,
        :LargeClass,
        :LongYieldList,
      ].each do |smell|
        it "should not have any #{smell} smells" do
          Dir[appropriate_file_glob].should_not reek_of(smell)
        end
      end
    end
    
    # specific classes or directories that we've cleaned up completely
    [
      NewClass,
      GoodClass,
      CleanClass,
      'app/models/my_clean_zone/**/*.rb'
    ].each do |klass_or_location|
      describe klass_or_location
        it "should be smell-free" do
          case klass_or_location
          when String then Dir[klass_or_location].should_not reek
          else                              klass.should_not reek
          end
        end
      end
    
    # Incompletely cleaned up classes (or directories) can be handled individually
    # and eventually moved into the category above (no smells)
    describe FooBarKindaDirty do
      it "should be free of some smells" do
        FooBarKindaDirty.should_not reek_of(:LongMethod, :UtilityFunction)
      end
    end
    
    

    What do you think?

    April 2nd, 2009 @ 02:23 PM

  8. Kevin Rutherford
    Kevin Rutherford

    Cool -- I'm on it this weekend. One smell tweak, while we're sketching code:

    
    # specific classes or directories that we've cleaned up completely
    [
      NewClass,
      GoodClass,
      CleanClass,
      Dir['app/models/my_clean_zone/**/*.rb']
    ].each do |klass_or_location|
      describe klass_or_location
        it "should be smell-free" do
          klass_or_location.should_not reek
        end
      end
    

    April 2nd, 2009 @ 02:27 PM

  9. nicholas a. evans
    nicholas a. evans

    please ignore the several typos in there (mismatched 'do...end', klass/klass_or_location). That's the downside of not having a 'preview' button. ;-)

    I can see why you might be reluctant to add numerical limits. It's better to just wipe out particular smells entirely within a single context.

    April 2nd, 2009 @ 02:30 PM

  10. nicholas a. evans
    nicholas a. evans

    I considered that, but Dir['nice/*/type/.glob'].to_s is quite atrocious. A good specdoc is important to me. ;-)

    so, if you made the 'reek' and 'reek_of' matchers automatically typeglob strings via Dir[], then we could say:

    
    
    # specific classes or directories that we've cleaned up completely
    [
      NewClass,
      GoodClass,
      CleanClass,
      'app/models/my_clean_zone/**/*.rb'
    ].each do |klass_or_location|
      describe klass_or_location
        it "should be smell-free" do
          klass_or_location.should_not reek
        end
      end
    end
    

    and the specdoc would still look nice, and it would work as expected.

    April 2nd, 2009 @ 02:37 PM

  11. Kevin Rutherford
    Kevin Rutherford

    There is already another way to do what you want:

    
    dir = Dir['lib/**/*.rb'].to_source
    dir.report.should have_at_most(85).smells
    

    Not quite so sugary, perhaps, but it does the job.

    April 3rd, 2009 @ 07:53 AM

  12. nicholas a. evans
  13. Kevin Rutherford
    Kevin Rutherford
    • State changed from new to open

    April 7th, 2009 @ 04:33 PM

  14. Kevin Rutherford
    Kevin Rutherford
    • State changed from open to resolved

    (from [0cbce97780af2001da68829fb8685dcc3076463a]) Rspec matchers now work for any object [#33 Allow matching on number of smell instances state:resolved] http://github.com/kevinrutherfor...

    April 9th, 2009 @ 09:05 PM

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.