67 comments

[ 3.0 ms ] story [ 111 ms ] thread
I agree, but the author doesn't tell us how to do that in practice.
It's not by the author but Rich Hickey (creator of clojure) has a great talk titled "Simple Made Easy"[1] which I always recommend.

Furthermore I have been using Golang and would say it is very simple language that anyone could pick up and become productive with quickly. One of Go's proverbs is "Clear is better than clever."[2] At the expense of a little verbosity there is much less ambiguity in the intent of code.

1. https://www.infoq.com/presentations/Simple-Made-Easy 2. https://go-proverbs.github.io/

while theres an esssense of truth in this blog ( write simple code ) the details given miss the mark. Software shouldn't be considered a cost, that could lead to the wrong choices, it's just part of a bigger system and costs generally should be measured at the system level.

Also you have to be careful with using "interns" as a measure of minimum code level. Often the interns opt for what they feel is simple to understand but can be prone to errors. There are many micro patterns that avoid many problems that come from experience. But the sentiment about not using too clever code is generally good.

> Software shouldn't be considered a cost

I fully disagree. Code is a liability taken on as a calculated risk to perform some task.

> Code is a liability

Code is an asset, like any other piece of capital. Like physical capital, using it and maintaining it may have costs. Like physical capital, the costs associated with it are not worth paying if you don't have a productive application for the capital asset that justifies those costs (and, also, the amortized cost of acquiring the asset in the first place.)

> Code is an asset, like any other piece of capital.

When you possess 200 units of some currency, that's strictly better than possessing only 100 units. However, 200 LOC are not strictly better than 100 LOC.

When I replace a 1000-LOC program by a shell one-liner that does the same thing by composing some coreutils, I have removed 999 LOC without losing any value.

The asset is not the code itself, the asset is the value that you derive from it. Given a certain value that you want, the best code is that which brings this value while being the most maintainable. The article argues that the simplest code is usually the most maintainable one, and thus to be preferred.

> When you possess 200 units of some currency, that's strictly better than possessing only 100 units. However, 200 LOC are not strictly better than 100 LOC.

Yes, which is why I didn't say lines of code is a currency (or even a commodity), only that code is an asset. Having 200 industrial robots comprising one factory isn't strictly better than 100 different industrial robots comprising a different factory — industrial robots aren't a currency. But factories, and the industrial robots within them, are still textbook capital assets.

> The asset is not the code itself, the asset is the value that you derive from it

No, the asset is the code. As with any asset, the reason you acquire the asset is the expectation of a deriving a value from it that is greater than the cost of acquisition + maintenance of the asset.

The “code is not an asset” argument is phenomenally ignorant about what assets are, because every element of that argument is perfectly normal for non-cash assets.

It depends. Any code that I see written in support of cross cutting concerns that are not specific to specialized needs of the business are a cost I look for others to bare. I'm not going to write my own logging framework, ORM, configuration, scheduler, etc.

Recently I removed three custom written classes - one involving a friendly wrapper around .Net Http client, one to handle writing and reading CSV files and one to handle retry logic. I found better implementations on Nuget and replaced my custom versions.

Why? Because I knew my future self would appreciate not having to maintain them. Of course I had unit tests around the existing functionality first to help keep me from breaking stuff.

Any company has a narrow niche for which it is writing software. Avoid writing anything outside of that niche and use third party software. You are not giving your company any competitive advantage by writing yet another wrapper around database access.

Parallels Strunk & White's Elements of Style. Who knew there was so much similarity between writing code and writing prose?

Omit needless words.

Write in a way that comes easily and naturally to you, using words and phrases that come readily to hand.

It is always a good idea to reread your writing later and ruthlessly delete the excess.

Avoid the elaborate, the pretentious, the coy, and the cute. Do not be tempted by a twenty-dollar word when there is a ten-center handy, ready and able.

"Je n’ai fait celle-ci plus longue que parce que je n’ai pas eu le loisir de la faire plus courte." -Blaise Pascal
> Parallels Strunk & White's Elements of Style.

