Josh Thompson     about     archive     turing     office hours

Deliberate Practice in Programming with Avdi Grimm and the Rake gem

I’ve had the concept of Deliberate Practice stuck in my head for a while.

I want to improve at things (all the things!) in general, but writing and reading code, specifically. Writing and reading code is germane to my primary occupation (software developer) and drives most of my effectiveness on my team.

If there are two people, one who knows more about something, and one who knows less, but the person who knows less is learning new things faster than the other person, in short order, they’ll switch places.

This concept comes from John Ousterhout, and his talk at Stanford about how the slope is more important than y-intercept.

slope and intercept

I know how to write the code I know how to write; I don’t know how to write better code than that. So, I’m taking advantage of code written by people who are really good at what they do, and I’m modeling my code after them.

Deliberate Practice

I’m trying to apply the principles of deliberate practice to writing code.

Eric Anders coined the term Deliberate Practice, and it boils down to four components:

  1. You must be motivated to attend to the task and exert effort to improve your performance.
  2. The design of the task should take into account your pre-existing knowledge so that the task can be correctly understood after a brief period of instruction.
  3. You should receive immediate informative feedback and knowledge of results of your performance.
  4. You should repeatedly perform the same or similar tasks.

If this is curious to you, read Peak: Secrets from the New Science of Expertise.

Programming is tricky to parse apart and apply elements of deliberate practice.

Deliberate Practice applied to programming

The hardest part (in my opinion) is getting immediate informative feedback and repeating the same/similar tasks. Usually, while working, once I complete a task, I move on to the next one. I don’t get feedback on it, nor do I repeat it.

So, it’s difficult to exercise the “deliberate practice” muscles while working. I wanted to find an effective means of getting feedback and getting repetition.

So, I scheduled a rubber ducking session with Avdi Grimm, brought him this problem and told him some solutions I had.

My proposed solution was to find “good code” in an open-source gem that is well tested, delete the file, break the tests, and re-build the file until the tests pass.

He thought it was a great idea, so that’s what we did. We worked through the Rake gem, specifically the FileList class. (FileList on github)

Here’s some of what we worked on:

This was great, and I’ve been working through re-implementing this class, slowly, on my own time.

It’s a great way to encounter novel/new patterns for approaching problems. For example, this block of code has consumed a bit of time, as I’ve fully unpacked it and worked with the underlying concepts:

DELEGATING_METHODS.each do |sym|
  if SPECIAL_RETURN.include?(sym)
    ln = __LINE__ + 1
    class_eval %{
      def #{sym}(*args, &block)
        resolve
        result = @items.send(:#{sym}, *args, &block)
        self.class.new.import(result)
      end
    }, __FILE__, ln
  else
    ln = __LINE__ + 1
    class_eval %{
      def #{sym}(*args, &block)
        resolve
        result = @items.send(:#{sym}, *args, &block)
        result.object_id == @items.object_id ? self : result
      end
    }, __FILE__, ln
  end
end

We’ve got a few things happening:

  • Module#class_eval (docs)
  • Object#__LINE__ (docs)
  • Object#__FILE__ (docs)
  • blocks and Procs
  • Object#send (docs)

This single each block is walking me through metaprogramming, and other bits of ways of using Ruby that are currently ways I use it now.

This blog post, and subsequent posts, will be unpacking exactly what’s going on. I want to learn via deliberate practice, and I learn by documenting what I’m learning. Often I refer back to these posts later, so they serve as references and guides for future-Josh.

What does class_eval do?

Lets look at the first class_eval call:

ln = __LINE__ + 1
class_eval %{
  def #{sym}(*args, &block)
    resolve
    result = @items.send(:#{sym}, *args, &block)
    self.class.new.import(result)
  end
}, __FILE__, ln

We can see in the docs that class_eval can take three arguments (or a block, but we’ll get there later):

class_eval(string [, filename [, lineno]]) -> obj

__FILE__ and __LINE__

a quick hunt around and some Pry sessions reveals that __LINE__ and __FILE__ are to help give informative error messages, as they help the program find it’s current location in terms of file_name and line_number

__LINE__
=> 45
__FILE__
=> "(pry)"
File.expand_path(__FILE__)
=> "/Users/joshthompson/josh-works.github.io/(pry)"

So, with this information, you could have something printed like

/Users/joshthompson/josh-works.github.io/(pry):45

which looks suspiciously like how error messages are formatted in stack traces. Here’s an error I got yesterday:

[73861] ! Unable to load application: NoMethodError: undefined method `=~' for #<Pathname:0x00007f89cf9eb6e0>
/Users/joshthompson/.rvm/gems/ruby-2.4.4/gems/rails-dev-boost-0.3.0/lib/rails_development_boost/dependencies_patch.rb:109:in `load_path_to_real_path': undefined method `=~' for #<Pathname:0x00007f89cf9eb6e0> (NoMethodError)
	from /Users/joshthompson/.rvm/gems/ruby-2.4.4/gems/rails-dev-boost-0.3.0/lib/rails_development_boost/loadable_patch.rb:8:in `load'

I have no doubt this stack trace makes great use of File.expand_path(__FILE__) and __LINE__ to build up these errors.

If you’re curious, I clobbered an ugly hotfix in like so:

# /Users/joshthompson/.rvm/gems/ruby-2.4.4/gems/rails-dev-boost-0.3.0/lib/rails_development_boost/dependencies_patch.rb:105
def load_path_to_real_path(path)
  # below line added by Josh, it's 💩, remove ASAP
  path = path.to_s if path.class == Pathname
  expanded_path = File.expand_path(path)
  expanded_path << '.rb' unless path =~ /\.r(?:b|ake)\Z/
  expanded_path
end

So, with the __FILE__ and __LINE__ arguments now understood, back to the class_eval method’s first argument, string.

class_eval(string, filename, line_number)

I worked through this post about class_eval, and worked with the docs on the same.

It’s reasonably straight forward. We’re just writing a new method definition, that is created at run-time.

If I stick a pry in the block, and then just copy-paste the contents within the %{}, I can see what is happening:

Note the pry, and what I copied-pasted:

class_eval

It looks a bit difficult to read at first:

class_eval_string

but when we puts foo, it cleans right up:

class_eval_puts

So, class_eval simply lets us define new classes at runtime.

In this particular block, sym was equal to &; if we continue through the list a few more times, we can find a class_eval with sym equal to collect. What do you think the method definition there will be?

If you guessed def collect, you’re following how this works!

class_eval_puts

So, this last evaluation of class_eval results in a method like so:

def collect(*args, &block)
  resolve
  result = @items.send(:collect, *args, &block)
  self.class.new.import(result)
end

&block, Blocks

That &block, I see everywhere.

Cracking open my copy of Metaprogramming Ruby 2: Program Like the Ruby Pros (Facets of Ruby), I took my first pass through this topic a while ago, during which I wrote Blocks and Closures in Ruby.

Paolo Perrotta describes the & operator, in the context of blocks:

A block is like an additional, anonymous argument to a method. In most cases, you execute the block right there in the method, using yield. In two cases, yield is not enough:

  • You want to pass the block to another method (or even another block)
  • You want to convert the block to a Proc

In both cases, you need to point at the block and say, “I want to use this block” –to do that, you need a name. To attach a binding to the block, you can add one special argument to the method. This argument must be the last in the list of arguments and prefixed by an & sign.

& is shorthand for calling to_proc on whatever it’s bound to, I think.

OK, I’m not going to get too bogged down in this code just yet. I’ve got enough of an understanding that I can move forward, for now, on making this classes’ test pass.

More to come soon.

Get occasional emails

If I've written anything new, you'll get an email with summaries on Friday.