Marty Andrews

artful code

Friday, May 29, 2009

Enforcing Ruby code quality

Over the last year or so, there's been lots of great tools coming out in the Ruby community that help with static analysis of code. We've now got flay, flog, reek and roodi. metric-fu runs all of these tools (plus more) to generate nice reports.

Generating nice reports is all well and good, but frankly it's not enough. We need to start enforcing code quality, not just reporting on it. The Java community is way ahead on this front, which I find rather embarrassing as a Ruby developer. Fear not though, I'm going to show you exactly how to set it up here.

Getting Started

First of all, you need a continuous integration build. Don't have one? Fail. Go have a good hard look at yourself in the mirror and consider if you really want to be a professional software developer or not.

If you're still here, you need the tools installed. That's pretty easy:

sudo gem install flay
sudo gem install flog
sudo gem install reek
sudo gem install roodi
sudo gem sources -a
sudo gem install jscruggs-metric_fu

What you've just installed is a set of tools that check for duplicate code, complex code, code smells and code problems. The last one (metric-fu) will give you a report to look at if you need to browse for more details.

Next, you need to add some tasks to your rakefile. For my rails applications, I have this in lib/tasks/quality.rake, but you can put it anywhere that gets loaded by your build.

    1 require 'flog'
    2 require 'flay'
    3 require 'roodi'
    4 require 'roodi_task'
    5 require 'metric_fu'
    7 desc "Analyze for code complexity"
    8 task :flog do
    9   flog =
   10   flog.flog_files ['app']
   11   threshold = 40
   13   bad_methods = do |name, score|
   14     score > threshold
   15   end
   16   bad_methods.sort { |a,b| a[1] <=> b[1] }.each do |name, score|
   17     puts "%8.1f: %s" % [score, name]
   18   end
   20   raise "#{bad_methods.size} methods have a flog complexity > #{threshold}" unless bad_methods.empty?
   21 end
   23 desc "Analyze for code duplication"
   24 task :flay do
   25   threshold = 25
   26   flay ={:fuzzy => false, :verbose => false, :mass => threshold})
   27   flay.process(*Flay.expand_dirs_to_files(['app']))
   31   raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}" unless flay.masses.empty?
   32 end
   34 'roodi', ['app/**/*.rb'], 'roodi.yml'
   36 task :quality => [:flog, :flay, :roodi, 'metrics:all']

Now, add the quality task to the list of things done in your continuous integration build. That will make sure these things run all the time. Try running rake quality manually. It will almost certainly fail on either flog, flay or roodi. I'll show you how to tweak them one at a time.


When I ran rake flog for the first time, I got something like this:

~/Data/runway $ rake flog
(in /Users/marty/Data/runway)
    42.3: Token#parse_incubation_days
    60.4: Token#parse_incubation_date
    81.7: ActionParser#parse
    89.7: ActionFormat#format
rake aborted!
4 methods have a flog complexity > 40

This tells me that I have four methods in my code base that have a flog complexity greater than 40. If you look back at line 11 of quality.rake shown above, you'll see that this threshold is configurable. Set that number to something that is just higher than the most complex method in your application (in my case, I set it to 90) and run rake flog again. It should pass.

What you've just done is create a stop loss for complexity in your application. If anyone checks in a method that is more complex than the configured threshold, your build will fail. In other words, methods are not allowed to get any more complex than the most complex method in your application now.

Unless you're working on a green fields application, I'll bet that the threshold you set was higher than you want it be. That method that was the most complex in your application needs some refactoring. You've got somewhere to focus your refactoring efforts now though. Pick that method, refactor it, and ratchet down the threshold to the next most complex method. Now you're incrementally improving the quality in your app in terms of complexity.


When I ran rake flay for the first time, I got something like this:

~/Data/runway $ rake flay
(in /Users/marty/Data/runway)
Total score (lower is better) = 164

1) Similar code found in :defn (mass = 60)

2) Similar code found in :if (mass = 54)

3) Similar code found in :defn (mass = 50)
rake aborted!
3 chunks of code have a duplicate mass > 25

This tells me that I have two pieces of code with a mass greater than 30 that have been duplicated. If you look back at line 25 of quality.rake shown above, you'll see that this threshold is configurable. Set that number to something that is just higher than the largest mass of duplicated code in your application. This can be a little confusing, because the tool reports a number equal to that mass multiplied by the number of times it appears. In my case, the reported mass of 60 is actually 30 (a mass of 30 found in 2 places). So I set the threshold to 30. Run rake flay again. It should pass.

