I don’t rate myself as much of an expert in IDEs, refactoring, history of programming, or in my ability to observe trends, but I still find these topics fascinating and I have had a bit of experience evaluating different products (mainly relating to C development) and will make an attempt at presenting my viewpoint. This is really just based on my own worm’s eye view of what is going during my few short years of observations, hindsight, past fallacies and many shortcomings.

Hopefully, it will provide a bit of fun reading to those who share these interests, and with a bit of luck it will raise some awareness of the value refactoring can bring to an IDE and how it can enhance one’s productivity.

Introduction

Embed from Getty Images

One complaint I very often hear from new developers is that legacy code is really hard to work with. And they are right. When it was still new, using C to write firmware was a radical new step, and it needed to compete with some of the assembly code it was replacing. It was an order of magnitude more efficient to code in (fewer bugs and one could produce functionality faster, with fewer lines of code). However RAM and processing power were still very limited, so coding and getting the functionality to fit was more of an art than a skill, and whoever produced it smaller and faster was the hero. And whoever did this fastest got extra bonus points (as is the case today). People competed to see who could get the most functionality out of the least resources. Writing a convoluted procedure no-one else could understand was almost a badge of honour. Readability tended to go out the window.

An interesting and amusing side note is that complaints about legacy code is not a new phenomenon, it is something that has and will be always with us. Yesterday’s coders often complained about even older code, often written in Assembly, or written in an even less readable way. No doubt it is human nature to dislike old styles and designs – future programmers will doubtlessly have similar feelings of dread.

Why is Refactoring Needed?

Embed from Getty Images

The reality is that a majority of the world still runs on generally unmaintainable legacy code and many of the really successful systems of today are built on code that was developed over a period of decades and the consequence is often a mixed bag of old and new code.

Product rewrites have been often been attempted, but seldom are they successful without a huge amount of pain and suffering, often resulting in products that take many years to mature and stabilize. From a user perspective, they are often inferior to the products they are meant to replace, so for better or for worse, we are often left to make the most of what we already have. Growing and replacing these products from the inside out is often the most successful strategy and this is where having a powerful IDE with good refactoring support really shines.

I have worked with a lot of legacy code and having battled with the readability and structure aspects thereof (or lack thereof), there is one activity I find my fingers itching to do over and over again. That is to make the code readable and understandable.

To be more specific, the types of issues usually include:

  • badly named variables and functions,
  • functions that are way too long,
  • too many global variables,
  • Hungarian notation,
  • modules that include a mishmash of functions which share very little relationships,
  • redundant blocks of code (dead code),
  • functions with unexpected side effects,
  • spaghetti code,
  • copied and pasted code,
  • variables with names such as “temp” used over and over again for different things,

I am sure you get the picture…

Something else I find myself wanting to do is to retrofit unit tests to legacy code. This is actually an incredibly difficult task with legacy code. This is because with this code not having been written in logically abstracted layers of manageable bite-size pieces, or in a way that is testable, it can be extremely difficult to simulate conditions that will exercise the functionality you want to test. It can also often be very difficult to monitor the outcome of tests when the intermediate states are not stored.

Michael Feather wrote an excellent book: Working Effectively with Legacy Code where he explains how to breath new life into old code by adding unit tests and thereby improving the structure, readability and maintainability. One feature he stresses as being important in an IDE (Integrated Development Environment) is having the ability to safely refactor.

IDEs to the Rescue:

Embed from Getty Images

There are many IDEs, some of which I have evaluated. Over the past few years, I have seen a trend for the leading ones to introduce refactoring features, which has made me really excited. This has the obvious benefit of enabling one to safely and quickly improve code as one writes it, but it also gives new hope to the future of legacy systems. Unfortunately, this feature has been maturing at what feels like a snail’s pace, but it is still happening, and that is the important part.

From my experience, I would say the languages where IDE’s seem to have had the most traction in adding refactoring support (where the best results can be found) are Java and C#. The main benefits of these languages are that you can polish up and neaten your code as you write it. You can obviously also safely improve any badly written code (including legacy).

Focusing on C

