1. 44
  1.  

  2. 70

    I call my coding style the “Bear of Very Little Brain” coding style.

    Give me a big hairy bundle of spaghetti to maintain and I do this…

    • Whatever I’m trying to understand, I add the finest grained unit test I can.
    • I walk through the code trying to understand what it does, and record my beliefs about what it does, and what it requires as executable asserts within the code.
    • I check every single damn return code. Every one. I hunt and kill all “cast return code to (void)”. (It’s ludicrous how many so called “hard bugs” just vanish when I do this.)
    • I turn comments into executable asserts.
    • I use the early return pattern to clean out all unhappy paths as soon as possible.
    • I reduce the scope of every variable or symbol to the minimum, and make them const if the compiler let’s me.
    • Anything that is not referenced anywhere gets deleted. Version Control is where dead code is stored.
    • #ifdef spaghetti is essentially multiple versions of the same code copypasta’d by the preprocessor. Often I make that explicit with unifdef. This might temporarily explode the line count… but usually after awhile the line count gets smaller and smaller.
    • Every change I make I trigger a rebuild and run tests. I’m a Bear with Very Little Brain…. but the compiler and CPU can do a lot to help me.
    • Any time I meet a function that makes me feel even slightly uncertain as to what is going on…. I extract a subfunction. (Hint: That reduce scope and make const step above really helps a lot with that!)
    • I write a unit test for that subfunction.
    • Where something mutates global state, I pull it into two functions. One that mutates the global state… and one that doesn’t touch anything that isn’t passed as a parameter or returned. After doing that for awhile, I know what owns that state, and I can delete the global state.
    • Where possible I change all functions that “Might Do X (and return error code if you called me wrong)” into ones that “Will Do X (or die noisily and immediately if you called me wrong)”.

    Why? It removes insane amounts of error checking complexity, that is untested, untestable and unused.

    • Repeat until everything crumbles into small understandable chunks with executable evidence for everything I believe about it.
    • If looking at some code makes my “Very Little Brain” hurt… I make it simpler and simpler until my brain stops hurting. (The result might in an intermediate step create more lines of code…. but usually after a few more rounds of this I find that some of the variants are unused…. and I delete them and usually end up with less code.)
    • At every step things get smaller, simpler, more const, better named, usually static single assignment form. Each chunk is simple and obvious.
    1. 7

      One thing I’ve been doing that I find simplifies things a lot in my head is separating a program out until multiple subprograms that get executed by a driver program. The guarantees of the operating system are pretty well understood so you know how that subprogram can affect the caller. Testing usually becomes synthesizing something over stdio. qmail is probably the most famous program to do this, beyond being standard unix practice. Yeah, there is a cost in terms of serialization but, IMO, it’s much easier to catch and handle serialization bugs than all the other possible bugs that can happen by existing in the same memory space, and the serialization cost is really not all that expensive for most problems I’m solving right now.

      On a random tangential rant, one thing that I think Java really screwed up for a whole generation of programmers is the desire to jam everything into the JVM. The JVM has much less useful constraints imposed on it than the operating system (or at least a UNIX-like operating system), and it allows for a whole bunch of bad states that just don’t happen if one has memory isolation. I like to see the OS as the development environment, not as something that embarrassingly needs to be hidden away at all costs. And I know some people will say you lose the portability of your application if you do that, but I’d say most Java applications are running on two OS: MacOS and Linux, and those are close enough to not be a problem.

      1. 5

        A colleague and I have long friendly flame wars over the vim vs emacs, bash vs ruby. (I’m on the emacs / ruby side)….

        But one thing I noticed about my ruby code vs his bash code was his stuff was implicitly decomposed into hundreds of processes, and hence implicitly parallelized by the shell and OS.

        There are many many things I dislike about the *shells, but this is a pattern I really need to adapt much much more in my ruby code.

        I know why I went “all in one process” originally. There was a fairly high “wake the interpreter” cost in early versions of ruby, but there are patterns (most typically pipelines) and ways of countering that.

        1. 3

          but this is a pattern I really need to adapt much much more in my ruby code.

          I think it will be harder to do right than it may appear at first glance. The beauty of the OS with processes is that you get memory isolation which makes a lot of failure cases easier to handle. But yeah, it’s great just to be able to pop in a call to gnu-parallel to turn a single core script into one that can saturate all the cores on a server.

        2. 3

          A long time ago I briefly thought I was a genius when I did something like this until I found out I basically reinvented CGI.

          1. 3

            It depends on the usecase, of course, but something like CGI works pretty well for low-traffic problems. I do wish more data pipeline frameworks were really more about gluing programs together. One miserable thing about Hadoop is one has little choice but to push as much as possible into the JVM. I haven’t had a chance to use it much but something like Joyent’s Manta is much more along the lines of how I wish data pipeline frameworks worked.

        3. 4

          It’s always valuable to reduce cognitive load where possible. The whole premise behind layers of abstraction is this. So I think anything where your processes and tools prevent mistakes is valuable. Especially if you’re working in a context where mistakes might cost human lives.

          1. 4

            Anything that is not referenced anywhere gets deleted. Version Control is where dead code is stored.

            That, or any time I realize a coworker or otherwise made a commit where code is commented out.

          2. 22

            Comments really aren’t a “code smell.”

            1. 16

              Nothing stinks quite like uncommented complicated code.

              1. 7

                Exactly! Margaret Hamilton’s code itself, whom the author cites, is full of comments. Possibly more comments than source code. Which, if you’re sending a ship with the processing power of a toothbrush to the Moon, is a great idea.

                1. 10

                  This code is not readable on it’s own, if it was possible to use variable and function names most of those comments could be removed. It’s also quite likely that every detail of the program was decided before writing the code. In a modern codebase things are always evolving and comments can get left behind.

                  1. 5

                    This is my fear with comments. I code in a team of 2, so we don’t really comment stuff. I know it’s bad, but I’m a team of two, we kind of know the whole code anyway.

                    We also don’t write tests. We’re bad people.

                    1. 4

                      Oh man, save yourself some pain and write unit tests. You don’t need 100% test coverage, even non-zero coverage of basic functionality will save you so much time. If you don’t know how to use test frameworks then you don’t have to bother, just write one big main file with a function per test you want to do, and call them all in main. That’s basically what test frameworks are, so if you need a low barrier to entry then don’t bother learning one yet, just do something. If you program in a language with a REPL you can literally just save the stuff you use to manually test into a file so you don’t have to type it more than once.

                      I personally couldn’t develop without unit tests. You can test the application and do something that hits the code path you just changed, which is time consuming and tedious, especially to do repeatedly, or you can write a little bit of code that calls the code and run it with zero effort every time all the time for the rest of forever. Even a small sanity test of the happy path is better than nothing, you can at least check your code doesn’t blatantly fuck up with normal input and save yourself the round trip through the application.

                      If I had to code without unit tests I’d quit. And I have worked on teams that didn’t want to unit test, so I had out-of-tree tests I wrote for myself. The amount of bugs I fixed a couple hours after someone else committed was mind boggling.

                      1. 4

                        How do you even develop without unit tests?

                        I’d avoid this kind of shaming, especially since the commenter has already noted (in a self-deprecating manner) that they’re aware of the stigma associated with not using tests.

                        If the intent is to encourage the use of tests, I would put your last paragraph first and focus on how it would help GP.

                        1. 3

                          Revised, thank you for the feedback. 😊

                        2. 2

                          Depends on the language and coding style though. I wrote a 25000 line game in C++ without a single test, and I never had a regression. I obviously had occasional bugs in new code, but they’re unavoidable either way. Now my preferred language is Haskell, and I feel the need for tests even less. I generally prefer correct-by-construction to correct-by-our-tests-pass. My purpose isn’t to discredit tests though, just that not every codebase has as much need for them.

                          1. 2

                            I’m just self taught and kind of out of my depth on it. I had a dev friend who did integration tests, and they were really brittle and slowed us down a lot. Are unit tests not as bad at slowing down a small team of two devs who are both self taught? We’re good / mediocre / we build good stuff (I consider ourselves hackers) but we don’t have a ton of time.

                            1. 1

                              Unit tests don’t have to slow things down like integration tests. In your situation, I’d wait until the next bug comes up, then instead of fixing the bug immediately, I’d write a test that reproduces the bug. Usually doing that helps narrow down where the bug is, and after fixing it, the test passes and (here’s the cool part) you will never see that bug again

                              1. 1

                                That’s what i was told about integration tests, but I had to set up all these extra dependencies so that the integration tests continued to work every time we added an external service… we’d have to mock it or shit would break.

                                I’m assuming since Unit tests don’t run like that, they don’t have external dependencies like that? You’d mock on a component by component basis, and wouldn’t have to mock unrelated shit just to keep them running… hmm… maybe i will.

                                Any unit testing video series I could watch as a noob to get started you’d recommend? Or anything like that?

                            2. 1

                              I second saving yourself pain with writing tests! I’ve avoided lots of rakes with a handful of tests

                          2. 2

                            What makes everybody think that the programmers who change code so that it no longer matches the comments they just used to understand it will somehow write code so clear you don’t need comments to understand it?

                            1. 1

                              Often people write code like total = price * 1.10 #This is tax which can be rewritten as total = price * TAX A lot of comments like that can be removed by just putting them in the actual code.

                              1. 2

                                I’m not suggesting it can’t be done I’m suggesting it won’t be done

                          3. 4

                            I’ll also correct the article to say a team did the code and review per the reports I read. She describes it here in “Apollo Beginnings” as a team with a lot of freedom and management backing with unusual requirement to get software right the first time. Unfortunately, a rare environment to work in.

                          4. 5

                            You can’t write test coverage for a comment. You can’t have your compiler warn you that a comment is inaccurate.

                            If you have no tests, and your code is full of dead paths, you can’t even percieve the risk posed by an errant, out of date, or unintentionally misleading comment.

                            Sometimes they’re necessary. But the best default advice to a ‘mediocre’ developer is to write better code, not add more comments.

                            1. 5

                              You can’t write test coverage for a comment. You can’t have your compiler warn you that a comment is inaccurate.

                              https://docs.python.org/3/library/doctest.html

                              If you have no tests, and your code is full of dead paths, you can’t even percieve the risk posed by an errant, out of date, or unintentionally misleading comment.

                              If you have no tests or comments you have no way of knowing whether your code is actually matching your spec, anyway.

                              Sometimes they’re necessary. But the best default advice to a ‘mediocre’ developer is to write better code, not add more comments.

                              That’s like saying that the best default advice to a ‘mediocre’ developer is to write less buggy code, not add unit tests.

                              1. 2

                                doctest is great for testing comments that include code, but nothing else… If a comment says “Framework X is expecting variable foo in JSON format inside the array bar.” I would be inclined to believe it at first and then test the hypothesis that the comment is wrong. That’s the danger of comments.

                                1. 1

                                  A couple of times today I caught myself committing deleted or changed lines without deleting or changing the associated comment. Luckily I could go back and fix things so that the comments weren’t complete nonsense. Sometimes though they escape detection.

                              2. 2

                                Once the code is cleaned as much as possible and still is hard to understand, or if something is tricky, comments help a lot!

                                I guess the author talked about comments that could be removed by making the code cleaner.

                                Maybe it depends on what motivates one to add comments, there might be good reasons as well.

                                1. 2

                                  True.

                                  But Comments that are wrong or out of date stink like dead rats.

                                  I view asserts as “executable comments” that are never out of date. Sometimes they are wrong… but testing will tell you that.

                                  If a plain comment is wrong… nothing will tell you except a very long, very Bad Day at work.

                                  1. 7

                                    But Comments that are wrong or out of date stink like dead rats.

                                    Valuable comments are something along the lines of “this looks weird, but I did it because of [historical reason that is likely to be forgotten] even though [other implementation] looks like the more obvious solution at first glance; it wouldn’t have worked because [rationale].”

                                    The longer I spend working with old codebases, the more I’ve come to treasure such comments. But comments that just explain what the code is doing rather than why are suspect.

                                2. 12

                                  And yet lots of advices in that article are memes popular amongst growth hacking ninjas and top VC bloggers:

                                  • “comments are code smell”

                                  • “use quality checks” with “complex heuristic algorithms”

                                    I’m strongly against this. Usually these are checks like “cyclomatic complexity”, and locally in each method, despite it’s intended for whole loosely defined “chunk of code”. For example, Rubocop has it turned on by default and forbids nested if’s even if logic is clear. It encourages (or even forces) creation of false abstractions by splitting code chunks to methods or even classes, which leads to classical spaghetti code (with method calls instead of gotos). Counting ifs is not “complex heuristic algorithm”, it’s dumb bureaucracy.

                                  • “use docker”

                                    Bundling operating system with program to fix library linking issues is huge increase of complexity and this article tells to reduce complexity. I think Docker is usable for clusters only, it’s not designed for provisioning development environments.

                                  1. 4

                                    I used to thing this way since I think I was molded by the ruby world, but I’m much happier now that I just use if’s when I need them and let my functions be 200 lines long if I have 200 lines of work to do. I don’t know why I used to be so averse to it, but now I use a lot of just raw scopes with comments rather than pulling code out into functions.

                                      var data
                                      { // get data
                                          ...
                                          data = x
                                      } 
                                    

                                    Rather than making a getData function that will never be used anywhere else to satisfy some need for short functions.

                                    1. 2

                                      I hope I won’t ever have to read your code. Function shape communicates intent.

                                      EDIT: Name, arity, argument and return value types. Even the body of the function can be easily glanced. Does it loop? Does it branch? Does it handle any failures?

                                      Your description have reminded me of a Haskell code base I have tried to understand recently. One with rather long functions with many local variables and nested functions without clear scope delimitation. The author freely mixed data processing with the domain logic (your getData example), which obscured the meaning by lots and lots of tiny details.

                                      I would say that programs should be broken into small units of understanding. Not necessarily for reuse, but mostly to eliminate the amount of context one needs to take into account.

                                      1. 1

                                        Shape? Like name + arity?

                                        Edit: I swear it’s actually pretty readable

                                        1. 3

                                          For myself, I think the value of pulling code into functions is to make the inputs and outputs clear. Just scoping a block of code in a function doesn’t limit what that scope can access and, IMO, can make it harder to understand. Functions enforce this. I find value in that.

                                          1. 1

                                            I go back and forth on it but I think it’s general fairly clear from the way I lay things out what the hypothetical return value you would be, and I think it’s nice to just be able to read a whole function and understand it entirely without jumping to function definitions. If you’re not interested in that detail, it’s also not hard to just skip over a block.

                                            I think I like having the details mordae is complaining about available, and won’t bother to split it out unless one of these sub-blocks feels overwhelming. I’m not against functions or anything, but I do think splitting things out is overvalued. These blocks are already in a function that has a shape, it’s just a nice intermediate level of structure between chopping it up and just dumping the code straight into the body.

                                  2. 8

                                    Two other things that help, unmentioned in the article: linting and static code analysis

                                    Installing and using them to fail builds means having your very own pedantic and uncompromising code reviewer.

                                    They can’t do everything and they are not for everyone, but if you don’t trust your own code (or the code you copy and paste from the web), then they are good tools to have in your workflow.

                                    1. 5

                                      I’ll corroborate your comment by adding that case studies on formal specification, proof, and automated analyzers often showed that just making the code (esp control flow) simple enough for those tools to handle caught errors by itself. Then, use over time reinforces that in coding style in a way that prevents and catches more.

                                      1. 1

                                        I’m conflicted about them.

                                        When they are good, they’re great.

                                        But I have seen ridiculous code that people have written just to shut the linter up. Grr. That’s worse than useless.

                                        On balance I think they are worthwhile, some of Rubocop’s “cops” are way over the top though.

                                      2. 7

                                        The only thing the author should try to work against in that list is memory, which is a muscle that can be developed. Exercising memory does increase your capacity to memorize. Also learning a few small memory techniques such as pinning and the major number system alone can have exponential effects. I went from being able to memorize 3 - 4 things to memorizing a deck of cards under 10 minutes. Yes 3-4 things, I have a sub par natural memory though no disability there. The Art of Memory forums is an invaluable resource on the subject, also Frances Yates’ book by the same title. Even though her book is largely a historical exploration the techniques in the book are quite usable.

                                        For me learning about memory was a way for me to compensate on a weakness, and now I impress average people with my memory. :)

                                        1. 1

                                          I appreciate the tips given I have memory damage. Ill check out the forum.

                                          1. 2

                                            Best of luck. If your memory damage is bad you might not be able to get the gains to the extent I’ve described, but I’m extremely curious what progress you end up having.

                                        2. 5

                                          I appreciate this article. However, the real meaning isn’t about how to keep things “simple”, or error free, etc. I think the main, deeper reason it was written was to fight the idea of the “rockstar” or “10x” developer. It was written as a rejection of the concept that a developer has to be perfect and know everything. That using Google and SO for simple questions shows your lack of skill. In fact, the idea that devs should keep things simple and rely on the ecosystem of other developers as a whole makes you better than being the lone wolf… that humility is superior, and being able openly talk about your weakness shows your true strength.

                                          Just a thought.

                                          1. 4

                                            There is good advice here. Every programmer has a cognitive limit, be it one method or 100,000 lines of Enterprise garbage.

                                            The only thing I disagree with is an overreliance on copy-pasted code from SO and elsewhere. You have to stop to take the time to understand what the code is doing at least once. This is particularly important when taking snippets from SO, which tend to be of very low quality and have a particularly cargo-cultish attitude towards making things work.

                                            Granted, most SDKs and Libraries have a cargo-cultish attitude about working too (requiring specific invocations in specific orders which can only be gleaned from readmes and ‘heres how I got it working’ posts), but reading those posts is the first step in breaking the cycle, not the last one.

                                            1. 2

                                              “Small brain” could means powerful computer (in comparison) In other words, exploit the resources computers offer further more.

                                              Does not look like a mediocre approach at least. Let’s be a mediocre developer who write good code.

                                              1. 1

                                                The “Pay other people to audit your code” link https://wemake.services/meta/rsdp/auditions/ is broken. Is there really such a service? I’m mostly looking for “code review trades” though.

                                                1. 4

                                                  Sorry about that. Here’s the correct link: https://wemake.services/meta/rsdp/audits/

                                                  1. 1

                                                    Thanks!

                                                  2. 1

                                                    Pretty confident the url is supposed to end in /audits/ instead of /auditions/