Question

As a programmer I have found my code frequently elicits the reaction "I don't understand". Whenever I get this response I try my best to explain my code patiently, and not make anyone feel afraid to ask questions.

I am pretty sure I've got the second part right, people are certainly not afraid to ask questions about my code!

However I have good reason to believe my explanations are not effective. I routinely have hour long discussions trying to explain my code, and on many occasions the conversations have ended with my coworker saying they still don't understand, but they have somewhere else to be (lunch, or home, or a meeting, etc).

I believe this is a problem with my code, as I cannot recall the last time some else's code has taken an hour of explanation to understand. As well, I rarely see my coworkers spending anywhere near as much time explaining their code to each other.

Specifically, when posed with the question "I don't understand your code", what are some strategies I can use to explain my code?

I have previously employed the following follow up questions, and I am looking for better, or at least more, follow questions:

  • What part specifically seems to be confusing?
    • Sometimes this works, but often the answer is "the whole thing". I have been in meetings with 5 other programmers, where all of the programmers agreed they didn't understand my code, but none of them could give any specifics parts which were confusing.
  • Are you familiar with pattern "X"?
    • I have tried to learn the names of the coding patterns I tend to use. I will bring up these names, such as "the visitor pattern", and ask them if they are familiar with this pattern. If they are familiar with it I try to show them how my code is an implementation of that pattern. This seems to stop them from immediately asking more questions, but invariably we seem to come back to the same code, and so I am afraid while they fully understand the pattern, the connection between the pattern and my code is not obvious.
  • What are some solutions to problem "X"?
    • Sometimes I try to get them to actively engage with solving the general problem, hoping that if they explain how they would solve it, I can show them the parallels between their solution and mine. This works, however often times the problem is a bit too complicated to just solve in your head, and so they can't quickly describe how they would solve it.

ADDITIONAL INFORMATION:

The code I work on most frequently is framework/architectural code, often legacy code which no one presently with the company is familiar with. My team is very busy, and while they are patient, they honestly don't have the time to help me work through legacy code. As a result my approach as been to fully understand it myself, and then try to explain it to my team during team meetings.

They will be interfacing with it though, and they interface with the existing code on a daily basis.

An example of this type of code would be our log pipeline, which takes browser errors, server errors, service errors, http logs, javascript logs, web logs and correctly joins time with session information, going through a few steps before the data eventually ends up in splunk. It isn't exactly complicated, but it also isn't exactly trivial, as the servers needed to handle tens of millions of logs per day, without any significant impact on server performance (our servers are already more expensive than my yearly salary).


CODE SAMPLES

(Please excuse the text dump. I tried to keep it short, but code samples seem like the best way to demonstrate my problem).

I put together a code sample of one piece of code that seemed to confuse my teammates the most. I no longer work at the company, so it isn't the exact code, and the exact code was scrapped anyway (it confused everyone, so we all agreed no one should use it).

A bit of background, our company was beginning a major rewrite, converting from a legacy framework to React/Typescript/Redux. There were regretting using Redux, but due to our browser support restrictions we were unable to use Mobx. As a result we were using Redux poorly, trying to make it work like Mobx, or KnockoutJS. The majority of our reducers simple set state, with the caller knowing exactly what they wanted to set (not how Redux action/reducers should work). However due to time constraints, we simply could not switch frameworks, and had to make Redux work. That was at least 3-4 years ago, and I would be surprised if the team was still using Redux now.

