354
votes

How would one know if the code one has created is easily readable, understandable, and maintainable? Of course from the author's point of view, the code is readable and maintainable, because the author wrote it and edited it, to begin with. However, there must be an objective and quantifiable standard by which our profession can measure code.

These goals are met when one may do the following with the code without the expert advice of the original author:

  • It is possible to read the code and understand at a basic level the flow of logic.

  • It is possible to understand at a deeper level what the code is doing to include inputs, outputs, and algorithms.

  • Other developers can make meaningful changes to the original code such as bug fixes or refactoring.

  • One can write new code such as a class or module that leverages the original code.

How do we quantify or measure code quality so that we know it readable, understandable, and maintainable?

2

19 Answers 19

400
votes
+100

Your peer tells you after reviewing the code.

You cannot determine this yourself easily because as the author, you know more than the code says by itself. A computer cannot tell you, for the same reasons that it cannot tell if a painting is art or not. Hence, you need another human - capable of maintaining the software - to look at what you have written and give his or her opinion. The formal name of said process is peer review.

21
  • 6
    Nothing beats empirical testing.
    – user28988
    Commented Mar 22, 2012 at 17:39
  • 27
    +1 Your most important audience is your peers who are immersed, with you, in knowing the whys and hows of the problem you are working on and its solutions. Good code reflects your peer group's current understanding of this. Assuming the team is capable, thoughtful, and open to new ideas, "your peer telling you its good/maintanable" is, in my experience, a better definition by far then any other.
    – Doug T.
    Commented Mar 23, 2012 at 0:55
  • 73
    In my experience, this only works when your colleague know what is good and what is bad. Otherwise it will sounds like this : "You should write those code in the same method, that's easier to find code"
    – Rangi Lin
    Commented Mar 23, 2012 at 5:30
  • 12
    @RangiLin, well, your colleague may be right.
    – user1249
    Commented Mar 23, 2012 at 14:25
  • 13
    @Rangi You have to work with the colleagues you have. if they find your code difficult that is a problem with your code. In the longer term, you can educate them, or try to get better colleagues (you can move or you can influence the hiring process)... Oh yes, and do always remembers that they may be right.
    – MarkJ
    Commented Mar 27, 2012 at 22:57
231
votes

Sometimes, the best way to know, is to come back to code you wrote six months ago and try and understand what it was written to do.

If you understand it quickly - it's readable.

11
  • 36
    Yeah, that sounds good (and is true), but it's not a very good approach for deciding what/how to do today... Commented Mar 22, 2012 at 18:05
  • 10
    Dissection suggests it's taking a while and you have to do it carefully...
    – deworde
    Commented Mar 23, 2012 at 9:06
  • 3
    I might drop the timeframe for revisiting down to a month, or even less, for a first revisit. I think it depends on the complexity of the project and domain, as well as your mental approach. I find that in six months, I get distracted by seeing refactoring or optimization opportunities using tools or techniques I've learned since i first wrote the code, instead of real readability.
    – Chris Bye
    Commented Mar 23, 2012 at 15:14
  • 1
    @MichaelDurrant Each time you review old code you'll find pieces that should have been written differently and then you'll take that into account for the code that you're writing "today". Yes, it does take time to learn how to write good code.
    – dj18
    Commented Mar 17, 2014 at 16:19
  • 1
    @MichaelDurrant it still kind of is, since you can learn what unclear things you were doing six months ago, and not do those things today.
    – djechlin
    Commented Sep 2, 2014 at 14:22
93
votes

It is:

  1. maintainable if you can maintain it.
  2. easily maintainable if someone else can maintain it without asking you for help
  3. readable if someone else, on reading it, correctly understands the design, layout and intent

The real test for 1. is (as Alex in Paris and quant_dev say) that you can pick it back up after a few months doing something else.

The test for 2. and 3. is that someone else can pick it up, and figure out how to extend or fix your code while following the grain of your design. If they can't understand the design, how it relates to the problem space, or how your code is intended to be used, they'll hack a solution across the grain instead.

There are rules of thumb, principles (ie, rules of thumb someone wrote up nicely and gave a name) and all sorts of suggestions that may lead you in the right direction, or away from common pitfalls. None of them will guarantee the qualities you're asking for, though.

