Hacker News new | past | comments | ask | show | jobs | submit login
Please do not attempt to simplify this code (github.com)
1552 points by whalesalad 86 days ago | hide | past | web | favorite | 627 comments



I love this! It's the "jazz music" of software development. Something which breaks all the "rules" but does so purposefully and explicitly so that it can become better than the "rules" allow.

A naive look at this and my head is screaming that this file is way too big, has way too many branches and nested if statements, has a lot of "pointless comments" that just describe what the line or few lines around it is doing, and has a lot of "logic" in the comments which could quickly become outdated or wrong compared to the actual code.

Yet at the same time, it's probably a hell of a lot easier to maintain and manage than splitting the logic up among tens or hundreds of files, it contains a lot of the inherently complex work it's doing to this file, and it is so well and heavily commented that it should be pretty easy to ensure that any changes also keep the comments up to date (after all, any change without changing the resulting comments should show up like a sore thumb, and will most likely prompt the reviewer to look into it at the very least).


> I love this! It's the "jazz music" of software development. Something which breaks all the "rules" but does so purposefully and explicitly so that it can become better than the "rules" allow.

I kind of see it as the opposite: “space shuttle style” is code that adheres to heavyweight rules that most software development has abandoned in favor of a more improvisational style.

But in either case it illustrates that code style rules are desirable, or not, based on the context; you need to understand what purpose rules serve and what trade-offs they involve to understand which rules to use; there's no one-size-fits-all solution.


I've had this latent thought for a while that I'm finally putting to words:

The complexity goes somewhere.

It's either into lots tests, or it's into something like shuttle style with lots of comments, or it's into a huge QA department, or it's into the type system / DB schema. It could even be going into the org structure!

But something, somewhere is handling the complexity and it is doing so as a partial function to the economic importance of the software to the stakeholders of the software.


In this case, the questionable choice of a "something" to handle the complexity is jarringly at odds with the high economic importance that the comments convey.

> tests

pv_controller.go has 1715 lines. To be generous, we might say half of it is comments. pv_controller_test.go has 359. Hopefully this code is exercised elsewhere in integration tests?

> a huge QA department

That's what you're signing up for when you choose a language that expresses cases mainly using if/then/else, and when you don't feel like testing every case.

> into the type system

Sum types aren't complex, although they're not familiar to everyone in the way i/t/e is. Even a result type (a straightforward example of a sum type, available e.g. in Rust) would simplify a lot of the "if err != nil" boilerplate, and greatly de-indent this code. It would probably also make invalid cases unrepresentable in a few parts of this file, eliminating a few more branches. In fact, a sum type typically requires an exhaustive pattern match, making practices such as their rule "every 'if' statement has a matching 'else'" the default.

My point is, complexity (and economic utility) are not conserved as you choose between these "somewhere"s. A few basic type system features can drastically reduce complexity for the QA team or eliminate comments and cases from the space shuttle.

Still - good on them for this degree of discipline & for so many well-written comments, with clear contracts about what is modified or not. ATBGE.


Monadic Try, biased Either, Some, pattern matching and destructuting... you don’t even need to know category theory to use and understand them in code, and you let the compiler do all the tough work.

This example - despise the perplexing celebrations - is a product of the limits of Go


Yet you can argue that despite it's limitations they could make working (complex) software with it.


That's not an argument, that's a truism. You can make working (complex) software using nothing but machine code keyed into volatile memory via front-panel switches.


can you really?


Isn't that a case of "shit crayons"? People can and do make great things with terrible tools, but that doesn't make those tools any less terrible.


> This example - despise the perplexing celebrations - is a product of the limits of Go