What you've just done is create a stop loss for duplication in your application. If anyone checks in code that duplicates an existing mass of code larger than the configured threshold, your build will fail. In other words, people can't copy and paste code in a fashion any worse than they already have.

Again, that threshold you set is almost certainly higher than you want it be. That largest mass of code that was duplicated needs some refactoring to make your code more DRY. Find that code, refactor it, and ratchet down the threshold to the next largest mass of code. Now you're incrementally improving the quality in your app in terms of duplication.


When I ran rake roodi for the first time, I got something like this:

~/Data/runway $ rake roodi
(in /Users/marty/Data/runway)
app/models/action_format.rb:10 - Method name "format" cyclomatic complexity is 12.  It should be 10 or less.
app/models/action_parser.rb:11 - Method name "attributes" cyclomatic complexity is 12.  It should be 10 or less.
app/models/action_parser.rb:30 - Method name "parse" cyclomatic complexity is 14.  It should be 10 or less.
app/models/token.rb:173 - Method name "parse_incubation_date" cyclomatic complexity is 11.  It should be 10 or less.
app/models/token.rb:226 - Method name "parse_incubation_days" 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 "apply_defaults_from_name" has 23 lines.  It should have 20 or less.
app/models/action_parser.rb:30 - Method "parse" has 23 lines.  It should have 20 or less.
rake aborted!
Found 9 errors.

This tells me that I have several pieces of code failing Roodi checks. If you look back at line 34 of quality.rake shown above, you'll see a reference to roodi.yml. This is a config file that allows you to configure Roodi. Create that file in the root directory of your application and paste the following contents into it. It's what I have in my config file.

# AssignmentInConditionalCheck:    { }
# CaseMissingElseCheck:            { }
ClassLineCountCheck:             { line_count: 300 }
ClassNameCheck:                  { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
# ClassVariableCheck:              { }
# TODO Get block cyclomatic complexity back down to 6 or less
CyclomaticComplexityBlockCheck:  { complexity: 12 }
# TODO Get method cyclomatic complexity back down to 8 or less
CyclomaticComplexityMethodCheck: { complexity: 14 }
EmptyRescueBodyCheck:            { }
ForLoopCheck:                    { }
# TODO Get method line count back down to 20 or less
MethodLineCountCheck:            { line_count: 27 }
MethodNameCheck:                 { pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/ }
ModuleLineCountCheck:            { line_count: 300 }
ModuleNameCheck:                 { pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/ }
ParameterNumberCheck:            { parameter_count: 5 }

This config file describes a list of checks that get run by Roodi, and some parameters that are used to initialise those checks. Have a look at for details on each of those checks. Make a call on which checks are appropriate for your project, and then change the configuration on each item so that your build just passes. What you've just done is create a stop loss for these coding problems in your application. If anyone checks in code that doesn't pass these configured checks, your build will fail.

It's possible that you've had to comment out some checks that you really want on, to get the build to pass. For the ones that are still turned on, you've probably had to increase some of the thresholds. You can find that code, refactor it, and turn checks back on or ratchet down the thresholds over time. Now you're incrementally improving the quality in your app in terms of coding checks.


The installation of metric-fu will give you some nice reports to browse and find the next worst problems in your application. Run rake metrics:all to get that report generated. Put some time aside regularly to clean up your code and ratchet down the thresholds as described above until you get them to a point you're happy with.

As far as I'm concerned, these tools are not an optional extra for your project. You must have them tools set up. It's unprofessional of you not to do everything you reasonably can to keep the quality of your code high. I hope this helps.

GTD Tweekly review

Kelly Forrister, who goes by the name GTDCoachKelly on twitter recently ran a GTD Tweekly review on twitter. I found it enourmously valuable, and we're in the middle of building support for weekly reviews into Runway, so I wanted to record the transcript of the session somewhere that I could reference it for myself again.

Here's how it went.

#Tweekly #GTD Hello everyone! Ready? We'll do this in 3 parts/11 steps
#Tweekly #GTD PART ONE: GET CLEAR. Collect loose paper and materials. Gather everything that's loose into an Inbox, Tray or folder.
#Tweekly #GTD You have 5 minutes for this step. Go...
#Tweekly #GTD You all have one more minute on step one: Collect loose papers and materials.
#Tweekly #GTD PART ONE-STEP TWO-GET CLEAR: Get In to Zero. Choose the inbox that can good progress on in 5 min--email? paper? VM? Go!
#Tweekly #GTD - a good way to process in is 4D's: Delete it, Do it (under 2 mins), Delegate it, Defer it (onto a list)
#Tweekly #GTD PART ONE-STEP THREE-GET CLEAR: Empty your head. Open a Word doc, or grab and pad and clear your head for 5 minutes. Go.
#Tweekly #GTD - STEP THREE - SOME MINDSWEEP TRIGGERS: Family, health, meetings you've had, meetings you're going to have...
#Tweekly #GTD SOME MORE MINDSWEEP TRIGGERS: Your direct reports, finances, 401k, the dog, your car, health appts you've been putting off...
#Tweekly #GTD PART TWO, STEP FOUR-GET CURRENT: Review your Action lists (or maybe you call them Tasks or To Do's.)5 minutes start now. Go!
#Tweekly #GTD (10:25am) 2 more minutes to review action lists--are they current? anything to mark done? anything trigger you to add?
#Tweekly #GTD (10:28am) PART TWO-STEP 5-Review previous calendar info. Any triggers?
#Tweekly #GTD Many times reviewing your old calendar (go back about 3 wks) catches things you meant to do. 3 more mins left (10:30am)
#Tweekly #GTD PART TWO-STEP 6-REVIEW UPCOMING CALENDAR DATA - anything you should start getting ready for? (10:35am) Go!
#Tweekly #GTD REVIEW UPCOMING CALENDAR TIP: if you find something you need to process, you can add to your mindsweep for now.
#Tweekly #GTD if you don't get anything on reviewing your calendar, try going further out. Recurring Tasks are great for calendar. 10:40am
#Tweekly #GTD PART TWO-STEP 7-REVIEW WAITING FOR - if you've got a list review it. If you don't have one, what are you waiting on? 10:41am
#Tweekly #GTD WAITING FOR TIP: Review your email Sent folder. Usually some waiting for's hiding in there. 10:43am
#Tweekly #GTD - PART TWO-STEP 8-REVIEW PROJECT LISTS. Projects are your outcomes that require more than one action step. 10:47am Go!
#Tweekly #GTD PROJECT TIP: Projects are typically completed within 18 mos. If you can NEVER mark it done, it's likely an Area of Focus.
#Tweekly #GTD PROJECT TIP: Most people we coach have 30-100 current personal & professional projects. Don't be surprised! 10:51am.
#Tweekly #GTD PROJECT TIP: If you are not willing to take any next action on a current project, are you sure it's not Someday/Maybe?
#Tweekly #GTD PART 2-STEP 9 - REVIEW CHECKLISTS - birthday checklists? travel checklists? home mntce? 10:55am Go!
#Tweekly #GTD CHECKLIST TIP: Maybe you want to CREATE a checklist? Anything recurring that would be good? What to always pack for vacation?
#Tweekly #GTD PART 3-GET CREATIVE!-STEP 10-REVIEW SOMEDAY/MAYBE: If you have one, update it. If you don't have one, create it! 11:00am
#Tweekly #GTD SOMEDAY /MAYBE TIP: S/M is not just a "fantasy wish" list. It can be a fantastic place to stage "not yet" projects. 11:02am
#Tweekly #GTD SOMEDAY TIP: You'll trust S/M list(s) more if you know you're actually going to review them again. Otherwise they'll die.
#Tweekly #GTD PART 3-STEP 11-BE CREATIVE & COURAGEOUS! Any new thought-provoking, creative, risk taking ideas to add to your system? 11:07a
#Tweekly #GTD CREATIVE & COURAGEOUS TIP: What's REALLY got your attention in your job, family, environment? This is the last step! 11:09am
#Tweekly #GTD Thanks everyone. Hope you got value! Please post comments here: Let me know if you want me to do again.

Thanks for a great review Kelly, I really enjoyed it.

Saturday, May 23, 2009

Geeking things done (GTD) with Runway

At Cogent Consulting, we've had a goal of building our own products in house for some time. What better product to build than something you know about and will use every day. We're geeks, and we use the process of Getting Things Done™ (GTD) on a daily basis to help plan our own work. None of the products on the market seem to understand the way we want to work though.

To define an action in GTD, you need to give it a name, context, time & energy. It might also be associated with a person and have some tags. The average bit of software out there thinks this is 4 - 6 different fields. You click in one, type, click in the next, type, and so on.

Enter Runway. Getting Things Done™ (GTD) for geeks

In Runway, there's only one input box that understands a simple structured language to parse all of the information needed. So I can type all of that in without clicking around.

When I'm done putting that information in, I end up with this. You can see that action listed, with the context, time, energy and tags all listed separately underneath it.

Runway gets out of my way and lets me worry about managing my actions, rather than managing it. I can navigate around the whole application just using the keyboard, and it gives me clues along the way about how to do it.

It's still early days for Runway, and we're in the process of adding lots more. We'd love you feedback though, so please have a play and let us know what you think.

Finding CRAP Ruby code

A couple of days ago, Uncle Bob blogged about the CRAP in FitNesse. That prompted a few people in the Twitterverse to ponder about whether it would be possible to build such a tool for Ruby. In particular, Jim Weirich and Kevin Rutherford considered it.

Kevin and I have both built code quality tools for Ruby already (Reek and Roodi respectively), so I pinged him to suggest we could have a go. The CRAP (Change Risk Analysis and Predictions) metric is a function of cyclomatic complexity and coverage. I already knew how to calculate cyclomatic complexity from Roodi, and Rcov was an obvious choice to get coverage information from.

After a couple of hours, we came up with crap4r. It's pretty rough, but it basically works. Here's what it looks like:

~/Data/roodi $ ruby -Ilib ../crap4r/bin/crap4r

1.0  ./lib/roodi/core/visitable_sexp.rb#accept
1.0  ./lib/roodi/core/visitable_sexp.rb#node_type
1.0  ./lib/roodi/core/visitable_sexp.rb#children


2.0625  /Users/marty/Data/roodi/lib/roodi/checks/name_check.rb#evaluate
2.0625  /Users/marty/Data/roodi/lib/roodi/checks/line_count_check.rb#evaluate
3.0  /Users/marty/Data/roodi/lib/roodi/checks/abc_metric_method_check.rb#branch?
3.04166666666667  /Users/marty/Data/roodi/lib/roodi/checks/check.rb#evaluate_node
3.04166666666667  /Users/marty/Data/roodi/lib/roodi/checks/assignment_in_conditional_check.rb#has_assignment?
3.04166666666667  /Users/marty/Data/roodi/lib/roodi/checks/parameter_number_check.rb#evaluate
3.140625  /Users/marty/Data/roodi/lib/roodi/core/runner.rb#parse
3.33333333333333  /Users/marty/Data/roodi/lib/roodi/core/iterator_visitor.rb#visit
4.128  /Users/marty/Data/roodi/lib/roodi/checks/empty_rescue_body_check.rb#has_statement?
6.0  /Users/marty/Data/roodi/lib/roodi/core/runner.rb#load_checks

Kevin and I will clean it up over the next few weeks and see if we can publish it somewhere in a more usable fashion.

Friday, May 15, 2009

Roodi 1.4.0 released

I released version 1.4.0 of Roodi while I was a RailsConf last week. It changes the underlying engine from ParseTree to ruby_parser because of ParseTree reaching end of life. That means Roodi should now work on Ruby 1.9.

Friday, May 8, 2009

RailsConf talks by rating

I did my talk at RailsConf today, which I thought went pretty well. I'm always keen to get some feedback though, so I slurped up the rating information from the RailsConf web site to see how I compared to the other speakers. I ended up 6th out of 61 talks, which I'm happy with. The Rails Envy guys were speaking at the same time as me and came second. I suspect that means I got less people attending and voting as a result. That might not have affected my position in the list though.

Here's the full list for those who are interested. This might still be changing over time as people review the sessions, but it should be reasonably indicative.

UPDATE: It's about seven hours since the conference finished now and some more reviews have come in. They must have been positive for me, because now I've moved up to second. I'll check again in a couple of days to see if anything else dribbles in.

UPDATE: It's a week later now, so I'm assuming the votes have stabilised. There's actually been quite a bit of movement. Here's the list from now. It may stil be changing, but I won't bother updating again. I've moved down to 6th, but I'm still amazed that I'm even that high.

14.65105Ryan SingerUI Fundamentals for Programmers
24.6425David BockModeling Workflow in Ruby and Rails
34.5639Matthew DeitersWhen to Tell Your Kids About Client Caching
44.5143Obie FernandezBlood, Sweat and Rails
54.5014Fernand GalianaR-House - Rails for Home Automation
64.4825Marty AndrewsAutomated Code Quality Checking In Ruby And Rails
74.4555Charles Nutter, Evan PhoenixWhat Makes Ruby Go: An Implementation Primer
84.4464Bryan HelmkampWebrat: Rails Acceptance Testing Evolved
94.4243Ben ScofieldAnd the Greatest of These Is ... Rack Support
104.3639Gregg Pollack, Jason SeiferRails: A Year of Innovation
114.3135Jake ScruggsUsing metric_fu to Make Your Rails Code Better
124.3116Michael BleighTwitter on Rails
134.3030Charles Nutter, Thomas EneboJRuby: State of the Art
144.2790Scott ChaconSmacking Git Around - Advanced Git Tricks
154.2458Ezra ZygmuntowiczRube Goldberg Contraptions, Building Scalable Decoupled Web Apps and Infrastructure with Ruby
164.1959Jim WeirichWriting Modular Applications
174.1469Larry Karnowski, Jason RudolphJavaScript Testing in Rails: Fast, Headless, In-Browser. Pick Any Three.
184.0824Ilya GrigorikArt of the Ruby Proxy for Scale, Performance, and Monitoring
194.0014Jeff DeanAdvanced Views with Erector
204.0010Scott RaymondConfessions of a PackRat
214.0010David Czarnecki, Ola Mork, Eric TorreyGuitar Hero®: Behind the Music
224.0010Matt WoodOrchestrating the Cloud
233.9845Ilya GrigorikBuilding a Mini-Google: High-Performance Computing in Ruby
243.9344Scott Penberthy, Michael Bryzek, Geir Magnusson Jr, Yonatan FeldmanThe Gilt Effect: Handling 1000 Shopping Cart Updates per second in Rails
253.9187Aslak HellesoyQuality Code with Cucumber
263.9111Daniel Lathrop, Eric Mill, Wynn NetherlandGov 2.0: Transparency, Collaboration, and Participation in Practice
273.8918Pat Maddox, BJ ClarkWorking Effectively with Legacy Rails Code
283.8540David A. BlackGetting to Know Ruby 1.9
293.8126Blythe DunhamIntegrating SMS Messaging with your Rails Application
303.7740Davis W. FrankI Rock, I Suck, I am - Jumpstart Your Journey to Agile
313.7427Neal Ford, Paul GrossRails in the Large:How We're Developing the Largest Rails Project in the World
323.7121Jonathan DahlFive Musical Patterns for Programmers
333.7117Jay PhillipsCall into your Ruby code! Writing voice-enabled apps in Ruby with Adhearsion
343.6670Alexander DymoAdvanced Performance Optimization of Rails Applications
353.6531Michael KoziarskiAre You Taking Things Too Far?
363.6543Ryan TomaykoHTTP's Best-Kept Secret: Caching
373.6414Brian HoganRails and Legacy Databases
383.6234Chris Wanstrath, Tom Preston-Werner, PJ Hyett, Scott Chacon, Jon MaddoxThe GitHub Panel
393.6118Mike SubelskyIt's Not Always Sunny In the Clouds: Lessons Learned
403.6010Greg BorensteinGiving Rails the Big 'F': Surviving Facebook Integration Unscarred
413.5937Noel RappinBelow and Beneath TDD: Test Last Development and Other Real-World Test Patterns
423.5777Adam WigginsRails Metal, Rack, and Sinatra
433.5657Paolo Negri%w(map reduce).first - A Tale About Rabbits, Latency, and Slim Crontabs
443.5659Matt AimonettiRails3: Step Off of the Golden Path
453.569Jeremy HinegardnerCrate: Packaging Standalone Ruby Applications
463.5480David ChelimskyDon't Mock Yourself Out
473.5263Yehuda Katz, Carl LercheThe Russian Doll Pattern: Mountable apps in Rails 3
483.5018Desi McAdam, Sarah Mei, Lori OlsonDiscussion Panel: Women In Rails
493.3869James AdamThe Even-Darker Art of Rails Engines
503.2959Ninh Bui, Hongli LaiScaling Rails
513.2711Wynn Netherland, Jim Mulholl, Bradley JoyceBuild an App, Start a Movement
523.147Jimmy SchementiIronRuby on Rails
533.0911John Woodell, Ryan BrownJRuby on Google App Engine
542.9225Erik KastnerInterfaces are Dumb (and that's a Very Good Thing)
552.9134Jason LaPortePWN Your Infrastructure: Behind Call of Duty: World at War
562.8723Rein HenrichsRails Is from Mars, Ruby Is from Venus
572.8318Ed Laczynski, Nathaniel BiblerBuilding a Video Portal in Rails - Or How the Teenage Mutant Ninja Turtles Started Streaming
582.7315Tony HillersonIntegrating Flex and Rails with RubyAMF
592.6929Nick Plante, Joe Fiorini, Ben Scofield, Chris Saylor, James GolickStarting Up Fast: Lessons from the Rails Rumble
602.5428Marc-Andre Cournoyer, Christian Neukirchen, Blake Mizerany, Ryan Tomayko, Adam Wiggins, James LindenbaumThe Future of Deployment: A Killer Panel
611.8746Kevin BarnesIn Praise of Non-Fixtured Data