Refactoring simply means to re-work source code, to improve its design without changing its functionality. To perform refactoring is especially needed if source code has grown for a while and is hard to comprehend and even harder to perform any maintenance on it. There is literature available with hints for refactoring for Java and C++. However, this is not really suitable for programming in C for microcontroller software.
Although some of the principles could be transfered to simple C programming, we developed some additinal methods which especially match the C programming and application of microcontrollers in safety critical systems. This can be found in the sub-pages about principles and methods of refactoring.
Refactoring is sometimes also called "perfective maintenance". It is a way of constantly improving software without changing its functionality. This is for example applied by the LINUX community. Since years they do maintenance on the code and constantly improve it without really changing the functionality and interfaces.
Especially for those companies who have to provide software for safety critical or other highly reliable applications refactoring should be applied in every day product development.
Software development for safety critical microcontroller applications needs a specific approach to refactoring. First of all there is the MISRA guideline which aims at defining a sub-set of C for safe programming. The code which is produced has to comply with this guideline. Further, it is common practice in this community to have a rigid set of design and coding rules and patterns. The aim of refactoring therefore has to be first of all to bring the code into compliance with these rules and guidelines. This is the background of the refactoring principles I want to describe on these pages.
Refactoring should have clear goals. It is no good to just poke around in a code and see what you can do to make
it better. There should be e.g. clear design guidlines which a code has to comply with and coding pattern which a code has to use. If you do not have such guidelines and pattern for microcontroller programming you should set them up as soon as possible.
You should try to improve the code to comply to these rules and standards, and stop refactoring when the goal is reached. Of course in the process of doing this, there will be enough places to do things smarter, better and faster.
Other goals may be to achieve easier maintenance and improved understandability e.g. by adding comments or regrouping functions into other modules. You can split up functions or extract portions of a function to build a new sub-function. On the other end of the scale you can combine functions, to make one out of two. This may also involve the modification and improvement of interfaces, data structures and ways of programming an algorithm. Most of these items can not be easily described in a design guideline. Therefore I called them "Soft Goals". It all depends on personal experience and sometimes taste of the programmer. A certain amount of freedom is granted here for own ways of improving the code, however it never should violate clear design rules and resource constraints.
Code refactoring has an improved design and code but has to refrain from any functional changes. The functionality of the improved code is the same as it was for the old code. Changes of the functionality in the progress of refactoring is strongly discouraged, even if it was found to be faulty. Finish the refactoring, make sure by sufficient tests that you did not add new functionality, that you did not reduce or modify functionality, and then work again on the functionality.
Prior to refactoring functional regression tests (repeatable tests) have to be set up. You should
use a suitable test environment for this. These tests will be reused to check the functionality of the
improved code. The focus should be that the development loop from changing the code to compiling it and finally running the tests should be easy to handle or even be automatic. Further the loop should be fast in execution and the comparison of the latest results produced by the refactored code with the initial results should be also automatic.
You should perform always small refactoring steps and then re-execute the test. This way you immediately detect if you made an error and are likely to find it very fast. You are in big trouble if you perform a lot of changes in the process of refactoring and then are stuck with a code which does not work any more.
Since refactoring is mostly concerned with individual portions (modules or units) of a complete software, it may not be able to improve the overall architecture of the software. You have to be aware of this! Refactoring can not improve the architecture of software. If you missed to introduce a needed layer in your architecture, or if you scattered a certain functionality over several modules instead of providing a dedicated functional block, you are very unlikely to fix the problem by refactoring. You better should go first of all for a redesign of the architecture.
In the microcontroller world you sooner or later come across the need to save resources. This could be done in one of the refactoring iterations. A way of saving resources is to reduce code lines and to reduce access to static memory. Less code lines means less ROM, RAM and time to execute. However, you should refrain from over-optimizing the code by cramming and stuffing things into single code lines which usually does not save any execution time. There are ways of messing up a code which makes it only a little bit faster, but not maintainable and understandable any more. This contradicts refactoring completely.