Avoiding the copy-paste-modify trap

So, we've got a piece of code that has been proven to work well and we have a new project coming up. How should we go about creating the new project—just copy the working project and start making changes? After all, if the code is being copied, it is being reused, right? Creating copies of a code base like this can inadvertently create a mountain of technical debt over time. The problem isn't the act of copying and modifying the code, it is trying to maintain all of the copies over time.

Here's a look at what a monolithic architecture for algorithm.c might look like over the course of six projects. Let's assume that that actual algorithm is intended to be identical across all six projects:

Here are some of the main points in the diagram:

  • It is impossible to tell whether the actual algorithm being used is the same since there are six copies of the file.
  • In some cases, algorithm.c is implemented with different hardware. Since these changes were made in algorithm.c, it is isn't easily tell whether or not the algorithm implemented is actually the same without examining each file in detail.

Now, let's take a look at the drawbacks to copy-paste-modify in our example:

  • If Algo has a bug, it will need to be fixed in six different places.
  • Testing a potential fix for Algo will need to be validated separately for each project. The only way to tell if a fix corrected the bug is probably by testing on actual hardware "in-system"; this is probably a very time-intensive task and it is potentially technically difficult to hit all of the edge cases.
  • The forked Algo function will likely morph over time (possibly inadvertently); this will further complicate maintenance because examining the differences between implementations will be even more difficult.
  • Bugs are harder to find, understand, and fix because of all of the slight differences between the six projects.
  • Creating project 7 may come with a high degree of uncertainty (it is hard to tell exactly which features of Algo will be brought in, which intricacies/bugs from the SPI or ADC drivers will follow, and so on).
  • If MCU1 goes obsolete, porting algorithm.c will need to happen four separate times.

All of these duplicates can be avoided by creating consistent reusable abstractions for the common components:

  • Each common component needs to have a consistent interface.
  • Any code that is meant to be reused uses the interface rather than the implementation (Algo would use an ADC interface).
  • Common drivers, interfaces, and middleware should only have one copy.
  • Implementations are provided by the use of board support packages (BSPs), which provide an implementation for required interfaces.

If the same algorithm were designed using the preceding guidelines, we might have something that looks more like this:

Here are some of the main points in the diagram:

  • There is only one copy of algorithm.c—it is immediately obvious that the algorithm used is identical across all six projects.
  • Even though there are six projects, there are only four BSP folders—BSP1 has been reused across three projects.
  • An ADC interface is specified in a common location (Interfaces).
  • BSPs define an implementation of ADC, which is tied to specific hardware. These implementations are used by main.c and passed to algorithm.c.
  • The ADC interface, which is referenced by Algo, rather than a specific implementation. 
  • There is only one copy of the I2C and SPI drivers for MCU1 and MCU2.
  • There is only one copy of the driver for the SPI-based ADC.
  • There is only one copy of the driver for the I2C-based ADC.

Reused code has the following advantages:

  • If Algo has a bug, it will only need to be fixed in one place.
  • Although final integration testing for Algo will still need to be performed in-system with real hardware (but probably only needs to be performed on the four BSP's, rather than all six projects), the bulk of the testing and development can be done by mocking the ADC interface, which is fast and simple.
  • It is impossible for Algo to morph over time since there is only one copy. It will always be trivial to see whether or not the algorithm used is different between projects.
  • Bugs are easier to find, understand, and fix due to the decreased interdependencies of the dependencies. A bug in Algo is guaranteed to show up in all six projects (since there is only one copy). However, it is less likely to occur, since testing Algo during development was easier, thanks to the interface.
  • Creating project 7 is likely to be fast and efficient with a high degree of certainty due to all of the consistency across the other six projects.
  • If MCU1 goes obsolete, porting algorithm.c isn't even necessary since it has no direct dependency on an MCU—only the ADC interface. Instead, a different BSP will need to be selected/developed.

One exception to copy-paste-modify is extremely low-level code that needs to be written to support similar but different hardware. This is typically the driver-level code that directly interfaces with MCU peripheral hardware registers. When two MCU families share the same peripherals with only minor differences, it can be tempting to try and develop common code to implement them both, but this is often more confusing for everyone (both the original author and the maintenance developers). 

In these cases, it can be quite time-intensive and error-prone to force an existing piece of code to support a different piece of hardware, especially as the code ages and more hardware platforms are added. Eventually, if the code base becomes old enough, a new hardware target will vary significantly enough that it will no longer be remotely viable to incorporate those changes into the existing low-level code. As long as the low-level drivers are conforming to the same interface, they will still hold quite a bit of value in the long term. Keeping this low-level code easily understood and bug-free is the highest priority, followed by conforming to a consistent interface.

Now that we have a good idea of what abstraction is, let's take a closer look at some real-world examples of how to write code that can be easily reused.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.178.207