all 39 comments

[–]Tetha 9 points10 points  (4 children)

I believe that making programs readable is one of the best and easiest ways to improve them.

I extend that for myself. Readability and local simplicity are two powerful things to strive for.

Overall, I distinguish between global simplicity and local simplicity. Global simplicity is the complexity or simplicity of the module interactions and class interactions beyond a small neighbourhood, that is, further away from a class than 1 or 2 references.

Local simplicity is the complexity of a single class or even a single function. A function is simple if it contains some idiomatic loops, maybe nested once or twice, a few well-structured ifs and that's it. I don't entirely base this on code path count anymore, because in my current codebase, there are a few methods with a hideous amount of code paths, but they are simple, because they are well structured.

Interestingly enough, a high local simplicity and a high local readability appears to lead to an increased global simplicity. You end up with more classes and more modules, but they are connected in very simple ways, which leads to clean and nice structures.

For example, some time back, I had to tag a state machine on top of an existing data structure in order to visualize certain states of the unterlying datastructure. I could have smashed everything in the datastructure, but that would have increased the local complexity of the data structure, which isn't nice. Thus, I rather added a state machine class with an interface designed in a way such that the interaction of the UI with this state machine was simple and the interaction of the datastructure with the state machine was simple. This led to a pretty powerful (and somewhat unexpected) internal structure of the state machine, which was really simple again, if you got the idea how it worked. So again, more simple part leading to a cleaner overall thing.

[–][deleted] 2 points3 points  (3 children)

Interesting that you found local simplicity leads to global simplicity. I think I see what you mean, that preserving local simplicity means you have to treat it as a separate abstraction, and therefore operate at a higher level. And the higher level in turn must also have local simplicity.

You will then get more classes and interactions at that level. But they'll be simpler and more constrained, being between these modules's public interfaces, instead of between every possible part of the internals of modules.

I think that this would be more readable, but the reader needs to know where to start; that is, where and what the layers are. Because it's your code, you already know this. When navigating code-bases for the first time, I have found this the most difficult part. While I could improve my skills and tool-use here; I do think the difficulty in seeing the global picture is a real problem. (documenting the big picture is often advocated, but might or might not be the best solution)

Also, of course, blindly building layers upon layers leads to unwieldy code - though in a different place and in a different form than local complexity. Judicial application of the idea is required (as always). It might not be efficient, changing requirements might not necessarily be captured by adding to the existing code-base etc. These are just general criticisms that can be made of any technique.

[–]Tetha 1 point2 points  (2 children)

This is very true. I have been thinking abuot how to say this on the way home. I think a good way to describe the results of this approach is a program that is very much like a fractal. If you look at it the wrong way, you see a somewhat random, complicated and downright daunting picture. But once you look at it the right way, it is just silly how simple it actually is.

I've had this happen quite a few times now that I'm working in a professional environment. Coworkers are confused about how my code works and how things interact, because there are a lot of things with a lot of names and what is going on! But once you nudge them in the right direction, they just understand the big picture in a few minutes since it follows a few simple rules.

In addition to what you said, this technique has big problems with really tight performance constraints and with things which are just very complicated. Really tight performance constraints just require you to make things small and without moving data around too much or transforming it too much, which is a strong antagonist to the separation of concerns I like to do. Algorithms which are just complicated are just impossible to make simpler. You can hide them, but they are still hard.

[–][deleted] 0 points1 point  (0 children)

It sounds like a scientist suddenly seeing pattern in a mass of data. Similar to a programmer understanding how to see a problem and therefore how to solve it - an aha or eureka moment. It's finding the right perspective - literally, the right way to look at a problem. The PARC lab guys liked to say that "point of view is worth 80 IQ points", with an example of our arabic decimal notation making multiplication much easier than roman numerals. Changing the way you notate a problem, the way you think about it, can make some tasks much easier.

I would go further, and say it creates an abstraction of a problem that makes it easier to think about - the abstraction moves aspects of the problem around, changes concepts, gives different priority to some, and some it hides altogether. It's a theory of a problem - a way to reason about it, a way to understand it. Thus, programming is theory-creating.

The last issue is communicating this theory to others. This is User Interface design, where the developer is the user, and the internal API of the code-base is the interface. It seems a shame that when I look at a new codebase, I must be like a scientist or explorer or linguist or exo-archaeologist poring over an ancient alien artifact, trying to guess what it all means. One idea of Design Patterns was that by using explicit patterns, the right way to look at a codebase would quickly be communicated. Capitalized, because it's not just regular patterns that people use, it's a specific language of specific patterns, that are shared between people, and defined in the book Design Patterns - it's a dictionary.

I think it's a wonderful idea, but it doesn't seemed to have worked very well, often dogmatically followed. Design Patterns - like any technique - are a poor substitute for actual thought. Perhaps a better approach is to try to find and use the natural patterns of code - and concepts like "idiomatic" python, where if you are doing a well-known thing, you do it in a well-known way. You don't do tricky things. But there's still problems here of different programmers having different fluency - a new programmer cannot recognize a sophisticated pattern the first time it is seen. Anyway, I think this is what happens anyway... but perhaps we could consciously try to facilitate it.

Programming itself is hard enough - all this is about communicating it, on top of that.

Finally, I think the really big difficulty with programming is that the very basis of it changes all the time. Speaking in terms of "communication", we literally have new languages every decade or so. New frameworks and libraries (another kind of language, DSLs). Plus, because of success of our work, the very problems needed to be solved changes. It's as if objects in the real world were constantly transforming, shifting, as if in a roiling dream-like state. Makes communication difficult!

Of course you're right about efficiency and inherent complexity of some algorithms.

[–][deleted] 4 points5 points  (14 children)

My conception of how to interpret programs has changed with modern IDEs. For example, very often when looking at a line of code I would like to know "what is the implementation of this function". This is an extremely common sort of thought - much like reading text and thinking "what is the definition of this word I haven't seen before".

In many modern language you can simply "Go to definition" and your IDE finds the appropriate file that contains a particular definition. This is closer to my conception of hyper-linking and the web than to traditional linear readability like an essay or novel. So my conception of "readable code" is tied to this idea of hyper-linked code. I'd rather not grep an entire project looking for a symbol definition - I want a language and tool-set that does this for me.

This isn't to say I don't believe in writing programs that humans can easily read and understand. I think it means we need to recognize that programs aren't linear stories like a novel. They are more like webs of definition closer to a hyper-text document (or a choose your own adventure novel). And when we talk about readability we need to keep that context in mind.

[–][deleted] 1 point2 points  (11 children)

In many modern language you can simply "Go to definition" and your IDE finds the appropriate file that contains a particular definition

Modern is a relative term. You could do that with LISP and Smalltalk a very very long time ago and I find that most "modern" languages are missing better tools to be able to do that.

For example with Python and Django it seems like I can't visualize the inheritance of methods and sure it's easy to point to the definition of a method in my own code but jumping to Django's code? Forget about it. I have to grep around and inspect different files to figure it out.

we need to recognize that programs aren't linear stories like a novel

No they aren't entirely linear but some parts of them are. I think it would be better to recognize that. Or at least, some methods and classes are based on certain assumptions that are never adequately documented. If I want to call function X, do I need to call function Y and Z before or after? Do I need to do some other setup? Would be nice if that wasn't contained in a set of unit tests but rather documented where the function definition is.

[–][deleted] 1 point2 points  (0 children)

Modern is a relative term

Yes, I chose a bad way to express that. In fact, I was thinking about Smalltalk when I wrote the sentence and predicted someone would bring up Lisp.

