In the beginning, there was no code…

James GrenningAugust 16, 20233 comments

…and it was good.

Why is it that code starts out nice and deteriorates over time?

What happened to make the code badness grow? Probably something joyous like the first order for a new product or adding people to the team. Or maybe something sad like losing a key employee.

I see a lot of legacy C code. How does a function get to be 1000 lines long? How did that file get to be 40 KLOC or 100 KLOC? A friend showed me a 27-page C function in some telecom code. Amazingly enough, it worked! How does code get that way?

One Copy/Paste at a time!

Long functions often contain deep nesting, complex conditional logic, special case handling and the list goes on. The functions do their work with primitive data types and access to global data structures. They loop over collections of entities as they manipulate each entity. A bug is reported, a special case is added.

In short, long functions are doing too much! Your code did not start that way. With each change, functions collect more responsibility. The growth is insidious and hardly noticed until it is a serious problem.

(In all fairness, you probably can find a few long functions that should be long. Those are a tiny minority of the population of long functions. )

Developers are often quick to blame unreasonable deadlines and their managers and the people before them for their poor code.

Some blame may be appropriate. The pressure is real. Programmers are in a hurry. They hear “just get it working”. Maybe your company has institutionalized creating bad code. Bad code costs a lot, now and in the future.

Some programmers may not know better. Getting code to work, is essential, though by itself it is not sustainable.

Programmers Cannot Just Blame Management

Getting code to work is what I call the programmer’s App-titude Test. If you can get the App to work (warning: this is not easy), you may have the aptitude to be a professional programmer. Not only does the code have to work, it needs to be written to make it easier to understand and change.

As a programmer, I cannot just blame management. I need to take responsibility for my product, my behavior, my skills. Teams I’ve worked with pretty quickly can learn to identify code smells. What to do about the smell is usually harder. Improving the structure without breaking the system is also difficult. Just the same, like your kitchen, you need to regularly clean up.

With the business side not knowing better and stressing that development just get the code out, coupled with no clear vision and path to better code, the cycle continues.

Why Do Functions Grow?

I was with a group of developers helping them learn Test-Driven Development. Many were struggling while defining a new C++ class. It’s C++ so it is kind of understandable. (I struggle with it too, that’s why I like to go carefully with a tight feedback loop.) I asked “when was the last time you defined a new C module or C++ class?” It had been at least 6 months. I then asked, “Who of you writes code every week?”. Most the hands went up. “Where do you put that code?” As it turns out, they wedge their changes right into the existing code, not even considering adding new structure as the needs change.

This behavior means functions gradually collect more to do as a system encounters the real world. There are new features, new special cases, and slight variations in behavior that require code to be changed.

A common thing that happens is that a function’s local data grows, as functionality grows. Local variables act like glue, cementing the lines of a function together. The cleanup effort inevitably involves extracting functions. The local variable glue shows up in the extracted functions as another code smell: duplicate and long parameter list. This smell is actually great news if you can understand what the code is telling you, you need a new abstract data type! You found a separable responsibility that can be extracted to a new module.

Why does it matter?

It matters because developers spend most their time reading code. Long functions are usually hard to understand. When functions are hard to understand you cannot quickly determine if you are in the right place. So you dig deeper. The hunt goes on. You make your change. You check the new functionality and hope there are no unintended consequences.

What can we do?

Changing existing code is risky. For good reasons, programmers are often afraid to clean up their code. (BTW, they should be unless they have a good test suite.) That fear leads to leaving the code as is. Code got bad incrementally, you need to start to improve it incrementally. What if every time you touch the code it gets better instead of worse.

I’m talking about Bob Martin’s Boy Scout Rule. It is hard but rewarding work. Over time your code base can improve. You don’t have to believe me, read these Stories from the Field from people have saved their product’s code.

C and C++ programmers can get help from my book Test-Driven Development for Embedded C and my training courses. Visit Wingman Software.

[ - ]
Comment by MatthewEshlemanSeptember 1, 2023

Great article, thank you! I spent over a decade leading and architecting a large embedded C/C++ code base. Our code was exposed to near-constant changes in requirements, platforms, operating systems, and feature sets. I suppose my Boy Scout years prepared me for the challenge, as our team frequently identified opportunities to improve and clean up, especially when branching for the next round of changes or new product lines. Maintainability was critical.

We did not have unit tests though. Oh how I wished for it. I was always in a state-of-fear as critical mass-production deadlines approached. Those years would have been substantially less stressful with TDD backed by modern DevOps mentalities. Live and learn. I would strongly encourage all Tenderfoots to read Grenning and other experienced authors on this site.

Thank you again for your book and contributions to our collective wisdom!



[ - ]
Comment by CustomSargeSeptember 6, 2023

Well written, by somebody who's obviously spent time in the "trenches". When I revisit code, I know what I'm there for, maybe not where and/or how to do it. But, while I'm there, I try to keep an eye out for poorly written functions, since I've had time away and see it differently now. This is usually, but not universally, good. As for code devolving: it's thermodynamics - order descends into chaos.

[ - ]
Comment by JamesGrenningSeptember 7, 2023

I find that when I come back to code that I have not looked at for a while, I come with a beginner's mind.  I wonder, who broke into my repo and messed up my code!  Oh, it was me; The code is just as I left it.  

This is a perfect time to improve the code.  My fresh eyes and mind know how to make it easier to look at the next time.  It's like getting code reviewed by another person; not much time needs to go by between the initial writing and the second viewing.

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: