Click here to Skip to main content
15,860,859 members
Articles / All Topics

Why Your Code Is So Hard to Understand

Rate me:
Please Sign up or sign in to vote.
4.89/5 (59 votes)
6 Nov 2014CPOL8 min read 38.5K   38   12
Why is your code so hard to understand

how-to-become-a-web-developer

“What the hell was I thinking?!?”

It’s 1:30AM and I am staring at a piece of code I wrote no more than a month ago. At the time, it seemed like a work of art. It all made sense. It was elegant and simple and amazing. Not anymore. I have a deadline tomorrow and discovered a bug a few hours ago. What seemed simple and logical at the time just doesn’t make sense anymore. Surely, if I wrote the code, I should be smart enough to understand it?

After one too many experiences like this, I started thinking seriously about why my code makes perfect sense while I am writing it but looks like gibberish when I go back to it a few weeks or months later.

Problem #1, Overly Complex Mental Models

The first step in understanding why your code is hard to read when you come back to it after a break is understanding how we mentally model problems. Almost all the code you write is trying to solve a real world problem. Before you can write any code, you need to understand the problem you are trying to solve. This is often the hardest step in programming.

In order to solve any real world problem, we first need to form a mental model of that problem. Think of this as the intent of your program. Next, you need to form a model of a solution that will achieve your programs’ intent. Let's call this the semantic model. Never confuse the intent of your program with your solution to that intent. We tend to think primarily in terms of solutions, and often bypass the formation of a model of intent.

Your next step is to form the simplest semantic model possible. This is the second place things can go wrong. If you don’t take the time to really understand the problem you are trying to solve, you tend to stumble onto a model as you code. If on the other hand, you really think about what you are trying to do, you can often come up with a much simpler model that is sufficient to achieve your original intent.

Eliminating as much of this accidental complexity as possible is crucial if you want easy to maintain, simple code. The problems we are trying to solve are complex enough. Don’t add to it if you don’t have to.

Problem #2, Poor Translation of Semantic Models into Code

Once you have formed the best semantic model you can, it’s time to translate that into code. We’ll call this the syntactic model. You are trying to translate the meaning of your semantic model into syntax that a computer can understand.

If you have an amazing semantic model but then mess it up in the translation to code you are going to have a hard time when you need to come back to change your code at a later stage. When you have the semantic model fresh in your mind, it’s easy to map your code onto it. It’s not hard to remember that a variable named “x” is actually the date a record was created and “y” the date it was deleted. When you come back 3 months later, you don’t have this semantic model in your head so now those same variable names make no sense.

Your task in translating a semantic model into syntax is to try and leave as many clues as possible that will allow you to rebuild the semantic model when you come back at a later time.

So how do you do this?

Class Structure and Names

If you are using an OO language, try and keep your class structure and names as close to your semantic model as possible. Domain Driven Design is a movement that places extreme importance on this practice. Even if you don’t buy into the full DDD approach, you should think very carefully about class structure and names. Each class is a clue you leave for yourself and others that will help you re-build your mental model when you return later.

Variable, Parameter and Method Names

Try avoiding generic variable and method names. Don’t call a method “Process” when “PaySalesCommision” makes more sense. Don’t call a variable “x” when it should be “currentContract”. Don’t have a parameter named “input” when “outstandingInvoices” is better.

Single Responsibility Principle (SRP)

The SRP is one of the core Object Oriented Design Principles and ties in with good class and variable names. It states that any class or method should do one thing and one thing only. If you want to give classes and methods meaningful names, they need to have a single well defined purpose. If a single class reads and writes from your database, calculates sales tax, notifies clients of a sale and generates an invoice, you aren’t going to have much luck giving it a good name. I often end up refactoring a class because I struggle to give it a short enough name that describes everything it does.

Appropriate Comments

If you need to do something for a reason that isn’t made clear in your code, have pity on your future self and leave a note describing why you had to do it. Comments tend to get stale quickly so I prefer having the code as self describing as possible and the comments are there to say why you had to do something, not how it was done.

Problem #3, Not Enough Chunking

Chunking in psychology is defined as the grouping of information as a single entity. So, how does this apply to programming? As you gain experience as a developer, you start to see repeating patterns that crop up over and over again in your solutions. The highly influential Design Patterns: Elements of Reusable Object-Oriented Software was the first book to list and explain some of these patterns. Chunking doesn’t only apply to design patterns and OO though. In functional programming (FP), there are a number of well known standard functions that serve the same purpose. Algorithms are another form of chunking (more on this later).

When you use chunking (design patterns, algorithms and standard functions) appropriately, it allows you to stop thinking about how the code you write does something and instead think about what it does. This reduces the distance between your syntactic model (your code) and the semantic model (the model in your head). The shorter this distance, the easier it is to re-build your mental model when you return to your code at a later stage.

If you are interested in learning more about the functions used in FP, have a look at my article on functional programming for web developers.

Problem #4, Obscured Usage

Up to now, we have mainly spoken about how to structure your classes, methods and variable names. Another important part of your mental model is understanding how these methods are supposed to be used. Once again, this is quite clear when you initially form your mental model. When you come back later, it’s often quite difficult to reconstruct all the intended uses of your classes and methods. Usually, this is because different usages are scattered throughout the rest of your program. Sometimes, even across many different projects.

This is where I find test cases to be very useful. Besides the obvious benefits associated with knowing if a change broke your code, tests provide a full set of example use cases for your code. Instead of having to trawl through a hundred files looking for references, you can get a full picture just by looking at your tests.

Bear in mind that in order for this to be useful, you need to have a complete set of test cases. If your tests only cover some of your intended uses, you are going to be in trouble later on if you assume the tests are complete.

Problem #5, No Clear Path Between the Different Models

Often your code is technically very good, and extremely elegant, but there is a very unnatural jump from program intent to semantic model to code. It’s important to consider the transparency of the stack of models you select. The journey from the program intent to semantic model to code needs to be as smooth as possible. You should be able to see all the way through each model to the problem. It may at times be better to choose a particular class structure or algorithm not for its elegance in isolation, but for its ability to connect the various models and leave a natural path towards reconstructing intent. As you go from abstract program intent to concrete code, the choices you make should be driven by the clarity with which you’re able to represent the more abstract model below it.

Problem #6, Inventing Algorithms

Often, we as programmers think we are inventing algorithms to solve our problems. This is hardly ever the case. In almost all cases, there are existing algorithms that can be put together to solve your problem. Algorithms like Dijkstra’s algorithm, levenshtein distance, voronoi tessellations, etc. Programming for the most part consists of choosing existing algorithms in the right combination to solve your problem. If you are inventing new algorithms, you either don’t know the right algorithm or are working on your PhD thesis.

Conclusion

In the end, it boils down to this: as a programmer, your goal is to construct the simplest possible semantic model that would solve your problem. Translate that semantic model as closely as possible into a syntactic model (code) and provide as many clues as possible so that whoever looks at your code after you can re-create the same semantic model you originally had in mind.

Imagine you are leaving breadcrumbs behind you as you walk through the brightly lit forest of your code. Trust me, when you need to find your way back later on, that forest is going to seem dark and misty and foreboding.

It sounds simple, but in reality it is very difficult to do well.

A special thanks to Nic Young and Ulvi Guliyev for their input on this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Allan Gray
South Africa South Africa
Since I was young I always preferred teaching myself to learning from a structured syllabus.

This didn’t always make me popular with my teachers. I guess sleeping through the whole of Romeo and Juliet in an English class doesn’t make a great impression. On the other hand I formed an obsession with always researching something.

Over the years these obsessions would jump from topic to topic ranging from physics to lean manufacturing. Photography to functional programming. Cognition to physiology and strength training. Kitesurfing to fire poi. My obsessions may be random but are always led by my curiosity.

Over time I noticed that I was forming connections and finding patterns in unrelated fields and using knowledge I gained from studying the rudiments of things like cognitive psychology when trying to figure out why I was seeing certain behaviours in the teams I was working in.

I'm interested in communicating those underlying principles and connections in a way that is useful to others.

I have been working as a programmer, architect and manager for the last 12 years so there is a strong bias to helping junior programmers and software development teams improve.

I focus on underlying principles and not technologies.

Comments and Discussions

 
QuestionGood subject Pin
KP Lee6-Jan-15 10:08
KP Lee6-Jan-15 10:08 
Question[My vote of 2] My vote of 3 Pin
jediYL5-Jan-15 13:46
professionaljediYL5-Jan-15 13:46 
QuestionClear, concise - Excellent Pin
Leo565-Jan-15 2:38
Leo565-Jan-15 2:38 
GeneralExcellent subject and article! Pin
Omid Saadat5-Jan-15 0:16
Omid Saadat5-Jan-15 0:16 
GeneralMy vote of 5 Pin
Renju Vinod16-Dec-14 17:37
professionalRenju Vinod16-Dec-14 17:37 
QuestionCode examples would be nice Pin
Steve Wellens14-Dec-14 5:45
Steve Wellens14-Dec-14 5:45 
QuestionNice one Pin
R. Giskard Reventlov14-Dec-14 5:33
R. Giskard Reventlov14-Dec-14 5:33 
GeneralMy vote of 5 Pin
Afzaal Ahmad Zeeshan12-Dec-14 7:27
professionalAfzaal Ahmad Zeeshan12-Dec-14 7:27 
GeneralMy vote of 5 Pin
Peter Leow12-Dec-14 1:52
professionalPeter Leow12-Dec-14 1:52 
GeneralI wish I have this during start of schooling Pin
Member 1111017914-Nov-14 12:02
Member 1111017914-Nov-14 12:02 
GeneralRe: I wish I have this during start of schooling Pin
KarstenK12-Dec-14 4:33
mveKarstenK12-Dec-14 4:33 
code can get clearly written, so you wont need much comments.

a simple example:

int multiply(int factor1, int factor2);


You need common standard so called "coding guide lines" which are clear and understandable.

Most important rule: if you do something not clear: WRITE A COMMENT!!! Wink | ;-)
Press F1 for help or google it.
Greetings from Germany

QuestionRight on target Pin
jrscherer8-Nov-14 5:31
jrscherer8-Nov-14 5:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.