(I've linked to the Typescript playground for my code, as it is a bit long for a question)

An example of existing code can be found here: original code

I am opposed to this style, as although it is clear, it requires changing 4 pieces of code (spread across 3 different files) to add a variable. The steps to adding a new variables are: update the state definition, add a new action, add to the actions union, and add a reducer handler.

I made a builder class (a term I may not be using correctly, basically it is like yargs, https://www.npmjs.com/package/yargs, where you make a series of chained function calls to create a more complex object) that makes it possible to only add properties to one place, while preserving the types of everything.

(This was before Typescript mapped types, which provides alternatives to the builder approach).

A recreation of my proposed code can be found: changed code

Was it helpful?

Solution

Siloed

Framework and Infrastructure code is tricky. It is the dark and messy parts of the code base that hit actual walls, and the worst part is that often the solutions are counter-intuitive, as they have to work around the user (aka. programmer), language decisions, and platform idiosyncrasies.

What has happened is that you've become an expert, and become effectively siloed.

The worst part is this kind of code does not have an effective boundary between your code and the users code.

There are a few ways to deal with this situation.

Share the Pain

Nothing breeds knowledge quite like having to shovel the S#*T yourself.

Not everyone on the team will have the head for infrastructure/framework work, but there will be a few. The best way to start distributing knowledge is to start getting these developers to work on small areas of the infrastructure/framework.

Of course maintain oversight (it is critical after all), but you need to start getting the other developers thinking across the silo border.

Enforce the Border

If for one reason or another tearing down the silo cannot be done. The other strategy is to enforce better boundaries between your code and their code.

This can be done in a number of ways.

  • Push code into libraries, and while you do it stabilise and document the interface.
  • Institute Idioms. Idioms are easily remembered and can vastly simplify a complex implementation to a simple story from the users perspective.
  • Documentation. If your going to explain it more than once, best to capture it for future reference. Better yet if you can multimedia it with a recording of your voice/presenting it/graphs/pictures, or can link it to the source itself somehow.
  • Push back on explaining. Its not your job to describe the systems nuances to everyone who asks. If it were, you would be a technical writer not a software developer. Knowledge that was not hard won, does not stay learned. Don't waste your efforts.
  • Push implementation out of the framework up into user space. If they are seeking you out a lot to understand it, they are trying to alter it, ergo it is in flux and at the wrong shearing layer in the tech stack. Provide a plugin interface for it (such as a strategy pattern) and a default if others need it in the framework.

OTHER TIPS

Personally, I have encountered multiple variants of code-that-is-hard-to-understand, and each needs a different way to cope with:

  • Simply messy. Formatting is off, variable names are not clear, convoluted control structures, methods with multiple responsibilities. => Learn cleanliness, use a formatter.
  • Patternitis: applying patterns for each and every aspect of the code. Instead of a simple "if" apply a subclass strategy with two visitors derived from an abstract visitor, which is created by two factories which are derived from an abstract factory, which is selected via a strategy... (search for "FizzBuzz enterprise edition" on the net) => Understand the basis of patterns. A pattern is not a standard way of solving things, it is a name that you tack on a solution that you found. You don't go from a problem to "which pattern might I apply here", you go from a problem to a solution, and then say "oh look, my solution follows the form of a visitor pattern, it has a name!"
  • Abstraction mess: instead of having a simple class that does a thing, have two to five abstract base classes, which results in a control structure, where a simple function call goes through abstract and overriden methods in subclasses all over the place. => YAGNI. Embrace this piece of extreme programming. There is no "I might need this in the future, so I split it off now".
  • Misunderstood clean code: "good code needs no comments", and then writing code, that is not self-explanatory, but without any comments b/c "it's good". => These are the hardest to crack. If anyone knows a solution, I'd love to hear suggestions myself.
  • Mathematicians code: looks like a proof on a whiteboard. No variable name longer than a single character, no comments, no explanation. => Teach the mathematician the values of software development, and hand them a copy of "clean code".

What many junior programmers don't understand at first is, that the greatest value in software is SIMPLICITY. Don't try to be clever, don't try to optimize runtime (at least, not until you actually find a concrete problem), don't add an extra abstraction because you might need it in the future.

Always do the simplest thing that solves the problem at hand. No more. No less.


Seemingly, the part about the "misunderstood clean code" needs some clarification. I never meant to tell anyone personally that good code needs no comments.

The remark comes from the following situation, which I often encountered with some ex-colleagues:

Programmer A: I have written cool code, I understand it. As I have read the book "clean code", I know that comments are not necessary for self-explanatory code, therefore I do not comment.

Programmer B: I don't understand a single line of what you have written.

Programmer A: Then you are not smart enough to understand good code.

The problem here is, that Programmer A does not see his own mistake, but loads it off to lack of understanding on B's side. As this is his understanding, he'll probably never change his ways, and continue to write mumble-jumble which only he understands, and refuse to comment it, as he sees it as plainly self-explanatory. (Unfortunately, nobody else shares that view.)


Regarding your code samples: I am not really proficient in TypeScript, so frankly, I don't exactly understand the finer points of what you have done there. (Which probably already points to the first problem.)

What I can see from the first glance and a few line counts:

You have replaces 40 lines of perfectly readable code (heck, even I can understand that) with roughly 60 lines of hard-to-understand code.

The resulting change in usage is probably something along the lines of:

// old
let v = userReducer(x, y);

// new
let v = new ReducerFactory().addStringProp("x").addStringProp("y").createReducer();

So, the question is "why?".

Let us assume that you have taken half a workday to do the concept, the implementation, and the testing. Let us further assume, that one developer day costs $1000.

It is quite well known that code that must be maintained has a much higher cost of ownership than the price of initial development. From experience, a good guess is times ten for simple code, and times twenty for complicated code (which I apply here.)

Therefore, you have taken $500 * 20 = $10000 of company money to create which business value? That the creation of a given object is somewhat "more elegant" in your personal view?

Sorry, as I see it, you do not need arguments to explain what you have done. You need education and experience in software architecture, where you learn to put value on the right things in business.

The original code is extremely obvious, that is a very good thing. It is boring in the best way, there are no suprises there and you can very quickly see what it is doing.

Your code is very abstract and hides what is actually happening in your reducers. If I were entirely new and had to understand the codebase, it would be much harder in your example. And even if you understand it in general, for me it is much harder to reason about this because it is so abstract. There is of course a place for this kind of abstraction, they're not inherently bad (I think Redux Toolkit has some similar things, but I haven't used Redux in a long time). But you have to think hard about whether the abstractions are worth the cost.

My experience with similar abstractions is that you very quickly run into cases that differ just slightly from the common case. You can then either make your abstraction more powerful and complex, or fall back to plain, boring code. In those cases I did mostly regret my previous choices.

I can understand the original code in seconds, I think a bit of repetition is a low price to pay for that. You should take it seriously when people tell you they don't understand your code, and it's not just a single person. Either your code is actually overly complex and abstract, or your team is simply not comfortable with certain programming styles. In both cases you need to write simpler code to work efficiently in a team.

With out wishing to be rude, if you find that other experienced engineers are regularly struggling to understand your code, then your actual problem is how to make the code simpler, not how to explain the complexity.

If you are working as part of a team your first priority is to make your code as readable as possible. Sooner or later somebody is going to have to maintain that code - either to fix a bug or add a new feature. If they struggle to understand what's going on you will have a buggy updated system and an unhappy engineer. That engineer might be somebody who is not currently in your team and you didn't have the luxury to explain the code to. Worse, it might be YOU in 6 months time when you've forgotten all the little tricks you employed.

Get people to look at your code sooner.

Every code base I've ever touched conveys a mindset. A way of looking at things. A big part of my job is molding my brain into something that can work with this existing mindset. This can be jarring if the mindset clashes with my personal style. I try to put more effort into being effective with the existing mindset than into imposing my personal style.

Ask yourself: how well do you understand your coworkers code? Even if you aren't working in it if you don't understand it you don't understand your coworkers. You don't understand the mindset.

If you're working alone on something that doesn't connect much to the existing code it's easy to let your personal style take over. Doing this feels comfortable as you write code but will bite you when others, who are steeped in the mind set, look at your code.

I'm not saying you're forever a slave to the existing mindset. But anytime you step away from it check with your team and see if you aren't making things too weird for them. Because when you go too far what you're doing is imposing a new mindset. It doesn't matter if yours is better. It matters how many mindsets you're making people deal with. One at a time please.

Judging from your experiences you need to get people looking at your code sooner. All of your energy seems to be aimed at convincing them that they should understand your code as is. The problem is they shouldn't need an explanation.

If you let too much time go by changing your code becomes expensive. If this issue had come up sooner you'd have been more likely to change your code in reaction. It seems now you're too far gone and are simply trying to convince people to like it as is. The problem with that is this code will still be here long after you're no longer around to explain it.

This might seem a little soul crushing, but in my experience my wild new ideas catch on much more effectively if they aren't only my ideas.

For what its worth, I also find your changed code quite confusing compared to the original code.

The original code is beautifully simple. Apart from the interface declaration it is basically a three-way switch. Your code is a lot more complex including a factory, a builder pattern and something called a randomIdentifier (WTF?). Complexity leads to bugs and code that is hard to change, so it is the enemy of maintenance. If I had to review this code as pull request, my first question is what real-world problem the old code has which justifies this manifold increase in complexity?

The problem is not that I don't understand the patterns in use. The problem is I don't understand why you need all these patterns. To put it another way, it is not that any particular part of the code is especially confusing, it is more that I don't understand why you think you need all this complexity in the first place.

So I think you should focus more on explaining why you write the code you do, rather than how the code works. Show some concrete problem with the existing code which everybody will agree is a problem. For example that you use a lot of time adding new properties or that you often have bugs where actions are added incorrectly. Then explain why your code makes these problems go away.

I believe this is a problem with my code, as I cannot recall the last time some else's code has taken an hour of explanation to understand.

I'm happy to see this conclusion. Most commonly, people blame others for not understanding. It shows you can see things from other people's perspective, which is going to help you solve this issue.

Specifically, when posed with the question "I don't understand your code", what are some strategies I can use to explain my code?

Clean coding means writing readable code, preferably code that is readable enough that it requires no documentation. Your explanation, even if not written down, counts as "documentation".

Rather than trying to explain your code, rewrite your code to explain itself. The better response here isn't to tell your coworker what the code does (I'll humorously refer to that as "devsplaining"), but instead ask your coworker what is unclear about it. Take that feedback and revisit your code to see if you can rewrite it so that the unclear parts become clear.