2
  • 2
    How about the considering the amount of time to spend on fixing a bug or modify existing behavior as a factor of maintainability? A piece of code which requires less time to do the same change is supposed to be more maintainable right?
    – VCD
    Commented Jan 4, 2018 at 1:25
  • 1
    Depends; sometimes making a modification well will require refactoring (which you can tell is needed because legible code makes it clear it's not intended to be used like that), which takes longer than hacking in a special case (which illegible code encourages because the intent is unclear).
    – Useless
    Commented Jan 4, 2018 at 11:07
30
votes

If your code follows the principles of SOLID and DRY and has a good set of unit tests around it, it is probably maintainable.

Is it readable? Read it. Do method and variable names make sense? Can you follow program logic without a problem? If the answer is yes, then the code is readable.

5
  • 8
    ...and after you read it, hand it to someone else to try to read.
    – jcmeloni
    Commented Mar 22, 2012 at 17:01
  • 20
    This is not a particularly good test. Many applications of those rules are subjective, and you can almost always read your own code right after it's been written.
    – DeadMG
    Commented Mar 22, 2012 at 17:39
  • 1
    "If the answer is yes, then the code is readable" ... by you. To see if it is readable to others, others must try reading it.
    – user1249
    Commented Apr 5, 2012 at 6:54
  • 2
    IMO, SOLID is overrated. Especially the 'S.' That or everybody misreads it. Commented Jun 16, 2013 at 2:34
  • I've run countless times into code that was DRY and SOLID and yet horrible. Following principles can give false impression that what you're writing isn't crap. Commented Sep 19, 2017 at 8:13
26
votes

Reading How To Write Unmaintainable Code - Ensure a job for life by Roedy Green, laughing, and learning.

...how to write code that is so difficult to maintain, that the people who come after you will take years to make even the simplest changes. Further, if you follow all these rules religiously, you will even guarantee yourself a lifetime of employment, since no one but you has a hope in hell of maintaining the code...

The essay gives you numerous examples of how to write bad code, using plenty of funny examples. It continues to explain how to utilize Creative Miss-spelling, Reuse of Names, the highly appreciated technique of Reuse of Global Names as Private.

In a humorous way the essay teaches you how to avoid all of the examples of unreadable and unmaintainable code.

Actually, I found it hard to believe that anyone would write code with similarities to the examples in the text. That was when I was fresh from school. But, after working for a few years I see code from the text every day…

1
  • See also waterfall2006.com/gorman.html Refuctoring is the process of taking a welldesigned piece of code and, through a series of small, reversible changes, making it completely unmaintainable by anybody except yourself.
    – Sjoerd
    Commented Apr 18, 2012 at 13:48
22
votes

Despite how it seems, there are some fairly objective measures you can consider. Books like C++ Coding Standards, Refactoring, and Clean Code have long lists of criteria to judge your code by, looking at things like meaningful names, function sizes, principles like coupling and cohesion, object design, unit testing, successive refinement, etc.

The list is too large to be amenable to a checklist, but you read the book and pick out a few key things to work on, then after several months read it again to improve further.

0
19
votes

The proof is in the pudding. Watch what happens after handing it off to a reasonably competent person. If they don't need to ask many questions relative to the difficulty of the code, you've done a good job.

This was an early lesson in my career. A mentor said, "Document everything, so that you can escape the program later on. If you don't anticipate questions when the answers are fresh in your mind, you'll have to figure them out when they're not."

3
  • 10
    Beware that people may refrain from asking questions due to fear of exposing their ignorance. You may even be perceiving those people as 'reasonably competent' in the first place due to their tendency to refrain from exposing it. So lack of questions may not be a good thing unless you know you both are being sincere.
    – john-jones
    Commented Nov 10, 2012 at 12:03
  • 1
    @HermannIngjaldsson - Fair enough. Of course if they're not competent and something breaks, you'll hear about it soon enough. "Help!!!!"
    – MathAttack
    Commented Nov 11, 2012 at 13:59
  • this seems to merely repeat what was stated in top answer
    – gnat
    Commented Jul 21, 2015 at 0:44
19
votes

I read through all the answers, and I noticed nobody mentioned code complexity.

There's a tight correlation between code complexity and readability/maintainability. There are numerous code complexity scoring algorithms, but I'll just talk about how McCabe complexity scoring works.

Basically, McCabe scoring reads through your code and computes the number of unique "paths" there are through it. If you use McCabe as your numerator and lines of code as your denominator, you get a pretty good approximation of "readability" as well.

If you've got 10 lines of code, and there are 300 paths through that code, that is some pretty unmaintainable code (difficult to change safely and easily), and it's probably not very readable. Conversely, if you've got 300 lines of code, but there is only 1 path through it (it has no conditions), it's both readable and easily maintainable.

Where McCabe falls down however is in that latter example. If I've got 300 lines of code with no conditions, there's a really good chance I've done "copy/paste reuse", and obviously that's not a good thing either. So there are system-wide metrics that you apply in addition to McCabe -- like duplicate or near-duplicate code detection.

2
  • 2
    This should be the answer. Measure. Other answers are more opinion than fact, if I can understand it, it must good? First measure using complexity analysis, then human refactor to look for duplication, etc.
    – Jon Raynor
    Commented Jun 20, 2014 at 14:36
  • 1
    Your final paragraph mentioning the drawbacks of McCabe score divided by LOC rather tends to invalidate the whole idea. If you need 300 paths through your code, why on earth do you think it will make the code more maintainable to use more lines? That's like saying that if a book presents intricate ideas, it should be a really big book rather than trying to communicate concisely. -1.
    – Wildcard
    Commented Jan 5, 2017 at 3:20
8
votes

One point I'd share is if the code is built in "modules," and when I say that I mean that you can change one thing in a module and easily have it working with the whole. It eliminates effects between unrelated things. Also:

  • Code is easy to reuse
  • Your code is flexible (this ties in with building in modules)
  • DRY - Don't Repeat Yourself

I highly recommend reading, The Pragmatic Programmer.

8
votes

Some Tests/Indicators:

  • Turn off the IDE. Can you still read your own code? When there's a bug is it fairly easy to trace through it by hand and figure out what class you'll need a breakpoint in to figure out that's where the problem is? Or when you do use the IDE do you just not even bother and just step through from the very beginning?

  • Does debug often becomes a game of wack-a-mole where fixing one bug creates 2+ more.

  • From trigger pull to something useful actually happening, how many method calls does it take? How many methods pass the exact same or most of the same exact parameters on to another method call?

  • How many files do you have to open to just add a simple new method to a class?

  • Think on patterns and practices you've adopted. Did you do it because they made perfect sense or because somebody convinced you that "it's the only way to do it?" or because you wanted it on your resume or because some rockstar dev said so.

1
  • 4
    Reading code without an IDE seems daft, especially as a measure of readability. This type of metric results in Hungarian notation style "solutions" which really undermine readability in the end.
    – rubenvb
    Commented Jun 15, 2017 at 11:22
8
votes

How would one know if the code he created is easily maintainable and readable?

You can spot easy to maintain and readable code by looking for these properties:

  1. Objects, methods and/or functions always do one thing.
  2. Methods and/or functions are concise (as in "brief but comprehensive").
  3. Objects, methods and/or functions do essentially what you think they are supposed to do based on their names.
  4. Code that is destined for re-use is actually re-usable.
  5. Last but not least, if you can immediately unit-test the code, you have likely written single-responsibility, modular code at the very least.

How would we know if we've written pretty messy and unmaintanable code? Are there any constructs or guidelines to know if we developed messy software?

  1. If you are reading through a method and it isn't apparent what the intent was, this is inelegant at best and likely unmaintainable at worst.
  2. If it doesn't seem simple, it probably isn't simple and that is a sign of unmaintainable code or code that will soon become unmaintainable.
  3. If there is a lack of symmetry (consistency) across the codebase, you are likely looking at unmaintainable code.
4
  • I disagree that your refactoring example is clearer. I agree the original code needs work, but purely in terms of clarity and communicating intent I think the original is much better. I'm very suspect of any refactoring that claims it improves clarity while introducing regex.
    – Roy
    Commented Oct 1, 2014 at 11:17
  • 1
    @Roy, yes, fair enough. I probably should have never added that code sample. Granted, it was almost 3 years ago, but even then, I probably should not have used PHP (I'm cringing just looking at it now), and I should have not used a regex since it is one of those things that some people can look at and immediately get it but for others, no matter how succinct, regexes are an immediate turn off. I'm going to edit the answer and drop the code sample. Thanks for the comment. Commented Oct 1, 2014 at 17:09
  • How can an object do one thing? Commented May 5, 2015 at 17:09
  • @KorayTugay Think of it this way. If an object is describing more than a single cohesive concept, you've got a smell. Commented May 7, 2015 at 6:37
7
votes

In a single word, Experience.

To start, you need to have put in the ground work, so I can't recommend more highly that programmers should take the time to read books such as Refactoring, which will provide some of the more essential tools in a programmers arsenal that will improve your ability to maintain code, and Clean Code which has been written by some of the most highly recognizable talents in our field, and which describes nearly everything you need to understand in order to ensure your code is clean and readable.

No amount of reading however is a substitute for hard-earned experience. You really need to have worked with code for a while in order to fully appreciate the difference that attention to code quality can make. Through experiencing the pleasure of working with clean, well factored code, as well as the pain of working with code spaghetti, you learn to better understand what the authors of these books were really trying to teach you, but you do so in the wider context of real live production code, where the quality of what you do really matters, and impacts your ability to work easily with your code on a daily basis.

It also helps to have either a good mentor, or a peer with the experience to confirm that you are putting the effort into writing code to a high standard. This is just one reason why code reviews can be so useful. Using code checking and formatting tools can also be a very useful aid to ensure that you are keeping things clean. Nothing however compares to experience earned through years of writing software, such that you automatically find yourself writing code that is clean, readable, and structured simply for ease of maintenance, and all because you've made it a habit to apply best practices for so long.

3
votes

Readable and maintainable code: Code that, upon first-sight, a programmer can understand well enough to be able to easily:

  • re-use it via its interface, or
  • debug it, or
  • change its behaviour. (add/remove a feature), or
  • optimise it
  • test it

This boils down to 'clarity'. i.e How many questions does the programmer have to ask of a particular segment of code before they are sure that they 'understand what it does well enough' to achieve the current task in hand without causing unexpected side-effects.

The book 'Code Complete, by Steve McConnell' goes into this in great detail.

He goes through various metrics that you can use determine if code is of good quality.

See an example here: http://books.google.co.uk/books?id=3JfE7TGUwvgC&lpg=PT376&pg=PT389#v=onepage&q&f=false

2
  • this doesn't seem to add anything substantial over points made and explained in prior answers
    – gnat
    Commented Jul 21, 2015 at 0:54
  • I think one of the most substantial things it adds is a reference to Code Complete, which no previous answer mentions. That book is one of the most significant and influential books that is focused on writing readable and maintainable code. Anyone who has read that book would not need to ask 'How would you know if you've written readable and easily maintainable code?'.
    – JW01
    Commented Aug 26, 2015 at 11:35
3
votes

Without being puritanical: prefer functional style. Most languages these days (.NET, Ruby, Python, Javascript, etc.) support and promote it (e.g. LINQ, underscorejs).

It is exceedingly easy to read.

var recentTopCustomers = OrderList
    .Where(o => o.Date >= DateTime.Now.AddDays(-5))
    .Where(o => o.Amount > 1000)
    .OrderBy(o => -o.Amount)
    .Take(10)
    .Select(o => o.Customer);

It forces each node to have single, focused intent lending to clarity of purpose. And because each discrete task is isolated popping out, plugging in, and rearranging nodes (operations) to different ends is trivial. This lends to ease of maintenance.

2
votes

Minimize side effects (ideally have none)

A function that causes 3 changes to states outside of its own scope is much more difficult to reason about and maintain than one just which just inputs something and outputs something else. You can't just know what the function does, you have to remember what it did and how that effects all other relevant functions.

For OOP minimizing side effects also means classes with fewer members, and especially fewer members that can modify the class's state, since member functions can modify states beyond their own and have side effects (they can manipulate the class's internals, e.g.). It also means classes with fewer data members of their own so that there's less state for those methods to tamper with and fewer side effects they can cause.

As a simple example, imagine trying to design a fancy data structure which can maintain a sorted state which it uses to determine whether to perform binary or linear searches. In such a case, it might be useful to separate the design into two classes. Calling sorted on the unsorted class might then return a data structure of another class which always keeps its contents sorted. Now you have less side effects (therefore less error-prone and easier to comprehend code) as well as more widely applicable code (the former design would be wasteful both in processing and human intellectual efficiency for small arrays that never need to be sorted).

Avoid Superfluous External Dependencies

You might be able to implement the most terse code imaginable with maximum code reuse by using 13 different libraries to accomplish a relatively simple task. However, that transfers intellectual overhead to your readers by then having to make them understand at least parts of 13 different libraries. This inherent complexity should be immediately appreciated by anyone who tried to build and comprehend a third party library which required pulling in and building a dozen other libraries to function.

This is probably a very controversial view but I would prefer some modest code duplication to the opposite extreme provided the end result is well-tested (nothing worse than untested faulty code duplicated many times over). If the choice is between 3-lines of duplicated code to compute a vector cross product or pulling in an epic math library just to shave off 3 lines of code, I'd suggest the former unless your entire team is on board with this math library, at which point you might still consider just writing 3 lines of code instead of 1 if it's trivial enough in exchange for the decoupling benefits.

Code reuse is a balancing act. Reuse too much and you transfer intellectual complexity in a one-to-many kind of way, as in those 3 lines of simple code you saved above comes at the cost of requiring the readers and maintainers to understand far more information than 3 lines of code. It also makes your code less stable, since if the math library changes, so too might your code. Reuse too little and you also multiply the intellectual overhead and your code ceases to benefit from central improvements, so it's a balancing act, but the idea that it's a balancing act is worth mentioning since trying to stamp out every little form of modest duplication can lead to a result that's as difficult to maintain, if not more, than the opposite extreme.

Test the Crap Out of It

This is a given but if your code doesn't handle all input cases and misses some edge cases, then how can you expect others to maintain code you wrote that you didn't even get right before it transferred to their eyes and hands? It's difficult enough to make changes to code that works perfectly let alone code which was never quite right in the first place.

On top of that, code that passes thorough tests is generally going to find fewer reasons to change. That relates to stability which is even more of a holy grail to achieve than maintainability, since stable code that never needs to be changed incurs no maintenance cost.

Interface Documentation

Prioritize "what things do" over "how things do them" if you can't devote equal time to documenting both. A clear interface that is obvious in its intentions about what it will do (or at the very least, what it's supposed to do) in all possible input cases will yield a clarity of context to its own implementation which will guide in understanding not only how to use the code, but also how it works.

Meanwhile code that lacks these qualities where people don't even know what it's supposed to do is SOL no matter how well-documented its implementation details are. A 20-page manual on how source code is implemented is worthless to people who can't even figure out exactly how it's supposed to be used in the first place and what it's even supposed to do in all possible scenarios.

For the implementation side, prioritize documenting what you do differently from everyone else. As an example, Intel has a bounding volume hierarchy for their raytracing kernels. Since I work in this field, I can recognize the bulk of what their code is doing at a glance without sifting through documentation. However, they do something unique which is the idea of traversing the BVH and performing intersections in parallel using ray packets. That's where I want them to prioritize their documentation because those parts of the code are exotic and unusual from most historical BVH implementations.

Readability

This part's very subjective. I don't really care that much about readability of a kind that's close to human thought processes. The most well-documented code describing things at the highest level is still difficult for me to follow if the author uses bizarre and convoluted thought processes to solve a problem. Meanwhile terse code using 2 or 3 character names can often be easier for me to understand if the logic is very straightforward. I guess you could peer review and see what other people prefer.

I'm mostly interested in maintainability and, even more importantly, stability. Code that finds no reasons to change is one that has zero maintenance costs.

1
vote

I would say one way to know would be if new team members can pick up the code, understand it, and modify it to fix defects / meet new requirements with relative ease.

1
vote

Here is a technique I like to use:

Show the code to one of your peer programmers and have them explain to you what it does. Watch for these things.

1) If they can't easily explain the purpose of a block of code refactor it.
2) If they have to jump to another section of code to understand the current section, refactor it.
4) Anytime you feel an urge to speak during the process, that section of code needs refactoring. (The code isn't speaking for itself).

2
  • Assuming the peer programmer is at least equally experienced and read into the programming language and its idioms. All to often techniques like these can lead to people writing code in a subset of the expressiveness of the language in a misguided attempt to make int understandable to even the most junior team member. The result being a larger body of code in a dumbed down subset of the language. And no matter how much you dumb down the language subset, 500KLOC of small language subset code will always be more work to maintain than 200KLOC code that uses a more expressive subset. Commented Jan 21, 2015 at 10:44
  • this seems to merely repeat what was stated in top answer
    – gnat
    Commented Jul 21, 2015 at 0:48
0
votes

The most maintainable code is code that isn't there. So instead of adding to the LOC count, new code that 'reduces' the LOC count (even if slightly less maintainable when viewed in isolation) could make the total code-base more maintainable simply by reducing its size. Thus the primary rule for maintainable code:

  • maximize DRY.

Secondly, there is nothing worse for maintainability than hidden dependencies. So for rule number 2:

  • Make all of your dependencies explicit.

Thirdly, not all programmers are equally skilled at maintaining or writing using specific more advanced techniques language features or idioms. Dumbing down the whole code base will get you a large code base that due to its size is hard to maintain. Allowing advanced techniques features and idioms throughout the code will make all of the code maintainable only by senior developers what also is bad. The key to maintainability is skill level based layering For example:

Cross-project libraries: senior devs, full back of tricks code/idiom/techniques Project specific libraries and system backend: medior developers, avoid most advanced and hard to explain stuff. Seniors will go trough this code looking for DRY improvement opportunities.

Front-end: junior devs, use a strict style guide and set of techniques language constructs and idioms to avoid. Medior devs will go trough this code looking for DRY opportunities and hidden business logic.

So for rule number 3:

  • Layer your code by developer skill level and write maintainable code accordingly.

I

3
  • 1
    this doesn't seem to offer anything substantial over points made and explained in prior 25 answers
    – gnat
    Commented Jan 15, 2015 at 21:01
  • @gnat, I was hoping to add 'nuance' to many (potentially harmful) oversimplifications in other answers. Especially with point 3. Commented Jan 15, 2015 at 21:21
  • 1
    @user1703394 this question and its answers are community wiki. If you feel an existing answer could be improved you are able to edit it even without the "edit other users' posts" privilege.
    – user22815
    Commented Jul 21, 2015 at 1:16
0
votes

An apparently underappreciated aspect is naming. I'd like to focus on that now.

It all starts with naming things and how they map to reality in the minds of the people involved. If a name leaves any room for interpretation, expect trouble. I learned that poor natural language skills typically result in poor coding.

Beware of "fixing" names in existing environments though. If a lot of people have learned and adapted to some silly term over time and they all think about the same thing when they hear it, fixing it may cause more trouble than it prevents. If you still want to do it because it is really bad, make sure to fix it everywhere in the domain. This may be impossible though.

Naming components and classes goes a long way. What goes in it? How does it relate to other components and classes? All these decisions will be impacted by the interpretation of names. Settling on names too quickly just to get on with it is never a good thing.

I am extra keen on naming this week because I have been working on an update of a protocol implementation that required me to read a specification document and add new features to existing code. It turned out my predecessor didn't like any of the terms used in the specification document which was a PDF originating from the client. So he had "fixed" that for all the maintainers who had to deal with his code later. As you might expect I had some trouble mapping the specification to the code and getting my head around the logic. His names were way more different than they were better. I had to match everything first and resynchronize the names before I could start to do anything.

In software development there is no such thing as "just a name". If a name is not exactly right it will require knowledge to get the right idea and that is an obstacle.

Not the answer you're looking for? Browse other questions tagged or ask your own question.