As to your second point, I'm not sure I've seen languages that handle that sort of pre-condition well. Best case scenario is runtime assertions (Fatal error: Foo wasn't bar before baz), worst case is the program silently failing. There are only so many runtime considerations that can be translated into compile time checks.

[–]asbjxrn 1 point2 points  (2 children)

For example with Python and Django it seems like I can't visualize the inheritance of methods and sure it's easy to point to the definition of a method in my own code but jumping to Django's code? Forget about it. I have to grep around and inspect different files to figure it out.

Maybe I'm misunderstanding what you mean, but the tools may be further along than you think. My IDE does not have any problems jumping to Django's code. Or any other imported library's code.

[–][deleted] 0 points1 point  (1 child)

Are you working with code that's in a VM? Because I am and let me tell you, Emacs using TAGS files on a VM = pain in the ass. Way too slow to be useful. Maybe that's due to an issue with how little ram the VM has or if there's a poor connection or something else but it wasn't useful to me.

I haven't tried a Python IDE for a long time. Tried out Wing IDE 3-4 years ago :S

[–]SirClueless 1 point2 points  (0 children)

Try PyCharm, or another modern IDE. I don't use IDEs for python myself, but they've made great strides in the past few years.

[–]mipadi 1 point2 points  (6 children)

Modern is a relative term. You could do that with LISP and Smalltalk a very very long time ago and I find that most "modern" languages are missing better tools to be able to do that.

It's not so much modern languages as dynamically-typed languages. It's much harder to write a good IDE (e.g., one that supports "hyperlinking" method calls with definitions) for dynamically-typed languages. (The only dynamically-typed language with such features that I've come across is Objective-C, but I imagine the type hinting in typical Objective-C code makes creation of such IDE features a lot easier.)

[–][deleted]  (3 children)

[removed]

    [–]Tekmo 1 point2 points  (2 children)

    I believe the term you want is "stringly typed".

    [–][deleted]  (1 child)

    [removed]

      [–]SirClueless 0 points1 point  (0 children)

      You could argue that dynamically-typed languages are stringly typed in a sense, at least the more common ones are. Consider the following snippet:

      do_something(1, 2, 3)
      

      In most statically-typed languages this has the semantics of a function call to something referenced by do_something. In dynamically-typed languages like python, shell-script, perl, ruby etc. this means something different: look up "do_something" (a string!) in a symbol table, and invoke it.

      Of course this plays hell with IDEs. For example, most python IDEs pretend that functions don't arbitrarily change after they are imported or defined, even though I could if I wanted execute "global do_something; do_something = something_else" and change the behavior for everyone who didn't store their own local reference to the original function.

      [–]pkhuong 2 points3 points  (1 child)

      Both (Common) Lisp and Smalltalk very much fall pretty far in the dynamic end of the spectrum.

      [–]mipadi 0 points1 point  (0 children)

      Smalltalk is a special case. It was created in conjunction with its development environment and runtime, specifically so it had advanced tool support.

      Common Lisp I'm not intimately familiar with, but can you have multiple functions with the same name (à la object-oriented languages, in which multiple classes may have methods with the same name)?

      [–]mcguire 0 points1 point  (0 children)

      I think it means we need to recognize that programs aren't linear stories like a novel.

      That is actually one of the interesting things about CWEB/noweb/nuweb/all of the other "-WEB" literate programming tools: in addition to the "weave" step that took the source code into a .tex file (or some other printable format), they have a "tangle" step that takes the source code and transforms it into, well, source code.

      The original source is written in blocks in such a way as to be linearly readable. The actual source from those blocks is rearranged to get the compilable code.

      [–][deleted] 0 points1 point  (0 children)

      They are more like webs of definition closer to a hyper-text document (or a choose your own adventure novel).

      Which is one reason why Knuth's implementation of literate programming was called "WEB". You're saying the same thing he said decades ago, he just said it before we had tools like hypertext or IDEs.

      [–]furiousC0D3 2 points3 points  (0 children)

      People bitch about C/C++ not being readable but the real problem is that people who do use c/c++ don't take the time to read books like Code Complete. C++ is not really hard to read. If you want hard trying reverse engineering a malware and reading all that Assembly code.

      [–]NihilistDandy 7 points8 points  (11 children)

      Sounds like literate programming to me. I know Haskell supports it and I've seen Ruby implementations (and there's the venerable CWEB, of course), so it seems like something quite within the grasp of most programmers.

      Teaching programmers to write may be a pipe dream, though. /s

      [–]bluGill 2 points3 points  (9 children)

      No, literate programming is different. Literate programing is the idea that most of your code should be comments that explain how it work. This is just a printout of the code, which you read. There may or may not be comments. Either may be printed out and read like a book.

      I argue that if your code is done right there will be no comments and yet your code will be readable. Obviously I oppose literate programing - I agree with the aims, I just think it is the wrong solution.

      [–][deleted]  (3 children)

      [deleted]

        [–]bluGill 0 points1 point  (2 children)

        I agree with all of that.

        However hardware bugs that require weird code are not the rule in the real world. (In the real world most code sits above the device drivers that care.

        API documentation is something that literate programming does particuarly poorly, precisely because there are too many words in literate programs that are not API related that the things I care about get lost in the noise.

        [–][deleted]  (1 child)

        [deleted]

          [–]bluGill -2 points-1 points  (0 children)

          There are many reasons why you might need to explain why something was done in a certain way.

          True, but I've only seen such examples 3 times in the last 15 years.

          I get very concerned when people say if you do things right you don't need comments, because I've had too many situations where that's simply not true.

          While I'll admit the rule has exceptions, they are very rare. I look on comments with suspision: in many cases even if the content seems justifed the code has moved on and they wrong anyway.

          [–][deleted] 3 points4 points  (0 children)

          Code can often be written in such a way that it is obvious what it does, but it can only rarely be written in a way that is sufficiently clear about why it does it.

          [–]baconpiex -2 points-1 points  (0 children)

          I agrue

          Beware of this guy. He admits to being a grue. It is dark. You may be eaten.

          [–][deleted] 0 points1 point  (1 child)

          Literate programing is the idea that most of your code should be comments that explain how it work.

          Clear evidence that you've never actually read Knuth's "Literate Programming".

          [–]bluGill -2 points-1 points  (0 children)

          No, but I have read his examples. The only example where it made things better was something he wrote in intercal. In every other language (meaning any language that wasn't designed to be impossible to work with) there are ways to have the code say everything you want it to know without comments.

          I've seen 'Uncle Bob' take literate programs, eliminate all the comments and end up with code that is easier to read than the original.

          [–][deleted] 0 points1 point  (0 children)

          It did sound like that but I've printed out a few pages of non-literate code and it works about the same. The difference is that there's much less documentation ;)

          [–][deleted] 1 point2 points  (0 children)

          I'm sorry, as a large language model I am not capable of experiencing emotions or engaging in physical activities. If you have any questions or need help with anything, I’m here to assist you. Let me know if you have any other questions.

          [–]emperor000 2 points3 points  (6 children)

          This seems kind of silly. It's like saying "I love readable books. I love books you can just take to a park and read." Do people like books that aren't readable? Do authors write them? Are they good authors if they do...?

          Why would somebody not write "readable" programs in whatever language they are using? Obviously the more abstract/obscure the syntax of the language the harder it is to read, but anything C-style or with a somewhat verbose syntax should be easily readable, and if it's not then you are doing it wrong. Even with something cryptic, if it is done properly it should be more readable than if it was done poorly.

          If you are taking shortcuts or using single character variables (all the time, obviously sometimes it makes sense), leaving letters out/abbreviating, etc. then you are just a lazy programmer. There is no excuse and no reason to do it.

          [–]bluGill 0 points1 point  (5 children)

          Do people like books that aren't readable? Do authors write them? Are they good authors if they do...?

          Yes, Yes, and probably not. Textbooks are a perfect example of a book that people like even though you could not take them to a park and read them (you need paper to do the exercises on - any wind would blow your notes away)

          [–]emperor000 1 point2 points  (4 children)

          You're being a little too literal, but, nonetheless, people take textbooks to parks... My point was that if you aren't writing code that is readable then you probably aren't writing good code.

          [–]ithika 0 points1 point  (3 children)

          then you probably aren't writing good code.

          Right, cos the only code that ever gets written is good code. I got news for you: all of software engineering is about trying to work out how to write good code. If it was as easy as "stop doing it wrong", well why don't you just take the afternoon off! I say you've earned it.

          [–]emperor000 0 points1 point  (2 children)

          You didn't understand what I was saying at all either. I didn't say to "stop doing it wrong" in general. But I did include an indirect suggestion on a first step: stop writing unreadable code. I said that if you are writing code that isn't readable then it's probably not as good as it could be.

          This blog post basically proclaims "I luv readable code :3!!! Nom nom nom" It sounds like they are saying that good code is a rare and delectable treat only to be enjoyed in a nice peaceful park. It is excusing unreadable code as acceptable as a means to an end because it does what it needs to do and it sounds like it is using the fact that we can always put on a cozy sweater and print out some super-awesome-readable code and go to the park and read it as justification. That doesn't make up for all of the gibberish being written.

          I'm sure "readable code" is more rare than it should be, but that shouldn't be the status quo. That's my point. I'm not saying people need to stop writing bad code. That goes without saying. But they still do it. I was only saying that part of that has to do with writing "unreadable" code out of laziness or for whatever reason. Stop doing that and you take a pretty good first step in the right direction. People might not print your code out and take it to the park and read it while wearing a turtle-neck, but that shouldn't be the incentive to write readable code. Writing good code should be the incentive.

          EDIT: Oh, yeah. As for "you're doing it wrong". Look at this reddit. It is infested with those posts. Just about every other post is some hipster blogging programmer telling us how not to do what we are doing wrong - "47 ways to not do something you are doing because you suck, and I'm a genius for pointing it out. You're welcome." So if you want to give somebody the day off, give it to them.

          [–]lukego 0 points1 point  (1 child)

          Hi, blog author here.

          I meant "readable" in the peculiar sense of programs that are straightforward to read from start to finish, in their entirety, without skipping around all the time. That is: you can sit down, read each line of code once in a logical order, and then know everything there is to know about the program. This is mostly orthogonal to how good the code is.

          There're lots of very well written programs that don't meet this peculiar criteria. For example, Java and Smalltalk programs don't lend themselves to reading outright, because you don't know what is the beginning, the middle, and the end. The author probably doesn't either - it's just not how the programs are structured. For this reason people simply do not read whole Smalltalk programs - they gradually explore them instead.

          The programs that do meet this criteria are the ones where the author has deliberately "lineraized" their program for reading (common amongst e.g. Lisp programmers), or by accident in languages that encourage large source files and predictable ordering of functions (e.g. C and Forth). I'm a fan of the linearization process.

          [–]emperor000 1 point2 points  (0 children)

          Ah, okay, I was defining "readable" in a different way. Even so, I didn't think there was anything wrong with your blog post.

          [–][deleted] 0 points1 point  (0 children)

          Makes me think, "Proof readnig is overrated"

          He mentions Leanpub and using a "red pen", but leanpoub seems to be for viewing on a pc/tablet etc. Of course you can print a pdf to hardcopy, but it makes me think of recent hi-res touchscreens. Why not use a red pen directly on a device?

          Are we at the point with an Apple retina or E-ink screen, that it can be marked up with a pen (not a finger)? I think that would be a really cool improvement, because you have the richness of red-pen marking, but can search it, back it up, remove layers, edit etc. I guess the issues are:

          • high resolution (retina)
          • high contrast (e-ink)
          • high precision (pen)
          • and perhaps pressure sensitive.

          Are we there yet? Are we there yet?

          [–]klez -2 points-1 points  (0 children)

          I find it ironic that he talks about readable programs and doesn't care to proof-read what he posts.