I can't tell you that all code ever written should be crystal clear without any documentation or comments - that's overreaching. But crystal clear code is the goal, even if you never perfectly attain it.

Sometimes this works, but often the answer is "the whole thing". I have been in meetings with 5 other programmers, where all of the programmers agreed they didn't understand my code, but none of them could give any specifics parts which were confusing.

Assuming these developers are all of equal or greater skill to you, and they have the expected contextual knowledge of the application, this signals to me that you need to rework your code from the ground up.

"Are you familiar with concept X?"

I can't make any final conclusions here, but I do want to point two things out:

  • Watch out with your tone when you ask if someone is familiar with something. It can come across as passive aggressive or suggesting that the other person lacks the required skill to understand your code. Even if that is objectively correct (e.g. a junior dev), it's still not the best way to express yourself.
  • If you are genuinely dealing with developers who don't know a design pattern you've used, that seems to suggest that maybe your coworkers are lower skilled than you. However, even if you used a design pattern correctly, it's still possible (and I would guess likely, based on some indirect inferences from your question) that your low code readability is obfuscating an otherwise correct implementation of a design pattern.

Sometimes I try to get them to actively engage with solving the general problem, hoping that if they explain how they would solve it, I can show them the parallels between their solution and mine. This works, however often times the problem is a bit too complicated to just solve in your head

