MENU

 

24-Sep-2018

Considerations for Code Refactoring

By Bryan Clark (Firmware Engineer)

Tunnel vision. Every engineer suffers from it at one time or another. A programmer gets so lost in the details they miss the forest through the trees. Instead of writing clear, concise, well organized code, little things may fall through the cracks. Two functionally identical behaviors get implemented. Code creeps. Behaviors get jumbled together. All in the name of delivering that feature that was requested. It happens, and even stellar engineers may not even be aware for months. At the time, the code made sense, and thus assumed it was good enough. The code is reviewed, merged into the mainline, and time passes. Eventually, a new feature is requested and you, my lucky friend, are tasked with doing the implementation. You peruse the code, and your head spins. What does ‘variable’ do? Wait, didn’t I read this exact code segment above? Man, the feature I need to implement would be easy if I could leverage this portion that is in the body of these two other functions. So, what do you do? These are all hints that the given code could be refactored; the process where functional code is rewritten to keep behaviors identical while improving maintainability and extensibility.

When Should I Refactor Code?

Clarity, code duplication, and extensibility are all facets of technical debt and point to a need for refactorization, however, they should not dictate a refactor occurring. Instead, time is the mitigating factor here. As with any job, an engineer has many demands on their time and reinventing a wheel may not be the best use. An engineer must consider if the effort required to clear the technical debt is worth the cost in time. Not only in present implementation time, but also in consideration of future time sink as well. Did the ramp up time on learning the code seem unreasonable? Were there things that took longer to understand than if the code was written more clearly? If so, these are good candidates for refactorization.

Code Refactorization Considerations

Determining if code needs to be refactored is the easy part. Deconstructing and rebuilding the code in an effective manner is where the cost of a refactor occurs. Refactoring can seem daunting. Unclear variable names, magic numbers, poor use of global variables; these are issues that will need to be addressed but put them on the back burner initially. 

Before beginning the nitty-gritty of refactorization take a moment and evaluate if you have unit tests in place that cover the currently implemented functionality. Unit tests are a powerful development tool that clearly define scope and keep an engineer designing towards the spec and reducing the chance of missing functionality. Used in initial development, they add two important safety valves to an engineer during refactorization. Firstly, it is a good way to refresh or learn what the true goal of the code was intended to be. Secondly, and of more pertinence, it provides test cases that ensure no functionality is lost in refactorization. If no unit tests exist, it is well worth the time to create the unit test before diving into refactorization.

When beginning a refactorization, as with writing new code, consider the design first. Does the functionality in a file or module make sense being there? Often, design choice is the most systemic issue contributing to a need for a refactor. Consider the design, and if there is an opportunity to compartmentalize functionality into similar containers and create accessor functions for variables owned by these logical containers, it is usually in an engineer’s best interest to do so. This clarifies what specific variables do, while having the added benefit of better-defining the scope. If two counters are defined with similar names, but for different purposes moving them each into corresponding modules eliminates the chance of confusing their usage. Furthermore, providing these variables with accessor functions further clarifies which module they are being modified in. To continue, the compartmentalization coupled with accessor functions can also eliminate global variables from modules that do not rely on the variable itself, which can reduce namespace pollution and size of initialized data read from the program file.

Compartmentalization can also help with stack considerations. A poorly designed program may have stack corruption issues generating difficult to diagnose defects as as the stack pointer goes off into the weeds. Compartmentalization of code into independent threads (OS or interrupt contexts) can provide the benefit of localizing defects, and thus making it easier to pinpoint the offending code.

Another design consideration is the principle of generalization. Code creep can occur because the same functionality is, unintentionally or for expediency, recreated multiple times throughout a project. For example, if iteration over an array of user names occurs multiple times just to return a given name index, consider transferring this behavior to a helper function. While reducing code size, it has the added benefit of eliminating opportunities for bugs to propagate, as instead of having multiple entry points; only one exists. Also, redundant code requires more code space. Of course, the trade-off is that stack space is required to make the function calls if they are not put in line by the compiler. As with any manual work, using the eyeball test to find and remove repetitive code can easily miss less obvious offender. To avoid this pitfall, consider usage of tools such as Simian or PMD’s Copy/Paste Detector.

