Refactoring in Ruby: An Interview with Bill Wake and Kevin Rutherford
Russ Olsen is the author of Design Patterns in Ruby.
Russ Olsen: First let me congratulate the two of you on the publication of your book, Refactoring in Ruby. Refactoring is one of those-two sided software engineering terms: It has a very specific technical meaning, but people also tend to use it informally as a synonym for 'rewrite'. So help me understand the difference: What's the difference between refactoring code and simply rewriting it?
Kevin Rutherford: Refactoring involves carrying out a series of numerous small well-understood steps, each of which leaves the code's behavior unchanged. So refactoring is like crossing a river using stepping-stones: you can go a long way one step at a time, you can turn back and re-trace your steps at any time, and you never get wet.
Bill Wake: It can be a question of scale. If you're returning the system to stability every few minutes, you may be refactoring. If the system is torn apart for days, weeks, months, you're not refactoring. If the system has new features, you may have been refactoring along the way, but there was more than just that going on.
There's a lot of value in knowing your system performs the same before and after; watering down the term loses a useful distinction.
Russ: How is refactoring different in Ruby than in one of the more traditional languages like Java? Are there particular refactorings that are more difficult? Easier?
Kevin: The mechanics of a particular refactoring in Java, say, can make use of the safety provided by compile-time checks; indeed some of the steps in Martin Fowler's original Refactoring book use compiler errors as a to-do list. But in Ruby there's no static checking, so one degree of safety is missing. For that reason, it's even more essential to have very good test coverage when refactoring Ruby.
Another interesting aspect of dynamic languages has to do with code smells. In a language like Java the code is littered with type clues, which hint at overuse of primitives, for example. These clues aren't available in Ruby, and it can therefore be much harder to carry out enough static analysis to determine whether a given code smell is present.
Bill: A language as dynamic as Ruby does have challenges for tool providers. There will be situations where the tool is making a weaker promise than it would in an equivalent Java situation. (It's not hopeless; refactoring tools originated in Smalltalk, another dynamic language facing similar issues.)
Russ: There are a lot of features of Ruby that simply do not exist in other widely used programming languages—things like open classes and metaprogramming. Did you come across any new refactorings, ones that are specific to Ruby?
Bill: Our focus is on smells more than the mechanics of refactorings. (You can see Fields et al for those.) We described some new smells, notably Dynamic Code Generation and Procedural Code, specifically for Ruby's more unusual features.
A lot of code has more workaday issues before you get to these more exotic aspects of Ruby.
Kevin: We discovered that the details of many code smells differ depending on language features, although the overall forces of Good Design (communicates intent, contains no duplication, contains no unnecessary stuff) remain constant. For example, Fowler's original listing of code smells includes Incomplete Library Class; we've expanded this to Incomplete Library Module and included some discussion of when it is safe to open up a module that someone else wrote.
Russ: So if refactoring is all about chasing out code smells, how do you know when enough is enough? When do you stop refactoring?
Bill: Sometimes you get that sense of "ah" when things click into place.
Most times, I'm refactoring in the context of developing something, and I try to pay attention to what's "on track" for that. I'm happy to refactor the code I'm touching, or the code it touches, but I watch out when I go beyond that. (Cleanup may be needed there, but I only want to go there mindfully.)
Kevin: My guiding light is Kent Beck's rules of Simple Design: The code must first be correct (as defined by tests); then it should be a clear statement of the design (what J.B.Rainsberger calls "no bad names"); then it should contain no duplication (of text, of ideas, or of responsibility); and finally it must be the smallest code that meets all of the above. It's time to stop refactoring when the code makes a reasonable stab at meeting those goals, and when any further changes would add no further benefit. There's a lot more on this topic in Chapter 2 of our book.
Russ: If you each could change one thing in Ruby, something that would make refactoring easier or otherwise improve the language, what would that be?
Kevin: I'd like to see more work done towards some kind of standardized abstract syntax tree for parsed Ruby code, to help make it easier to write refactoring tools, style checkers and other code analysis tools. My other major gripe is the relative inconsistency of parts of the core API (why doesn't Dir return an array of File objects, for example?); rough edges like that mean we all write the same code to compensate, which in turn makes refactoring somewhat harder..
Bill: Ruby libraries tend to take a "union" approach to adding terms. "Length or size? Let's add both. Map or collect? Let's add both." This may be helpful for some adopters, but it creates APIs that are wider than necessary, and it makes it harder to spot duplication textually.
Russ: It's hard to say the word refactoring without also saying 'agile'. Do you think that software development in general is really becoming more agile, or is it something we are just talking about without really acting on?
Bill: It's hard for me as an individual to get a read on it. I'm a consultant for Industrial Logic, so most people I work with either are agile or want to be. Techniques such as refactoring, continuous integration, and TDD are penetrating into more places, but we have a long way to go, and those aren't the whole of agile or what it offers. I see more interest in craft, XP-style approaches, and learning; that's good for the long run.
Kevin: As an industry, we definitely know more now about how to develop "good" software than we did fifteen or twenty years ago, both in terms of technical practices and organizationally. But I still find that TDD and "simple design" are widely misunderstood; and far too few organizations involve a coach to help them improve.
Russ: Kevin, you are the lead developer for reek, the popular tool for detecting code smells in Ruby programs. Did you learn anything in the process of writing the book that made you rethink something you did in reek?
Kevin: I actually did it for the opposite reason: The heart of the book is Part II, in which we examine over forty code smells in great detail, looking at how to detect them, how they might arise in real code, what problems they each cause, how to fix them, and what might happen to code when the smell is removed. I began writing reek because I wanted to look at how easy or hard it is to detect certain code smells in Ruby, so that we could provide clear guidance to our readers. I learned a lot about what can happen when you push code to the limit, eradicating smells that few human eyes (noses?) detect; much of that experience is reflected in the descriptions of the smells and in the book's exercises -- and much of it involves appreciation of the varying effects of change on different parts of the code.
Russ: As someone who can barely stand myself when I am writing, I am curious as to how you worked out the process of writing a book together. Were there lessons from developing software in groups that you could apply to your joint writing project? Did you consciously refactor the book?
Kevin: We had Bill's excellent Java Refactoring Workbook as a starting-point, so in a sense we were refactoring an existing book. We converted the text to LaTeX, broke it into sections and stored it in over a hundred text files in a Subversion repository, so that we could both work on different sections without conflicting. During the most intense few months we had weekly stand-up meetings over Skype (amazingly, Bill and I have never met). I think it's a great tribute to Bill's tolerance and skill as a coach that he managed to work calmly and for long periods with someone so intense, driven and opinionated as me!
Bill: Kevin's definitely too kind; you can see why he was easy to work with.
I think we were closer to rewriting than refactoring--we had some new things to say! We had some global changes to make; we just divided them up. We tended to have temporary ownership by chapter; one would own it, the other would polish it after it was stable.
Subversion was a win, as was being able to build the book when we wanted. LaTeX was OK (except for tables!) but I'm open to better tools for shared writing.
Russ: What do you guys do when you are not off writing books?
Bill: I'm a consultant, as I mentioned. Outside of work, I've recently taken up mountain dulcimer. It'll be a while before I perform in public, but I'm having fun learning. I've always got little software or writing projects going, and my web site (xp123.com).
Kevin: I work full-time as a freelance agile coach (www.kevinrutherford.co.uk), and I have three young kids who occupy the remainder of my time.
Russ: So with the book complete, what's next?
Kevin: I have a pact with my wife; first I got to obsess over a book for two years, now it's her turn. Donna begins work on her Ph.D. in Medical Sociology this month, so I can have fun being a proper dad and home-builder again. (Don't tell her, but I also plan to do some more work on Ruby quality tools, and maybe some writing on certain aspects of "simple design".)
Bill: "Analysis" has been on my mind a lot; I'd like to write something about that. I'm also interested in writing something outside the software arena; I'm still chasing ideas there.