If that is the case, then the code you wrote has not been reasonably abstracted. Using clean coding and good practice, your code should already be subdivided into easily digestible chunks of logic, and you should be able to discuss one of these chunks by themselves. If you can't to that with your code, then your code is not separating its responsibilities correctly.


You haven't really shown concrete examples so I cannot judge your code. By extension, I cannot conclude whether you're making things too difficult or your coworker's skill it too low. However, based on what I read in your question, my educated guess is that your code works but is unreadable, and you're currently not in an environment where clean coding is actively enforced, so you're only relying on wondering if the code works or not, not whether it passes the smell test.

You said your colleagues communicate among themselves without much confusion, so what I would do is inspect their code. Do you understand their code better than they understand yours? How is their code different?

Secondly, putting your code itself aside for a minute, you also seem to struggle with explaining your intentions to others. That's also a problem. Me and my coworkers are generally able to explain design decisions to each other without even looking at the code itself. We of course don't delve into specifics, but explaining the general design (i.e. "mental diagram") is not something you need to see concrete code for. For reasonably experienced developers, they can fill in the blanks on the concrete implementations of the design for as much as they need to in that conversation.

I think both your code quality issues and problems with explaining your design stem from the same problem. I'm paraphrasing here, but there's a well known quote that strikes at the heart of this:

Software development is breaking a problem down into a series of smaller and smaller problems until each individual problem is trivial to solve.

My personal addition to that adage is that programming (as opposed to development) is implementing those individually trivial problems.

If your code and explanation are too convoluted, then you haven't broken the problem down enough for things to become trivial.

Never forget that any solution to a problem, no matter how difficult that problem is, is really just a sequence of steps, each of which is trivially simple by itself.

Two suggestions come to my mind:

  • Use models/diagrams to represent the different aspects of your solution/implementation. You can use a paper or a dashboard just to simply draw the ideas/concepts and the relations between them. That could help to represent your code in a simpler and more understandable way, and would allow you to explain it better.
    • The idea is to just put drawing into your ideas, as the phrase “A picture is worth a thousand words”. Simplified UML-like diagrams can be used or whatever that fits into the subject so it can help to simplify the understanding.
  • Show your code running. It could be as simple as giving play in debugging mode on your development environment with a few breakpoints in the key places. Maybe this couldn't be possible in all scenarios, but if it is possible it could be useful.

Then, having into account that all software processes are about [input]=>{process}=>[output], you can choose some relevant inputs for your code with its corresponding expected outputs, and guide your coworkers through the transformation process that your code is performing (using the help of the created models and the debugger -if available-).

I agree with the other answers and comments that point to the issue that the code should be understandable to allow maintenance, etc, but as I understand your question was not about the code itself, but about how to best explain it to your colleagues.

I ask: What part specifically seems to be confusing? Sometimes this works, but often the answer is "the whole thing".

What this tells me is that they do not know where to start. To understand anything, you need some anchors and a clear entry point. You need to see the coarse logic first and then work from there to the nitty gritty. I suggest to focus on that coarse logic, make it as visible as you possibly can and hide from view what is not essential to the big picture.

I will bring up these names, such as "the visitor pattern"

Hmja... I once had to work with that. And I can totally relate to your co-workers. I looked it up and got to understand what it was but then still did not see a good reason to apply it, to solve the problem at hand. So I am thinking you may be using the wrong patterns to solve problems. That can be most confusing.

The visitor pattern is hard because it lacks a clear hierarchy. You cannot find your way by simply zooming in from coarse to detailed as I suggested. Most programmers are stack oriented. Take away that backbone and they are lost.

So, is that fancy pattern really appropriate or is it just something you got used to applying?

It would be worth passing you code to someone who is not in a hurry, to have him look at it seriously. Now you can only guess what is wrong.

Common names, terms and language

Others have talked about code-style, but I think you might be struggling to explain things because you're naming standard things in an unexpected way, either in the code or in your verbal explanation. If you use the same terminology as your co-workers then you can save a lot of time on the explanation as you can skip over the bits of standard implementation. Some of these common terms are quite natural (file, , some of it is built into programming languages (class, pointer etc.), but some has to be learned (abstract patterns, language/domain specific words), that can be achieved by reading books/articles or just listening to your co-workers and using (where appropriate) the same terms that they do to describe code objects.

Taking a slightly facetious example, imagine someone explaining that a tool "enables RAM to disk bit flushing when the floppy-icon command instance is activated", that might require some detailed explanation as to what's going on, but "click the save button" probably won't. As a bonus, if your naming things the same way, chances are your code will be more standard and your style will be more consistent.

This is something I struggled with in my early days as a developer, but reading and listening helped me understand more terms and communicate better. It's also something that you can take with you throughout your career to different languages, employers, and even different countries.

I don't know the language used in the examples, but I understand the existing code. I could easily modify it or fix bugs. However I don't understand the proposed code. This in itself isn't a problem, since I don't know the language. But it makes me think the code is harder to understand than it should be.

In your code I'm seeing 2 patterns I've seen before:

  • The code is very complex. The result is that no one except you understands it without a lot of work. Which takes time and energy for something that should be relatively simple. I've had a colleague that coded like this. His code was great, and performed well. But it was impossible for anyone else to understand what was happening, we had to refuse touching the code. What I learned from that was that it's more important to make code that's easy to understand than to make it perform, or even to follow rules and patterns.
  • The code is completely refactored, instead of just fixing what needed to be fixed. I had another colleague that refactored code he didn't know in order to understand it better. The problem with this approach is that most people in the team knew the code, and could do changes pretty easy. That is until he had refactored the code, after which we didn't know the code any more and needed to relearn it. My lesson from that is that small changes should be as small as possible, refactoring should only be done when it's really needed. The cost of the refactoring is to high otherwise.

Now let's try to answer your question. You ask:

Specifically, when posed with the question "I don't understand your code", what are some strategies I can use to explain my code?

If it's multiple persons that don't understand your code it's likely a problem with the code you write, and not with the other developers. Instead of trying to explain your code, try to get information from them about what they don't understand. And change the architecture and code to make sure it's easier to understand. Do this early and often, from the planning and architecture until the code is finished.

If this happens to you repeatedly, then there are two possibilities: Either your colleagues are pranking you, or you are writing code that isn’t understandable.

I suspect that you are writing code that is just too complicated for its own good. And frankly, I wouldn’t discuss your code with you for four hours in a code review. If I don’t understand it, then I can’t maintain it, so it cannot pass the code review.

Maybe the next time you discuss how to approach a problem with someone before you start writing the first line of code, and you will see a much simpler solution. And maybe specifically before you try to apply the visitor pattern you think twice or three times whether it actually gains you anything.

PS. I saw your code example. I wouldn't ask you to explain it, I would ask you to throw it away. If I found it in an existing code base, it would have to be refactored to the original code. Your coworkers are very, very patient.

PPS. You managed to take some very, very simple code that I can read line by line and understand everything immediately, into a convoluted mess that doesn't make any sense except after a thorough analysis. So what are you going to do if you have code to handle something that is in itself difficult? When you have a difficult problem?

I guess there are two possibilities:

  1. Your code IS a bit overly complex
  2. Your code isn't too complex but your team-mates are struggling to understand it for some reason

Either way, you're right to be concerned as they will likely be the ones maintaining it so it's important to bring them along with you.

With respect to 1, it's hard to tell without code samples but do YOU think it's too complex?

More objectively, how well does the code adhere to SOLID principles? How well is the code commented? Are the classes/methods/functions self-documenting?

How easy is the code to unit test? If unit tests are hard to write, it's often a sign that the code is overly complex.

Also static code analysis could provide an objective measure of complexity and might indicate where you could refactor.

But assuming it's NOT too complex, I would go for early and frequent peer review as you write the code. That way you can bring the other developers along with you as the implementation develops rather than present the finished article which seems to be causing confusion.

If they are struggling with some of the concepts you are introducing (design patterns etc.) then maybe some brown-bag sessions on those topics where you have space to discuss and learn outside of the code you are writing.

Idiomatic code and uniform style

Many things can be written in many different ways that work equally well, however, for anyone maintaining and reading the code it's much better if they are written in the "expected" default style, using common patterns and avoiding deviation from the standard way without a good reason.

This essentially comes to code standards - some languages (e.g. Python) have universally accepted standards of how code is supposed to look like and what is 'idiomatic code'; in other cases there are company-specific guidelines (e.g. Google C++ style guid https://google.github.io/styleguide/cppguide.html)

All these cases restrict the writer of the code in order to ensure that the result is more uniform, with the benefit of increased readability (as all code uses the same, familiar patterns) and easier maintenance by others, as it's easier from them to understand the code if it's written in the same style as they write their code; and it's harder to understand code (needing more explanations) if each developer codes in a different style.

Licensed under: CC-BY-SA with attribution
scroll top