Deliberate Practice in Programming with Avdi Grimm and the Rake gem
Article Table of Contents
- Deliberate Practice
- Deliberate Practice applied to programming
__FILE__
and__LINE__
class_eval(string, filename, line_number)
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.
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:
- You must be motivated to attend to the task and exert effort to improve your performance.
- 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.
- You should receive immediate informative feedback and knowledge of results of your performance.
- 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:
In a recent Rubber Duck session, @josh_works proposed the novel idea of practicing code skills by deleting a class from an open-source codebase and re-implementing it using the tests as a guide. He generously agreed to let me share the video! https://t.co/4HQNrtsHnU
— Avdi Grimm (@avdi) May 14, 2019
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:
It looks a bit difficult to read at first:
but when we puts foo
, it cleans right up:
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!
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.