Although do bear in mind that some professional linguists (Geoff Pullum being the ur-example) consider EoS to be an abomination of ignorant claptrap.

"Write in a way that comes easily and naturally to you"

That's pretty much what I did at the start of my career. Nowadays I think things through a lot more (and generally come up with a lot more robust solutions).

This post is by the author of the dask parallel computing framework, which is written in Python and getting some traction in astronomy circles.
Our company's full-time job is to shrink our customer's codebase size aggressively. Your team will not do that because it's a job security to them. Let us make your company where it's easy to fire anyone without risking major drawdowns.
So you're with a consulting company specialising in refactoring!?

How do you sell that to people?

>Your team will not do that because it's a job security to them

Every team I've ever been on [albeit that's only two], it's the opposite, if the devs had their way they'd spend all day every day simplifying and refactoring. The only reason we don't, is because actual business needs demand attention.

Agreed. Putting out fires sadly takes precedence over future fireproofing.
Process/policy often prohibits refactoring for its own sake. I've always got round this by rewriting everything 5 times before checking it in for the first time.
If I had a free reign over my time at the office, I'd spend at least a month just cleaning up the mess that has accumulated.
> Our company's full-time job is to shrink our customer's codebase size aggressively. Your team will not do that because it's a job security to them.

No, they did do that, that's where the layers of bloat originated from. Everything turns out to be not quite as generic as the authors thought and that helper function end up as it's own class with an inheritance tree, etc. That fancy mapper that uses reflection to eliminate hundreds of lines of boilerplate becomes a liability because information is now lost to future maintainers.

I don't want less code, I want simple, greppable code that allows me to trace data across the codebase.

Very similar to the "the principle of least power":

- https://en.wikipedia.org/wiki/Rule_of_least_power

- https://www.w3.org/2001/tag/doc/leastPower.html

- https://blog.codinghorror.com/the-principle-of-least-power/

I also would like to add this blog post by Li Haoyi (from Scala.js fame)

- http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLe...

I think the tradeoff is maintainability and flexibility. I think code cannot be allowed to be complex. Complex code will eventually be broken during maintenance.

It is often the case that the simplest solutions require the greatest skill in achieving. Thus, I agree immensely with avoiding unnecessary complexity whenever possible, but not "We should endeavor to write code that a novice programmer can easily understand without explanation or that a maintainer can understand without significant time investment." --- which tends to lead to extremely verbose, inefficient, and dumbed-down code.

In other words, if you only ever use the very simplest features of the language, you are effectively reducing micro complexity but increasing macro complexity. One of the best examples of this is the classic arguments over the use of the ternary/conditional (?:) operator --- those against say it's hard to read and should always be replaced with if/else, while those for say it simplifies the code by removing redundancy and should always be used in place of an if/else with a single assignment statement with the same LHS in both clauses.

Similarly, if you believe programmers are almost completely incapable of understanding operator precedence, or that it "makes code hard to read" and restrict expressions to only one operator (and mandate that variables be created for all intermediate results), then you will get code with all the verbosity of Asm but none of the benefits.

In other words, good code may not be immediately obvious; but your reaction when you do understand it should be one of pleasant enlightenment, and not "I can think of a simpler way to do this."

A somewhat related article: http://typicalprogrammer.com/what-does-code-readability-mean

Yes definitely. If you code using a VB6 style subset of your language you'll probably end up with spaghetti. That's easy to understand at an individual procedure level but pretty hard to refactor and maintain.
> If you code using a VB6 style subset of your language you'll probably end up with spaghetti

Unless I'm remembering incorrectly I'd say the opposite, vb6 had a pretty sane feature set comparable to "modern" languages like go and rust. Coding with that feature set will give you simple and understandable code, if a little verbose at times.

The spaghetti code it's famous for was mostly a result of the event driven, drag and drop nature of the IDE and community. If you want real spaghetti code then try vb.net which suffers from the same problem but allows devs to pile on a bunch of OO features that obscure what code is doing.

I disagree. I think OO can make things a lot simpler. But the fact that I disagree is just an example that there is no one 'correct' style, it depends on taste and the team should decide based on their needs.
> vb6 had a pretty sane feature set comparable to "modern" languages like go

What's not sane about Go's feature set? (Except for its debatable omissions.)

I think you misread that. They said "comparable to" (equally sane), not "compared to".
> It is often the case that the simplest solutions require the greatest skill in achieving.

Totally agree, and I think this is what a lot of the "keep code simple" articles miss. To create something simple, you almost always have to create something complex first. All the best designs I've seen created were the result of 10+ iterations and several major refactors.

Seconded.

It's getting ridiculous at times. I've seen cases when people complained about "too complex" features in a language, where it would take them literally two hours to acquire necessary understanding, and become more productive forever. But no, it's easier to keep wasting much more time complaining. It smells of extreme anti-intellectualism.

Code is meant to be read, yes, but to be read by educated people. Programming is a profession, and it's unreasonable to artificially handicap everyone in a company just because some people can't be bothered to spend even just a little time learning if they don't understand something.

I like the idea of "Simple doesn't mean it's not hard".

For a newbie in a number of subjects many things may seem complex but are actually quite simple once you study enough of the concepts or theory behind something.

Sometimes to see the beauty of simple there's not other option then learn more. And I'm against of trying to avoid people to learn something that is worth it but may be new just to keep the status quo of any anybody should be able to understand it even without any knowledge of things making it more complex instead.

Apparently the author mistakes dumb for simple.

Usually dumb code is layers upon layers of needless complexity, grown because the coder had neither time nor inclination to understand the problem, and kept tweaking.

Such dumb code becomes a huge support and troubleshooting cost, letting alone quickly modifying it.

Don't write dumb code. Write simple code. The recipe is known: think about the data first, untangle and minimize dependencies (including any global state), write simple, orthogonal functions over these data. Use the language's standard library and built-in data structures. Crave for these negative line count PRs.

It takes much shorter time than working with dumb code.

Curious, what are your top 2 favorite languages (or CS thinkers)?
Simple also gets us into trouble.

Straightforward is the way to go.

If it's complex and hard to maintain, then it's not dumb code. It's genius code. Dumb code is actually very hard to write successfully in production and there isn't a recipe for it. The novice flails and makes the code too complicated trying to add one hack on top of another without doing any causal analysis, the journeyman programmer starts throwing in a lot of defensive abstraction they learned about yesterday that doesn't tame the problem at hand, and the expert writes a correct solution that is exactly what is needed and no more. As a bonus, when you enter a new problem domain you're a novice again. All that stuff you knew about working with databases is irrelevant if you're working with real-time audio. But you get to skip through the cycle faster this time.

Ergo, dumb code. It's hard to make dumb code that actually stays dumb instead of turning into genius-level complexity or bad abstraction.

That is some interesting definition of genius you put in here. Usually the criticism of so called genius code is about manual optimization and sometimes domain specific optimization and not about crust achieved over years of lacklustre maintenance.

There ate actual real reasons you shouldn't or cannot write dumb code. One piece of such "cruft" is handling numerical corner cases which is unavoidable unless proven mathematically to be impossible (And commented why). Using IEEE floating point? Guess you get to handle ULP precision, NaN and Inf. Using integers? Welcome to wraparound. Memory management? What happens if an allocation fails? Threads? Race freedom is quite interesting to achieve.

Using Java? Then figure out what happens when you choke GC with hundreds of objects. Etc.

You can only go so far in passing the buck to library authors, they can make mistakes too.

> Software is the currency that we pay to solve problems, which is our actual goal. We should endeavor to build as little software as possible to solve our problems.

Amazingly well put. I’m gonna try and not forget this so I quote it when discussing with people who love to come up with bloated, overengineered solutions.

My point today is that, if we wish to count lines of code, we should not regard them as “lines produced” but as “lines spent”: the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger. — E. W. Dijkstra

One of my favorite quotes :-)

> Software is the currency that we pay to solve problems,

A great mind shift there.

IMHO this is the philosophy behind ruby and rails. Any kind of software has a people cost. We should be able to spend less time writing software and it should be a time efficient process as we pay for it using human time.
Ironically I find Ruby code one of the hardest languages to read when I see a random snippet.
Kiran addressed a very similar (if not the same) topic at the PyCon India 2013 keynote: https://www.youtube.com/watch?v=_stsJlNgGfA (40 mins). I was there at the keynote and it really helped set my basics very strongly. Highly recommended.
Writing dumb code is a lot more expensive up-front than writing smart code. It is like writing something in simple-to-understand prose; that costs more on write than hard-to-understand prose, but pays off when read.

So I feel like the author of this essay is talking about some costs (the reader's) without considering the cost of the writer. If the code is never read because other solutions are considered more viable, then all that cost in making it simple for not. As always:

1. make it work.

2. make it nice (if it works and is going to survive to code review).

3. make it fast (if it has performance problems).

Unless you are completely on waterfall, you aren't going to be able to do 1. and 2. concurrently.

I'm not sure if it's more expensive either. The first correct algorithm I can think of, expressed in the programming equivalent of pre-K level English, usually is less buggy and 10X easier to maintain than the advanced-language-feature-filled code that certain engineers love writing. And it takes a fraction of the time to write because it doesn't require much refinement. Is it slower than finely tuned C++? Yes. Does that matter? michael-scott-NO-GOD-NO.gif

It boggles my mind when people who are just building an everyday website decide that querying models from the DB means we need a multi-level class hierarchy that requires a 5 page paper to explain. Or that anything remotely related to math means we need to start overloading operators. Congratulations! Your brain recognizes patterns! Wow, someone should make you the President of all Engineers Everywhere.

The code you're writing now will likely not survive another 2 years, especially if every time there's a bug or even a question like "how does feature X work under condition ABC?", everyone shrugs their shoulders and has to read 1000 lines of horribly nested abstractions.

> The first correct algorithm I can think of, expressed in the programming equivalent of pre-K level English

If I can think of it, then I hope it already exists and I can just reuse it. The tricky stuff that needs a lot of thought is the same stuff that you can't just reuse off the shelf.

> It boggles my mind when people who are just building an everyday website

If it is something so routine that it doesn't need to really be designed, then I guess you can do it right the first time from memory (shame you just can't reuse some functionality if it is so routine though).

> The code you're writing now will likely not survive another 2 years

I work in research where your code is lucky to survive 2 months. I don't disagree with you, but you can't crank out perfectly simple code on the first go if the design space is clear. Those abstractions usually don't exist because the programmer was trying to be clever, they often exist because the programmer was trying to solve the problem. Its only with hindsight that we can often see a better more simple way (and hopefully have time to go back and do the simpler way).

> If I can think of it, then I hope it already exists and I can just reuse it.

There is also a cognitive load of 'using it' for simple algos and packages, like left-pad. Some end up the de facto standard of a community (Python's `requests` package), but other communities fracture (Node's SuperAgent, Axios, got, Request, reqwest, ...).

I like to think of code I write today as automatically being 'obsolete' 12 months from now. I might not replace it +12 mo, but if I _were_ to replace it, what comments, patterns, and hints might I leave my future-self to make that easier? Simpler code is easier to test and replace. Less magic code is easier to swap out with other languages. Verbose code is easier for my new team of +N engineers to digest, even if it takes twice as long to read.

The author wrote a nice piece, but it should be called, "Write Dumb Code," but, "Write correct & concise code." Dumb code doesn't mean the same thing as short code.
I've heard and read about this time and again over the years, but am yet to find a blog post -- including this one -- or video that take an existing set of code snippets that is un-necessarily complex, and then refactored / re-written that code to be dumb code that performs similarly but is easier for any rookie to understand and maintain.

Can anyone point me to real examples for C# or Java?

