Information Technology Dark Side

Struggles of a Self-Taught Coder

Information Technology Dark Side header image 2

Functionally repetitive code is best served DRY. Coincidentally repetitive code is best served simple.

August 20th, 2014 · 2 Comments

I’m starting to realize there is a difference between code that is functionally repetitive and code that is coincidentally repetitive.

Here’s an example of some functionally repetitive code:

class Admin::CoursesController < Admin::AdminController

  def index
    @courses = Course.all
  end

  def show
    @course = Course.find(params[:id])
  end

  def engagement
    @course = Course.find(params[:id])
  end

  def new
    @course = Course.new
  end
  ...
end

Functionally repetitive code is repetitive because of the functional nature of the activity being coded – for instance, finding a resource on a controller in all the RUD actions. That’s functionally repetitive. “Find the course” always means the same thing here.

Which is why that controller is better like this:

class Admin::CoursesController < Admin::AdminController
  before_filter :find_course
  
  def index
    @courses = Course.all
  end

  def show

  end

  def engagement

  end

  def new
    @course = Course.new
  end

  ...
  protected

  def find_course
    unless params[:id].blank?
      @course = Course.find(params[:id])
    end
  end
end

Drying this code up makes things simpler only because “find the course” always means the same thing at a functional level.

Not all repetitive code is functionally repetitive though. This is especially true in view code for me in TroopTrack where I have lots of conditional code based on the type of organization the user is in. I end up with lots of code like this haml partial:

- if @troop.is_tlu_troop?
  %li= link_to 'Advancement Instructions', '/assets/Advancement_Instructions_20140801.pdf', target: '_blank'
%li= link_to "#{achieve_title} Overview", achieve_root_path
- if @can_edit_achievement_records || @can_view_achievement_records
  - unless @troop.is_troop? || @troop.is_venture_crew?
    %li= link_to 'Record Individual Progress', [:new, :achieve, :individual_progress]
  - if @troop.is_tlu_troop?
    %li= link_to 'Record Progress (Bulk)', [:new, :achieve, :bulk_achievement]
  - else
    %li= link_to "Record Progress (Bulk)", record_progress_achieve_workflows_path

  - if @troop.bsa?
    %li= link_to "Print Advancement Report", advancement_report_achieve_workflows_path
  - if @troop.bsa? && @can_use_turbonet
    %li= link_to "TurboNET Advancement Report", [:new, :achieve, :e_advancement_report], {'data-no-turbolink' => true}
    %li= link_to "TurboNET Roster Import", [:new, :achieve, :turbonet_import], {'data-no-turbolink' => true}
  - if @troop.is_ahg_troop? 
    %li= link_to "TurboNET Advancement Report", [:new, :achieve, :ahg_advancement_report]
    %li= link_to "TurboNET Hugs Reports", [:achieve, :hugs_reports]
  - if @troop.is_troop?
    %li= link_to "Print Blue Cards", blue_cards_achieve_workflows_path
  - if @troop.is_troop?
    %li= link_to 'Board of Review Worksheets', bulk_board_of_review_achieve_users_path
  - unless @troop.aidmatrix?
    - if @troop.is_tlu_troop?
      %li= link_to 'Purchase Completed Awards', [:new, :achieve, :award_order]
      %li= link_to 'Past Award Orders', [:achieve, :award_orders]
    - else
      %li= link_to 'Shopping List', shopping_list_achieve_workflows_path
    %li= link_to 'Print Award Cards', award_cards_achieve_workflows_path
    %li= link_to "Print Agenda by #{scout_title}", agenda_by_scout_achieve_workflows_path(:format => :pdf), :target => '_blank'
    %li= link_to "Print Agenda by #{subunit_name}", agenda_by_level_achieve_workflows_path(:format => :pdf), :target => '_blank'
    %li= link_to "Present Awards", present_awards_achieve_workflows_path

  - if @troop.is_troop?
    %li= link_to 'Hours, Nights, & Miles', trackables_achieve_users_path

I don’t like this code much at all. All those conditionals based on the type of troop we are looking at makes it hard to read and to think about, but the first alternative I think of involves creating a different partial for relevant type of troop. We support seven different types of troops in TroopTrack, so the idea of potentially having seven different partials with big swaths of them being the same made me cringe a bit at first. But the more I work on TroopTrack, which is now in its sixth year, the more palatable the idea has become. I now think it’s better than what you see above, especially if I introduce some conventions to make it easier, such as including the troop type in the partial file name, like this:
– achieve_menu.bsa_troop.html.haml
– achieve_menu.bsa_pack.html.haml
– achieve_menu.scouts_au.html.haml
– achieve_menu.ahg.html.haml

That makes the intent extremely clear, and I could use interpolation to render the right partial with a single line of code.

To me, that code may be repetitive, but it’s a lot easier to reason about and to work on. DRYing it up, at least in the way I originally did it, only makes it worse. I think the reason DRYing it up isn’t helpful is because this code is only repetitive by COINCIDENCE. By this, I mean that the features that are common are not common due to something functionally intrinsic about them, but just because they happen to share some common needs.

I mentioned this realization to @kofno and here is what he said. I like his perspective:

I think you’re having an important realization about the intent of DRY. It’ s not about repetitive code, but it’s about having a single canonical place for some piece of system information.
Whether that is how to render users, or what makes a valid course, etc.
Drying up things that change together is what’s important.
If you try to dry things that are orthogonal but only look similar, you’ll be miserable.
For removing repeating code patterns and getting rid of boilerplate, that’s what macros are for (provided your language supports them). Ruby kinda does w/ metaprogramming.

Like @kofno, you might have had a “well duh” reaction to all this. That’s cool. I’m learning…

If you enjoyed this post, make sure you subscribe to my RSS feed!
Stumble it!

Tags: Uncategorized

2 responses so far ↓

Leave a Comment