Rubies in the Rough

This is where I try to teach how I think about programming.
  • 1

    DEC
    2011

    Dreamy Testing (Part 2)

    In Part 1 of this article, I began building out my ideal testing interface, or at least my best attempt at such a thing.

    In that article, I worked primarily on the "assertion" interface: a file full of calls to ok() with a block that returns true or false to pass or fail tests. I also built some standard test printers to show us familiar output.

    As I wrapped up, I was running this code in example/basic_test.rb:

    ok("Is true")  { true        }
    ok("Is false") { false       }
    ok("Is error") { fail "Oops" }
    

    and seeing these results:

    $ ruby -I lib -r ok example/basic_test.rb 
    Running tests:
    .FE
    
    0) Failure: Is false
      example/basic_test.rb:2:in `<main>'
    1) Error: Is error
      example/basic_test.rb:3:in `block in <main>'
      example/basic_test.rb:3:in `<main>'
    
    Finished tests in 0.000300s
    3 tests, 1 failure, 1 error
    

    Of course, there was still a lot missing in my code. Let's work on adding some of the other must have features and perhaps a nicety or two.

    Running Tests

    In the first article, I spent a lot of time talking about how all of the references to things other than my code in tests are a distraction. I wanted to remove as much of that as possible. We have done pretty well on that front.

    Read more…

  • 21

    NOV
    2011

    Dreamy Testing (Part 1)

    I want to take a swing at one last rule before I wrap up this Breaking All of the Rules miniseries, at least for now. I'm not the type of guy to come out full on against many things and I won't do that here. But there is one rule I think is on pretty shaky ground for how often I hear it thrown about. Let's analyze it and break it.

    Don't Reinvent the Wheel

    It should be pretty thoroughly drilled into most programmer's minds that we don't want to waste our time reinventing wheels. Well, let's try to find the why behind that before we accept it as law.

    First, what's the not-so-hidden assumption this time? It's that we are wasting our time. If we aren't, should the rule still hold?

    As always, there are good reasons that this rule exists. Here are a couple I feel are worth honoring:

    • When you are in the middle of a job and you figure out that you need something, it's usually a much better idea to go with an existing, ready-to-use solution. It would take you time to rebuild it and your version isn't likely to be as robust (just due to it being newer).
    • If there's an existing solution that is 90% of what you need, it's probably better to contribute the other 10% than to separately build a new 100% solution. Contributing should be faster for you and help others in return.

    Read more…

  • 11

    NOV
    2011

    Doing it Wrong

    Continuing with my Breaking All of the Rules series, I want to peek into several little areas where I've been caught doing the wrong thing. I'm a rule breaker and I'm determined to take someone down with me!

    My Forbidden Parser

    In one application, I work with an API that hands me very simple data like this:

    <emails>
      <email>user1@example.com</email>
      <email>user2@example.com</email>
      <email>user3@example.com</email></emails>
    

    Now I need to make a dirty confession: I parsed this with a Regular Expression.

    I know, I know. We should never parse HTML or XML with a Regular Expression. If you don't believe me, just take a moment to actually read that response. Yikes!

    Oh and you shouldn't validate emails with a Regular Expression. Oops. We're talking about at least two violations here.

    But it gets worse.

    You may be think I rolled a little parser based on Regular Expressions. That might look like this:

    #!/usr/bin/env ruby -w
    
    require "strscan"
    
    class EmailParser
      def initialize(data)
        @scanner = StringScanner.new(data)
      end
    
      def parse(&block)
        parse_emails(&block)
      end
    
      private
    
      def parse_emails(&block)
        @scanner.scan(%r{\s*<emails>\s*}) or fail "Failed to match list start"
        loop do
          parse_email(&block) or break
        end
        @scanner.scan(%r{\s*</emails>}) or fail "Failed to match list end"
      end
    
      def parse_email(&block)
        if @scanner.scan(%r{<email>\s*})
          if email = @scanner.scan_until(%r{</email>\s*})
            block[email.strip[0..-9].strip]
            return true
          else
            fail "Failed to match email end"
          end
        end
        false
      end
    end
    
    EmailParser.new(ARGF.read).parse do |email|
      puts email
    end
    

    Read more…

  • 1

    NOV
    2011

    The Wrong Tool for the Job

    I want to start our exploration of how to think about Ruby programming with a miniseries called Breaking All of the Rules. As I'm sure you know, programmers have a lot of rules. You can barely speak to a programmer for a few minutes without them quoting some axiom. We have a huge collection of advice to hand out.

    Fortunately, I'm not just a programmer. I'm also a tournament chess player. Getting good at chess has really helped my programming. That's because chess players also have a ton of rules.

    Perhaps you've seen our books of opening chess moves? They are literally hundred of pages that just list the various moves that you can "start" a chess game with. I use the word start very loosely there because some combinations can go 20 moves into the game or more. Chess games generally only average about 60 moves, so the first third of what we do is often straight out of a book. In fact, while you are playing a chess opening, we say that you are "in book." That means you are following the rules.

    Read more…