Simplifying Code



Simplifying Code

0 1


refactoring-workshop-slides-sep-2015

Slides for refactoring workshop

On Github tute / refactoring-workshop-slides-sep-2015

Simplifying Code

https://github.com/tute/refactoring-workshop

Tute Costa - @tutec

Refactoring Patterns

A Pattern is a general reusable solution to a reccurring problem. There's many ways to skin a cat, so how do we do X in this new application?   A Pattern is a description for how to solve a problem that can be used in many different situations. Patterns are peer-tested best practices. Refactoring is restructuring existing code without changing its behavior. Advantages are reduced complexity, more readable code, and easier to maintain. Also useful for creating an expressive architecture, so that our objects reflect objects in the real world, improving cross-teams communications. Refactoring Patterns, then, are...

Why?

Smaller code base

Simpler

Prettier

I'm very PASSIONATE about refactoring, I think it's POWERFUL in improving productivity and thus enjoying better daily work.

Code speaks by itself, it's suddenly better than Pivotal Tracker itself or any meeting.

1: Unreadable Code

# Probably a comment
if hash[row[1]][date] != row[0]
vs
if remove_duplicates?
  • Responsability of the code vs implementation details.
  • Comments show at least what was in our mind when we wrote it.
  • Comment are fragile: we edit the code and may forget to update the comments when we see it working. "Lies waiting to happen".
  • Code is like jokes, when you have to explain/comment it can lose it's charm.
  • Idea: Extract relevant concepts into methods with proper names.
  • Most relevant responsibilities should be at the top
  • Code now reads itself, which is the most beautiful thing we can achieve. The Ruby language takes pride on this: it does what you think it does.
  • Code grows with more lines, but also readability. It's arguably shorter to read, cause we don't really need to see how do we compute if there's duplicates (implementation, low-level detail).

Intention Revealing Method

Add comments when needed

Transform comments into methods

Comments are now code. Code describes itself.

Comments are now code Code describes itself

MASSIVE SUCCESS

Intention Revealing Method

It's arguably the easiest pattern.

But the hardest as well.

Seemingly the easiest pattern (simple syntactical transformations), but also the hardest because it implies naming And not only naming, but also deciding on the levels of abstraction of the code: what counts as your app's responsability and what as mere implementation details? How do we model the objects of the real world into running OO software? Apply changes, see before and after, does it look better? Keep it. Does it not? Ignore it, you anyway learned something about your code!

1. Unreadable Code -> Intention Revealing Method Turns comments unnecessary. Code readsdescribes itself.

-> 2. Undefined Method on nil -> Null ObjectsAvoids nil objects and associated conditionals

3. Ginormous Method -> Replace it with Method Object Refactor big methods at ease in a clean new container

Questions time!

2: Ohai, nil!

Undefined method `name' for nil
session[:current_user]     # => nil
session[:current_dinozauh] # => nil
if false then 1 end        # => nil
empty_method()             # => nil
Where did this nil come from in my whole code base? From a blog post? From a user? From a car or whatever? Hash with `nil` as value for the key Hash with a key that doesn't exist An if that evaluates to false but there's no else branch An empty method

2: Ohai, nil!

A symbol is better than nil:

def current_user
  User.find_by(id: id) || :guest_user
end
current_user.email
undefined method `email' for :guest_user:Symbol Now we can find in project that symbol and trace the unexpected nil.

2: ifs everywhere

If potentially nil, got to surround with an if

if current_user
  "Hi #{current_user.name}!"
else
  "Hi guest!"
end
Do you know your name or do I need to specify what's the "name" for nil? Easy to forget them. The logic of what does it mean an object is nil is scattered accross the entire application.

Null Objects

Instead of nil, return a new object

class NullUser
  def name
    'guest'
  end
end
def current_user
  User.find_by_id(id) || NullUser.new
end
"Ohai, #{current_user.name}!"

1. Unreadable Code -> Intention Revealing Method Turns comments unnecessary. Code readsdescribes itself.

2. Undefined Method on nil -> Null ObjectsAvoids nil objects and associated conditionals

-> 3. Ginormous Method -> Replace it with Method Object Refactor big methods at ease in a clean new container

Questions time!

3: Ginormous Method

class ExportJob
  # Instance variables
  # Many other methods
  # And...
  def row_per_day_format(file_name)
    file = File.open file_name, 'r:ISO-8859-1'
    # hash[NivelConsistencia][date] = [[value, status]]
    hash = { '1' => {}, '2' => {} }
    dates = []
    str = ''

    CSV.parse(file, col_sep: ';').each do |row|
      next if row.empty?
      next if row[0] =~ /^\/\//
      date = Date.parse(row[2])
      (13..43).each do |i|
        measurement_date = date + (i-13)

        # If NumDiasDeChuva is empty it means no data
        value  = row[7].nil? ? -99.9 : row[i]
        status = row[i + 31]
        hash_value = [value, status]

        dates << measurement_date
        hash[row[1]][measurement_date] = hash_value
      end
    end

    dates.uniq.each do |date|
      if !hash['1'][date].nil? && hash['2'][date].nil?
        # Only 'bruto' (good)
        value = hash['1'][date]
        str << "#{date}\t#{value[0]}\t#{value[1]}\n"
      elsif hash['1'][date].nil? && !hash['2'][date].nil?
        # Only 'consistido' (kind of good)
        value = hash['2'][date]
        str << "#{date}\t#{value[0]}\t#{value[1]}\n"
      else
        # 'bruto' y 'consistido' (has new and old data)
        old_value = hash['1'][date]
        new_value = hash['2'][date]
        str << "#{date}\t#{new_value[0]}\t#{old_value[1]}\t#{old_value[0]}\n"
      end
    end

    str
  end
"Where to even start" anxiety. The method is itself like a whole class with variables, code, different pieces of logic. Any change has repercusions both for the method and for its containing class, which was also big. An instance variable in this method is also used in others. I can't even ...! Refactoring a big method is hard because there's coupling to behavior in the same class, changes here break something there and you have to refactor it as well. Extracting Intention Revealing Methods might require too much state to be pass around in arguments There's too much context, we have to isolate the long method so that any change we make affects only that method, and nothing else.

Extract Method Object

Create a class with same arguments as BIG method

Copy & Paste the method's body in the new class

Replace original method with a call to the new class

Apply "Intention Revealing Method" to new class. Voilà.

Let's work!

Therapeutic Refactoring

Katrina Owens

http://confreaks.com/videos/1071-cascadiaruby2012-therapeutic-refactoring

1. Unreadable Code -> Intention Revealing Method Turns comments unnecessary. Code readsdescribes itself.

2. Undefined Method on nil -> Null ObjectsAvoids nil objects and associated conditionals

3. Ginormous Method -> Replace it with Method Object Refactor big methods at ease in a clean new container

Questions time!

Next Steps: "4 rules"

Classes of at most 100 lines of code

Methods of at most 5 lines of code

A method accepts at most 4 arguments

A controller instantiates only one object

They are simple to explain and grasp, and encode many best practices.

There's dozens of refactoring and design patterns, there's many different software-related metrics.

No need to be an erudite: follow the four rules as they lead to green pastures.

Questions time!

Simplifying Code https://github.com/tute/refactoring-workshop Tute Costa - @tutec