Jumbo shrimp. Instant classic. Military intelligence. C++ refactoring browser. Spot the pattern yet? Up until recently, there have been more sightings of Nessy and Bigfoot than of working C++ refactoring browsers. After months of using refactoring intensively in C++, my fingers are screaming for mercy and threatening me with repetitive stress syndrome. Fortunately, things seem to be changing a bit. I recently learned of SlickEdit‘s support for C++ refactoring, so I couldn’t resist taking it for a test drive.
C++ refactoring is hard. There’s no doubt about that. Just parsing C++ is hard, as I quickly found out when I wrote some scripts a couple of years ago to analyze the structure of a C++ program. Even with the help of Doxygen pre-processing, my analysis was full of holes and exceptions. Writing a robust program to correctly interpret and modify the structure of a C++ program is a nightmare come true.
Frankly, I had pretty much given up all hope of every having good automated refactoring tools for C++. My best hope was that new language designers keep refactoring in mind as a primary goal of their languages, so at least we’ll have those tools in whatever language we’re programming with in ten years.
So along comes SlickEdit version 10 with great promises of C++ refactoring. Surprisingly enough, that’s just one small bullet point completely hidden by a bunch of bullet-point items of no great interest or uniqueness (smart line selections? keyboard emulation? what year is this, 1992?). As a matter of fact, refactoring isn’t even mentioned in SlickEdit’s “cool features” web page! That’s not an auspicious beginning.
Fortunately, things get better. The installation for the Linux demo version of SlickEdit v10 worked without a hitch. The refactoring actions feature prominently in the Tools menu and the right-click context menu. I loaded a small project I was working on (just a handful of classes-nothing big) and started applying them one at the time.
This is the big one. If I can only have one refactoring, I’ll take this one. Being able to rename variables, functions, and classes takes care of 75% of the refactorings I do. A tool that could reliably do that would be a major productivity boost (reliably being the key word there).
How does SlickEdit’s rename work? Member variables were a dream to rename. But frankly, I can usually do that with “replace in file” and doing one edit in the header file, so that’s not very challenging. Still, good start.
Trying to rename member functions was not quite as successful. Sometimes it would work like a charm, and rename the function in the cpp file, the header file, and everywhere else it was called from. Sometimes however, it would look at my code and proclaim that there was nothing to rename. Nothing? I had just right clicked on top of the function to rename! Some other times it would rename the function in a couple of classes and totally miss where it was being used.
After some experimentation, it turns out that “Quick Rename” (which supposedly is not nearly as robust) worked time and time again without a single problem. That’s good I guess, but it left me with a very uneasy feeling.
Thumbs up for the user interface for refactoring too. As soon as you enter your refactoring and your parameters, you are presented with a list of the files that will be modified and a diff of each of them, so you can cancel the operation if something went horribly wrong. It certainly makes you feel a lot more in control.
This is a fairly common refactoring. I actually thought it would be fairly complicated, but in every test I did, SlickEdit handled it admirably. The tricky part is to figure out what arguments the new function needs to take, and every time it figured it out correctly. You even get an option to name the new function and tweak the arguments it takes before applying the refactoring. Again, great user interface.
My only quibble with this refactoring is that the new methods are all made public. Uh? Would it have been so difficult to give us an option? If anything, I have just moved code from a member function to another member function in that class. By default it should be private, not public. Oh well, I just have to move one line. But it’s a bit annoying to have to tweak the header file by hand.
Modify parameter list
Another big one. Sometimes it’s quite painful to add a new parameter to a function that is called from dozens or hundreds of tests. So much so that it really discourages us from doing the “right” thing and is tempting to create a new function instead. Sometimes, after the second time you need to add a new parameter, we just create a struct with the parameters that the function takes and save some pain in the future. Not a particularly elegant solution, so this refactoring tool would come in very handy.
Unfortunately this one completely failed for me. Most of the time, trying to add a new parameter to a function would just come back and tell me there’s nothing to do. Same thing with rearranging the order of parameters. At best, once I got it to actually add the new parameter, it only changed the function and the header file, not the calling code. Doh!
Misc inheritance chain refactorings
SlickEdit has a set of refactorings intended to do operations in the inheritance chain: push down, pull up, extract super class, etc. I don’t work with big inheritance chains these days, so they’re not very useful to me and I didn’t test them at all. Actually, now that I think about it, the only inheritance I use are interfaces and mock objects. Then again, I suspect this would be extremely handy for manipulating legacy code (like some currently popular game engines).
This seems like a really large refactoring. Extracting a whole class out of a set of code? Rename didn’t work consistently, and modify parameter list didn’t work at all. I certainly wouldn’t want to try something of this magnitude. Let’s get the other ones working before we jump on this one.
Convert local to field
This one seems marginally useful, and it’s not something I do in a regular basis. All it does is to prepend an m_ to the variable and add it to the header file. What if you don’t prefix your member variables with m_? Is there a way to turn that off? Also, the new member variable wasn’t added to the constructor(s) initialization list.
At first I thought that it was a totally pointless refactoring since it’s describing as moving a method from one class to another (I can’t remember when was the last time I did that). But it turns out it can be quite useful to move a non-member function into a class. That’s something I do sometimes when a helper function in an anonymous namespace starts needing more and more state from the object that uses it, and eventually it becomes clear that it wants to be a member function.
Move method will make it a static member function, and then you can follow that up with Convert static method to instance method and voila.
I’m sure some people will rave about this one, but I’m not too thrilled about it. It creates set/get methods of a member variable. Frankly, I don’t like set/get methods. I think that, most of the time, they’re a smell of bad design. I’m not sure I want to make it any easier for people to create them, so I would be happier without this refactoring tool at all. Besides, what’s with making them inline by default? At least give us an option to put them in the cpp file.
Replace literal with declared constant
This one takes a literal (magic number/string) and replaces with with a constant or a define. Now we’re really reaching the bottom of the barrel. Is it really faster to navigate a three-level menu to select this than to do it by hand? It’s good that you get an option to create the new constant as const, static const, or an evil #define, but it doesn’t place it in an anonymous namespace, even if one already exists in that compilation unit.
Create standard methods
This one will add a constructor, destructor, copy constructor, and assignment operator to the class. At first I thought it wasn’t a big deal. I already have macros that do that for new classes. Then I realized you can do this with an existing class, and it will take care of assigning all member variables. That’s pretty cool, but of limited practical use. I haven’t had to go back to many existing classes and add assignment operators. Still, I could see how it could be useful.
All in all, SlickEdit managed to impress me because I wasn’t really expecting functional C++ refactorings. On the other hand, it is clearly far from being robust enough to be relied upon for production work. Maybe the next version? For me, they just need to fix the rename and I’m almost there.
One cool aspect of SlickEdit is its powerful scripting language, where it has access to all the commands you can do from the menu. That means that I can write a script that, as part of what it does, kicks off some refactoring step. That’s extremely useful to adapt the refactorings to my own environment. One thing I didn’t see is whether the script language has access to the logical structure of the C++ program or whether it’s just limited to calling the refactoring function. It would be great to have more control and be able to create custom refactorings.
SlickEdit is a bit puzzling because it parses all the header files during each refactoring. And by all, I mean all. Not just the ones in your projects, but all the standard include files that you might have included. I don’t really understand why it does that. I’m clearly not going to change standard header files, so I think that limiting a refactoring only to files that are presently loaded in the project would be good. With my toy projects with a handful of classes, the refactorings were very fast (a second or two), but I fear how it might scale to a large project. Next time they have an improved version I’ll have to load it up with a large codebase and see how it fares.
It seems as if refactoring tools for C++ are just around the corner, but still tantalizingly out of reach. At this point I’m not ready to switch to SlickEdit based on the strength of its refactoring tools alone, although I’m still going to take a closer look at it. Right now I would prefer a standalone command-line refactoring tool that could be easily integrated in any environment. Anybody knows of any other decent C++ refactoring tools?