On discipline

I threw out a couple days worth of code a couple weeks ago.

I read somewhere that knowing that you can throw out code and start over should be uplifting and exhilarating. It's not. When I threw it out the topic branch in my local git repository, I was annoyed, I was disgusted, I was unhappy. Perhaps that has to do with why I threw it out.

I have this project I'm working on in my spare time. As I've mentioned before, I'm trying out BDD with RSpec and cucumber. And I came to a point that I realized that most of what I was doing was not behavior-driven. It wasn't even adhering to the YAGNI principle. I had a possible case, one that may not arise, and I was writing to try to meet it without a test case. I had realized I was doing this early on and instead of stopping then, I just said that I would run it through rcov and add tests later.

Sometimes I am very, very stupid.

The problem with BDD or TDD or any new way of doing things is that it requires discipline to properly follow them until you get in the habit of doing them. Once you're in the habit, it's not so hard but you have to get there first. Until then, you have to face your urges to use the old method, to take the quick way you've already learned, and deny them. It requires discipline.

And discipline, at least for me, is hard.

I will be trying again on this particular feature tonight or tomorrow night. Hopefully I will have the discipline this time.

A sign in git tag's documentation

I was reading the git tag and found this bit:

But if you have pushed things out (or others could just read your repository directly), then others will have already seen the old tag. In that case you can do one of two things:

  1. The sane thing. Just admit you screwed up, and use a different name. Others have already seen one tag-name, and if you keep the same name, you may be in the situation that two people both have "version X", but they actually have different "X"'s. So just call it "X.1" and be done with it.
  2. The insane thing. You really want to call the new version "X" too, even though others have already seen the old one. So just use git-tag -f again, as if you hadn't already published the old one.

I find it surprising that this has to be said in the documentation. The "sane" method is, to me, clearly the most obvious one. However, as Joel Spolsky once wrote of a "No Dogs Allowed" sign at a restaurant: "The real reason that sign is there is historical: it is a historical marker that indicates that people used to try to bring their dogs into the restaurant." So I suppose enough people have used or wanted to use the "insane" method for it to be mentioned.

When rake spec doesn't work

I mentioned over a week ago that rake rspec wasn't working for me. After looking around for a bit today, I found a solution.

Something in the RSpec chain (maybe RSpec itself) requires version 1.2.3 of the test-unit gem. However, gem install test-unit will install version 2.0.3 by default. You can have both versions of the gem installed. However, having both versions causes the rake task to not work properly. Removing the 2.0.3 version of the gem corrects the issue and the rake task will work normally.

Array#compact vs. checking for nil manually

In the prototype code for a project, I wrote something like:

  1. items << item if item

As is, it's not an ugly line. However, it's inside a loop that creates an object based on each line in a text file. So, to provide context:

  1. items = []
  3. File.open( filename, 'r' ) do |file|
  4.   file.each_lines do |line|
  5.     item = build_item( line.strip )
  6.     items << item if item
  7.   end
  8. end

So if the line contains one million lines, it runs that statement one million times. (As to why I'm testing, build_item may return nil if the line matches a given set of criteria.)

Array#compact returns a copy of the array with all nil elements removed from the array. (Array#compact! removes all nil elements from the current array.) Removing the if condition from the loop and adding items.compact! after the File.open block should do the same job as what I originally wrote.

To test, I wrote this program:

  1. require 'benchmark'
  3. test_array1 = Array.new
  5. 10_000_000.times do
  6.   test_array1 << "Foo"
  7.   test_array1 << nil
  8. end
  10. test_array2 = Array.new( test_array1 )
  11. test_array3 = Array.new( test_array1 )
  13. new_array = Array.new
  15. Benchmark.bm do |x|
  16.   x.report( '#compact' ) { test_array1.compact }
  17.   x.report( '#compact!' ) { test_array2.compact! }
  18.   x.report( '#delete' ) { test_array3.delete( nil ) }
  19.   x.report( '#each' ) do
  20.     test_array1.each do |item|
  21.       new_array << item if item
  22.     end
  23.   end
  24. end

Running this under Ruby 1.9.1 (p243) returned these results:

      user     system      total        real
#compact  0.240000   0.100000   0.340000 (  0.336496)
#compact!  1.100000   0.120000   1.220000 (  1.218401)
#delete  3.140000   0.070000   3.210000 (  3.213278)
#each  3.130000   0.050000   3.180000 (  4.720402)

(For the curious, twenty runs of the same script returned similar numbers.)

The numbers indicate that Array#compact is significantly faster than the original code. Whether or not this optimization is needed is another matter entirely. Even for an array of twenty million elements, the difference in time is about four seconds. I suppose the difference between the two should come down to which is easier to read and understand.

I also decided to test Array#delete since items.delete( nil ) should be equivalent to item.compact!. Array#compact!, somewhat unsurprisingly, is faster although only by about two seconds. I'm not sure why Array#compact! should be almost a second slower than Array#compact.

First Experiences with BDD, Cucumber, And RSpec

I've restarted the project I have been working on. The prototype code worked well enough but I don't think the code is in good shape. I didn't add any testing to the prototype and this started to cause significant problems.

I don't know when or where I first heard about RSpec or behavior-driven development. However, when I found out that a book on RSpec, the aptly titled The RSpec Book, was being published, I pre-ordered it. I have been reading the beta PDF and decided to apply it to the rewrite.

Like any new programming methodology, it takes a little bit of getting used to. It took me a few hours last night to implement a single action on a controller but I'm certain this will improve over time. (I find also that switching between the PDF and TextMate on my laptop costs time too. This is definitely a time for which having a second monitor would be useful.)

I find BDD interesting because it forces a different way of programming than I'm used to. I spend a lot of time working on the model and seem to get around to the controllers and views near the end (if ever). Instead, when writing the feature for cucumber, I have to establish what behavior should occur for the user and then make sure that the controller and view gets written for that behavior. I obviously lack the experience to make an informed decision on whether or not BDD is a better software development methodology than TDD or other testing-enhanced processes. (Any process that involves testing is, in my opinion, significantly better than any that does not.)

One omission I found in the book (or maybe I haven't looked in the right place) is that there seems to be no discussion about spec'ing routes. RSpec does have methods for it. In discussion on the rspec-users mailing list, David Chelimsky provided a link to some examples so some documentation does exist. (I've left a note in the book errata so we'll see what happens.)

An issue I found working with RSpec is that ruby script/spec spec/ works but rake rspec does not. This seems to be because the rake task is not Rails-aware while script/spec is. I haven't done much more research into it though. Since the first command works, I'll continue to use it.


Subscribe to Ithiriel RSS