Unfortunately, there are two really important languages where refactoring support has been lagging – C and C++ (probably because they are difficult languages to parse). However, there are a couple of examples of IDEs where reasonably good and usable attempts have been made. This is important since there are so much legacy C and C++ code (being much older than Java and C#) out there to be maintained and refactored.

To me there are a few types of refactoring features that add real value and distinguish good products from the rest:

  • Entity renaming (Header Files, Functions, Parameters, Global variables, Local Variables, and Macros),
  • Function Extraction,
  • Changing the Order of Parameters in function calls,

Many IDEs claim to support refactoring, but I have found most of these either don’t work accurately (especially when it comes to function extraction), or only offer types of refactoring that are of little value.

In my opinion, the two leading IDEs for C and C++ development are Visual Studio and Eclipse CDT.

Out of the box, Visual Studio (VS) has almost no refactoring support for C and C++. However, I have found a plugin which I have been rather impressed by. This plugin is called Visual Assist. I have found it to be very good in all respects – easy to set up, usable, feature rich, many shortcuts, and most importantly accurate and reliable refactoring. The company that wrote it The Whole Tomato (I know, a weird name), has constantly improved their product and I would say that it is possibly one of the best in its class. I also like the fact that it is relatively affordable.

Out of the box, recent versions of Eclipse CDT (Luna 2 in this case) offers fairly good refactoring support, which has historically been marred by some quirks and rough edges as is common with Eclipse CDT. The latest versions have got a lot better, but there are a few things to be leery of:

  • When renaming entities, occasionally not all instances get changed – some instances are mysteriously ignored,
    • My guess is that the cause of this is Eclipse CDT’s slightly flaky indexing mechanism (which appears to occasionally miss things and also sometimes gets corrupt somehow – this can often be resolved by rebuilding the index).
  • Eclipse renames most entities, then suddenly back-peddles and undoes most of its changes – weird and annoying,
  • It very occasional corrupts one of the files and doesn’t let you know about it.
    • fortunately, this can be easily resolved using the local Eclipse history. As long as you discover the corruption in time – the corrupted code will almost never compile, so compile often when refactoring.
  • When extracting a function with pointers to variables, it sometimes gets the dereferencing, usually leaving the code in an uncompilable state.
    • you need to manually fix whatever was not done correctly – usually a simple task.
  • It doesn’t offer an automated way of changing the parameter order in a function.
    • It is strange they haven’t bothered adding this feature since it seems like one of the more simple ones to implement.

As mentioned in a previous blog, I have found one plugin that has improved Eclipse CDT’s native refactoring support (based on past experience). This plugin is called CUTE, mainly known as a unit testing framework. However, in my experience, its main benefit is its enhanced refactoring support. Although it doesn’t add parameter reordering, this plugin has in the past helped to make Eclipse CDT work somewhat smoother and provide a more refined experience when it comes to refactoring. It is probably not too surprising, since from what I can see it is written by the same group that implemented the refactoring support that comes natively with Eclipse CDT.

So far, with possibly one exception, I haven’t  found another IDE/Plugin combination that supports C/C++ refactoring to anywhere near the levels of sophistication and accuracy that Eclipse CDT (even with its rough edges)  and VS offers when used in combination with the above-mentioned plugins.

The one exception I have found is Xrefactory for Emacs. I have tried this tool briefly some time ago and it gave reasonably good results. It is probably best suited to those more comfortable working with Emacs. I, however, did find that function extraction required a bit of extra rework compared to the other tools mentioned.

Conclusion:

Embed from Getty Images

As every year goes by and with each new versions of a particular IDE, I have noticed a steady improvement in refactoring support. With most of my time being spent enhancing and growing products written predominantly in C, many of which are now many years old, there is always a need to constantly breath new life into them to keep them modern and competitive. I find the continued enhancements in refactoring support to be the most important trend in modern IDEs.

If anyone knows of other good refactoring tools that support C/C++ which I haven’t mentioned, I would be very curious to know about them.


7 Comments

Eric Roesinger · March 9, 2016 at 7:19 pm

Personally, I’d like to see a single, adequate, order-stable-tagfile-based refactoring tool integrated with a few, popular editors (Vim, due to my thirty years of ‘vi’ muscle memory, and emacs for so many others).

Never been a fan of IDEs because:

(1) I’ve seen them do too much unreliable magic (though some can avoid this by using makefiles–which aren’t as hard to understand as too many people seem to think, and a hell of a lot easier to read than other grammars–under the hood);

(2) they tend to generate configuration files that aren’t order-stable or reasonable to edit and compare (inimical to sound revision control);

(3) many fail to identify user-specific state-keeping files, or even to separate them (in which case they should also automate excluding them from revision control where present); and

(4) I really hate having to learn different editors as I switch tool chains (muscle memory is too important to my productivity and breaking it is a monumental distraction), but most don’t integrate that well with external editors!

That said, evolution of refactoring tools *is* important, to me: while I can, for the most part, “do it the hard way” from within my usual editor (again with muscle memory coming into play heavily as I get into a mental flow state), it’s more work than I would prefer.

    Firmware Programming by Bryan Jarmain · March 10, 2016 at 8:16 am

    Thanks for you reply, Eric. A good pure refactoring tool would be highly valuable, even if not integrated with a good IDE/Editor.

    The line between a source code editor and an IDE is quite a blurred one. Out of the box, an IDE generally has a deeper understanding of a particular language, giving it the ability to perform more language orientated things rather than a purely semantic exercises. However, many editors (e.g EMACS) can be given great deep language support through plugins. That said, Eclipse can also be given good non-core language support through the use of plugins.

    A few years back I was hooked on using a now old version of SlickEdit (a source code editor). I had also developed muscle memory for a lot of the keystrokes. It was (and is) a great editor. Changing to Eclipse CDT was a challenging and rewarding experience. While getting used to all its quirks, and learning where everything was, was often frustrating and time consuming, I am pleased I have done so. Its ability to integrate well with other tools (including ones I write myself), and the fact that it offers a lot of useful native language integration and understanding (e.g highlighting errors as one codes) adds a lot of value by speeding up the feedback loop of addressing many defects quickly and seamlessly as they are added.

    In my experience, top IDEs are also good editors, and that is definitely where one gets most benefit. In my experience, I have seen that the use of IDEs for editing source code has slowly been replacing the use of pure editors for that task, and today I would say most development is done using IDEs.

    I believe that an IDEs native support for a specific language makes it more suited to performing certain types of difficult refactoring exercises where a deeper language understanding is required. Not to say that certain editors with the appropriate plugins cannot also do so. Emacs, as mentioned in the blog, is one good example.

Jim Kendall · March 29, 2016 at 9:55 am

Well, I am not a big fan of refactoring…..I prefer cut & paste…which is one of the sins in your list:

badly named variables and functions,
functions that are way too long,
too many global variables,
Hungarian notation,
modules that include a mishmash of functions which share very little relationships,
redundant blocks of code (dead code),
functions with unexpected side effects,
spaghetti code,
copied and pasted code,
variables with names such as “temp” used over and over again for for different things,

badly named stuff…..depends upon the history of development….
long functions…. well….it it can’t be/doesn’t need to be split…then why do it ?
there are those things which need to be done in a monolith, or do you just want to see a lot of subroutines and functions ?

too many global variables: Usually from the same people who want to see a lot of subroutines and functions !!
I have no problem with hungarian notation…..it is redundant in this age of declared types.

modules that include a mishmash of functions : Yup, mine are usually stuffed into modules called “utils” or someting. little snippets that are called from various places.

redundant blocks of code (dead code): Well redundant and dead code are two different things.
I prefer to see dead code commented out…..with an explanation of why it was used/tried.
I like comments on WHY I am looking at redundant code…..cut & paste boo boo or are you writing to a register twice for a reason ?

functions with unexpected side effects : This is the stuff I get hired to debug.
spaghetti code: don’t see too much of this, but you DO see it in a different way….classes that are “friend” classes of almost everything!!

copied and pasted code : HEY! Easy there…I do a *lot* of that. I am a lazy typer.

variables with names such as “temp” used over and over again for for different things :
yep…I do this too…..if it’s called “temp” or junk….don’t pay attention to it……as opposed to my buddy int retval…..which will be the return value (of an integer function)……then I can
return( retval ) instead of the ugly gob of return( some number ) I can look back or debug through and see where ‘retval’ was changed.

I use vi (vim) a lot. I also liked CodeWright. Also NetBeans is good for what I do.
I keep my Makefiles simple though.

Firmware Programming by Bryan Jarmain · March 29, 2016 at 12:00 pm

Thanks for the honest feedback and listing all your sins of the past and present, and providing a really contrarian view. I have sometimes looked at some of the old code I wrote and spend way longer than I probably should have to figure it out or remove a bug. LOL, also keeping your code a mess may be a good obfuscation strategy – discourages anyone else from helping you to maintain it. I know of other people who also won’t try to improve the maintainability of old code – afraid their house of cards may collapse if any of the black magic holding it together is messed with, I guess…

Toby · April 11, 2016 at 11:38 am

I don’t often have to look at legacy code other than my own, but I do like eclipse CDT as opposed to managing the entire build and debug through various different tools & interfaces – despite eclipse’s bloat most of the issues that Eric mentions above do not occur, or are negligible with it.

2c on your listed sins:

Badly named variables and functions – In FW I find it can be hard to strike a decent balance between overly long function names and terseness. E.g. configurePwmFordigitalPower() vs configDpPwm() the later is much terser, but it someone looking at it doesn’t know what the digital power aspect requires then how will the former help them much more? Variable names are easier, either descriptive or of a very short scope and named according to convention, such as i, j, k for counters or x, y, z for throwaway intermediary variables.

Functions that are way too long – This can be dangerous, introducing unnecessary context switches, say in interrupt service routines adds time and size overheads that could otherwise be avoided. Also, to use another example, should I have a monolithic configPwm(handle, freq, margin) which would obviously only ever be used with the PWM, or should a higher level function that itself will needed to be separated to do essentially the same thing call configPwmBase(handle); configPwmFreq(handle freq), configPwmMalrgin(handle, margin), etc.

Too many global variables – Global variables should indeed be kept to a minimum, but again with C FW programmers should not be afraid to use them if they are the correct solution to a problem.

Hungarian notation – Meh, I don’t use it but if a name suits other general rules of being correctly descriptive and so forth then why change legacy uses for no other reason? This way dragons be, working code should only be changed for more solid reasons.

Modules that include a mishmash of functions which share very little relationships – Sometimes unavoidable (this stuff exists, each part may be too small for its own module, yet it has to go somewhere).

Redundant blocks of code (dead code) – I’d say remove these as they are created. If something about the code that replaces it needs documenting with respect to the older dead code, then put it in the *documentation*. Also, use a version control system. It puzzles me to find that others might leave this in…

Functions with unexpected side effects – A big no-no.

Spaghetti code – Also sometimes unavoidable with C FW where things intrude upon an abstract levelled code design, some things just operate differently at different levels, sticking the layering can cause instances of spaghetti code, moving from the layering for specific instances can make it harder to follow… choose wisely on a case-by-case basis.

Copied and pasted code – Again choose wisely, I’d err on the side of moving copied code to it’s own function, but pretty soon one can start falling afoul of the other sins (
modules that include a mishmash of functions, badly named variables and functions, too much context switch overhead, etc)

Variables with names such as “temp” used over and over again for for different things – Another no-no. especially in functions that are long as it’s hard to see where the reuse begins and ends. Naming variables descriptively is not hard for the most part – refer to the first sin!

Firmware Programming by Bryan Jarmain · April 11, 2016 at 1:16 pm

Toby, you make some good points from a practical perspective. The focus of this article wasn’t so much on a list of coding “sins” as on ways to improve the code you later come to realise is unmaintainable.
Coding is a constant conflict between being practical and being idealistic. Many times, one needs to take the middle ground and leave pieces of non-ideal code, especially when the sin is relatively benign, and the module doesn’t have any real problems. However, too many benign bad practices can erode the maintainability of the code into an unworkable state. If one needs to add features to a piece of this code, you probably want to reduce the risks and first, make the code maintainable again by going through a low-risk refactoring exercise. Sometimes code has too many dependencies, and this has made it fragile or brittle, in which case you may need to do some redesign, which often includes a fair amount of refactoring.

Refactoring Trend in IDEs, why I think its so Important – Firmware Programming · July 14, 2018 at 6:04 pm

[…] This article has moved. Please visit it by clicking here. […]

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: