One of the programming projects I both love and dread is refactoring. I dread it because some of the code I have to refactor is amazingly old, dense, uncommented, undocumented and really hairy. But it's also the most satisfying feeling in the world to take code like that and turn it into something much better.
For the past few weeks, most of my work life has consisted of refactoring in some way, shape, or form. I have one major refactoring project that I am working on, as well as some smaller "clean up" jobs. As I get burnt out with the one task, I can easily switch to the other and feel like I'm making major head way. It's a very nice symbiosis, if you ask me.
I have a few different "tricks" I use when refactoring, mostly just letting the compiler do the hard work for me. For instance, if I want to see whether a particular class property is no longer being used, I can delete it, compile and see if anything breaks. If everything continues to compile, then my refactoring is done! If something does break, my next task is to undo the deletion, and set the scope to private. Compile again -- did anything break? Yes? Set it to Protected and try again. You get the picture -- it's basically a way to clean up some nasty encapsulation issues, or code cruft. I use these same tricks whether in REALbasic, or in C++ -- they're a general tactic.
Search and replace is another great refactoring tool, but one you have to be somewhat careful of. For instance, we had a class in our hierarchy called ContainerPane, but over the years, its functionality was gradually moved to other classes in the same hierarchy. Eventually, it left us with a class that had nothing more than a Constructor, Destructor and no data members! Basically, it wasn't pulling its weight. So it was time for the class to go. However, this class had been around since the dawn of time, and so it had its furry little fingers up and down the class hierarchy. So I started doing a search and replace scheme. First, I took the entire class out of the project since it was no longer required. Then I did a search for ContainerPane and replaced instances of it with the superclass (SubPane). Had I simply done a "replace everything" scheme, I would have missed a method called IsContainerPane as it would have simply been renamed to IsSubPane (of which there was already a call with that name, on SubPane). So while find and replace can be a very quick refactoring tool, you have to have enough of a gut feeling for the safety of it and keep a very keen eye out for unintended consequences when using it.
The biggest problem I run into when doing refactoring is unintentional breakage of virtual method call chains. The trouble with refactoring the parameter list for a virtual method is the fact that you have to be certain to hit all calls in the virtual method chain, or else things like Super.SomeVirtualMethod may continue to compile, but *skip* a class in the hierarchy accidentally! I would love to see a refactoring tool built into the REALbasic IDE to alleviate this issue. Since all instance methods in REALbasic are virtual, it shouldn't be too terribly difficult to apply a datatype change across the entire hierarchy's call chain. I think the hardest part would be coming up with reasonable UI for it! However, it's a much harder nut to crack in C++ since methods linkage is determined on a case-by-case basis.
What refactoring patterns and tricks do you like to use? What pitfalls would you suggest others watch out for?
Search and replace for refactoring is one place where the RB IDE actually outshines Visual Studio.
The Search/Replace issue is something that ive raised FRs about before in RB. As you rightly say Aaron, this simply doesnt do the job well. What is needed is a true object name replacement feature to allow a specifi name of any specific RB object, property or method name etc to be renamed - ALONG WITH all the code references to it throughout the RB project.
4D managed to do this, i think, due to the fact that the way source code was stored meant that every object referred to in code was not stored in text, but by some form of ID. THis meant for example that simply renaming a method would cause all source code to reflect the change automatically when next opened in an editor. this was one one of the few cool things in 4D i havent seen elsewhere.
@Daniel -- it's actually a very common refactoring trick where you use the compiler to tell you the information. The compiler knows every place you refer to that symbol in a specific sense instead of a general sense. So you ask the compiler to give you the source code locations where that symbol is used and rename that way.
This way, you can rename every local variable in a method named "i" to be "counter" without having to worry about renaming every variable everywhere named "i." The compiler already understands the scope of the variable, and knows every place it is used.
I totally hate refactoring my code. It basically means that I have to admit to an ill conceived idea or that my original logic was retarded. Hate it.
Once I've baked another plan and I feel ready to "jump in", I just start removing methods and properties that won't be used. This seems to work better than trying to implement the new/changed stuff (and then deleting unused portions) because it gives me a birds eye view of everything.
I used to just try to run the project and use the list of errors to fix everything but that seems to crash the IDE hard nowadays. So now I just check the project for errors..
After that it's all about implementing the new idea and hoping I've thought it out more thoroughly than the 1st one :-)
Hi Aaron,
I also try to let the compiler or the ide to the most work.
Sometimes I rename a method to "deprecated" or spend it an additional Parameter to let the compiler guide me through its usage.
But most of all: First I write some good Tests from the User´s perspective to be sure that after the Refactoring I still have the same behaviour of the Code, at least from the User´s Perspective!
Jens