All coding style is a product of the limitations of the language (well, except when it is coding style dictated by the limitations of a different language applied in a cargo cult fashion out of it's appropriate context); and celebrating the choice of how to approach this problem given that it was being done in this language isn't the same as celebrating the language choice. Go would be pretty close to my last choice for anything, but that doesn't stop me from recognizing this as a thoughtful way to apply Go to this problem.


> pv_controller.go has 1715 lines. To be generous, we might say half of it is comments. pv_controller_test.go has 359. Hopefully this code is exercised elsewhere in integration tests?

The PV subsystem interfaces with external storage systems, so integration/end-to-end tests are much more useful than unit tests (yes, they do exist).


I don’t think this changes the inherent complexity of the code right? The basic logic of what is being done is still the same. De-indenting code but still having the same complexity, some of which is now abstracted by the language is still complexity. Maybe it somewhat helps the humans reading the code? But don’t you still have to reason about the basic state changes of the system the same way?


the code itself has the same complexity, but you are pushing the workload to the compiler, not to the human. Given the two options I typically prefer the compiler.


Compilers and libraries are written by humans and are probably the most fallible things ever, optimization wise. I'm not talking 1% here. I'd love to see an opposing compiler with an automated proof that it does what it claims.

Well, unless your computer can read and parse high order proofs over code. Not even Haskell can do that. (It can barely parse F types much less optimize them.) The closest I have come to that feature is Isabelle's simplify and even that is limited.

GCC and Clang can do basic bounds proofs at best and otherwise are tough buggy heuristic beasts. Just look at the trackers.

GHC has a slightly easier job but it's paid for in programming complexity. There are hoops in type system you have to jump to get performant code... and even then the code generated its at best meh quality. There is always some joker telling that it could be compiled for SIMD or multithreaded but that never materialized in usable form.


Compilers can only tell if programs are internally consistent, they can't help ensure that they are correct. Compilers don't know about inputs. Compilers don't know if the branch you wrote goes in the correct direction for a given input.


Well, the compiler can optimize away any superfluous branches which exist entirely to act as hints for the programmer.


> they can't help ensure that they are correct

Of course they can help. Maybe you meant:

> they can't ensure that they are correct


It prevents dumb mistakes like not covering all cases


So basically complexity would go in the language


A language that spends its complexity budget well can save complexity from a lot of programs. E.g. if you look at https://philipnilsson.github.io/Badness10k/escaping-hell-wit... , language A could offer all 5 of those ad-hoc solutions (making it a very complex language) and language B could just offer monads (making it a relatively simple language), but both languages would be just as effective in alleviating the complexity of programs written in that language.


And then language C(++) just skips unnecessary monads which are syntactic salt and uses exceptions, while providing the optional type for where you really, really want this behavior. Tradeoffs of course, but these were considered.

The main problem here with this "solution" is that any monad looks like every other monad. You can easily lose context and have to rely on naming conventions, which is quite terrible. You cannot spot what the code is doing at a glance.

And if you mess up, the type system and compiler will spout some completely unhelpful message because it parses everything the same.

Code that does very different things should look different, not identical. (Unlike what Lisp and FP people think.) Just think on why we do not use textual buttons everywhere in UI. It's a major reason fewer people accept Lisp than could...


> And then language C(++) just skips unnecessary monads which are syntactic salt and uses exceptions

Exceptions are an ad-hoc solution to a sixth problem. You still have the other five. (Ok, it's possible to use exceptions to replace null. But that still leaves the other four).

C does not have exceptions, or any solution to error-checking hell at all. C++ is a notoriously non-simple language. Neither is at all convincing as a counterargument.

> Code that does very different things should look different, not identical.

In a language with monads, code that does things that are specific to error-handling still looks very different from code that does things that are specific to async I/O. But code that does a thing that is fundamentally the same (parametric), such as composition, looks the same. This is the very essence of programming (and indeed of mathematics): "2 + 2" does something that is, on the surface, very different from "3 + 5", yet there is an important underlying commonality. Code that sorts a list of integers is doing something that is, on the surface, very different from sorting a list of strings, but there's an important underlying commonality. Monads just take the same thing one level higher.


> Code that does very different things should look different, not identical.

I think the major point you're missing here is that the whole point of pushing monads as a core abstraction is the way they shift what "different things" means.


This is a misreading, almost diametric, of what I wrote:

Me: "complexity is not conserved as we choose beween implementations"

You: "So basically complexity is conserved"

Maybe we're getting confused by this word "complexity", so let's break it down into two things: complexity of task handled (COTH) and complexity for programmer (CFP). For a given task of a given complexity, COTH is tautologically the same no matter how you choose to implement. The level of CFP, on the other hand, depends on how you decide to handle complexity. Implementing it in assembly language? High CFP. Implementing it with i/t/e, and commenting loudly your intention to handle all cases? Medium CFP. Enforcing these good, exhaustive standards with the type system? Low CFP. Why does CFP differ? Because the tradeoff matters. In particular, my point was that sum types add a tiny bit of complexity to the language, and remove a TON of complexity from the code. It's not a wash, or even a close contest.

(Notice that I don't make any super-radical suggestions, e.g. that their discipline around mutation/purity should be enforced with types or monads instead of exclamatory comments -- in which case, the CFP added to the language might outweigh the CFP alleviated from the typical file. But my point holds: CFP is not conserved)


I would say this is the fundamental problem of making software, and the reason that the whole world could learn JavaScript and yet being able to make great software would still be a rare skill and why even CRUD apps tax the minds of smart people.

Making the right component tackle the right amount of the right complexity is just a hard problem, and I think when it looks easy it's only because your options were constrained.


>Tesler's Law, also known as The Law of Conservation of Complexity, states that for any system there is a certain amount of complexity which cannot be reduced.

https://lawsofux.com/teslers-law.html


Cute, but objectively not true.. using the right tool, or right approach can drastically simplify the solution, sometimes even making intractable problems solvable.


>

I take this more to mean that the logic you're trying to implement has a fixed, non-zero level of complexity (sometimes called "essential" or "inherent" complexity), which forms the complexity floor of your application. On top of that, your implementation adds additional complexity (sometimes called "accidental" or "incidental" complexity), which is not-zero but not fixed.

So, my reading is that in saying "every application has an inherent amount of complexity that cannot be removed or hidden", the law is referring to the essential complexity. Meaning, the law says "some of the complexity is unavoidable in every application" vs. "the amount of complexity is fixed in every application". I do think the name of the law is a little weird, as it implies the latter meaning.


The trouble with Tesler's Law is that it's often difficult to distinguish between essential complexity and incidental complexity. When I face a UX problem that seems like an insurmountable barrier, if I step back and consider many perspectives, I often find a way to change the context so that the problem becomes easy to solve. What feels like essential complexity is often surprisingly incidental.


What I'm objecting to is the notion that there is a "fixed, non-zero level of complexity." Often real innovations in organization and structure allow for fundamentally simpler implementations. I'm reminded of physics: there's an inherent complexity in solving a rotational problem. But introduce polar coordinates and you fundamentally change the game, removing a dimension from your analysis for some problems, and making the solution trivial.


In this case your original estimate of the complexity was high due to a choice of coordinates increasing the incidental complexity of the solution


If it was objectively not true, then you could have infinite compression and any program could be reduced to a single bit.


If "conservation of complexity" were universally true then ANY compression would be impossible.

This isn't a dichotomy. My point is that there are clear examples of situations where you aren't just pushing complexity around, but actually achieving great simplifications.


>If "conservation of complexity" were universally true then ANY compression would be impossible.

No it wouldn't. The complexity of a pattern can usually be conserved while reducing its length, but for each pattern there is a limit. This is the entire concept behind the Kolmogorov complexity of a system and any patterns that cannot be reduced any further without removing complexity are at their limit already.

This is also related to the idea that you cannot have a universal compression algorithm.


I don’t see how your example makes it false.


Wow, that site is beautiful beyond words.


I'd move the unnecessary big banner somewhere. A website is not a book and does not have cover pages.


Precisely, you can only move the complexity around but not eliminate it completely.


That's kind of a truism though. There will always be a non-zero entropy for the distinct kind of systems you want to build.

Unless there is one system to rule them all, configured exactly the way it is needed out the box.


In the most ideal way, the complexity becomes the code structure. People can only keep track of 7 or so odd things in working memory at a time, we tackle complexity through chunking (abstraction), so that these new 7 things are in turn each 7 more things which are in turn 7 more things (ideally).


Your code structure is not my memory chunking structure and it will interfere.

At a point you run the risk of just overloading memory with way to many names and entities by merciless chunking, not to mention create a file or function maze.

Inlining works surprisingly well because you do not have to memorize things and can just read them. Theoretically it is a tooling problem, but nobody wrote a good enough "inline" tool so instead everyone relies on incomplete and buggy textual descriptions.


I'm not sure what to say, just do the right thing and be more practical than dogmatic I guess? There are situations where it's better not to install an abstraction. There are situations where an abstraction fits super well and is well understood by your dev team.


I had this thought when working with a legacy app the company was migrating to a modern platform. The legacy app was enormously complex and contained huge amounts of business logic. Migrating it didn't get rid of all that business logic. Now, using modern software patterns inside a framework with it's own complexities, it was a lot more organised but perhaps even more complex thanks to business logic now needing to fit inside the frameworks idioms and structures. It was nicer to read through, but no less complex.


I added this comment to my quotes collection.

I think you are more or less spot on. Personally I'll mention explicitly (one could argue that you already say this above) that well designed libraries and languages helps you by containing some of the inherent complexity while avoiding to add incidental complexity.


Probably the reason why libraries and frameworks are so omnipresent.

They solve the complexities the average programmer couldn't control on their own.


The job is to distinguish between inherent complexity and accidental complexity.


This^


> I kind of see it as the opposite: “space shuttle style” is code that adheres to heavyweight rules that most software development has abandoned in favor of a more improvisational style.

Most software other than that written for space shuttles and other seriously critical applications.

A colleague of mine works for a telco, and changes to software that runs on satellites takes months to approve and goes through verification stages that include running on simulators and duplicate hardware that is on the ground.


> probably a hell of a lot easier to maintain and manage than splitting the logic up among tens or hundreds of files

I'm only halfway through John Ousterhout's book Philosophy of Software Design but I think it agrees with you on this -- that smallness-of-file or smallness-of-function is not a target to shoot for because it prevents the things you build from being deep. That you should strive to build modules which have deep functionality and small interfaces and should contain their complexity within them so the users don't have to know that complexity.


I just finished his book yesterday; he has a lot to say about size and comments. For size, your summary is spot-on. I'd only add that he notes overeager splitting of methods and classes makes code involved in a particular abstraction to be no longer in one place, leading developers to constantly jump around files, which makes it more difficult to understand the code and increases the chances of making bugs.

As for comments, this file is essentially Ousterhout taken to the extreme. Still, I think he would have like it, given how critical this file is. In the book, he encourages writing more comments than the current trends would suggest, pointing out that you can't fully express abstractions in code, so all the things the code doesn't contain - the high-level concepts, the rationale, the caveats - should be documented in comments in appropriate places.

Overall, I'm extremely impressed by the book, and its focus on reducing and mitigating complexity.


> I'd only add that he notes overeager splitting of methods and classes makes code involved in a particular abstraction to be no longer in one place, leading developers to constantly jump around files, which makes it more difficult to understand the code and increases the chances of making bugs.

Another way of describing this, that I ran across recently, is that this increases the cognitive load for developers working on the code, and cognitive load is one metric by which code can be measured as "good" or "bad". Things like excessive scrolling and switching between files increases cognitive load.

So heavily-commented code can decrease cognitive load if the programmer can read the comments and the related code block at the same time and if the comments help to explain why the code exists the way it does. Or, heavily-commented code can increase cognitive load if the comments don't accurately describe the code, or if they're so verbose that the programmer has to scroll up and down to digest both the comments and the code together.


Scrolling is fixable by code folding. You've read and understood the part, now you can fold it. It takes some factoring discipline to not end up with a woven structure.

Unlike a single use function, it does not have a name to remember and is in-place - and definitely is not shared so can be assumed to be safe to modify.

Comments would be much nicer if we could still use column-based commenting which unfortunately is not usable in any modern IDE. Reading code and comments side by side tends to work much better than interleaving.


> Another way of describing this, that I ran across recently, is that this increases the cognitive load for developers working on the code, and cognitive load is one metric by which code can be measured as "good" or "bad". Things like excessive scrolling and switching between files increases cognitive load.

Actually that’s one the reasons why he advocates not splitting code (arbitrarily) in the book.


Martin Fowler of the Agile world, and Garret Smith of the Erlang community, are both excellent programmers whom I respect, and they both take the approach of breaking code into lots of extremely small functions.

Having tried that style, I notice that I don't particularly favor it, and for the very reason you site: the code is no longer all in one place.

I've switched to moderately sized methods/functions with comments every few lines. Some say that comments like this are a smell, and that you should refactor the commented section of code into it's own function, but honestly comments are easier to read than method names (and again, there's the benefit of locality).

I'll have to take a look at the book.


I assume everyone who splits code into smaller pieces use modern IDEs that makes it trivial to navigate to functions by clicking them etc.

I say this because I'm always astonished by the number of "modern" programmers who refuse to use IDEs.


> I assume everyone who splits code into smaller pieces use modern IDEs that makes it trivial to navigate to functions by clicking them etc.

That's... not the point. Jumping around is. Imagine reading this comment thread on a bizarro-HN, where you only get to see a short camelCased summary like: debunk(this.previousComment), and have to click to open each comment in a new tab. This is how jumping around small functions feel.

> I say this because I'm always astonished by the number of "modern" programmers who refuse to use IDEs.

There are reasons for it. Many languages don't have an IDE. Many are not suitable for one (especially ones closer to Lisp on expressiveness spectrum). IDEs are heavy and often mouse-oriented, and not efficient for reading and editing text. Sometimes (read: Java) they are a crutch to work around the expressive deficiencies of the language.

Mind you, I have nothing but good things to say about IntelliJ. I've spent a lot of time in it even recently, and I pick it up any time I have to do anything in Java. But for everything else, I launch Emacs, because it can handle all other languages well, and has superior editing capabilities.


> Many languages don't have an IDE. Many are not suitable for one (especially ones closer to Lisp on expressiveness spectrum).

This is a minor point of your comment, but I'd like to refute it: Lispers actually often cite IDE integration as one of the great features of Lisp. It may have lost pace a bit with some of the very best modern ones, but Lisps have had "modern" IDEs since about the 70s or 80s, with stuff like jumping to function definitions, finding usages of a function, looking up the docs etc. Even better, this is usually a part of the language runtime (predating most other language servers by quite a bit), so it can even apply to dynamic uses.


> That's... not the point. Jumping around is.

There is a tradeoff: Small functions make high-level logic clearly visible and easy to find, at the price of forcing you to jump around when you want to dive into implementation details. Putting everything into one big function lets you follow all the implementation details without jumping, at the price of making you read everything to actually understand what the code is doing.

The latter is the biggest price you can possibly make me pay. Jumping around is a minor inconvenience.


You're describing an unstructured single function.

There's a middle ground - sectioned code, esp. with code folding or similar commented sections. Good IDEs support such a feature so you can hide the code you think you understand to not look at it every time.

You will have to read this code at least once or trust the documentation and name. I found that in "mature" code the latter is a recipe for disaster and days of debugging.


Wasn’t that the purpose of the long comments: To sketch out the high level logic, while keeping all the code in one place. Jumping around, you now have to do that to be sure of the details and the complexity can be hidden in layers of functions. Complexity that often matters.


By choosing the right names for a function or class a lot of jumping around can be prevented. Most IDEs also have a feature (including key combination) to show the documentation of the method/function. The only reason that remains is when you doubt the correctness of the method and need to look at its definition.


The only and critical reason - skipping the check will net you tons of debugging all the time because hidden assumptions are often not documented. (or otherwise visible)


IDE or not, jumping around between functions amd their callers to understand a process is annoying.


Sure.

In sane code the name of the function should describe what they do well enough that you rarely have to click in to learn how they do it.

Or something like that...


That applies recursively to the function you're just reading :).

I.e. I wouldn't be inside a particular function of a particular module if I didn't have to know something about its implementation. There's a good chance I need to understand all of it at the level of abstraction of the module (often because I'm supposed to change something about it). Making that less painful leads to better and less bug-inducing experience.

Elsewhere[0], 'usrusr brings attention to nested functions, lack of which I see as a huge factor contributing to overeager splitting of code. With nested functions, you can have "best of both worlds" - a function whose implementation is divvied up into well-named pieces, while keeping those same pieces in the correct conceptual place, close to where they're used, and restricted from polluting unrelated code.

--

[0] - https://news.ycombinator.com/item?id=18773691


Totally agree on nested functions. I currently have to deal with Java, and those are the biggest thing I miss from Python.


I would make no claims to being an exceptional programmer, but fwiw I don't like nested functions - it always takes me a lot longer to reason about what a function is doing, when it has functions defined inside it.


This can depend on the syntax and semantics of nested functions.

Pascal nested procedures are pretty easy to parse and if they're defined before the `var` block then you don't need to worry about non-local state modification (apart from the parameters, but modifying parameters is unusual).

First-class nested functions with variable capture are harder to understand. Nested functions in e.g. JS are more like object construction than function definition, and it's generally expected that such nested functions will be capturing state from the enclosing context.

Standard Pascal permitted outer scope variable access combined with downward funargs - the nested functions could be passed to other functions, but could not be stored in variables or returned as arguments. This reduces the non-local effects, and handily doesn't require a GC to ensure memory safety, since the captured state can stay on the stack, because the nested function won't outlive the frame.

For nested decomposition of a problem, I'm more of a fan of Pascal-style nested procedures than nested function expressions like in JS. Idiomatically, one expects function expressions to capture state, while nested procedures are just more procedural decomposition.


I second the answer about having them before var-block: In python I always follow the style of having nested functions directly after the outer function header, and only going one layer deep. This way the only thing in their scope is their parameters as well as the parameters of the outer function. Using this restriction I find them very useful and concise - much better than having one-off helper functions in the outer module scope.


Kotlin supports nested functions along with quite smooth Java interop.


You seem to be arguing against the concept of functions in general here.

That's probably not what you mean to say, but I don't find much to really discuss here.

I do like local functions in Python, but I don't see a huge difference from having the called function right below the calling one.


I have read an extreme counter-opinion in some J or APL article. It said that it is a bad pactice to name small and common functions:

The example was maybe the average function - and the reasoning was, if I recall correctly:

1. The defintion is shorter then the name average ;)

2. Every praticioning programmer will recognise the definition as a common idiom

3. From the definition it is immediately clear how corner cases are handled (e.g. zero length array)

I just leave this here as an example to show, that programming communities/cultures exists with completely different/alien? ideas about what clean code is ;)


Allegedly. And then you suddenly want to write a SIMD version of an average or optimize it... Mass search and replace time? That'd bloat the code a lot.

Common repeated well defined and mostly immutable code is best left as functions. This is why for example in C strcmp exists instead of everyone writing a two-liner - and the specialized variant gives big performance gains.


I've got your point.

Just for the sake of nitpicking: both your actual examples have been solved by compilers automatically.

SIMD/AVX: https://code.jsoftware.com/wiki/Guides/AVX

JVM generates vectorized string instructions from your simple for cycles - if availabe: http://jcdav.is/2016/09/01/How-the-JVM-compares-your-strings...

edit: my second example link is maybe wrong, but anyway, auto-vectorization is a real thing...


For Java programming, I definitely use IDE. Even for Erlang programming I use Emacs with EDTS to get a more or less IDE experience.

I'm saying that, even with that convenience, I don't wrapping a line or two in it's own function is more readable than just adding a comment above those two lines within the context of a (somewhat) larger function.


A technique I use quite a bit is to group functionality within a method using `#{` and `#}` to bound the code doing the thing. It gets you the "grouping" idea of lots of methods, but if the code is only used in one place, there is no reason to pull it out into a method.

Something like:

  #{ Parse input parameters
  .... <several lines of code here>
  #}


I like this idea and will try to apply it to our code. Why do you prefer it over splitting the code out in a different method? Is it so you can read everything in one glance? If so, an ‘inline’ feature could be added to IDEs to show definitions inline.

Also, when using applicative or Monadic style programming there is little reason not to split things of in small separate functions and chain them together in the right order.


Yea, it probably doesn't make sense for every coding style. I just find that if I split things apart too much it turns into spaghetti that I have to try to weave my way through to understand what's going on.

There are definitely places where it works out really well though.


I think it might come with experience. I definately gravitate to very small classses/functions, personally prefering classes to fit within a single screen.

Obviously this isn't always achievable.

It comes down to: "What do you want to focus on?" Each drill down should be to a lower level of abstraction. It is seperating the what from the how.

It results in functions/classes that either detail a flow (set of decisions) or that implement actions. For example I generally don't need to know how to read a file from disk where I am trying to decide if I should read a file.

When looking at code you drill down in a very vertical fashion when you want to know "How does it do operation X?" Meaning the operation largely can be understood in isolation. There shouldn't be a lot of context that is required for it. A side-effect is that all the unit tests are very self contained.

This utlimately steers you towards a much more functional and compositional programming style. Certainly it is moving complexity around, but the end result is heavy compartmentalization and limiting the context required to understand any section of code.


I like your description of separating flow from actions, I try to do that too. I want my functions higher up the abstraction stack to read like they are orchestrating black boxes of functionality.


In general I prefer smaller functions. I think the threshold is when they become hard to name. Then I will step back and think about whether it's worthwhile.


I really enjoyed the Ousterhout book as well. It's my current user manual for reviewing pull requests.

Putting my PR reviewer hat on I would say the code in question would pass muster if it were relatively stable so you would not be constantly redoing the comments. I love nice comments but Golang does not give you much help keeping them in sync.

(Weirdly enough I'm working on a PR for persistent volume documentation at VMware today so the code is very apropos.)


> Golang does not give you much help keeping them in sync.

What do you have in mind, here? Something like python's doctest?


Javadoc. It generates warnings if method argument documentation does not line up with the method signature, for example.


Elixir tests code in comments for correctness


There is a review and discussion of that book here, https://news.ycombinator.com/item?id=18331219


That is the exact thread that prompted me to buy the book.


Now i'm squarely in frontend web-app development right now which definitely changes things (mainly the complexity is centered around enabling fast changes/additions to the codebase, and not the actual business logic for the most part), but while "deep functionality and small interfaces" sounds good on paper, most of the time giant files with a few functions exported aren't a good way to manage that.

Sure, it solves the problem when viewed from the outside. "users" of the software (users being other devs in this case) get a nice small interface and docs that explain how to use it, but internally it's much harder to work with. Having everything in one file like this without breaking it into "sub modules" for various parts of the module means that you need to almost have a complete understanding of the module before working on it.

In this case, I have a feeling that is a pro not a con. Because this file is so core to the system, and has so much complexity, that breaking it up into smaller parts could cause a dev to feel like they understand it only to find out they don't after it's released. And it means that any devs that truly do understand it top-to-bottom would just waste a lot of time switching around files if it were split up.

Putting it all in the same file here nudges you to really make sure you understand it top to bottom before making any big changes. It's intimidating and scary for a reason, because at its core it is a complex piece of code, and dressing it up in "simple code's clothing" won't help.


Breaking things down into smaller pieces is good when it’s good... but there are drawbacks that I feel are often ignored.

For example, as you say, a small piece of stand alone code implies that someone can do meaningful work on it without understanding the entire context around it. It also implies that it is suitable for reuse. But if it’s both reused liberally and encourages you to keep working on it, it will almost certainly mean that your changes will have unanticipated consequences. So you still can’t get away with being unaware of the context.

When you break things apart you also fossilize that way of approaching the problem, which often makes it more difficult to see orthogonal approaches and refactor towards them later. Instead you keep working within the structure that’s already there, which often leads to concerns being spread out across different modules. Too often people decide on the structure before they even understand the problem.

I think it has similar problems as religiously following the DRY principle. There are so many situations where your code will be much, much worse if you insist on always sticking to DRY.


> When you break things apart you also fossilize that way of approaching the problem, which often makes it more difficult to see orthogonal approaches and refactor towards them later.

This is particularly true on teams where a significant proportion of the developers is reticent to refactor code as they go. It seems that given a developer with a sub-80th (or so)-percentile propensity to refactor, the more broken up the solution is (into smaller functions, methods, classes, modules, etc.), the less likely that developer will be to refactor the solution when an obviously better approach exists.


this.

> Too often people decide on the structure before they even understand the problem.


> In this case, I have a feeling that is a pro not a con. Because this file is so core to the system, and has so much complexity, that breaking it up into smaller parts could cause a dev to feel like they understand it only to find out they don't after it's released.

I think you're on the money here. This was done intentionally, to ensure that devs understand the whole thing before making changes, because the functionality is so critical and so easy to mess up.

Also, breaking single logical things up into smaller files (rather than into a composition of smaller logical things) just leads to unneccessary file hopping and wrecks locality of reference for the dev.


> Having everything in one file like this without breaking it into "sub modules" for various parts of the module means that you need to almost have a complete understanding of the module before working on it.

There's a balance to be struck here; you want to minimize the size of the code a developer has to understand to work on (or with) a given abstraction, but you don't want to split beyond that point, as it only makes the developer jump around files. In my experience (with Java in particular), current development trends involve splitting the code too much.


Java (like many other languages of that generation) suffers from a lack of idiomatic 1:1 visibility. Whenever you split something up in Java it litters a namespace that is much bigger than necessary. Even private is too big when the class is full of tiny methods most of which most will never be meaningful to any of their peers except for that one call site. Sure, you can create inner function objects and with 8+ it's not even completely weird anymore, but that's still a far cry from the nested functions goodness of Pascal style "structured programming".


Agreed. I don't remember much from my brief exposure to Pascal years ago, but spending a good chunk of my programming years in Common Lisp has spoiled me; the simple ability to nest functions is something I sorely missed when working on Java codebases.


One of my favorite features of Haskell is that I can use where to write the auxiliary function definitions after the code that use them, but keep them scoped inside of a single function.

A relatively bad example here: https://wiki.haskell.org/Worker_wrapper#Hiding_the_worker


Bindings in the `where` clause can still access names in the main binding: https://wiki.haskell.org/Let_vs._Where#Advantages_of_where

This runs the risk of strongly coupling them to the main binding and making them less reusable. IMHO strong coupling where two bindings are aware of each others' internals should be avoided.


Like your link says: doesn’t lambda lifting solve that problem?

That’s what I always do if I want to move an auxillary function to the top-level for reusing elsewhere.


Indeed, I also prefer lifting, especially internally inside a module. It jives with the idea of loose coupling, strong cohesion.


> That you should strive to build modules which have deep functionality and small interfaces...

this. interfaces are not the most important thing in a software but definitely one of the most. you can live with a crappy implementation but your interfaces must cater to the usecase and should only hcange when core premises change.


Haven't read the book. Goes to my Wish List. I completely agree about encapsulation of complexity inside the module.

Similar to UX design principles. Just like google or other successful sites present a very simple interface (in case of google the home page is quite clean) but behind it lies very complex code.

IMO, encapsulating complexity and handling all edge cases also lends to total functional programming [1] even without using pure functional languages. [1] https://softwareengineering.stackexchange.com/a/334876/38285


> that smallness-of-file or smallness-of-function is not a target to shoot for

I disagree. Unless you are methodical and know what you are doing (like NASA or the authors of Kubernetes), it is hard to create large functions and files by keeping levels of detail consistent and not repeating code.

How do you test a function that has 100 unique outcomes? How do you safely maintain it to ensure it won't break? How do you even know it's working?


Write automated proofs? Probably best but tooling is junk - it's rarely supported.

Constraint or property based tests as opposed to value tests? (That depends on how unique.) Usually the only thing that does unique things is a direct mapping, everything else is either an equation or has other preconditions, postconditions and internal or external properties. It also makes the logic more directly visible and refactoring easier presuming tests are reasonably written.

Ensure coverage with a tool to be sure.


Write 100 tests?


I agree. My day job is working on code that isn't this level of critical, but also has the characteristic of being low level, both closer to the metal than typical backend code and also called by so much frontend and backend code that if there was such a thing as "even backend-ier code" this would be a good example. If you miss a nuance, a horde of angry developers will show up at your desk the moment the build deploys, and if you don't fix it fast, that turns into a horde of angry customers.

With code that far down in the inner loops of mission critical code, the dominant time cost isn't really writing or even understanding the code, it's ramping up on the nuances of the scenarios you serve, checking the direct effects of your changes, and then checking the second order and third order effects of your changes.

When your code is so far down that every change you make has third order effects, it's a lot easier to maintain code that is meticulously commented and written for maximum thoroughness explicitly baked into the file in front of you, because you're much more likely to miss the nuances if you just finished piecing together ten different files to figure out how the happy path works.


Well said. I feel that 80% of this thread is people talking past each other, with different assumptions about what the code in question is going to do.


Note to others: 3rd order is not necessarily 10^10^10. It can easily be 10^100^1000.


I hope you mean x (multiplication) rather than ^ (exponentiation). If we're talking about fanout (each of 10 items of has 10 subitems, each of which has 10 subitems), multiplication is the relevant operation. And 10^(10^10) or (10^10)^10 is a hopelessly, uselessly, inconceivably large number. (Exponentiation is not associative; 3^(3^3)=3^9= 19683 while (3^3)^3=9^3=729.)


I took it to mean something fairly vague about potential super-exponential blowup in the complexity of the states you have to reason about, when you have to reason directly about third-order effects in a code base.


Interesting! Is there a convention to follow?


I completely agree. For code that is unavoidably complex, I love this style too.

I am all for code that is concise and whose syntax/naming is expressive, but sometimes comments are necessary to clearly spell out the logic or business use case. Expressive code can only go so far. Well-crafted comments significantly reduce the amount of time required for other developers to dive in and become productive with an unfamiliar code base.

The key is keeping the comments up to date. There's nothing worse than an inaccurate comment. One's code review process must include a review of the comments accompanying the modified lines.


Outdated comments that explain the business use case or purpose are still better than no comments. It gives you background information how the code evolved or what it was supposed to do.

It's probably because reading comments only is worse than reading code without comments, that some devs developed an aversion towards outdated comments and thus comments in general.

Comments are additional information and no source of truth, always take them as that and read code and comments.


It’s much worse in codebases that predate version control. At least a commit shows the context of why it was added.


Yeah and over the course of 2 or 3 decades of development a lot of software has moved through several different version control,ticketing systems and developers.

So you wind up with files stating an author who no longer works there with an email address that the company used 3 acquisitions ago, a ticket number you aren't even sure what system it was for but you just know it isn't being used anymore and source control history that goes back 5 years out of a total 25 years of development.

The entropy is real.


You just described LibreOffice!


I just described a lot of offices.


Agreed. Also, comments that emphasize the "why" over the "what" are not obviated by descriptive names.


I've found it very helpful to rubber duck comments to get into the right why mode. Consider how you'd explain this piece of code to a less experienced guy on the team - and write down exactly that. Why was this added? Why was the old thing in place changed, what broke and had to be changed? Why didn't you do the other obvious thing? And remember - usually you should explain something until it's clear for you, and then one more step.


About five years ago I worked on a codebase with a similar bit of code. It wasn't nearly this big, but it was branchy, procedural, and verbosely commented. I didn't write the initial version but worked on it quite a bit and learned to appreciate the advantages of the style for the nasty bit of logic it implemented. I ended up having to vigorously defend it against another developer's half-cocked attempt at "refactoring", which got deployed more than once (behind my back) and thankfully broke in pretty obvious ways each time.

I warned him at the beginning that he needed to spend a couple of hours wrapping his head around the code before trying to modify it.

I warned him that the code arrived at its current state after long and painful experience.

I warned him that other developers had worked on the code, had exactly his initial reaction, and eventually admitted they couldn't find a way to improve it.

I warned him, after he confidently declared that it just needed to be "more object-oriented," that I had written a lot of object-oriented code, was open to writing it in an object-oriented style if it would improve things, and could not think of any way to do so that would not make things worse.

But he could not even bring himself to read the existing code. He never did. He worked on it for three weeks, wrote thousands of lines of code, and even tried to deploy his own version without ever spending a single contiguous, focused, hour-long block of time reading the existing code. He was "refactoring" the whole time.

The code he produced was exactly how you imagine it. The code went from several hundred lines in a single file with no classes (just a containing class acting basically as a namespace) to thousands of lines scattered over half a dozen files with at least that many classes. The guy kept adding classes and kept adding test after test after test. He couldn't get his code to pass the test cases I had written, so several times he declared that my test cases were wrong and changed or removed assertions. He also claimed that the existing code couldn't "pass" his tests because he had hundreds of lines of tests for components that only existed in his code. According to him, this proved that there was a ton of "hidden untested functionality" in the existing code, which was therefore "dangerous."

I was pretty busy with other things, and this was his project now, so if he had been a little bit more clever he probably could have made his abomination stable enough to replace the existing version over my objections. Thankfully it was never good enough to survive in production.


Honestly, after the experience described, I would consider the person a work hazard and try to make sure they didn't touch anything business critical.


I think he realized his comfort zone is the middle tier of three-tier web apps and is doing his best to stay there.

On the other hand, this code was in the middle tier of a three-tier Grails web app, so....


Why do all anti oop posts sound like completely unlikely, exaggerated lies? A few hundred lines to thousands with dozens of files and classes (gasp). Lol


I don't have anything against OOP as a style to have in your repertoire and use when it's beneficial. Unfortunately, OOP was promoted in a pernicious way for a couple of decades, resulting in stories like this where a guy who

* didn't understand a difficult piece of code

* knew that the code was written and maintained in its current form by multiple programmers known and respected by him who had more exposure to the problem than he did

* knew that said programmers were well-versed in OO programming and had considered and rejected OO style as a way of improving the code

... this guy, because he had learned software development in OOP's heyday, simply knew as a self-evident truth that the code ought to be OO, because OO was better, and if the code was hard to understand then the proper way to approach it and try to understand it was to incrementally refactor it into classes. And he never stopped believing these things even after his multiple failures.

This is not a knock against OOP. This is a knock against the people in the 1990s and 2000s who thought software engineering was a solved problem, thought class-oriented OOP (exemplified by design-patterns-driven corporate Java style) was the solution, and thought the only hard problem left in the field was to teach all programmers to understand and accept these truths.


> A naive look at this and my head is screaming that this file is way too big, has way too many branches and nested if statements. A naive look at this and my head is screaming that this file is way too big, has way too many branches and nested if statements, has a lot of "pointless comments" that just describe what the line or few lines around it is doing, and has a lot of "logic" in the comments which could quickly become outdated or wrong compared to the actual code.

I actually understood it immediately when I read it. There's something to be said when you have a decent roadmap for debugging critical code segments. It's not just software maintenance here, it's for the "holy crap something went wrong and we can't understand how we got here!" moments.


This is why I prefer older JavaScript to the more recent stuff, with transpiration pipelines and AI-like optimizing compilers that gnaw on giant state trees.

That stuff is lovely and terse when it works. And when it doesn’t you are at a total loss.

The beauty of imperative code is there’s a beginning and an end, and at every step you can easily see what follows and without too much work what came before.


I don't understand the objection to having more, smaller files, at least in Go where they can all be in the same package.

Once two functions are too far apart to be on screen at the same time, jumping back and forth between two functions in the same file doesn't seem any easier than switching between different files. If anything, switching between two different files is easier since they each get an editor tab.

On the other hand, being wary about extracting functions (which moves things logically together further apart) makes more sense.


For me, it's less about number of files than it is "hoeany files do I have to open to figure out how something works? How many levels of indirection do I have to keep in my head?"

I started out writing low-ish level code. Motion control, image processing, digital imaging, and the application level code that coordinated it all. I've steadily moved up the abstraction tree over the last 13 years and there's one thing I hate about it; too many fundtions and classes which do far too little. Abstraction for abstraction's sake.

It's a bit subjective of course and not everyone has their own style, but there is a Starbucks difference in philosophy between the two types of groups.


> [how many] files do I have to open to figure out how something works?

With jump-to-definition editor integration, who cares?


Anyone who actually worked with code requiring so much jumping.

I did, in an IDE, and I can tell you, jump-to-definition removes the problem of "what file do I have to open now?", but still leaves you with the questions like "where am I?", "how did I get here?" and "what was I trying to understand, again?", which you start asking yourself after ~sixth jump.


Not to language bash, but situations like this are why I avoid "enterprise" patterns and abstractions. Not that they aren't ever useful, but they're often implemented before they are needed, or not needed at all in practical terms. Some C# and Java tend to be some of the worst examples I've encountered. Even if the languages themselves can have much simpler implementations.


Java has these scaffolding developers who spend great deal of time in writing, 'ticket booking', 'toy/video stores' etc with all technology, framework, libs they are looking to learn. None of this code does anything useful but developers add to their resume claiming they developed reservation systems and e-commerce platform with mongoDB or whatever.

I'd add this kind of bullshit started directly from Sun Microsystems when they gave us Java EE blueprints to develop end-to-end enterprise solutions in all seriousness.


> "how did I get here?"

In emacs go-mode, pop-tag-mark will take you back to where you jumped from.


I know.

It'll take me one level up, which is not sufficient to answer my question, and by the time I find my bearings again, I have to jump back down a couple levels.

I've done a lot of this, both for Common Lisp codebases in Emacs, and for Java codebases in IntelliJ. Having to jump around and remember stuff eats into "7 ± 2" short-term memory limit that you need for the code you're working on.

(With Emacs, it's at least a tad easier to split the window to have 4+ different in view at the same time.)


Yeah, I like to use a couple of big monitors plus my laptop screen, and have all the relevant code visually accessible at once.


"far too little" it sounds good but you need to think about what you're saying. Do you want a function to be doing too much or too little. I think any sensible programmer will choose too little. It's easier to understand. This isn't about abstraction and the trendy fashion that is the irrational fear of it nowadays. Is about people being better at tackling problems in small doses.


You should learn markers in vim; really useful for jumping between marks in the code


This is what a lot of Go code looks like. This "space shuttle" code honestly isn't much more verbose than most Go code I interact with. The main difference is they have more comments here.


Likewise, I've seen a lot of Go that is far away from the idomatic Go heaven that is the standard library. Although that happens in every language as soon as there's reasonable complexity.

As for the "space shuttle" term, it's more of a euphemism for "do not even attempt to refactor this mess, it's so complex that you'll surely fuck up if you do, and you'll make it harder for the one guy who can actually understand this"


"else/else if" is not used very often in Go.


It's not used very often here, either. Despite their claims at the top of the code of every `if` having a corresponding `else`, there are 140 `if`s, but only 20 `else`s and a mere 5 `else if`s.


Most of the ifs are for early termination. Using an else on one of these is wasteful.


It's freaking kubernetes, so obviously it will be a well understood file by a LOT of people. However this is the kind of file that also exists in unknown, non-open source products, and while may have been understood by lots of people (at the respective companies) sometimes a period of 5 years of inactivity and layoffs and hiring goes by... By gosh darn it- this is it. It's the reason we have shitty code but great products.


Straightforward concepts should be coded consisely and complex concepts should be coded verbosely. The commenting in this file slows down the thinking of the reader to an appropriate level.


To be honest, I remember myself back during my first dev seps struggling a lot reading code since a lot of the senior programmers tend to code very effectively, so it was extremely hard to follow an end-to-end solution without terribles headaches.

This is just brilliant, not only because as educational exercises explains perfectly what's doing but the business/thinking process/context.

Never read 1k lines so quickly before, never enjoyed so much.


> It's the "jazz music" of software development.

If Anthony Braxton wrote software...


oh no the "I love it" first post ... the fact this type of post is the top of so many threads leads me to believe theres more behind these posts.

the positive renforcement cheerleading to start off so that people dont get demoralized in the hateful comments, while effective also seems fake.


I also noticed this. I wouldn't be surprised if there was some editor-in-chief who basically scores the comments based on his taste.


Ignoring the initial boilerplate (license, imports) and the request to preserve the verbose ("space shuttle") style, the first line is:

    // Design:
    //
    // [... 4 paragraphs of English prose
    //      explaining goals and intent... ]
That's exactly the type of comment that should be at the beginning of most files!


As a novice programmer, I was absolutely stunned that this was not standard practice. A typical source file provides zero context, background on the subject, pointers to reference material/blog posts/books explaining the concepts, information on how it fits into the program's 'bigger picture', or (most importantly) the thought process that resulted in the file (i.e., why the choice was made to do _this_ rather than _that_, challenges faced, trade-offs, etc.).

It still baffles me. Every programmer has to start from scratch dealing with a new codebase, and it makes improving any non-trivial program impossible unless one is willing to spend hours of archaeological examination. To open-source developers: if you want to get people contributing to a project (and make everyone's effort much more enjoyable!), these sorts of comments are essential. Not to mention they'll save everyone boatloads of time; it's a shame that every programmer has to piece together knowledge from scratch, rather than being 'tutored' by their peers' comments.


There's plenty of good reasons to not write 95% of code with big walls of explanation. The first is a matter of cost: Writing a good explanation around everything is very expensive to do at first. A whole lot of the custom code you find in random companies, from the shiny SV startup to the old enterprise, is unimportant, cobbled together pieces. We have no idea of whether we are writing code that will be thrown away in a week, a month, a year or whether it will last two decades. Context can change fast enough that the comments become worse than useless, as the terminology might have changed, or had been misguided in the first place, turning the long comments into outright unintended deception. This gets even worse as we do not evaluate all the comments in all the files whenever there's a code change: It's crazy how a large comment block in one place can become harmful after it's forgotten, and someone else makes a correct, business critical change in another file. No matter where I am working, it's rare for me to not find multiple examples every year where the code and the comments provide very different impressions of what is going on, and it's the code that is accurate.

This is not to say that there aren't reasons to write large comment blocks, or architecture documents, but that they are often better written not while the system is being first built, but later, in a maintenance cycle, when someone already had wished for the comments, and has regained the knowledge the hard way. By then, it's clear which part of the system are dangerous, suspicious and unclear. Where there's more need for high quality error handling, and where thousands of lines of error handling never get hit, because the failing case that was originally considered doesn't really happen in this dimension anymore.

Writing code so that someone, even a future you, can pick it back up and improve it when it's needed, while still delivering the code at a good pace is a kind of skill that many just don't learn, either because they are always in greenfield teams that never pay for their mistakes, or have an approach to maintenance that involves not becoming intimate familiar with a system, and instead either hack or rewrite.

But nobody looks great in a resume by saying that they are a specialist in software maintenance.


> This is not to say that there aren't reasons to write large comment blocks, or architecture documents, but that they are often better written not while the system is being first built, but later, in a maintenance cycle, when someone already had wished for the comments, and has regained the knowledge the hard way.

I don't think the "Lean Manufacturing" approach works here. By the time someone "pulls" you for a comment, you've already lost the most important knowledge that should go into the comments - why the code is the way it is. Maybe you'll recall it when asked, hopefully not missing anything crucial. Meanwhile, comments are extremely cheap to write as you're writing the code, and even before you're writing the code (you did spend the time thinking about what you'll write, and aren't just "coding from the hip", right?).


Commit logs are also cheap to write, and it's easier for people to realize that whatever you read in "$vcs log" or "$vcs blame" might be severely outdated.


I down voted because although I think what you are saying is common parlance, it is really, really bad advice and mixes up causality. Projects which both do their job and are this well documented attract developers, both to maintain and reuse. Look, for example, at everything done by Armin Ronacher, or at SQLite.

And writing documentation only seems like a waste of time to the developer who just finished writing the code. It wastes their time. But spending time now to write good documentation saves the organization as a whole much more of other developer's time in the long run. It's a smart investment to make, even when the collateral damage is time wasted documenting code that really is thrown out.

(But, if you actually document the code well you'll find out that you throw things out much less often.)


I don't think downvoting is good way for showing disagreement.

I disagree with what you wrote. It does not waste developers time, it wastes money of business owners. Developers are usually paid for their time even if they are reading HN instead of working.

Now question is to people who pay money if they want to pay for "something in the future maybe will be useful". They will say hell no! They want time to market to be as short as possible and as much of end user value delivered.

Now you take example of SQLite or Armin Ronacher those are exceptions. There is a lot more software that is not anything done by Armin and is not SQLite.


This is why the business is usually not asked to make engineering decisions. Things like backups, disaster recovery, and redundancy could all be described as "something in the future maybe will be useful".


Bad advice should be kept highly visible.


Er, "should not".


> We have no idea of whether we are writing code that will be thrown away in a week, a month, a year or whether it will last two decades.

Sometime also a code is meant to be temporary but is later deems to useful not to be used but no resource is allocated to is maintenance (in this case we could say refactoring). It's the case of many internal tools.

I remember when I did my first internship, I was in the configuration management team and I was responsible for doing the smoke test. I made some tools for myself to speedup the process and once my manager saw how useful they were, he was interested of giving them to the QA department. I refused because it broke every few builds and I knew after my internship I wouldn't be there to maintains them and it would just create more problems. There's so many instance where it wouldn't break so often or that person wasn't considering he would no longer be there to maintains it or won't have the time.


I think that the example given is a great example of code that will be around for a while... as mentioned, the logic was in 3 files and needed heavy refactoring. That refactoring has happened and the logic is well documented. In this case it isn't going anywhere.

That said, I mostly agree... I tend to favor a discoverable code base, where the directory structure is organized over features... for example a given feature may have a few controls, some actions, state logic, api client for foreign system, etc... any of the above. I organize by the feature, not the type of files/components of those features.

I tend to favor using abstractions only when they are simple enough, or make the related code much more simplified and understandable. The exception to this is needed complexity, such as an enterprise product that needs to support being deployed on oracle, sql server, etc.

Simple, replaceable code is often better. That said, when you have a broadly used produce and a piece of functionality that is well used, moderately complex and unlikely to dramatically change, this level of detail isn't a bad thing.


Always say Software Archaeology instead; it sounds better.


I vehemently disagree.

Code without specification is a maintenance nightmare, a pure liability, a ticking time bomb.

And sure, unless you're creating the control system of a nuclear reactor/warhead you don't have to go full Coq and CMMI5 and "high assurance" and whatnot, but spilling a few sentences as a minimal kind of pseudocode before writing what you want (a function, a class, a method, change the build system, refactor a big ball of if-s) is almost ideal. It helps you double check yourself, and it helps reviewers too. (Throwing there links to some issue tracker is also nice, but just the links are not very helpful.)

If you have to write docs later, it's hacking (reverse engineering), not engineering.


Personally I consider that if the code don't speak for itself, then it's most probably bad code, if it's not, add a comments explaining why it's not.

> spilling a few sentences as a minimal kind of pseudocode before writing what you want

Isn't it simply repeating the code you'll write? Which in the end, will be just as bad as the code alone or will lose time of the one that will read it afterward.

Sure it's hard to write good code, sure it's not always obvious what's bad code, but that's a big reason why the review process is there. It's not only to catch mistakes, it's also to see if someone else can understands what the code meant, the context, the decisions, etc... Sadly many just go quickly over it and just check for obvious mistakes.


Code cannot explain the engineering constraints, the business orientation, the goals. Ie. it must work in IE10 so we use this or that.


> As a novice programmer, I was absolutely stunned that this was not standard practice.

I agree with your point, and I will be benefit from this style if it were the standard, too. But don't you think a good community culture can make people maintain a good git history for this purpose?

My daily job is a Linux kernel developer. I found that source codes are only the "What" part, git comments can and should state the "How/Why" part, and if all those still make no sense to me, I look for the original mailing list for the deeper "Why". Most of the time the information is sufficient.


I've never seen a git commit comment describing the "why" of code. "Added foo.\n\nImplemented az Bar because of blorgz." isn't nearly enough of a rationale, and that's the best description I see people making.

Also, the whole point of putting something in a comment is that you have to read it when working through code around that comment. Putting a note in a commit log instead ensures that crucial information important to the code will not be visible, and most likely not read at all, when working on that code.


Look at projects with high discipline and experience, such as the Linux kernel. You will find plenty of "why" examples.

It seems to be easy to find plenty of projects with bad Git commits though.

IMO the big architectural guidelines and structuring and other highest level things and API contracts etc. should be in an external file (not code). The high-level details around a certain implementation in the commit logs for that file/files. Relevant implementation details and important things in the code. The lower you go, the closer to a comment line in the code you should get. This reflects also the rigidity of the code: the high-level architecture should not change often; if it does, then the architecture is not really ready.

Things should be certainly documented, but not in great detail inside the code files. The lack of documentation is in my opinion OK, the code is always the last word for how things actually work anyway. Misleading or wrong documentation are the absolute worst.


The problem with trying to document anything in a commit message is that a commit message can't easily and unambiguously point to the specific section of code that each part of the commit message refers to. You can get general intent in a commit message, specific details in the code, and a yawning void in between.

Sure, the back-and-forth on a mailing list or review system such as Gerrit can provide more of those direct links between code and explanation, but then it's often buried in a bunch of other discussion and dead ends. It's the "oh noes, comments can become out of date" times ten, so hardly a good alternative. And even when that's not the case, it's a horribly context-switchy way to get that information. Separating the explanation and the concrete expression just isn't a great idea. Funny how many people who would rather die than write a design spec also suggest that an even greater separation is a good thing.


> it's often buried in a bunch of other discussion and dead ends

If your "why" doesn't contains this, I have an hard time understanding why you want it in the first place.

The "why" is important to not make the same mistake. Theses others discussions and dead ends are all related to the issue, they are all questions that was asked/answered during it, they are all mistakes that may have been made.

Personally every time I only needed a tiny bits more context, the commit message was enough and if I needed more, the full related ticket was essential (and not a simple why, but the full though process that became that decision).

> Funny how many people who would rather die than write a design spec

I'd rather die than lose time over something I consider won't save time in the long term. Writing any documentation is long and if that time is longer than the few time we'll need it, it's a loss of time.

It's pretty rare that we need to go back to that information, when we do, the ticket information is enough and if it's not, doing the though process a second time isn't so bad the rare time it happens (which will provide more context by the new ticket discussion too).

For sure if your ticket just say: - Break when we enter text - FIXED

You are going to have a pretty bad time and you need to change that.


> If your "why" doesn't contains this, I have an hard time understanding why you want it in the first place.

The "why" doesn't need to include every minor style nit (all the way down to variable naming) that came up during the review. It doesn't need to include every "I would have done it this way instead" comment which was in neither the original nor final version. That's noise, not signal.

> Personally every time I only needed a tiny bits more context

Lucky you. For much of the code I have read or written, that was not the case. In general it tends to be less and less the case as code climbs up the complexity/innovation scale.

> I'd rather die than lose time over something I consider won't save time in the long term.

Is that an unavoidable issue with the medium, or more of a reflection of how some people are bad at writing? I've whipped out a spec for a medium-complexity feature or component in under an hour, and been thanked for it five years later. Many times. If you've never had that experience, then I can only say I hope you'll be able to some day.

> when we do, the ticket information is enough and if it's not

Again, lucky you. Others have a different experience.


Seen from our offshore with regularity:

> Minor fix

> Showing 6 files with 171 additions and 203 deletions


Not all organizations have such robust communication structures, low levels of dysfunction, or git hygiene. Also, as mentioned, in a corporate environment where the software is proprietary and certain source files may sit dormant long enough for the original authors to move on from the company, it's important that they don't take the "how/why" part with them, so to speak, and leave the next maintainer SOL.


As much as I would implore you to document the why as a comment in the code after your experience, I have also learned the hard way that 'why' can be a moving target given enough time.


The project I'm working on has over 150 commits per day. It would take literally months of work to get up to speed with the codebase if you did it by reading just the git headers, not the diffs. This is considered a small project.

Histories of active projects grow with age. A well maintained artifact should plateau in complexity.


I think both comments and Git messages will depend on culture; in my local / current software development culture, an emphasis is put on the code being readable enough to be obvious (this isn't true by the way, even if it's good code). Nobody reads comments, nobody reads git history - mostly because they will always be outdated / no longer relevant.

The assumption is that the original author knew what he was doing, and if not, there was a code review and anyone can fix it if they see a problem.

Finally, it's code that probably won't be around anymore five years down the line, so detailed commit messages etc feel like a waste.

(Mind you, I don't agree with the above. At the same time, I don't want to do this type of meaningless throwaway work anymore)


> unless one is willing to spend hours of archaeological examination

Currently the only person in-office over Christmas on my first software dev job. Debugging a 50k LOC COBOL beast that digs into three other beast programs and ends up in a final 20k LOC uncommented piece where things are supposed to happen and be returned back.

Nothing is commented, the programs are huge and one can only debug one program at a time, requiring me to submit untested changes in one of them to the shared dev environment since I'm making changes in two, before debugging the other program.

If it just said somewhere what half of the stuff is I would save an insane amount of time getting to know the system.


  my first software dev job. Debugging a 50k LOC COBOL beast
We found the real MVP. tapland, what were you thinking when taking this job!?


Heard about COBOL for ages, always been into retro computing and setting up z/OS on the Hercules emulator at home I was offered COBOL-training from a company and two years of guaranteed employment (with pay even if they decide I'm not needed) to join a team that force-retired 40% of their developers this year.

I have no real previous development experience, only some Java and Python, since I chose an industrial engineering program at uni, and I got the offer the semester before my CS-classes (my chosen specialization) started.

Changed my university classes to equivalent distance-classes and took six months of school to get into training and a job.


I think your desire is right, but think about this every time you create a file, and how much slower your work would be. The question then becomes: "how much commenting exactly is needed before this becomes more time than the technical debt it creates?

I think this type of summary should not be per source file but per package/folder/module/project. A high-level developer overview with sufficient depth will also help stop repetition of philosophy in subsequent files.

In this case, I do agree with its existence though because of both the length and complexity of the file. For many files, neither are the case.


I think this commenting only makes sense if the design is atypical. Probably 90% of the code I write is following design patterns already used throughout the code base.


Since comnenter announce does not understand what I meant, it does not matter if design is typical. Functionality still has to be described, so the pattern itself buys you nothing on its own. Then the overarching idea and reasoning also has to be described to make it easier for everybody else to understand.

I've met more than one codebase which tried to use a big hammer pattern to open doors. Heavy indirection to do simple things because of design choices, usually caused by a forced framework - instead of easily factorizable simplicity. Patterns should be emergent not forced. Or more specifically, the choice of appropriate pattern should be based on the problem being solved, rarely one pattern can cleanly solve all problems.

Similarity is very seductive but end result is often complex and hard to reason about when followed religiously.


And the 10% is unmaintained.

Design patterns can tap you easily in bad designs. Usually the lost thing is performance, big way, or actual simplicity.


How long does it take you to write a sentence?


Sometimes a lot. Because what I'm really doing in my head then is the design work (writing a sentence actually helps with that) - the work that's actually necessary to write the code well.

When I read something saying "writing thoughtful comments is too much work" I really read this as "thinking about the code you write is too much work, it's better to write whatever crap that first comes to mind".


Twice the time it takes you to write half a sentence.


Hard disagree. You still need to think of the sentence to write the first half.


Beg to differ. I consider the time taken to think and write as the aggregate time to write the sentence.


You've had to think of the design whether you write the sentence or not.


Invoke gauge theory to set the floor from which we measure, so that the tautology always holds.


I don't understand what this means.


Well people keep picking arbitrary starting points in the coordinate system. I'm saying for any arbitrarily picked starting point in the coordinate system, the time taken is always twice that of getting from the arbitrarily picked starting point to the halfway mark.

A tautology is a tautology.

How long is a piece of string?


You're not a very effective communicator.


I'm glad we're on the same page.


You may want to checkout the python standard library files. heapq.py, for example, has some amazing documentation in it:

https://svn.python.org/projects/python/trunk/Lib/heapq.py


The information you are talking about is called documentation and it should exist outside of actual code files.

If you need to read comments to understand code, you don’t know how to read code. Comments can lie, only code is the source of truth, once you gain experience you won’t bother with comments.


> The information you are talking about is called documentation and it should exist outside of actual code files.

The file in the OP is Go. In Go, you are encouraged to write documentation and code in one place[0]. The end result is very nice, auto-generated, human readable documentation[1] which is tightly coupled to the code it documents.

[0]: https://golang.org/src/net/http/doc.go

[1]: https://golang.org/pkg/net/http/


Sure, and then the next day the whole thing is just a long boring text which has no correlation to reality because the business rule changed and the developer next to you refactored the code.


If the developer next to you refactored the code without refactoring the comments, they did a shit job. Period. They need to be told to go back and fix it.


Or maybe the original developer who wrote the code which was so poorly written that it needed 4 paragraphs of explanation did a shitty job. Period. They need to be told to go back and fix it. You see! You argument works both ways, yay!


Of course it does.

If the comments are completely redundant with the code, they're bad. But you can't express as code why the some code is the way it is, and why it does what it does. After you tried to express in code every important information that's expressible as code, whatever important information remains must go into comments.


False dichotomy. You are trying to make this black or white while there are many more options beside comments. There are even obvious ones beside comments.


You could. It is called an automated theorem prover, with the cheap limited version called constraint based programming. Sadly very unavailable for most programming languages.

Sometimes writing the proof of why it should be like this is tricky, especially when complexity or performance is involved. (But then the tests are hard too.)

Linters are essentially constraint systems for code, but not for the program.


Theorem provers can still only talk about what the code does and whether or not it does it correctly. Specifying why the code is there is an AI-complete problem. Hence, comments.


This is one reason I like cucumber -- capture features in plain english, and also write tests to verify your plain english features actually work.

It's an odd feeling, having the best of both worlds.


One thing that I wish Linux kernel code had. Maybe it does but the few times I have found myself reading Linux code I go to the top and there is zero context in the comments, just a bunch of licensing information.


There are few linux drivers for particularly buggy hardware that are written in this style. Although OTOH what I think of is hme.ko, where the comments are more on the hillariously funny side than descriptive.


I wasn't familiar with hme.ko comments, so looked it up.

https://github.com/torvalds/linux/blob/master/drivers/net/et...


The kernel code is nowhere near ideal nor "space-shuttle" code. It has a ton of single use functions which is essentially the opposite.

Magic numbers and timing without explanation why. Rarely used technical descriptions such as "Lance mode". Tons of unchecked assertions on lock when kernel provides a lockdep check for this. Custom logging macros. Flies in the face of kernel "goto error" error handling convention in a few places. XXX with obvious bugs mentioned, unfixed.

And more...

Splitting it into more files wouldn't help at all.


Strongly agreed. Wrt. comments, the more I read this file, the more I see it as a reference of how good code should look like.


Hews closely to the advice offered by Go's creators:

https://golang.org/doc/effective_go.html#commentary


Yea, but watch how soon it becomes outdated as the file changes.


This. Documentation (comment blocks in code, as well as all other forms of documentation) tends to become outdated because it is not maintained in sync with every code change.

Solution: write clear, simple, modular code that is self-documenting and does not need extensive commenting.


How soon? I've rarely seen it happen in a meaningful way (i.e. other than someone using automated refactoring on some name, and breaking some comment reference that wasn't written in a way the autorefactor tool could read). When it happens and you see it, you should fix it like any other bug.

Key trick is writing comments as close as possible to the code they affect. Then it's hard to miss affected comments if one's not doing a shoddy job.


> Key trick is writing comments as close as possible to the code they affect. Then it's hard to miss affected comments if one's not doing a shoddy job.

That also helps it show up in a PR so it may be caught in code review. Even so, IME comments get missed enough that over time I can't be as confident as we'd like that they match the code.

I think it's also worth calling out that this key trick is not being applied in the recommendation up-thread, which asks for a block comment at the top of the module.


I've done it frequently myself when I've changed a line that I originally wrote myself in a way that invalidates a comment written directly above.


Overall liked the original post. But also liked what you said.

But here are a couple of Martin Fowler quotes (from his Refactoring book) I tend to follow:

“A heuristic we follow is that whenever we feel the need to comment something, we write a method instead.”

“Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent.”

https://www.goodreads.com/author/quotes/25215.Martin_Fowler


Reject pull requests that don't update the documentation (and tests).


Having spent 25+ years writing, viewing, commenting on and reviewing code in a multitude of languages, this is good stuff to see - regardless of the 'style' of programming (or the language broadly-speaking).

Stepping back and whilst we can all overlook it, good code comments can make an enormous difference in productivity - both for an individual, a team and indeed a business. It aids repository knowledge (something that is easily lost between current and prior teams/individuals), which shouldn't be mistaken for the intelligence of someone looking at code...

I've spent far too much time personally and otherwise attempting to reverse-engineer code written by someone with no comments or explanation. At times, super experienced programmers/developers will take performance-enhancing shortcuts that the less-experienced don't understand; They'll compress routines and functions that are a result of their explicit knowledge of a language and/or domain but without explanation...

On a basic level, comments should: inform, educate, outline and help others understand the sometimes complex routines and functions that we all create and often under an enormous amount of pressure.

There are those that believe good code shouldn't need explanation and to some degree that's true, but you can't apply that brush to every codebase. Code can become complex, awkward, spaghetti-like and almost unfathomable at times.

I've always strived to teach less experienced developers to comment well, efficiently and with a little humor/humour (where possible); Something that allows us to understand code quickly, appreciate the efforts of those before us and smile/grin at the complexity of a challenge.

Personally, I don't really care about the code/comment ratio - It's a complete red herring. At times, code comments can be worth more than the code itself. At other times, they just help you get your job done; quickly, efficiently, no fuss, just great code.


> There are those that believe good code shouldn't need explanation and to some degree that's true, but you can't apply that brush to every codebase. Code can become complex, awkward, spaghetti-like and almost unfathomable at times.

I have yet to find a codebase that couldn't be made clear as soon as a programmer actually put some effort into doing so. Far too often adding a comment is used as an excuse to give up on making the code readable before you've even started.


Here’s a great practice:

1. Write some piece of code

2. Now write a comment about it

3. Is the comment adding more information, making the code more clear?

If Yes: Put that information into the code. Rename variables. Pull out code into subroutines.

If No: Delete the comment.

You’ll be amazed at how often this practice works. Doing it all the time will make your code more readable.

We have a second, enforced practice at work, thanks to code review. The question in your head is simply: “Am I gonna get a comment about this at review time?” If yes, you gotta make the code clearer/simpler/better. Because you’ll have to answer and address the comment, and that’s just gonna slow you down more than if you just fix the problem now.


There are concerns and aspects of software engineering that are important to document but don't sit well in code, but they become important when reading / maintaining that code. Design decisions (implementation details), specification details, other design or engineering constraints. And of course business modeling fundamentals and constraints.

Sure, in theory, you can do everything in code, but I find that usually this trade off is taken, as there's no time/budget to go that 80% extra time. (Or however the Pareto curve looks for the actual problem. And usually it's not less than 80, but more.)


I would never suggest documentation in code alone... Your point is very much noted, echoed and a hat-tip to you here. Yes, documentation is incredibly important, and it should be used as an driver/accompaniment along with comments.


If that works for you then great, but I don't see the value in adding the comment only to remove it one way or another a moment later. What I do is 1. write some code 2. is code clear enough? If yes, stop. 3. clarify the code 4. goto 2.


I agree with you entirely but I can also see that the strategy mentioned could be a good bridge (for those who currently litter code with comments) to a better world where comments are rarely needed.


Well, you seem to be providing data against your own theory.

Usually codebases are a mess, so were people commenting more on what's the goal then refactoring it would be easier.

Software is very susceptible to the "perfect is the enemy of done" mantra. Software works as soon as it works for the first time. And then it gets a shipped. It's 1.0, even if it looks like a mess internally. Because IT development is expensive, there is rarely budget to go that extra mile (which is again usually would cost a lot more than the first few miles).


I think you're giving very bad advice.

> On a basic level, comments should: inform, educate, outline and help others understand the sometimes complex routines and functions that we all create and often under an enormous amount of pressure.

Take the time to simplify the complex routines, clean them up and make them readable and don't waste your precious time in writing "good comments".

> Code can become complex, awkward, spaghetti-like and almost unfathomable at times.

The solution for this is not "comment more". It's "clean up the mess".

> At times, code comments can be worth more than the code itself.

That doesn't make any sense. If the code has no value, you can remove it.

> I've always strived to teach less experienced developers to comment well, efficiently and with a little humor/humour (where possible); Something that allows us to understand code quickly, appreciate the efforts of those before us and smile/grin at the complexity of a challenge.

Please don't do that. Of course it's your code base, but usually it's best to find better venues to express your humor than code comments. Also: teach the junior developers to write clean code first.

Comments too often are used as a bad device to fix code. A developer first writes a horrible mess of code and stops. He may then realize that perhaps no-one will understand it. But if we now teach them not to clean up the code but rather just "write some funny comments", do you think it's any better?

I've seen too many code bases written with this attitude. The comments usually don't help at all, they distract the reader and they distract the author. It's too often useless noise. Many developers hide the comments from these code bases so that they can concentrate on what ACTUALLY is relevant: the code.


The worst part about comments is that they just add a cognitive load of reading and parsing extra lines of text.

What if the underlying logic changes? Do I need to update the original witty comment as well?

How do I know if the comment is still relevant? No compiler nor tests will tell me that and now I'm left with a task of parsing a language with much more degrees of freedom (English), without any support from the IDE. It becomes even harder in international teams.


Well said, I was cringing when I was reading the bad advice and you addressed the points very well.

It is so frustrating to hear when someone thinks commenting more and adding humour is somehow cleaning up the code. But to then hear that they are teaching this to juniors just hurts so much.

Simpler advice to juniors would be to read Clean Code by Robert C Martin, apply some of those techniques and then politely ignore “more experienced” developers who think writing humourous essays as code comments is a good thing.

I’ve even heard phrases banded around like “the more comments the better” - I mean seriously WTF.

I like the analogy of...

When in an unfamiliar city, having no map at all is much better than a map that you have no idea whether you can trust or not. Code comments are absolutely that untrustworthy map, doesn’t matter how many code reviews or convoluted PR process takes place, you still need to read the code to know the truth so more effort making the code communicate is the key to maintainable code. Simple things like good variable names and grouping behaviour into functions at the same level of abstraction can completely negate the need for comments.

Comments have their place, but should be the exception not the norm.


I disagree with nickharr's opinion on humor just as starkly as you, but agree with the need for comments very much. And his/her point still stands regardless of how much you name and group functions. There are concerns and aspects (design/engineering decisions become implementation details that might need rationale) that can't be presented efficiently that way. (Workarounds for issues benefit a lot from links to the relevant issue trackers, and so on.)


Point taken, but my point wasn't about adding humor as some form of default - just something that's amusing to come across when you find it as a developer and where others have struggled/suffered. You are reading too much into it, but appreciate I wasn't clear enough in my initial description.


Agreed.

One of my pet peeves is JS projects. I am not sure why but the front-end developers refuse to add any comments at all. This trend, oddly enough, started with ES6. Pre-ES6, JSDoc style comments at least, were quite common.

Just because some popular Javascript project doesn't have comments doesn't mean yours shouldn't. Straight-forward code may not need comments but most of these projects definitely need to explain why or how are things supposed to work or why things are done a certain way.


> Straight-forward code may not need comments but most of these projects definitely need to explain why or how are things supposed to work or why things are done a certain way.

Code comments usually are not the best place to elaborate on "why things are done a certain way". One can also write documentation about the architecture, design etc.


That'd be great, but rarely done. And usually there's a big impedance mismatch between the design document's level of detail and the level that'd be actually useful were someone trying to understand the actual code. (Hence comments seem to me to be the ideal place for documentation. Especially that they can be maintained right at the time the code changes.)


"it became clear that we needed to ensure that every single condition was handled and accounted for in the code"

This is a feature of several (mostly functional) programming languages, e.g. Haskell. Fun to see that often people figure out that these types of concepts are a smart way to write your code. Too bad it usually means many people reinvent the wheel instead of learning about computer science history and other languages.


I know a business coach who regularly asks his audience "Who here makes better burgers than McDonalds?". When half the audience raises their hand, he asks them why they don't outsell this giant company.

Functional programming advocats, especially for the "pure" ones like Haskell, always strike me as odd. It seems that all the beauty of those languages make people obsess over that beauty and purity while keeping them from being productive.

Meanwhile, people with simpler languages like Go just get stuff done that is useful and makes people happy. Now if I _ever_ came across a useful Haskell product, I'd be happy to test drive it, shouldn't be a problem by now with all the container technology. But the closest I ever came to using a functionally developed product was RabbitMQ (written in Erlang). That one was _such_ a pain to use and operate — must have been the developers still dreaming in the purity of its code instead of writing some installation docs. I moved on to Kafka later and didn't regret it a minute.

Rant off.


Selling a lot of burgers encompasses much more than making good burgers. By the same token, good products entails much more than making a programming language choice.

Functional programming, at its heart, is about using self-imposed constraints to avoid certain classes of programming mistakes.

If your application domain doesn't have big consequences for these classes of programming mistakes, then it can seem like functional purity can be a luxury or frivolous. However, if your application domain suffers greatly from those classes of programming mistakes, such as distributed systems, then it may be worth it to consider what functional programming might buy you.

So yes, just because you use a functional programming language won't help you sell your widgets or make a great product. And you can spend lots of time fucking around with it for its own sake and still not sell widgets or make a great product. However, if you understand what its constraints buys you, then you can make your job of building these things in certain domains much easier.


Now that's an excellent point, I agree. However, at its heart, Kubernetes does not even have that many distributed features. It's a rather classic master-slave architecture that shells out a lot of distributed primitives to etcd (which could be a much better candidate for the points you mentioned), but on the other hand, it's a mainly a system orchestration system that juggles an operating system, syscalls, SECCOMP, firewalls and related stuff — exactly the area where Golang shines and Haskell would have a rather hard time.

In this way, I rather like the tradeoff that the OP post shows: It's at least _possible_ to write Go code that's almost as defensive, safe and exhaustive as a safer language could provide, it's just a lot of manual work and discipline. For the small subset of code that needs these guarantees, it's possibly overall more consistent and efficient to take this route than splitting code into different languages with different strengths.


> So yes, just because you use a functional programming language won't help you sell your widgets or make a great product. And you can spend lots of time fucking around with it for its own sake and still not sell widgets or make a great product.

It comes down to trust. You're either fucking with your code in a powerful programming language that lets you do everything, or your fucking with the language restrictions to get your code to compile in the first place.

You can either go eat at McD's which sells pre-cooked burgers from minimum wage employees which kills the flavor and the taste of the meat being served but is extremely safe, or you can go to an upscale burger joint where artisans grind their meat in house and cook it to a perfect medium rare.

These days, I prefer the latter.


> It comes down to trust. You're either fucking with your code in a powerful programming language that lets you do everything, or your fucking with the language restrictions to get your code to compile in the first place.

> ...or your fucking with the language restrictions to get your code to compile in the first place

I think that's a wrong and outdated view on strong type systems.

A good type system is also an ergonomic one. What people start to experience is that the compiler is actually a friend that helps you write code and keep yourself true to your own promises. When new people start Elm, PureScript or Haskell (or another lang with ADTs and Type Inference), they might be a bit overwhelmed with the paradigm shift, if coming elsewhere, but if you are new to programming, there's nothing inherently more difficult in Haskell than in other languages—the cost of wrong code is just apparent earlier.

It doesn't come down to trust, it comes down to the realization that your mind can keep track of less information than a computer, and that you, as a programmer, are forgetful and make mistakes. The compiler is there to help you when you stumble over your own feet.

NOTE: I'm only including strongly, statically typed languages with type inference in the above. "FP langs" by itself is far too broad to be a useful categorization.


Even experiences FP folks complain about how difficult it is to program in Haskell due to its purity. Further, the high barrier to entry you describe is an even bigger deal to organizations than to individuals. It’s a cost that needs to be paid for every employee. And it’s not just the language features, but FP languages tend to have issues like poor documentation, poor editor integrations, multiple standard libraries, multiple build tools, multiple string types, home-grown project file syntax, multiple preludes, many extensions, etc. All of these boost the learning curve in addition to the issues with the language itself.


In my limited experience with functional programming, I've come away with the impression that the problems you're describing are why you don't see a lot of companies that use FP exclusively.

But I think the advantages that OP is lauding are also there, and "space shuttle code" might just be where it shines. Reading that comment in this post made me immediately think of Haskell. The Clojure components at my company fits the "space shuttle" description of importance, and its dependability is striking compared to the rest of our code. Part of it may be that being written in a different language allows its concerns to be separate from the rest of the application too, but I do think the FP paradigm is simply good in this domain.


Sure, but I doubt all of Kubernetes is written in this style, so it’s probably not worth writing everything in Haskell. Note also that there’s nothing about FP that prohibits it from addressing the aforementioned practical problems. Some Haskell-like could swoop in and totally steal Go’s lunch if they would simply prioritize practicality over experimentation.


> Some Haskell-like could swoop in and totally steal Go’s lunch if they would simply prioritize practicality over experimentation.

I don't know. It seems like it would be easy enough to build an AST to make sure there were no unknown conditions that didn't lead to a return statement.


I don’t understand how your post relates to mine. I was saying that if a static functional language focused more on simplicity, readability, good tooling and documentation, etc then it would eat Go’s lunch without trading off any of Haskell’s important characteristics (I.e., robust type safety).


> I think that's a wrong and outdated view on strong type systems.

I wasn't really talking about type systems specifically, I was thinking along the lines of the Rust borrow checker here, and lower level programming like assembly and C.

> A good type system is also an ergonomic one. What people start to experience is that the compiler is actually a friend that helps you write code and keep yourself true to your own promises.

I see a lot of people make the mistake that type safe code is bug free code. "It compiles, therefore ship it."

> It doesn't come down to trust, it comes down to the realization that your mind can keep track of less information than a computer, and that you, as a programmer, are forgetful and make mistakes. The compiler is there to help you when you stumble over your own feet.

You trust the compiler to find bugs. Awesome. Luck be to you. I trust unit tests more than the compiler. Mostly because I wrote them. The compiler, I didn't.


You don't write the compiler, of course, but you do write or rather design the types that your program uses. The typechecker ensures that they're consistent within the system of logic that they set up.

By your logic, it's not enough to have faith in unit tests because you wrote them; you must also write the test framework and runner.


I have also developed a strong preference for the latter. I was always on the fence until I got my hands on generative testing (quickcheck, etc).

Now I want the powerful language and I can just slam it with generated tests to get the same level of confidence as the guardrail languages (in practice definitely, although I understand this is not true in theory, so unspit your coffee Haskell people).

Oh right and this is currently Clojure, so a functional language that definitely does help me make a great product. Less time implementing correctly means more time for product refinement.


Writing good generative tests is extremely laborious, in general. It's hard to get an informative sample of the input space.


As a professional Haskell dev, it definitely makes me more productive even discounting the higher quality of the results. Everything the language does for me is something less I have to think about it. And reusable abstractions, surprise, reduces work later making things go faster still.

Hopefully sometime soon I can share an example of this.


Your language example, and the burger example seems flipped. I assume you prefer artisan burgers, which means you like no language restrictions.

Badly chosen restrictions are not good, we can all agree. But what do good restrictions look like? Back in the day, programmers prided themselves on being able to do their own memory management, and bristled at the idea of a compiler or the runtime doing it for them. Now, that's the exception, as most of the time, we don't think about memory management in most languages we program in.

As time marches on, we'll find more of these restrictions that we all eventually agree are good practices, and the next generation of programmers will take it as a given in programming.

As a look beyond functional programming restrictions, I encourage you to check out Peter Alvaro's talk on Distributed Systems. Here, he talks about how queries over distributed systems is really hard to reason about, because time is now relative--there's no central clock to measure time. However, if we restrict ourselves to a language whose queries cannot express negation, a lot of the hard stuff about distributed systems go away.

https://www.youtube.com/watch?v=R2Aa4PivG0g


Functional programming, at its heart, is about imposing a particular kind of constraints with the hope that it will have an impact on program quality. The evidence does not suggest that FP is successful at that goal. Its chosen constraints are likely the wrong ones. There are a few programming paradigms that are "about constraints," and perhaps one of them will end up making a big difference, but it appears FP is not the one.


Nearly all modern programming languages being created have adopted functional features.

Swift, rust, c#, reason


I really liked this explanation of functional programming, and why it might be useful in certain contexts.


> I know a business coach who regularly asks his audience "Who here makes better burgers than McDonalds?". When half the audience raises their hand, he asks them why they don't outsell this giant company.

There is indeed a lesson here, but which one do you think it is? It's certainly not that McDonald's makes better (or "simpler") hamburgers: once you taste good hamburgers you can never go back to McDonald's (and yes, I make better hamburgers too, even though mine won't win any prizes!). To me, the lesson is that there's more to success than product quality. The established brand matters, the scale at which you can sell a (possibly inferior) product matters, how low you can get away with paying your employees matters, how well you can survive PR disasters matters, etc.

If you had to make burgers, would you rather make cheap and mediocre ones, or would you rather enjoy making premium burgers? :)


> It's certainly not that McDonald's makes better (or "simpler") hamburgers

At the risk of derailing the thread, that would be the lesson I wish people would take away from that example.

Criticizing fast food like that is dumb signalling IMO; McDonald's!hamburger != homemade!hamburger. It's an entirely different product sharing the same name and some of the ingredients. It tastes different, and has a different form factor. People like this, even if many don't want to admit it to others (or themselves). When I go for a McD's hamburger instead of a foodtruck one, or when I say I prefer chain restaurant pizza over a home made one, it's because I want a different product. Treating the two as the same category is like treating tea and coffee as the same thing.


Plenty of places can get me a significantly better burger in under a minute. McDonald's is not high up inside its category.


Among my circle of friends it is. People either like it or not, and if they like let's say the BigMac they either want that or not, but wouldn't really substitute it with a Burger King Whopper or a KFC Grander. They usually like that too (more or less, depending on the individual), but that's then a conscious decision to okay, let's eat something different. Even if the burger is in the name.


The fries are great though. Sometimes I go there for the fries, and occasionally I get a burger on the side


You should visit Belgium.


This is what kills me: McDonald's doesn't even make good burgers for its price range and speed of service. Plenty of competitors where I live make equally mediocre burgers cheaper and faster. Of course, if you wanted to enjoy a good burger you wouldn't go to any of them, McD's included.

It's all about the brand, unfortunately.


>People like this, even if many don't want to admit it to others (or themselves)

People only like the cheapness and the convenience (and perhaps the no-surprise factor).

Everything else being the same (price and time to prepare), nobody would eat McDonalds vs a quality burger (except the kind of people who eat Hot Pockets for the taste, but that's a much smaller demographic than McDonalds buyers).


I disagree. I like the taste of McD!burger and I like the taste of foodtruck!burger. They are entirely different tastes; sometimes I want to experience the former, sometimes the latter.

No-surprise factor isn't that big of an issue if you're buying; restaurants tend to have consistent food quality & taste too.


Disagree. I’m not a hardcore foodie, but I enjoy the entire spectrum of food, from fast-food to food trucks to homemade to Michelin 3-star restaurants. And sometimes I want Taco Bell, or yes, even McDonalds.


Tons of the chefs who work at or own those Michelin starred restaurants would agree with you - they almost universally love to eat low tier, mass market fast food


The fact that there is a mass(ive) market for fast food is proof alone that people like it (enough to eat it and spend their money on it). I’m with any of the posters above who like fancy burgers as well as fast-food burgers.

Where McD’s really shines, though, is their breakfast offerings. And with that said, I think I will head there now for a quick breakfast (I live ~5 blocks from McDs).


I remember watching a TV program where they took some typical fast food (McDonalds, KFC, etc) and presented it on a plate in a way you would expect in a more expensive restaurant. Everybody who tasted it rated it higher than food from the the fast food chain.

It seems many or even most people can't tell the difference and the ambience and service is as important as the food.


Penn & Teller's "Bullshit" show did a similar experiment with "bottled" (actually, tap) water. It turns out most people rate tap water better if it's served in a bottle and told it comes from a natural spring.

It surprises me in the case of frozen patties like McDonald's though. A frozen patty looks nothing at all like an actual burger made from ground meat. It tastes differently as well, of course, but what matters is that it looks completely different!


> Meanwhile, people with simpler languages like Go just get stuff done that is useful and makes people happy.

Kubernetes' reputation is just the opposite: that far from being a simple and useful thing, it's an overengineered, overcomplicated solution to a self-inflicted problem (deploying a distributed monolith).

> But the closest I ever came to using a functionally developed product was RabbitMQ (written in Erlang). That one was _such_ a pain to use and operate — must have been the developers still dreaming in the purity of its code instead of writing some installation docs. I moved on to Kafka later and didn't regret it a minute.

Erm, Kakfa was developed in Scala, whose advocates far more of a reputation for purist pontification than Erlang developers do. Maybe all that beauty and purity is actually good for something?


Calling Scala "purist language" sounds riddiculous to me. It is a hybrid of object-oriented and functional styles, with tons of weird hacks like case classes for pattern matching, implicits, type inference that kind of works, but is not full Hindley-Milner, Java bindings that require weird conversions between Java and Scala collection types, _ as a wildcard in few different places. Scala is the opposite of purism. There's a reason why Odersky's "Programming in Scala" has over 800 pages - even listing all the hacks used in the language is not trivial.


Erlang is not at all a pure functional language, fyi. Functions definitely have three very important core side effects that are optional: message sending, message retrieving, and dying (raising an exception).

I would call the erlang system the "one white lie" category of purity on the FP purity scale. It is, by the way, a huge tradeoff that makes operating in EVM languages far easier than, say Haskell.


> deploying a distributed monolith

This is not what Kubernetes is for.

Take a step back and understand what you're criticising. You just look uninformed.


> Kubernetes' reputation is just the opposite: that far from being a simple and useful thing, it's an overengineered, overcomplicated solution to a self-inflicted problem (deploying a distributed monolith).

Thats why companies pay the buck to get 24/7 support. Because its that good!


The paucity of useful tools/libraries written in Haskell as compared to languages like Go are more due to the fact that there are far more people in the Go ecosystem than Haskell, rather than because Haskellers are too busy navel gazing.

This disparity in numbers is in turn is primarily because golang/Python/C is inherently much more approachable than Haskell because the average programmer has cut his/her teeth writing imperative code for a significant portion of their early programming career. The jump to functional way of thinking requires a certain leap of the mind, that most people don't want to take the trouble doing because they seem to be happy "getting things done" in golang or Python.

However, that is not to say that we shouldn't be striving for correctness in code that Haskell fosters, or the inherent simplicity that it forces on you because you are forced to separate your IO and effect-full code from the pure bits. These are things worth striving for. These are broad principles worth emulating even when you are coding in an imperative language.

To turn your McDonalds analogy around: sure, a McD will let you just get your food requirements out of the way quickly and cheaply (i.e. just "get stuff done"). But in the long run, it's bad for you.

Haskell is a healthy salad to an [insert favourite imperative language] burger.


The things that keep me from using Haskell aren’t the functional bits but the overall complexity of everything. There are apparently several build tools, preludes, compiler extensions, string types, etc and figuring out when to use which is a major pain. And to top it all off, there’s the tediousness of the syntax, the pervasive use of symbol identifiers, and the general preference for code-golf programming vs readable programming. And THEN there is the tedium of the functional purity.

I really, really want to use Haskell because it’s type system seems neat in general, but the type system doesn’t justify all of the tedium. Go definitely loses on type system, but it wins in many of these other areas and so the trade offs are just better.


I agree with the general sentiment of your post, but the idea that Kafka is somehow less painful to operate than Rabbit is ridiculous. I love Kafka, but I can't think of a more painful software to maintain in production.


In my experience, Kafka has been relatively painless. Sure, it needs zookeeper. But zk has turned out to be start it and forget it kind of infrastructure for us.


Util one day ZK fails in the most spectacular way and nobody knows the internals of it and you lose data. Just because is something fine for a time does not mean that it is reliable. Without telling us how many nodes you are running ZK/Kafka on this information is useless.

Others were not so lucky: https://issues.apache.org/jira/browse/ZOOKEEPER-801


I'm not claiming it's perfect. Our cluster of > 15 Kafka nodes and 3 node zk ensemble has had no major issues in over 4 years in production. That's something. And from the bug - "GC logging/tuning is clearly where I dropped the ball, just using the defaults;"


It will come for you, too, one day. We all float down here.


>But the closest I ever came to using a functionally developed product was RabbitMQ (written in Erlang). That one was _such_ a pain to use and operate — must have been the developers still dreaming in the purity of its code instead of writing some installation docs. I moved on to Kafka later and didn't regret it a minute.

Err, RabbitMQ is one of the easiest to setup and stabler queues/messaging systems. And I'm no user/fan of Erlang...


I found Kafka easier to set up. RabbitMQ is a bit finicky when it comes to config, monitoring, administration, but it's pretty okay, and works well enough if you really need AMQP semantics (selective ack of messages).


I agree with the spirit of what you're saying. Too often I've seen folks on HN dismiss languages because they didn't have features X or Y, usually with a condescending "maybe this language's creators should read some PL theory". I remember circa 2013 or 2014, it became impossible to read threads related to Go because the entire thread would always be "a Real Language(TM) needs Generics". Look at the success Go has seen in the last decade while doing the opposite of what HN thought was correct.

That said, there do exist useful products written in Haskell. For example, Facebook writes a lot of spam fighting code in Haskell. https://code.fb.com/security/fighting-spam-with-haskell/


The lack of "generics" (parametric polymorphism) in Go has certainly made the incidental complexity in Kubenetes much higher: https://medium.com/@arschles/go-experience-report-generics-i...

Go and Haskell are really at different ends of a spectrum. Haskell is a very high level language, which makes building and reasoning about very complex software much easier (at the expense of a learning curve and fine control of space usage).

Go is a low-level language with no learning curve and a very limited facility to abstract (by design it seems).

Personally I would have written something like Kubenetes in Haskell or OCaml, with perhaps the odd performance critical section in C or Rust.


Ocaml has a GIL. I've worked with people trying to build a distributed system in a language with a GIL and do not recommend it.


OCaml will lose the GIL when multicore lands in ... the next few years I guess :-)


It's part of the Blub Paradox[1]. If you don't know about better things, you won't miss them. But once you do know about more powerful things, it's hard to go back to living without them.

[1] http://paulgraham.com/avg.html


I wonder if it’s not the language that causes the products, but more that programmers that are drawn to functional languages are less likely to care about product.

That said, lots of things are functional: chunks of Facebook, Twitter, and Microsoft (and I assume Google) are written in OCaml, Haskell, Reason, and F#. Jet is built in F#. Jane Street famously uses OCaml, and Scala is becoming the standard language for hedge funds. Spark apps are usually written in Scala. Etc.


I use functional languages because I care about products. What makes you think otherwise?


> Now if I _ever_ came across a useful Haskell product

There's at least git-annex and pandoc


Shellcheck[0] is the Haskell program I use the most.

[0]: https://github.com/koalaman/shellcheck


also postgrest


Erm, excuse me? Idiomatic Erlang is definitely not the purely functional, obsessed with correctness and types oasis of Haskell. And a the community that birthed Kafka (Scala) has much more in common with the Haskell community than the Erlang community.

Furthermore, why are you using the user interface as a yard-stick to judge language paradigms with?


> I know a business coach who regularly asks his audience "Who here makes better burgers than McDonalds?". When half the audience raises their hand, he asks them why they don't outsell this giant company.

Because I'm not in the business of making hamburgers nor am I interested in doing so.

Struggling to see this business coach's point. Is he saying that McDonalds makes better burgers than me because I'm not selling a million burgers a day? Because that's a load of crap.


He's saying that when it comes to serving customers, there are critical concerns besides how good the burger is.

E.g. Low cost, made quickly, scalable (easy to train workers from any culture, uses ingredients that can be sourced at scale across the globe, etc), consistency (burger is always up to standard regardless of time or place).

Serving customers is not a contest of who can make the best burger.


Still, his point is easy to turn around on him. Why is he "just" a business coach, selling his personal labor? Why trust what he has to say if he isn't CEO of the world's largest MNC for business coaching services? It's easy to take it ad absurdum as well: if any pundit or political scientist thinks they have better ideas than Trump, why aren't they the sitting leader of the free world?


OCaml is not Haskell, but it does provide almost all of the guarantees Haskell does. Here's a list of popular software you might have used written in OCaml:

- the Flow and Hack compilers

- Facebook's Messenger app

- Jane Street's entire trading infrastructure (and everything else they do)

- XenServer

- some parts of Docker, including the TCP/IP networking stack on OS X and Windows


Also Pandoc and git-annex are both written in Haskell.


They don't really make a 'product' but Jane Street uses Ocaml for all their development.


Rust was also bootstrapped from OCaml.


Seems like you have bad experiences with functional programming, but it's a little strange to rat on the advocates that are trying to figure out how to take potentially useful functional programming concepts and make them mainstream and/or explore alternative ways to quickly build robust systems.

Good examples of this translating to huge gains for the overall community are React + Redux.

I'm quick to admit that functional language ecosystems are not as mature, which might lead to lesser organizational productivity but no need to rat on functional programming in general.


A lot of things seem to get lumped into “functional” programming. The lexicon changes so I may just be behind the times, but ensuring every condition of branch logic is covered is not an aspect of functional programming as I understand it. And I may be wrong.


Without getting into what is and isn’t functional programming, exhaustive pattern matching is a very useful feature present in some functional languages.


All the better hamburgers cost more


Not true in the UK, McDo really ain't that cheap anymore (or perhaps I'm poorer than I think).

The better burgers made by large commercial chains are more expensive though.


That largely depends on supply and demand in the particular locality. Primary value of McD is that they are global enough that such effects are mostly averaged out and the core products are essentially same globally and the pricing is consistent at least on country level. One thing that recently surprised me is that KFC does not work in the same way and the menu is totally different across not only EU countries, but even franchise holders in same country.


The most useful product in Haskell for me personally is pandoc the universal document converter.

https://pandoc.org


While I ashamedly admit to having using pandoc myself already (it's been a while, but it wasn't even a bad experience), to me it still feels a little bit odd that this relatively obscure document converter is already the most famous product in a language I see hyped in every second HN thread.


odd or not, Haskell isn't really a mainstream language and pandoc deserves all the praise that it gets.


> It seems that all the beauty of those languages make people obsess over that beauty and purity while keeping them from being productive.

Much of theoretical physics is beautiful. Only when one takes it off the blackboard and into the real world does it turn ugly.


If you a proffesional chef, I would hope you are fammilar with other methods of producing burgerz than McDonalds, even if they don't sell as well.

Haskell is a highly opinionated reasearch language. This makes it a great languages to talk about. People could make many of the same points with, say Scala or Ocaml, but because those languages arent as opinionated, it is harder to use them as a basis for discussion than a language which is heavily opinionated about the subject in question.


With all due respect, I wouldn't consider Golang any less opinionated than Haskell, just into a completely different direction. Whether or not that's a good thing: I don't know.


There are better operating systems than Windows; there are also better languages than English. (I think that's a quote but can't find the source atm.)


FP advocates strike me the odd way that they trying to argue FP can reduce the intrinsic complexity of the problem itself, but they have no proof of it, or their 'proovies' are essentially faith.


This is not true at all.

This:

    #include<stdio.h>
    main()
    {
      int sum; int x; sum=0;
      for(x=1;x<=50;++x) 
      {
         sum = sum + x; 
      }
        printf(" 1+2+...+50=%d\n",sum);
      }
Is more cognitive load and error prone than this:

    (println (reduce + (range 1 50)))
This is mostly what experienced FP advocates tell you. Btw. you can transpile the latter to the former.


.. that said, Kafka is written in Scala.



Maybe it was. Currently Kafka codebase has twice the Java code than Scala. Over time I have seen Scala code keeps shrinking and Java code keeps rising in repo.


Why do you say Haskell is an example of this? You can return undefined for anything, and have incomplete pattern matching, where if you don't mention a particular case, there is a runtime crash.

An example would be head, which takes the first element of a list and throws an error on an empty list.

I really love Haskell but it seems as if they are going for something different here than what Haskell provides.


Now Rust on the other hand ... (runs away).

In all seriousness though, Rust does get a little closer - it requires exhaustive matching (or explicit opt out) and deliberate error handling (or explicit opt out). There are still ways around it, but the happy path in Rust is handling errors.... well, maybe not happy. It is a little verbose.


For "Haskell-in-practice", incomplete pattern matching isn't an issue - there's a warning for that, you should have it on, and you should have it error. `undefined` is more of an issue, although easy to exclude with code review - relying on habits isn't great but especially when the habits are this simple it's not a problem in practice.

That said, Haskell still isn't actually an example, chiefly because exceptions can be thrown by any code, and are reasonably often used in practice.


That's not really a limitation of the "language" but more of an implementation detail of GHC and Prelude imo. There are very few escape hatches from the type system and you can run the compiler so that these cases are treated as a compile time error.


My point was not really to mention a particular programming language, but rather to make the point that people seem to continuously reinvent the wheel rather than reading up on history and investigating other programming languages. If you only know C/Java/Python/Go and you learn about <radically-different-language>, it will probably make you a better programmer even if you never write a single line of <radically-different-language>.


I've learned the most from shifting levels of abstraction, rather than between programming languages.

When I started at Oracle, on the first day my head near exploded as everyone communicated in ER diagrams instead of pseudo code or more procedural form. Learning to design databases at scale, and learning how far to go in making things metadata driven was hugely educational.

More recently, breaking up legacy application domains into microservices and defining the APIs between them feels to be like a "higher" skill than database design/object modeling.

Probably doing IoT projects would help bring some other skills to the fore.


I really like the idea of "sound" programming languages. Elm is a great example of a reasonably simple, very sound programming language that force you to handle all cases (short of a compiler bug, hardware failure, or an explicit fail the world statement, it basically cannot throw exceptions).

Unfortunately the odds of this ending up in a mainstream language this decade is pretty low: the extreme focus on terse code and DRY means the average dev balks at the verbosity (thus the coment in the linked piece of code being necessary). It's a shame, as it's objectively superior by many metrics.


Code terseness and case analysis (handling all cases) are totally orthogonal: OCaml is pretty terse and yet the compiler is great at letting you know if you forgot to handle some case.


"In case X, do Y". You can make that pretty terse with clever pattern matching, in term of amount of keystrokes typed, but someone will always come in and feel they can save even more keystrokes by not handling all of the cases.


Which metrics?


Amount of bugs that end up in production. A reasonably important and easy to measure metric.


Not really that easy. The amount of production bugs that end up getting measured are the ones that got reported, there are an unknown (and unknowable) number of unreported bugs that you never measure.


> This is a feature of several (mostly functional) programming languages

This is just one of a common practice of programming, not a feature of functional languages. These practices are not even "reinventing the wheel".

I mean, this is obvious in many areas: from implementing complex logics like Kubernetes to making hardware drivers in C. Programming languages themselves can't automagically ensure every single conditions because these often happen outside of the program (e.g. targeting hardware state). We need to cover and test all cases by hand anyway.


This is one of the painfully obvious facts that tend to be totally ignored by most. Even if you formally verify all of you code it does not mean that you have covered all the possible states of the outside world. And this can mean both unexpected states on the outside and unexpected hardware failures.

On one project I work with guy with significant railway signalling experience and this is one of the issues that I'm somewhat unable to explain to him. Probably because our product's design target is not to fail-fast-and-safe but fail-secure.


> This is a feature of several (mostly functional) programming languages, e.g. Haskell

I didn't see that.

In fact this speaks to me as mission critical software like this needs to be as tediously documented as possible to eliminate surprises. Those branches and conditions are collected through a huge pool of trial-and-errors, implying Haskell can provide those valuable use cases out-of-box is misleading, no it can't.

Model checker like TLA+ or Pluscad might do the trick.


I think what the parent comment referred to is that in Haskell if/then/else is an expression (like everything else) so you must by definition have to have an else “branch”. Basically it frees you from a subtle type of error.


Those are micro level features. For mission critical module like this, it is unlike those low-level bugs will be left without notice.

But the true complexity is coming from states, and combination of states, and many of those are unknown to the developer until certain incidents kicked in. Good programming practice can't relieve programmers from the cognitive burden of reasoning the outcome of those combinations.


In general, I think that if you find yourself "needing" to write in an intentionally complex and verbose style, this is symptomatic of bad design choices elsewhere, maybe even at the language level.


Is it possible that there are other criteria besides ADTs which factor into choosing a programming language?


If you need to make sure every condition is handled, write a test for every condition.


Why? That's both more work and more error-prone than just using a language that will ensure it.


If every condition is important to the business, as is implied in this case, it would be very valuable to have tests to cover them.


The business wants to know all conditions are handled appropriately, to a certain level of confidence. Tests are one way of achieving this but by no means the only way.


Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: