RSS Feed

Refactoring of C Code for Micro-Controllers

What is Refactoring and why is it Important?

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.


Principles of Code Refactoring

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.


Refactor Software to comply with Design Standards

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.

  1. Use the MISRA rules as a definition of a safer subset of the C syntax. This will eliminate the pitfalls and traps you can run into when you are programming in C. There are automatic code checkers which check your code for MISRA compliance. It is recommended to use one of them and employ it in your refactoring.
  2. Define templates and principles of design as e.g. the object oriented approach which I described in the design pages of this web-site. These principles prohibit global variables and promote the encapsulation of data in an object. The access to the data of such an object will be done via get functions or set functions like it is common to C++ or Java OO programming.
  3. Define additional rules and quality metrics for your low level design. E.g. the number of get functions for an object is ideal if it is smaller than 10. Up to 20 may be still o.k. but more than 20 is an indicator that your object is too big and you should spit it up. Another rule which is valueable is that a C-function should not be bigger than what you can display on one screen page. Split up any bigger ones.
  4. There are a few things which are not covered by MISRA or the rules of a good OO design. E.g. the use of bitfields is allowed in MISRA, although there are some restrictions. OO design is also not in contradiction with bitfields. However, the use of bitfields makes your code not portable and generates pitfalls e.g. when used in calculations. Therefore they must not be used.

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.


Some "Soft Goals"

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.


No Functional Changes by Refactoring

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.


Set up sufficient Tests prior to Refactoring

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.


Architecture and Refactoring

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.


Resource Optimizations

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.