Recently by Andrew Wagner
Consider this seemingly innocuous bit of code, presumably on a User class somewhere:
def manager
manager = User.ceo
User.all_managers.each do |manager|
return manager if self.reports_to?(manager)
end
return manager
end
There are certainly other ways to write this code, but the idea is straightforward enough. This is a pretty small company, and if there are no manager-level employees to whom the user reports, then they report directly to the CEO.
However, as you might have guessed, it's not quite that simple. In fact, in ruby 1.8x, it will always return one of the managers, and never the CEO. The "manager" variable in the block overwrites the variable defined previously. Note, however, that both variables, in some sense, shadow the method name, yet that doesn't come into play at all.
Now in ruby 1.9.2, all is well. The "manager" variable in the block shadows the previous one, but doesn't interact with it. And, to boot, it warns you about what you're doing, if you turn warnings on which of course you should.
However, this unfortunately doesn't help you in the slightest in ruby 1.8. And, in fact, it's worse than that. Here's a more complete implementation of the class, with some tests. These tests pass. Not only that, but they show 100% code coverage! This is why you cannot trust code coverage in and of itself.
So, this is a simple snippet, which behaves in mysteriously different ways between 1.8 and 1.9, gives you no warning in 1.8, and even deceives your code coverage numbers. Here be dragons!
- RSpec - I'm using RSpec to specify the behavior of my code. This gives me tremendous flexibility, and it's one of my favorite things about programming in ruby.
- BDD/TDD - The classic RSpec book talks about the process of going back and forth between acceptance tests and unit-level tests. I'm not going all the way to cucumber right now, but I am focusing on writing specs (using rspec) at both levels of abstraction.
- Code coverage - I'm using the simplecov gem to provide code coverage reports. I've hooked it up to rspec so that when I run my tests, it gives me a code coverage percentage right after the passed/pending/failed count, and also generates a more detailed HTML report if I need it.
- Static analysis - Yet another great gem, called Reek. This gem allows me to write a test which simply states that my code "should_not reek". A nice goal to strive for! If my code doesn't pass muster, my tests fail, and I'm forced to fix it before I move on. I've also hooked up flog, flay, and roodi, (this blog entry was a big help in setting them all up). Of these, flog seems to work the best.
- Write a high-level specification for a new feature.
- Write a (failing) unit test that moves the codebase in the direction of passing the acceptance test.
- Make the unit test pass.
- Fix any static code analysis issues while keeping the unit tests passing
- If the acceptance test from #1 is still failing, I return to number 2.
- "Programming is a social exercise" - I thought this was a really good point. It was mentioned in the context of pair-programming, but I think it has far-reaching implications. Software development is more than just running through a bunch of formulas and crunching out an answer. Interaction within the team and with domain experts is crucial, to not only build the software right, but build the right software.
- "A refactoring is something that takes a few seconds, or a few minutes at most" - I was really impressed with the importance of automated refactorings in his discussions. I think most of the time that I spend "refactoring" is in small, manual edits, whereas most of the time that he spends is in using automated refactoring to "chunk" his edits. I definitely need to learn these keystrokes and refactorings better. Maybe I should start a "Refactoring Driven Development" movement...
- "Don't put refactoring on the schedule; do it all the time" - Simple, but effective. My tendency is to want to spend all my time refactoring, but this curbs that, because it forces me to refactor while I'm delivering user stories.
- "There are 3 essential design skills: nose, vision, and plan" - A nose for recognizing design smells, a vision for seeing a good design for your codebase, and an ability to come up with a plan to get from point A to point B.
- "Testing trumps good design" - This bothered me at first, but I think it's a really good point. The idea here is not to say that design is not important. But, rather, if you are forced to choose between between a "bad" design that allows better test coverage (e.g., less encapsulation), and "good" design which is hard to test, choose testability. The reason here is that the biggest roadblock to changing your codebase is not bad design, but FEAR of breaking something. If you know you will know when you've broken something, then you can retrofit a better design later.
- "There is self-worth tied up in "finishing" something" - He also drew a distinction between having something working (which some developers will call "finishing" it), and finishing it - making it not only work, but be thoroughly tested, maintainable, etc.
- Presentation layer - he talked about having the thinnest possible UI layer, which talks to a presentation layer to find out everything about how it should render. Then test the UI and business logic completely separately
- "You aren't doing agile development unless you are tracking your velocity and remaining story points in terms of passing, automated acceptance tests" - I balked at this at first, feeling like it was too easy to use this as a performance to beat the team over the head with. After I thought about it, though, it's really about the definition of done. We are done if all the features we said would be working are working. And how can we know this? Only by testing them. Continuously. Which means it should be automated.
- Acceptance tests don't need to be end-to-end, and, in fact, shouldn't be. This is another one that made me hesitate. After all, how do you know the feature is really working unless you go all the way from the UI to the database and back? Well, in short, because you're the developer. There's more value in being able to test features fast, constantly, than in being able to truly test them all the way from one end to the other. Mock/stub out what you need to to make that happen.
- FitNesse is cool. This is the second time I've played around with that tool, and the second time I've been impressed with its power and simplicity. I definitely need to play around with it some more.