I haven't programmed in C# or Jave, but I was recently looking into expository literature on why functional programming helps write more modular code [1]. I found the following Haskell-based example to be really enlightening: http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way...

I think it's okay to skim over the Haskell specific parts; what I really liked how the end result (version 10 of the program) is nicely composed out of orthogonal pieces, compared to version 1, which is a monolithic blob, while "simple". So, if you want to solve a related but different problem, you can reuse everything you've written instead of duplicating code. So any bugfixes/improvements to one part can trivially be propagated to all other use cases.

[1]: https://blog.acolyer.org/2016/09/14/why-functional-programmi...

Martin Fowler -- Refactoring.

Best software practices book I've ever read, even though I don't use Java for a while now.

It's FULL of practical examples. Most of the book is examples, and it's not a small one.

> The best way you can contribute to an open source project is to remove lines of code from it.

Deleting code doesn't necessarily mean you're making it simpler. Compressing a 5 line function into a snazzy 1-liner is almost certainly making the code worse, not better.

> Every line that you write costs people time.

This kind of mantra is how you end up with nested ternary expressions and tightly wound functional compositions in imperative languages.

I don't think he meant compressing 5 lines of "normal code" into a one-liner. He meant, actually removing it. Maybe the logic it provides is not needed anymore, or you could do it somewhere else in a far more readable way (I'm thinking about abstraction levels)
Sure, and I think we can all agree that this type of refactoring is useful, but I have worked with many developers that really take this anti-LoC idea to heart and end up producing difficult to read code because they are so focused on finding the most terse way to express logic.
A favorite quip of mine is that I dislike Ruby because the entire community optimizes for one-liners.
Back in my days of writing Perl one liners were also kind of encouraged, because usually it would likely be more performant. At least thats what some instructors told us.
> Deleting code doesn't necessarily mean you're making it simpler. Compressing a 5 line function into a snazzy 1-liner is almost certainly making the code worse, not better.

What about using something like gofmt? That way you can't get rid of lines by just rearranging the code.

(comment deleted)
It took me a little longer to understand higher order functions in JavaScript than it took me to understand for loops but once I “got it” then using something like array.filter((x)=> x%2===1) seemed cleaner and arguably “simpler” than the alternative but not “dumber.”
Also, unlike the for loop alternative, it clearly expresses the intent. "I am filtering this array, using this condition". The functional programming style really shines here, and it shines even more whan you start chaining the operations.

I guess the rule of thumb is to write code to most clearly express the intent of it. Often harder said than done but a good goal to strive for.

To be fair the new arrow operator syntax is a bit cryptic. I am sure Ruby offers plenty of things like that, because when I look at Ruby codebases I often don't understand what they intend to achieve.
Consistency is the name of the game. Principle of least surprise. Keep flow happening. etc etc.
I find these kinds of articles ultimately unhelpful. I do agree with the overall message, but with the lack of practical examples, all I came away with is a description of the problem we're all too familiar with already (software design is hard, simplicity is desirable) but no answers to how to achieve that in the real world.

"Don't use recursion to solve a problem easily solved by a for-loop, except when recursion is the right tool for the job."

You're not wrong. Just not terribly useful advice when fighting against real complexity in systems.

I have to completely agree with the article, and that has been my learning working for biggies in the US. But not all people agree on that. They consider themselves smarter, after writing complex code with no documentation. Also, certain people make value with complex stuff instead of making it easy as a breeze for others to understand and contribute.
Here are links to two of my favorite essays that make a similar point:

Java for Everything[1]

Too DRY[2]

And then there's a third, which is not quite the same topic, but still in the same vein. It's arguably even more relevant today than three years ago when it was written, especially given how much attention is being captured by ecosystems that attract folks who end up generating a morass of terrible tooling:

Helping my students overcome command-line bullshittery[3]

1. https://www.teamten.com/lawrence/writings/java-for-everythin...

2. http://jamie-wong.com/2013/07/12/grep-test/

3. http://pgbovine.net/command-line-bullshittery.htm