As for those unclear variable names, magic numbers, and excess of global variables, there is a very real chance that using the design principles of compartmentalization and generalization will encapsulate the correction of these issues if you keep the principle of clarity in mind, as well. As you retool and rewrite the code, consider whether a term or number is ambiguous or ill-defined. Take our previous example, an array of user names; a variable defined as ‘i’ will not have the same clarity as one named ‘nameLocation’. While the given example is basic enough that a variable ‘i’ in a for loop is easy to understand at a glance, the underlying principle of clarity scales quickly with complexity.

As you’ve probably noticed, the principles for refactoring are the basics on which all solid coding is built. There is no magic bullet. Organization, design, and generalization are the guiding principles for successful initial implementation and refactorization. Reinforcing these behaviors will allow an engineer to grow and develop more extensible and maintainable code, that then helps other engineers understand a given code base more quickly and efficiently.

computer engineering,control system engineering,firmware,software

Subscribe For More

To receive more articles like the one above in your inbox, sign-up to our newsletter below.

Blog Posts


Better Firmware Faster

19-Nov-2018

Demystifying What it Takes to be a CEO

18-Oct-2018

Considerations for Code Refactoring

24-Sep-2018

American Association for Clinical Chemistry Annual Meeting

30-Aug-2018

Simplexity's Answer to Growing Pains

09-Aug-2018

Consumer Electronics Show 2018 | Part Three | So, What is a Robot?

29-Jan-2018

Consumer Electronics Show 2018 | Part Two | CES 2018 and IoT

22-Jan-2018

Consumer Electronics Show 2018 | Part One

15-Jan-2018

Why on earth are heavy weights being suspended from this printer?

22-Dec-2017

10 Best Places to Buy Parts for Product Development

29-Nov-2017

Robust Firmware Stack Sizing

08-Nov-2017

Senaptec Strobe: A Study in Simplification

19-Oct-2017

Treatment, Prevention, and Medical Engineering Solutions

04-Oct-2017

Options in Product Development Models: Internal, CDM, or Design Specialist

20-Sep-2017

Designing Thermal Control Systems

30-Aug-2017

Simplexity’s 7 Steps to Simplification©

15-Aug-2017

Battle of the Buttons: UCSD vs. PSU

02-Aug-2017

Considerations For Medical and Biotech Designs

13-Jul-2017

Risk Mitigation in Product Design: Part 2

11-Jul-2017

Risk Mitigation in Product Design: Part 1

28-Jun-2017

San Diego's Biotech Consortium

09-Jun-2017

Appropriate Models for 3D Motion Analysis: Part 3

25-Apr-2017

University of Oregon’s Product Design Program Is One of a Kind

19-Apr-2017

Appropriate Models for 3D Motion Analysis: Part 2

14-Apr-2017

From Engineer to Leader: How Do You Get There?

12-Apr-2017

Appropriate Models for 3D Motion Analysis: Part 1

06-Apr-2017

Why Engineering Still Matters in Product Development

29-Mar-2017

How Mechatronics Improve Drone Technology

16-Feb-2017

Why You Need a Gyro to Measure Position

20-Jan-2017

Why I, As The CEO, Get The Same Bonus As All My Other Employees

13-Dec-2016

Mechatronics Aids In Embedded System Design

07-Dec-2016

Top 10 Tips for Designing Injection Molded Plastic Parts

22-Nov-2016

British school kids and car hackers: the widespread appeal of open source

14-Nov-2016

When should you consider designing custom gears?

07-Nov-2016

Conference Report: Open Source Hardware Summit

31-Oct-2016

What is a Motion Control System?

20-Oct-2016

The Top 10 Questions to ask a Product Development Firm

05-Oct-2016

The Potential of the Apple AirPods To Disrupt A Whole Industry

12-Sep-2016

How to Use Open Source Hardware in Product Development

01-Sep-2016

What is the mech in mechatronics?

16-Aug-2016

3 Tips for IoT Product Success

02-Aug-2016

When Should I Start Designing For High-Volume Manufacturing?

19-Jul-2016

Designing a 3D Printer for the Home

29-Jun-2016

It Turns Out That EMC Is Not Black Magic

22-Jun-2016

Selecting the correct motor type and size

06-Jun-2016

When brainstorming fails, throw an imaginary cat

18-May-2016

Five Tips for Mechatronic System Integration

09-May-2016

Three Tips for Designing High Volume Mechatronic Products

28-Apr-2016

What Is Mechatronics?

18-Apr-2016

If I could only do 3 things to simplify a design, what should they be?

06-Apr-2016