13 ms·
Optimize for readability first. What slows down developers when reading code
- chton 12y agoI'm using "Optimize for readability first" from now on instead of the usual. I'm not sure if I'd call that 'optimization', but it definitely gets the point across a lot better, especially to newer developers.
- barrkel 12y agoThe most common form of unreadability I see is overly abstracted code in Java, littered with patterns that obscure the kernel of logic, forcing you to do global code searches in order to see through all the indirections.
- arethuza 12y agoI remember having to work on some J2EE based systems 10 years ago or so that were nothing but a vast (many many thousands of classes) collections of abstractions - actually finding the code that actually did anything was quite an exciting adventure. I swear one system had about 25-30 layers of stuff between Struts Actions and the underlying call to actually drop a message on a queue. Not helped by the need for whoever designed the system to use every single feature of J2EE even if really wasn't required.... CV driven development.
- couchand 12y agoCV driven development brilliant!
- deong 12y agoI'll go even further and say this is essentially the only form of readability I care about. The article talks about things like "unnecessary one-liners" with the ternary operator as an example. I don't care at all about one-liners, whether or not they're immediately understandable. If I'm trying to understand how to find a fix a bug or add a feature, then if, as the author did, assume that the obvious bad code things like meaningless variable names and spaghetti control flow aren't there, then the only hard problem is "where in the code does this thing happen". Make that problem easy to solve, and I can spare the 10 seconds it takes to understand a ternary operator. Hell, I can spare the 10 minutes it takes to understand some weird nested list comprehension. Stick a comment telling me what you're doing, and I may not even need to bother. The same is true with low-level optimizations. If you need to do something clever, then comment it, and we're fine. Obviously one shouldn't go overboard, but I don't see anything wrong with the kind of premature optimization in which the programmer sits down and immediately knows of a fast way and a slow way to do something and chooses the fast way (within reason, of course, and that's a subjective judgment, but it's one I'm OK with). Basically, make your code easy to approach. Make it as easy as possible for me to go from, "I need to change what happens when the user does X" to finding the code that governs that behavior. If all that code is in one tidy little location, then aside from the obvious need for good variable names, sensible whitespace, etc., write it however you like. I'll figure it out.
- pkolaczk 12y agoExactly! There is nothing wrong in complex code as long as it is isolated from the rest of the code and well documented in terms of its API.
- jtymes 12y agoI couldn't agree more. I came into development as an intern and it took me months to loosely wrap my head around how the code was organized. Here I am 4 years later on the same codebase (a Swing-based application), and it is very difficult to hunt down bugs. Fixing them is often easier than finding the place in the code where it's happening, even if you know where it theoretically is.
- seanmcdirmid 12y agoUse a proportional font for code reading! I really can't stand reading archaic type writer fonts anymore that are only optimized for ascii art.
- robert_tweed 12y agoThe problem with that is that it doesn't work when you have code (and comments) formatted in neat columns. Since plain text doesn't have any portable way to handle tabstops, everything has to be padded out (after the initial indent) with extra spaces. Characters must have equal widths or nothing lines up properly. This is pretty common in long repetitious declaration blocks, at least when the code is written by someone that cares about formatting. It vastly improves readability. Example: int some_variable = 100; // Foo float another_variable = 1000.0; // Bar The gofmt tool actually does this kind of column formatting automatically.
- morsch 12y agoKind of strikes me as something the editor could do ad-hoc. Maybe the tab character could return to let programmers tell the editor to align the adjoining lines according to the tabs. Would work well at least in your example.
- seanmcdirmid 12y agoElastic tab stops: http://nickgravgaard.com/elastictabstops/ http://nickgravgaard.com/elastictabstops/ You could also pad your spaces in the editor so that they align along a global axis. This does fix your code to a particular editor, however.
- morsch 12y agoExactly what I was thinking of, brilliant!
- robert_tweed 12y agoYeah this is nice. All we need now is for this to be supported in all major text editors and we're set! The only possible issue (not sure how it's handled, can't remember if/how gofmt handles it either) is when sometimes you actually want something to be misaligned because the width is so far out of the norm, e.g: int const1 = 10; // There's lots of space here for a long comment int const2 = 5; // Here too, unless the tabs get moved too far right float const3 = ( const1 * 123.45 ) / MAGIC_CONSTANT; // Not here. Normally when this happens I try to regroup the code so that "plain" constants are together and long formulas are in another group, but sometimes the order is important for some reason or doesn't make logical sense to regroup items, so the formatting has to suffer a bit.
- FollowSteph3 12y agoI'm sure it's been said many times before but the book Code Conplete 2 is a great follow up read to this article.
- givan 12y ago“Programs must be written for people to read, and only incidentally for machines to execute. ” - Abelson / Sussman
- rectangletangle 12y agoI've recently begun digging into Haskell; it has a lot of interesting and novel features. However, I can't get over it's syntax. It often uses non-alphanumeric characters in place of function names. Many functions are poorly named, e.g., `elem` vs Python's `in`. I strongly prefer the more natural English style syntax; even when you know a language well, it reads faster. That being said, Haskell is awesome for numerous other reasons. 500 line functions are the devil, they're harder to read, test, modify, and reason about. They tend to encourage code that isn't DRY, or reusable. The only benefit they have is an almost always insignificant performance boost, at the cost of everything else. Also magic isn't terrible if it's used in moderation, and it's well documented. Though it usually results in things being substantially harder to GREP.
- arethuza 12y ago"I can't get over it's syntax" I think there is a syntactical pain barrier for a lot of languages. However, once you are through that barrier you stop noticing the syntax. e.g. When I first looked at Python I remember thinking "significant white-space - that is evil". However, I forced myself to write some Python and after a fairly short time I didn't notice that aspect anymore and now I think significant white-space is actually pretty sensible. I've had similar experiences when starting out with Common Lisp, PostScript and a variety of other things that don't look like a variation on C. I would agree that Haskell does look a bit weird - but everything looks weird the first time you see it. Indeed, I can remember seeing C for the first time when I was 16 or so (early 80s) and thinking that looked weird compared to the Basic I was used to...
- seanmcdirmid 12y ago> I think there is a syntactical pain barrier for a lot of languages. However, once you are through that barrier you stop noticing the syntax. No, not always. Some languages are intrinsically worse with terseness than others; for example: APL, Perl, and Haskell. The association fallacy is so useful :)
- arethuza 12y ago
- VeejayRampay 12y agoI've always thought that what makes code hard to read is the fact that several persons are usually working on it. Beauty is in the eye of the beholder as we all know. Some developers like snake case, others prefer camel case. Some prefer 4-space indentation, while other can't stand it and much prefer 2 spaces. Some like braces on the same line, some others prefer it on the next line. What really bothers me is that editors in general force people to share a codebase formatted for a given style that may or may not satisfy everyone. And the worst is that I don't see why that has to be. Ideally "code" would be some form of abstract syntax tree and the editor would apply a user-generated "stylesheet" on top of it to display the code as the user wants to see it. Upon save, all the style would go away and only the abstract representation of the code would go to the shared repo. Not an easy thing to make happen for sure, but that'd be awesome.
- KiwiCoder 12y agoThe problem is that coding style goes deeper, it goes right down to the abstraction.
- VeejayRampay 12y agoYou're absolutely right. It's about design more than anything else, but at least for the cosmetic details, I feel the current state of affairs is sub-optimal.
- KiwiCoder 12y agoCosmetic? Visual Studio fixes layout problems in a few keystrokes but it can't fix, say, poorly named variables.
- VeejayRampay 12y agoBy cosmetic, I mean the pettier details like indentation, method names in bold or underlined, return types on the same line or before the function definition, position of braces, snake case versus camel case, etc. That I think the editor should take care of. For more serious issues like your example of poorly named variables, there's nothing it could do (and is really the core of the problem, as you pointed out).
- stinos 12y agoWhen by Go to Declaration you get to an interface or an abstract class and have to figure out what implementation there might be used. It gets worse with Dependency Injection, This seems far-fetched to me and doesn't have much to do with readability anymore. Moreover most of the time this is imo how code should be. If you start programming with readability in mind and as such start throwing away standard design practices because they lead to one more click or less readability, you are doing it wrong. That comes dangerously close to using copy-paste instead of a function because it is clearer since you can see the implementation directly. I'd argue that you should not have to figure out what the implementation is. That's the whole point of using interfaces (and dependency injection for that matter) - if you have to figure out what implementation used then you are either the implementer or the code is wrong somehow, like it is violating substitution/dependency inversion principles. Or you are studying the code in-depth, in which case you have to go through all implementations anyway. Calling all that 'readability problems' is a bridge too far.
- jeremyjh 12y agoWhen you are searching for a bug, you do need to understand the implementation. Of course by definition something is "wrong". Calling a function is fine by the author since you can easily jump to its definition. But if the actual function call is determined at run-time based on XML configuration, then you first need to figure out how that fits together for the particular case you need to investigate.
- valyard 12y agoOP here. You both are right. I'm not saying that DI and interfaces are bad. Of course not. I'm just trying to find a balance between modularity, loose coupling and code graph connectivity. Of course it's not just black or white.
- couchand 12y agoIn a case like this, you really ought to have a standard set of unit tests that covers the contract of the interface. If you run that against all the implementations and none of the tests fail, the bug just isn't in the implementation itself and you can safely move on to looking for integration issues. Of course, if the unit tests for the interface are incomplete you can't rely on them, but in that case you've built yourself a leaky abstraction that you should go back and look at before doing any more systems testing. Effective dependency injection simply requires comprehensive unit testing.
- annnnd 12y agoThis is a great write up and should be necessary read for every novice (and not so novice) programmer. That said, I would compose list "What makes code hard to understand?" differently: 1) improper naming of functions / variables (if you can't name it properly, you can't develop it) 2) excessive documentation where not needed (document only non-obvious stuff) 3) *complex* one-liners (because simple one-liners actually help readability) 4) and 5) can stay. :) The main point is valid though. I would phrase it like this: optimize for correctness first, readability close second, and don't optimize for performance until you can identify the significant gain (always by measuring it!).
- valyard 12y agoI don't mind improper naming because all the functions are essentially just nodes in code graph. Of course it helps if you can figure out what they do just from their names, but you have to read code anyway. Same for excessive docs. Sometimes you can find huge walls of text before every function, or meaningless javadoc comments which say that function GetData "returns data". But those are usually needed for 100% docs coverage which might be enforced by corporate policy.
- cessor 12y agoIn a study that I did I found that bad naming makes people approximatly 15% slower. The comparison was between single name identifiers, abbreviations and "normal" words. Have a look, you might have to translate it though... http://www.cessor.de/empra13 http://www.cessor.de/empra13 Cleaned up data linked as a spreadsheet at http://www.cessor.de/antwortdaten http://www.cessor.de/antwortdaten
- deong 12y ago> Of course it helps if you can figure out what they do just from their names, but you have to read code anyway. This is, I think, the root of the (very minor) disagreement I have with your article. I really don't want to have to read the code anyway. There may be 250,000 lines of code in a moderately sized system. If I have to read a significant fraction of them to figure out what's going on, that's the biggest sin you as the original developer can commit by a couple of orders of magnitude. I want it to be obvious where to look for the code that controls whatever it is I want to change. If you make it easy for me to find those 50 lines of code, I don't care if you write them in assembler. 50 lines of anything are vastly easier to deal with than 250,000 (or god help you million) lines of the absolute best thing you can write.
- cessor 12y agoMaking use of "chunks" makes code better to read. A function is essentially a proper chunk (whereas a comment is a bad way to do the same thing). Depending on the paradigm, I usually enjoy code that has tiny units, but many of those - i.e. a function with 100 lines is harder to read than 100 functions with one line each. Ideally each name adds proper meaning to the code so that the intent becomes clear. Code like this is usually easier to change, reuse and test (tdd). This can go the wrong way - many people mentioned how too many abstraction layers in big java projects are a pain, and I agree, as they often make the code more meaningless and abstract, rather than concrete. Oh yeah and pattern names do the same thing.
- ajuc 12y ago> i.e. a function with 100 lines is harder to read than 100 functions with one line each. I understand where you come from, but I disagree with that particular example. 100-lines long function is harder to read than nessesary, but can be understood without problems. 100 functions doing the same work will be a nightmare to keep in head at once, at least for me. If you refactor one function into 100 you're not doing it right.
- klibertp 12y agoIn this case (of 1 function split into 100) each of them is probably used exactly once. The effect on readability, especially if they are defined in an order of execution, is similar to placing a comment (function name and args and return value) next to every single line in the 100 line function. No matter if the comment was actually needed or not there. So I agree, it will probably result in worse readability than keeping the function as one, but commenting sections of it and extracting only repeated patterns into separate functions.
- ajuc 12y agoThe biggest problem are relations between the functions. In one function control flow is so simple we don't think about it. It's sequence. In 100 functions either each call the next one (and even if the 100 functions are placed in calling order I cannot assume that when reading the code - I must do 100 ctrl+clicks to see the control flow), or there's another function calling all of them (but what's the point - we still have 100-line function that way), or there's some more complicated calling sequence and I have to draw it somewhere to understand it (sorry, can't keep 100 things in my head at once). To understand the code I have to follow the control flow through these 100 function. I don't think any 100 lines of code in one function can be harder to read than that.
- gr3yh47 12y ago"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." by Brian Kernighan this is one of my favorite quotes ever
- vrybas1 12y agoI'm getting closer & closer to make this statement a rule: "Code need to be that good - no one in a team can write any better" If you, knowing current requirements, wrote a perfect code - it will still become messy at some point. Feature by feature new things will appear here and there. Functions size will grow, this CASE statement will grow etc. But still the "core" of it will be strong. If you, knowing current requirements, deliberately wrote a "good enough" code, what will happen with next change? The code won't survive it and will be deleted(at best case). Therefore, bad code is completely useless from the start.
- grymoire1 12y agoIn the 1980s, someone wrote an essay promoting "throw-away" code. Essentially he said every module (a) should be small in size, and (b) should have documentation that describes what the module does. Someone maintaining the code then has the option of keeping the interface, deleting the code and re-writing it, rather than try to understand the thought process of an overly clever programmer. I've always found this useful when I write code others have to maintain.
- joshka 12y agoAn additional point that solves some of this - learn the difference between declaration and implementation. Think, why you care about the declaration? Why would you generally want to navigate to the declaration instead of the implementation. The shortcut keys are similar, but just a little bit extra to navigate to the implementation over the declaration. Ctrl+F12 instead of F12 in Visual Studio, Ctrl+Alt+Click instead of Ctrl+Click with Resharper. Even easier, your IDE may support changing the default keybindings.
- pkolaczk 12y ago"[Scala] is essentially a write-only language (please don't quote me on this!); " I'm sorry, I quote you on this. If you ever compared Hadoop codebase (Java) with Apache Spark codebase (Scala) you'd know this is BS - Spark code is order of magnitude more readable and easy to understand compared to Hadoop. Lack of side effects and lack of low-level mutable shared state protected by locks everywhere are two of the many reasons. Of course, you can write terribly unreadable code in any language, and probably those academic languages with rich functionality and expressiveness make it a little easier to abuse features, or to choose right features for the job. But on the other hand I'd argue they make it easier to write readable code, once you care about readability and learn how to do this. Writing readable code is a skill you have to master. It is not something that comes for free in no time.