270 comments

[ 2.5 ms ] story [ 217 ms ] thread
Every new programming style epiphany is an ad hoc, intuitively-specified, hazy restatement of the Unix Way.
(comment deleted)
If Unix is your yardstick for simplicity, that just shows how far we've come…
Linux is incredibly simple compared to Windows once you actually your computer as a computer instead of as a really poorly made gaming console.
Try talking Grandma over the phone to help her share her photo directory with Aunt Jeanine, on the same PC. No command line allowed, and you know she can only focus on that type of task for 4, maybe 5 minutes top.
I can't imagine any system where you'd have a good chance of succeeding in that scenario without some kind of remote administration tool. Remote controlling a user over the phone is difficult, unreliable and time-consuming.
.. install remote admin tool? Gotomypc etc?

Or just tell them to copy it to a USB stick. Yep, same machine, it's still the best answer.

Share? Google Drive. Or email. Both of which are equally good/bad to explain to grandma on Windows and Linux.
No command line allowed

It's easier to tell someone what keys to press than to find and manipulate UI elements.

on the same PC

On a PC, everyone already has access by default, so "sharing" doesn't involve anything additional.

That wouldn't work reliably under Windows, either, as nobody knows anything about files and directories generically anymore.

(That's the reason why first the Windows explorer was suddenly sufficent enough and then almost completely disregarded)

A public folder on the same PC is way harder to explain than just telling your photo app to share it with your resident spying company/cloud provider, despite how wasteful the round trip is.

Not that that has anything to do with a discussion about the "unix way" as a special case or even superset of IDEs.

My mother can't reliably remember how to operate the remote control but uses linux just fine (browsing, editing text documents, etc).
I think you're confusing easy and simple. Windows is easier (for most people), but linux is certainly simpler.
Bullshit. You're talking about an operating system where the common advice for someone who wants to install up to date software is to fucking compile it from source because the whole community never got their collective shit together enough to allow developers to directly distribute binaries without a gigantic fucking headache.

Christ, it's such a fucking mess that one of the most compatible ways to distribute software is to write it for Windows and rely on WINE.

Hardly. I've maybe had to do that 5 times in the last...10 years?

And I split my time between Arch, CentOS & OpenBSD. The vast majority of things these days are packaged in a useful way. There's also flatpak and similar now.

> I've maybe had to do that 5 times in the last...10 years?

Good for you. Some of us have to install up to date software more often than every 5 or 10 years.

I'm running Arch on my office workstation. I install up to date software literally every day.
Only after a third party makes an AUR for it. I'll stick to an OS where the developer can publish directly to the users without 15 different packaging formats, thanks.
Again, flatpak, appimage, docker ... you can use one of these, don't have to use 15.
No, the developer can choose one of these. As a user, I have to deal with all of them because I cannot choose how the developer distributes it and there is no standard. If I could choose, I'd use AppImage for everything (since it is the only one that is portable) but I can't do that.
> compile it from source

Way simpler to do on Linux than M$ Windows.

> [no way to ] to directly distribute binaries without a gigantic fucking headache.

I mean, flatpak, appimage, docker, etc ...

> Way simpler to do on Linux than M$ Windows.

Completely unnecessary on Windows since it is an operating system, not a kludge of random source code from the internet.

> I mean, flatpak, appimage, docker, etc ...

Which of those is ubiquitous enough to ensure availability of what you're looking for in that format? In my experience: none of them. AppImage is easily the best, but the community seems to hate it because it makes things too flexible and simple or something.

> Way simpler to do on Linux than M$ Windows.

Where, pray tell, do people who write the software for this "operating system" store their source code if not somewhere that is connected to the internet? Do they use punch cards?

> Which of those is ubiquitous enough to ensure availability of what you're looking for in that format?

Have not met someone who hates AppImage. flatpak is also rather ubiquitous. I use both every day.

> Have not met someone who hates AppImage

Talk to Drew DeVault, to name one.

UNIX is very simple, it just needs a genius to understand its simplicity.

And it is 2020, I guess nowadays everybody understand how unix-like system works and how simple they are.

I disagree. I have an entire compressed text file on the simplicity of the Unix way. Once I remember the tar flags to extract it, I'll post its contents here.
>For example, an analytics dashboard built with a no-code analytics tool like Looker is likely to have more qualified people to fix it than one built with a patchwork of custom scripts and APIs. Nobody should have to pull data scientists or product developers away from their work to fix a bar chart.

I don't have experience with Looker, but the unseen complexity of no-code tools often leads to very complex systems with interactions that are hard to understand. A dashboard may not have that many interactions with other tools, but the black box nature of these kinds of tools usually leads to significant downtimes when there's a small detail not working as expected, or where you need a small customization that wasn't foreseen by the no-code API creators.

That's true for drag&drop tools that provide a user interface as an alternative but Looker or similar tools introduce their own language that limits your capability by sandboxing the environment but still lets you programmatically define your models. See LookML for Looker (https://docs.looker.com/data-modeling/learning-lookml/what-i...) or Jsonnet for Rakam. (https://docs.rakam.io/docs/compose)

P.S: I'm affiliated with the company Rakam.

I think that’s missing the point he was making. No matter what front-end representation of a system you get, the implementation backing a no-code system will have edge cases around interactions both internally and with third-party systems. The fact that you’re hiding a system behind a veneer of “no code” doesn’t hide the fact that code is behind the actions that are taking place, it just means you can’t introspect it (even if this “no code” is in fact code).

I think a better point would be to make that that’s not a bad thing, sometimes abstractions provided in “no code” systems can greatly simplify a solution. But even the most well-designed solution will fail to some essential complexity of the root-problem the original authors didn’t understand. Without debugging tools (or privileged access to the backend of the implementing system) it’s difficult or impossible to understand what went wrong.

As somebody who built a lot of Looker, it is not no-code for this very reason. It's why your Looker data model is stored in hand-editable code and under version control!
Looker isn't no-code. A lot of people consider Looker a slightly more robust Tableau, but the visualization aspect is almost inconsequential. It's the data modeling part, the "this is exactly what structure my data takes and we define that in code", that matters. Otherwise what you get is "Here's my Tableau workbook with an unholy pile of hand-written untested SQL as custom data sources"

Incidentally I'm all ears if there's a good open-source LookML style project out there.

I think the point, actually we don't have to think, it was stated explicitly, is that if Looker breaks, you can easily switch to an alternative.

This was presented as the rationale quite plainly, so I don't understand why people are missing it.

Spot on. Looker is a great tool, but it has its own language LookerMl and a set of abstractions. From my limited experience it‘s not easy to pick up. It‘s likely that an analyst is already familiar with it. But like with any BI if there is a problem that can‘t be solved with an existing abstractions and tools, you either go back to writing the pipeline step to solve or you come with a very ugly solution.
Usually, being easy is confused with being simple. Easy to use systems usually tend to be very complex but they'd only hide it from the average user. Once you go one step ahead, do something outside of quick start, you find yourself helpless. In that sense, the article is on point. Not only the management of the ship is simple, machinery behind the scenes is simple as well.

This applies to programming languages, too. Python is easy but when you do advanced stuff, often without realizing, you start to grok many complex details to make it work or make it work in a performant way. On the other hand, Go (and Rust supposedly, but I don't have experience) is simple. It doesn't let you do many things and that results in fewer ways of doing a specific task but when you do it, you feel safer because in the end, it does look and feel simple although you spent 1+ hour than you'd normally spend compared to coding in Python (this assumes you're doing some advanced stuff, not quick start).

Go is actually a pretty complex language, more so than Java for example, in my opinion. It has all sorts of primitives that you need to get used to, special rules for how the built-in types work, there are all sorts of rules to see what creates a copy of a struct and what doesn't, pointers have at least three completely different use-cases (optionality, mutability, and avoiding copies), several rules for what can and can't be addressed etc. And this is all before looking at the standard library.
I think concrete things you listed like the case for pointers is not much different than Java. While why you use a pointer changes, it's simply pointer in the end, just like Java.

I'd say Go is able to put so much meaning to those fundamentals because 1. they have to, there isn't many other mechanisms 2. they can because it's still early in the evolution of the language. After some adoption and wildly different use-cases that users wants to be addressed, those patterns start to disappear and people end up with lowest common denominators, pointers being just pointers in this case.

I do wonder what the trade off would've been in Go if something like: a := b was guaranteed, under all expressible circumstances, to create a completely new copy - regardless of the content of b, unless it was explicitly a pointer.

It does frequently feel like this could've been solved by making basic slice assignment always go as a := b[:], and adding some type of syntax and checker where you needed to confirm that you were doing a not-completely immutable copy.

>On the other hand, Go (and Rust supposedly, but I don't have experience) is simple.

I was reading most if this yesterday which seems to disagree: https://fasterthanli.me/blog/2020/i-want-off-mr-golangs-wild...

I really enjoyed this article. Thanks for linking it!

It’s interesting to see the relative naïveté in some of the implementations of what seem to be relatively key builtin libraries like file pathing. I also have never had to deal with cross platform support in Go, so I’d never seen the magic compilation comments/suffixes.

No comment on Go. One pattern I've seen are tools that make easy things easier, but make hard things impossible.

Which is fine until the built system needs to grow. Then the pain hits.

In real life, there are monsters lurking behind Go's "simplicity." An example: errors as values. It forces you to deal with errors as they come up, right? Not exactly... People just start ignoring errors. The extremely opinionated linter doesn't care if you just assign an error to _, or even just don't handle the return values at all. And it's not something you can easily spot in a pull request either. Then you end up with nil pointers or spaghetti errors that only show up at runtime and can be quite difficult to trace, where a Python exception would make debugging trivial.

This isn't necessarily a knock on Go. I've enjoyed working in it full time for several years now. I don't think there's a such thing as a simple programming language. Go just hides the complexity from initial inspection.

I think there are multiple levels to this phenomenon. With Go in particular, after having used it in some high performance scenarios (10k request per second for images that are rendered on the fly by GPUs, on systems holding 200GB of binary radar imagery in memory, that also need to ingest 50MB per second of new radar data), I've seen that the simplicity of the language is not a hindrance in these areas.

Edit: if you find yourself spending most of your time trying to come up with the perfect abstraction, Go may really piss you off, I won't deny that, it's not a strength. You have to be satisfied with 'good enough' and move on, solving edge if/when they arise. Go often encourages moving toward the concrete, and you can solve generic problems in really basic ways sometimes. As an example, I was writing a DAG server last year, and coming up with ways to move data between vertices in the graph in a general way. Rather than getting out my abstractions, I just pass around []byte, and leave the interpretation of those bytes up to each vertex (often just a type cast). I personally find this refreshing, and while there are costs to doing it this way, with a few basic helper funcs, you can get 95% of what you want from a generic server like this without doing a lot of modeling.

The problem is that for different people 'simple' has different meaning.
This feels like it is stating the obvious. However it is easily forgotten.

A lot of Software Engineers / Developers in my experience tend to over engineer projects or they are mandated they do so by so their dev leads. It is easy to get caught up into abstracting almost everything away almost for the sake of it while it provides no real benefit.

Don't forget the old classic Resume Driven Development.
His website had this annoying message about subscribing for updates that has no obvious way of getting rid of it excepting by clicking the accept button. I guess that's simpler than having an additional "no thanks" button. The article was a formulaic retelling of well known wisdom too.
Hey, there is an X in the corner to close it. The subscription slider is made by Mailchimp so I don't have much control over the size of that X, unfortunately.
Using the company this author helped to migrate to hubspot as an example.

I'm not sure if the simpler solution - hubspot existed when the company started hacking solutions together.

Also, the move to hobspot was relatively easy because the business processes and workflows were already well known and defined.

Starting from scratch, fighting the fires as they appear, a bit of patchwork seems unavoidable.

Everything is obvious in hindsight.

I guess the real lesson here:

Simplify once in a while. Like Facebook just did with messenger.

> I'm not sure if the simpler solution - hubspot existed when the company started hacking solutions together.

Author's value proposition is "I will help you outsource your complicated marketing tools". If you reject the gospel that your marketing tools are complicated, author's service would be not needed.

Not the intention at all. My job is to parachute into a startup facing some problem, fix the problem quickly, and get out. I have no business being at a startup that's running perfectly, or creating solutions that will require me to stick around.
I don’t know, most systems like the container ship he/she described just seem simple but are actually pretty intricate inside. The logic behind them might be easy to understand, but the devil is in the details. A combustion engine is also simple conceptually: make stuff explode in a closed chamber to move a rod, convert that the movement to a rotation of a shaft. Super simple, yet I doubt I could debug many problems my car engine has with such basic knowledge.
This seems like an oddly appropriate metaphor though - engines aren't super-complex once you know what everything does, but the biggest problem in car mechanics is just getting access to the specific part you need to measure or look at.
Do they also have less people using it?
It's important to find a compromise. The simplest system at my work would be something like a php/mysql backend hosted on a old fashion shared server using ftp.

Good luck attracting good developers with that. We use something over engineered but we have fun. The customers don't care but we do.

That's all good if your company is making money.
Since PHP has become more decent with the last versions, if you are starting a new project that maybe actually not even be too terrible.
There are a lot of companies where Senior PHP devs are paid more than Senior Java devs, because it is so hard to find a brilliant PHP dev
I like to “embrace complexity.”

Any line of code, especially one that introduces a new concept, like a class or a method, is in “addition” to the root problem we’re trying to solve.

The closer the code/data/org-processes/whatever matches the fundamental problems we’re trying to solve, the less complexity we introduce.

However, you cannot “remove” complexity! A common mistake I see in software companies is using a single Jira ticket for one or more reports, one or more conclusions, and one or more action items.

For example, if we get reports A, B and C, they may all go into a single Jira. Oh, but C is a totally different thing! It’s not a “duplicate”! Ok now someone needs to extract the info from a comment into a new Jira (which no one will... and it’d be a mess if they tried).

Now a bunch of discussions are happening in the comments of this mega-Jira. 3 conclusions come out of these discussions — perhaps what the cause is and what should be done about it.

Then let’s say there’s two action items, one is a temp fix and one is a long term fix. Unfortunately, I usually see people is the same Jira for both tasks (“Assign this back to me when the temp fix is in so I can do the real fix”).

But splitting Jiras up is frustrating. The actual splitting is frustrating to set up, and it’s a pain to browse them.

So most Jira workflows remove fundamental complexity (reports vs discussions/conclusions vs tasks) and introduce extraneous complexity (tonnes of fields and workflows that don’t necessarily apply).

One should “embrace” the fundamental complexity of the problem(s) they’re dealing with.

Agreed. It should be almost as simple to create a sub-task (of any task) as it's to comment. Also convert a comment to sub-task. Browsing is another problem...
And there a fundamental impedance mismatch between these tools and actual work rears its ugly head. Work is structured as a dependency graph - a DAG. To complete C, you need to do A and B, but a part of B depends on a part of A to be done, etc. Yet these tools insist on a list, or a very flat tree (a task and maybe a subtask, but no sub-subtasks).
My favorite variants of this phenomena are "let's just add a column instead of creating a new table" and "let's add a boolean argument instead of creating a new function."
Or worse: "we built a table for arbitrary key-value pairings, but now the value needs to be an array or object, so we'll store it as a JSON string"
I usually do the inverse: who knows what additional data we'll want to store for each row, let's put there a jsonb column, store anything not clearly critical there, and add columns or tables as these jsons get populated and used.
The only language that I have worked with that realizes that in the real world simple is _always_ a lie, is common lisp. It is the only language that has actually embraced the fact that its designers/committee were not geniuses and provided the tools for dealing with the complexity of the system. When unix tools fail, hope that you are on a system where it is possible to get the symbols and/or the source code, and even then good luck fixing things in situ. Most existing systems do not empower the consumer of code to do anything if it breaks. CL remains almost completely alone in this, because the debugger is part of the standard. Most of my code is written in python, and I can tell you for a fact that when python code really fails, e.g. in a context with threading, you might as well burn the whole thing to the ground, it will take days to resolve the issue, roll back the code and start over. The fact that people accept this as a matter of course is pure insanity, or a sign that most programmers are in an abusive relationship with their runtime environment.
Forgetting Smalltalk here?

Mesa/Cedar, Oberon System were also like that by the way.

Java and .NET also share a bit of it, after all Java is half-way to Lisp (as per Guy Steele), and .NET follows up on it.

Sort of. I have a bad habit of forgetting Smalltalk in these kinds of conversations, but that is possibly because Smalltalk images exist in their own happy little worlds. I love working in Smalltalk environments, it is always a mind blowing experience, the system is completely homogeneous, accessible, introspectable, modifiable, etc. -- until you hit the hard boundary between the image and the host system, be it hardware or software. That boundary leaves quite a gap that smalltalks tend to be unable to fill by themselves (mostly due to a relative lack of resources). I can imagine a smalltalk system that could go all the way down to modern assembly, but unfortunately no such system exists today (that I'm aware of). If you can live inside the image then yes, Smalltalk is possibly even better.
What about Erlang, it provides tools for real time debugging iirc.
> The only language that I have worked with that realizes that in the real world simple is _always_ a lie

This feels like an excuse to me. I’ve worked on a lot of rather simple web apps that all more or less do the same stuff. A few of them have managed to have delightfully simple codebases, most of them haven’t. There’s no reason that couldn’t be true for all of them. You usually end up having at least some complexity. But small complexity trade offs don’t necessarily require you to undermine the simplicity of the entire system.

> when python code really fails, e.g. in a context with threading, you might as well burn the whole thing to the ground

This sounds weird. Why burn the whole thing to the ground? You've got frames from all threads available. Why would it take days to resolve the issue? Why do you think it's easier to resolve it in common lisp?

(comment deleted)
I think I need to unpack what I mean by 'really fails' to capture what I was trying to convey. I deal with Python programs running in a number of different environments, and there are some where literally all you have on hand are the libs you brought with you. Maybe that is an oversight on my part, but the reality is that in many cases this means that I am just going to restart the daemon and hope the problem goes away, I don't have the time to manually instrument the system to see what was going on. I shudder to imagine having to debug a failure from some insane pip freeze running on a Windows system with the runtime packaged along with it.

Worst case for CL means that at the very least I don't have to wonder if gdb is installed on the system. It provides a level of assurance and certainty that vastly simplifies the decision making around what to do when something goes wrong.

To be entirely fair, the introduction of breakpoint in 3.7 has simplified my life immensely -- unless I run into a system still on 3.6. Oops! I use pudb with that, and the number of uncovered, insane, and broken edge cases when using it on random systems running in different contexts is one of the reasons I am starting no new projects in Python. When I want to debug a problem that occurred in a subprocess (because the gil actually is a good thing) there is a certain perverse absurdity of watching your keyboard inputs go to a random stdin so that you cant even C-d out of your situation. Should I ever be in this situation? Well the analogy is trying to use a hammer to pound in nailgun nails and discovering that doing such a thing opens a portal to the realm of eternal screaming -- a + b = pick you favorite extremely nonlinear unexpected process that is most definitely not addition. You can do lots of amazing things in Python, but you do them at your own peril. (Disclosure: see some of my old posts for similar rants.)

I disagree about Python. First, you can code a "manhole" in your code which lets you evaluate arbitrary string as Python code at runtime, basically a shell available via a socket.

Second, you don't even need that. Gdb with some tooling (see pyrasite) lets you attach to an arbitrary Python process and evaluate Python code in its context.

Sure lisp is a wonderful language, but let's not pretend there's no difference between incidental and accidental complexity.
Care to elaborate what corresponds to the incidental/accidental complexity in CL? I tried to understand these concepts, but it feels very subjective. Whether or not something is accidental looks in the eye's the beholder.
I'm referring to this part when I talk about the incidental-accidental complexity dichotomy:

[...] in the real world simple is _always_ a lie [...]

Do you mean inherent vs accidental?

I think "incidental" (non-essential, secondary, happenstance), and "accidental" (non-intentional, happenstance) are more or less synonyms here, not contrasts. I think there, uh, is basically no significant difference between "incidental complexity" and "accidental complexity".

Fred Brooks could have identified a third problem: accidental complexity is fun; inherent complexity is boring.
Fred Brooks called it "essential complexity" and "accidental complexity"[1].

Essential complexity is inherent to problem being solved and nothing can remove it.

Accidental complexity is introduced by programmers as they build solutions to the problem.

Lisp is nice because eliminates a lot of the accidental complexity through minimal syntax and lists as a near-universal data structure.

1: http://worrydream.com/refs/Brooks-NoSilverBullet.pdf

One of the traditional criticisms of Lisp, though, is that it lets programmers re-introduce a whole lot of accidental complexity in their Lisp code, and, worse, everyone introduces a completely different set of accidental complexities into their code.
Clojure is simpler than Common Lisp, but I still feel that the features are too many and too complicated, and continue to simplify, do not use some complex features, and insist on writing systems with pure pipeline structure.

As a result, the simplest system is obtained, but the design process is a systematic project.It is difficult to design a complex system into a simple and smooth pipeline system.

That problem is not limited to Lisp.

As a programming language, Common Lisp is large enough and multi-paradigm enough to allow for elegant solutions to problems. It does require some experience with the language and some wisdom and discipline to know what pieces to select and how to best use them.

However, like all large, multi-paradigm programming languages that have been around for a while (I'm looking at you C++), programmers tend to carve out their own subsets of the language which are not always as well understood by those who come after them, particularly as the language continues to evolve and grow.

There is also the problem where programmers try to be too clever and push the language to its limits or use too many language features when a simpler solution would do. All too often we are the creators of our own problems by over-thinking, over-designing, or misusing the tools at hand.

> programmers tend to carve out their own subsets of the language which are not always as well understood by those who come after them

if the language is not powerful enough to allow for that people will inevitably add preprocessors, code generators, etc... to do the things they want.

>> if the language is not powerful enough people will inevitably add preprocessors, code generators, etc... to do the things they want.

This is definitely true and it adds to the accidental complexity of the system, usually to save programmer time or implement layers of abstraction for convenience.

Common Lisp and C++ have both incorporated preprocessors and code generators through Common Lisp macros and C++ template metaprogramming and C++ preprocessor / macros. These features give the programmer metaprogramming powers, enable domain-specific language creation, implement sophisticated generics, etc.

They are powerful language facilities that need to be used wisely and judiciously or they can add exponential complexity and make the system much more difficult to understand, troubleshoot, and maintain.

This is a valid criticism, but it's not lisp specific.

We know somewhat how to control accidental complexity in systems that are highly constrained and don't let you deal well with essential complexity. And we know somewhat how to give you powerful tools to deal with essential complexity.

We haven't figured out in general how to make systems powerful enough to deal with the range of essential complexity, but constrained enough that this sort of accidental complexity isn't common.

Of course a lot of real world systems don't do a great job of either.

No language can prevent someone from exercising bad taste, but a language can prevent someone from exercising good taste.

Some languages attempt to discourage bad taste by limiting the power given to users. Common Lisp embraces the expression of good taste by giving users considerable power.

The fact that I can program in the highest level language and still have realistic, working tools to control machine code that is being generated or choose level or type of optimizations or lets me change all of that case by case and during lifetime of the application kind of blows my mind (FYI, I am using SBCL).
>Lisp

All those complicated lists? Forth is even further down the path to ultimate minimalism. Really only one data structure, a stack with binary values on it...

The point wasn't about minimalism.
Common lisp was never about minimalism. It was about lisp dialects like scheme.
Most proponents of Common Lisp swear by emacs/slime as the perfect IDE for it, but those may be not to everyone’s taste. Are there alternatives that are just as good?
There was a better alternative: Lisp machines. But in today's world, there's nothing as good as Emacs/SLIME. You can get almost there by connecting another editor to Lisp with an extension that speaks SLIME's SWANK protocol (like SLIMV for vim).
> most programmers are in an abusive relationship with their runtime environment.

Just pulling this out because it's a good sentence.

the article is self-refuting in a way, although the message makes sense. The author describes simplicity as "fewer levels of abstraction, and each of them well understood" , like a ship rudder (which is a good definition). But no-code tools etc are anything but well understood, and usually introduce hundreds of levels of abstraction (due to their genericality). The same goes for frameworks that introduce tons of abstractions in order to cover cases that are irrelevant to the problem at task. Ideally, the chain of scripts that do one thing and can be read in 10 minutes is actually simpler and a much better way to fix the ship.
This holds up to a point. The systems that require really little downtime get progressively more complex.

Need to handle machine failures? Push a bugfix without causing downtime? Maintain a single state across the redundant machines? Handle load spikes? Ones created on purpose to DOS you?

All of this requires additional mechanisms, which can themselves cause failures.

A bunch of paragraphs to type out KISS.
Sometimes the complexity is there for a reason. But if you can just start over, those reasons was not that important!? So don't hang on to functionality because it would be nice to have, you will have to pay the price for those features over and over.
Sometimes, but a lot of devs fetishise complex stuff. I disagree wit a lot of my tech leads decisions because he always wants to do the "most correct" way, whereas I want to keep things as simple as possible with the aim of keeping our code maintainable. For example, he wants to a add a graph database to the tech stack, while I realise that its one more thing to manage and potentially break and mongodb (that we already have) will be probably be good enough.
Things only get complexed if you mix them, like cords in a box. So you might be able to keep it simple as long as you do not have code that depends on both the databases, and that the two databases don't depend on each other.
Very good article. Please also preach this article to those who praise Kubernetes right and left (remember, Kubernetes it's the new blockchain hype; and just like blockchain you don't really need it unless you're a cryptocurrency; which 99% of companies who use Kubernetes aren't).
It's similar to optimization in that it follows a U-curve. Up to a point, simpler things have more performance, and simpler things have less downtime.

But after a certain point, it's no longer enough to make it simpler. You have to get a lot more involved again, and this time the complexity has to be intelligently focused around the end goal (be it performance or uptime).

"[The] more simple any thing is, the less liable it is to be disordered, and the easier repaired when disordered..."

- Thomas Paine, Common Sense, 1776

Perfect. Added it to the essay. Thank you!
Took a few years, but glad that Government degree finally came in useful! (=
I like the ship analogy from the article. The fact it is so robust is not because the system as a whole is simple, but because its components have well defined, narrow responsibilities and there are abstract interfaces between them, which hide a lot of complexity. You don't need to understand the internals of the pump to explain what it does, even though the actual internal technology may be quite complex and clever. Also you don't need to understand the internals of the pump to design a rudder. This is a property that many IT systems lack. Insufficient abstraction leads to a network of components where you can't reason about anything without understanding all of it. And such systems often break in weird ways.
A startup is not a container ship going between port A and port B where everything is known.

A startup is a new destroyer that has been floated, did not have sea trials and went to war with a crew that might have a couple of people that used to a drive container ship but mostly staffed with kids that thought it was cool to play with a destroyer. Oh, and 3/4 of the systems are still at best have been drawn on a napkin and most of the rest came from a salvage yard. Oh and if you complete your next mission you may get money you can spend on some of the systems but if you do get that money you would be expected to do a lot more runs, quicker.

A modern startup is a destroyer that had all its core operations systems fedexed to the yard, as they were rented from the cheapest third-party service providers, and nobody on the ship has ever bothered to read the contracts, much less know what a "SLA" is. Inevitably, at some point in the middle of the ocean, one of these systems will get remotely bricked because the company that provided it went under or got acquired.
Your startup is probably just a variation of CRUD.
The reason why we consider shipping to be a simple and solved problem is that in 1956 McLean had a brilliant idea that to ship efficiently and well everything imaginable would be put into identical containers with no variations.

That's why it is possible to have a container ship with a crew of 5-6 people pilot the load. Containers aren't sort of identical. They are completely identical and completely standard ( several standard sizes ). They have the weight distributed in a specific way and they are stacked on a ship in a specific way depending on the weight of every container.

The startup equivalent of a container ship in simplicity is a startup sorting a pile containing A4 paper, standard business envelopes and hang folders at a daily rate of 500 items.

But it's a B2B SAAS and it's in the cloud with edge processing and probably AI.
I would say a startup is a ship that has been floated. But it‘s unclear if it needs to be a destroyer or a cargo ship, or a cruise ship. Later on it may turn out to be a spaceship.
The author is not arguing for simplicity per se, but about being able to insert a human into a system that is normally automated. The example about the ship's steering system is perfect, actually. The system is not "simple" (if I were on a ship and the steering failed, I would be clueless), but it provides plenty of interjection points where a knowledgeable human can step in and either debug or fix the issue. It's those points that should be relatively simple - the whole system itself can be as complex as needed.

Your system should be testable, debuggable and it should allow a human to step in and take control if needed. An open system is generally better than a closed one.

Agreed, the first bit of the article isn't about simplicity as much as it is about visibility and the ability to directly control each aspect. This is very much in line with how modern SCADA systems are designed (and indeed the ship example is one of these). Each component can run automatically based on sensors and the state of its peers, or it can be directly controlled by the operator if required.

The latter part of the article talks about simplicity but doesn't seem tightly tied to the earlier part.

I considered using other labels besides "simple," like "redundant" or "straightforward," but decided no matter what word I used some ambiguity would remain. So I stick with "simple" and hoped the context would help carry the point across. I'm glad that it did, as it seems.
Humans in the loop are likely to be a source of trouble, and you will need to measure to figure out if they provide any overall benefit other than giving you somebody to blame when that trouble happens. You need to ensure that the "human in the loop" scenario is obviously worse than automation or else humans, always over-confident of their abilities, will put themselves in the loop when it'd be far safer not.

A good example of how to do this would be London's Docklands Light Railway. A DLR train is capable of autonomously going from one station to another. Trained operators are aboard every DLR train, and they have a key to a panel in the front of the train which reveals simple controls for manual operation. But very deliberately the maximum speed of the train with an operator at the controls is significantly lower than maximum speed under automation (there are two modes in fact, driving with the train still enforcing rules about where it can safely go which is a bit slower, and driving entirely without automation helping which is much slower). This underscores that manually driving the train is the way to sidestep a temporary problem, not a good idea in itself.

The DLR has had several incidents in which trains collided, it will come as no surprise that they involved manually controlled trains. Humans are very flexible but mostly worse as part of your safety system, don't use humans if you can help it.

I think what he meants by human and users is "programmers" and layers of abstractions in your code. The ability to change and go deeper when you need it.

But you example works nicely as well, you should avoid having to work at lowers abstractions, and limit the work there as well. "changing the core to adapt to new features rather than adding to the core"

How would a steering system that is simpler than that look like?

I think a key element is that this system is composed of parts that have one input. That allows a human to replace a component that failed and provide that single input to the next component in line.

(comment deleted)
There's way too much simplification and generalisation here, IMHO.

"Choose tools that are simple to operate over those that promise the most features."

Doing this very much depends on your requirements, doesn't it? What good is a simple-to-operate tool if it doesn't do what you need it to do? Sure, maybe you can simplify your requirements; then again, maybe not.

The example of troubleshooting a whitepaper form very much depends on the design of the system. Maybe there's a reason for having multiple forms. If they all shared common architecture and the problem lies in what they share, it's not necessarily more difficult to troubleshoot many as opposed to just one.

Moving from Marketo to HubSpot is good and well - for now. What about years from now? How did they end up with such complexity with the Marketo solution? Both solutions and requirements evolve. Down the line, you could end up with the same difficulties with HubSpot. I think it depends in part on how the organisation handles change.

Lastly, I agree with the sentiment of Mateusz Górski, who commented on the page: is there any hard proof beyond anecdote to back up the article? If you expand the article's concept of a system to beyond just software, all you need is a hardware fault in a third-party hosting company with lousy support for your simple piece of software to be down for weeks.

> all you need is a hardware fault in a third-party hosting company with lousy support for your simple piece of software to be down for weeks.

I suspect the author would reply that you can just replace the hosting provider. Since the interface and division of responsibilities is clear, one host is completely replaceable with another.