Simple, correct, fast: in that order
drewdevault.com
drewdevault.com
A computer was to control a new assembly line for a car company. They couldn't get the software to work. They called in an outside insultant. The outsider developed a program that worked. (It was more complex.) The book was about the psychology part: The original programmer says: "How fast does YOUR program process a punched card?". Answer: "About one card per second." "Ah!" said the original programmner, "but MY program processes ten cards per second!"
The outsider said, "Yes, but MY program ACTUALLY WORKS". If the program doesn't have to work, I could make it read 100 cards per second.
Correctness comes first. Simplicity is highly desirable, adds additional cost, but always comes after correctness.
I've never thought of simplicity adding upfront cost. That's probably true, but also true that it pays dividends later on in the project.
I think of refactoring as a series of SIMPLE transformations that clearly do not have any effect on the correctness (or incorrectness) of the code. That is, there is no possible change in behavior.
And think of the word "factoring" as in high school algebra. or rather "factoring out" something.
I have a dozen examples of this calculation. How about let's refactor it into a function, and replace all the instances with a function call?
This kind of transformation is precisely what the person who coined the term meant: Taking code which works and turning it into easier-to-read code which works precisely as well, because refactoring never introduces a change in behavior.
To quote Martin Fowler and Kent Beck:
> A change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior… It is a disciplined way to clean up code that minimizes the chances of introducing bugs.
[snip]
Not a direct quote this time:
> Fixing any bugs that you find along the way is not refactoring. Optimization is not refactoring. Tightening up error handling and adding defensive code is not refactoring. Making the code more testable is not refactoring – although this may happen as the result of refactoring. All of these are good things to do. But they aren’t refactoring.
As code is originally written, people are (or should be) using the most "obviously" simple approach.
A Breakthrough in simplicity is often the result of additional thinking and hard work. (And cost)
It is true mainly for one-time contracts where you actually might not care about simplicity at all. Enough is enough.
However, in the case of iterative projects keeping complexity under control has much higher priority including top priority for very big projects. Complexity and high entropy can easily kill everything.
Another thing: what was an entire program back then, is sometimes a mere function, or maybe a class or code library today.
> Correctness comes first. Simplicity is highly desirable, adds additional cost, but always comes after correctness.
Correctness isn't binary. Roughly no software today is 100% correct, but for most purposes you'd still pick the current version over a highly complex, slower, more-correct version.
Simplicity can save you a lot of cost as you edit the software, which helps you make it correct sooner. Simplicity and correctness go very well together.
Plenty of times correctness is binary. In some cases it would be: passes all tests. Or: meets all requirements. Even if it could be "more" correct (or "more" simple), but those aren't part of the tests / requirements.
Maybe it's supposed to move from A to B, maybe it should do it in under x seconds, maybe it should go via Y, maybe it has to be easily understood by a 6 years old, etc.
But I can't really imagine something that has simplicity as the only requirement ("nothing" is the simplest thing so that requirement would always be met with no action). So as long as the other requirements are met simplicity is usually the nice to have "add-on". And you can have correct and simple, or correct and complex. But correct (does the job) trumps simple. And the world is surrounded by examples that prove this point.
I think the author meant "simple should be part of good design" but couldn't properly convey the message. He focused on making the message simple and ignored the fact that it's not correct.
What about a process so painful nobody has even thought of it?
And you never know if it can be done in an even simpler fashion later.
A good program does only the correct thing in a particular area. It is known to be reliable in that area, sometimes even formally proved to be so.
Outside that area, an ideal program refuses to work, because it detects that it cannot obtain the correct result. This is normally called "error handling".
There's also some gray area where a program may fail to reliably detect whether it can produce the correct result, given the inputs / environment. A reasonably good program would warn the user about that, though.
A "garbage in, garbage out" program is only acceptable in very narrow set of circumstances (e.g. in cryptography).
"Good enough" is the destination of any piece of software. Sometimes that means correct, but more often it means "oh yeah, sometimes it starts acting funny, just restart it when that happens"
In which case please consider that everyone here is using "correctness" to mean "correctness that is achievable by reasonable human effort". :P It's easy to win any argument by taking one side to its logical extreme and asserting that it is therefore impossible, but that doesn't create a useful discussion. By the same logic we could assert that 100% simplicity is impossible, but that would be just as silly.
It is slightly better to be simple than correct.
See https://en.m.wikipedia.org/wiki/Worse_is_betterBecause the original author neglected to provide an adequate definition of correctness, thereby inspiring an epic HN flamewar as people now must run around endlessly debating semantics. :P
I use more-* phrases because it's always in a relation. Even NASA can't claim to have 0 bugs although people die if they fail.
bit OT: There's a great article about NASA programming: https://www.fastcompany.com/28121/they-write-right-stuff
One way to achieve greater simplicity is to negotiate for fewer/simpler requirements for the first revision. There's often a core set of functionality that can be implemented correctly in a simpler way, and that gets the work done. Once that's in place it's interesting to see how often people lose interest in what were "hard" requirements before. It's also common that new asks have little to no resemblance to those unimplemented features, and are instead things that they found out they needed after using the new system.
So correctness is generally never satisfied in my mind. At any given moment, the programs I am working on are in some way broken in my mind. Even if the other programmers thought that correctness was priority number 1, I will never consider the program correct. I will always suspect there is some snake in the grasses.
I suppose you could feel the same way about simplicity. I think the most charitable stance would be to give them the same level of importance. Overtly complex code cannot easily be proven to be correct amid changing business requirements. Easily testable, complex code with a full functional test suite is at less simple in one sense. Patently incorrect code is hardly valuable regardless of how easily one can understand its function.
It is relative preferences, more about what takes precedence over what than an absolute measure. Nothing is ever perfectly correct, nor perfectly simple nor perfectly fast.
Not always. Have you ever used a SNES emulator? There is one emulator that is more correct than all others combined - it's called BSNES and it's the most true to the original SNES hardware of all the available emulators. Yet it is horrifically memory/cpu hungry - that correctness comes at a huge cost.
So no, correctness does not always come first, especially if you value other things like user experience.
But I'll assume that you want the software that calculates your paycheck to be correct.
I think you're using a different definition of "correctness" than most other people in this thread. Which is understandable, a lot of folks are using different senses of it. What matters is not, "Does this perfectly and unobservably play hardware" in the definition of correctness for an emulator. What matters is, "Can this emulate the cart I want to play right now with a good experience?" and perhaps, "Will this allow a malware maker to own my entire computer if they run a cleverly crafted fake cart file?"
There's no clinical definition of correctness here. Intent matters.
I believe that it does so through attemptng to mimic the working circuit logic and chips, the physical hardware, within code alone, hens it requiring a powerful computer. This is an incredibly unoptimized way of doing it, especially since it's formed out of incorrect assumptions n what "accurate emulation" is.
It's the effects that we want, not the logic. If you're going to emulate something that, through common sense, shouldn't even require that much power, you're doing it wrong.
The saying goes, "keep it simple, stupid!" To overcomplicate things, like the programmer of BNES did, results in unweildy an unoptimized code.
Even Nintendo doesn't do this tactic with their official emulators. Yeah, sure, they're known to be inaccurate at times, but that's only because Nintendo's not aiming to build a general emulator to handle all case scenarios. Besides, much of the inaccuracies, as far as I could understand, deal with undefined behaviors of the system, something only things like glitches and bugs ever take advantage of.
In that sense, simplicity is like insurance against the future, and so at any given moment you don’t solely care about the system’s total correctness or performance right now but also you care about some diversification benefit of investing in simplicity too.
Very much like how you don’t choose stocks based solely on what will have the highest expected return right now, but instead you also incorporate some notion of risk management when optimizing.
I think the author implicitly assumes the software basically works right from the beginning of the article.
Folks are using "simple" and "easy" interchangeably here. That's probably inappropriate.
That said, Coq itself is not the best vehicle for this. There are nicer high order logic languages.
Agreed, see Rich Hickey's "Simplicity Matters" presentation on the difference [0].
Simple-Complex vs Easy-Hard
Third is performance.
1. Write a working piece of software that does the job.
2. Refactor to make the working piece of software do the job more efficiently and elegantly.
3. Refactor to make the working piece of software do the job as fast as possible.
Seconded. I'm highly confused at how many upvotes the OP has gotten in such a short time despite appearing to say that implementation details matter more than program output. A beautiful machine that doesn't work is, at best, a statue. I'm all for the existence of pretty things that do not need to demonstrate inherent practicality, but most people are not printing out source code for use as wallpaper.
I think the author made this a little inflammatory to get people to think about it in these terms.
Easier to extend, almost never. Proper design for extensibility has an extra bit of complexity over the most obvious. Simplistic implementations tend to be tossed away and are good for unscalable prototypes.
Easier to learn, definitely not. The simplest code comes from deep understanding of problem domain and algorithms. It is almost exactly like with writing brevity while not losing the point. It is easy to end up with simplistic instead of simple. There is that famous quote by Dijkstra which I'd rather not butcher from memory.
What happens when it breaks? What happens when you need to produce doodads as well as gizmos, or a different size gizmo is desired? Who wants to reach inside the silly string and hope for the best?
I'm reminded of that old saying that even a broken clock is right twice a day; an overly complicated piece of software that produces the correct output is only coincidentally correct. Which I think is the point of the article.
The problem are not the programs that obviously do not work or who break in a very visible fashion. Programs whose deficiencies are known can be fixed or worked around.
The real problem are programs that appear to work correctly but aren't.
To say it with the words of Tony Hoare:
There are two ways of constructing a software design:
One way is to make it so simple that there are obviously
no deficiencies, and the other way is to make it so
complicated that there are no obvious deficiencies. The
first method is far more difficult. It demands the same
skill, devotion, insight, and even inspiration as the
discovery of the simple physical laws which underlie the
complex phenomena of nature.
Source: 1980 Turing Award Lecture; Communications of the ACM 24 (2), (February 1981): pp. 75-83.That's... literally what I just said? "Achieving correctness is the whole point of making things simple, after all."
If your solution is not simple, it will not be correct or fast.
Correctness may be the end-goal. But correctness is absolute. So it is a bad performance indicator to set as the goal. Yes, we can track bugs. But the absence of open bugs is no guarantee for correctness.I can never say "We are 5% more correct than last week. Keep up the good work!"
Simplicity is a much better goal for the day-to-day work. Because it can be tracked, measured and evaluated for every individual change.
Excellent, so we both agree with the author that correctness is the ultimate point and that simplicity is just a useful tool for achieving correctness. :)
> Simplicity is a much better goal for the day-to-day work. Because it can be tracked, measured and evaluated for every individual change.
How does one purport to measure simplicity?
http://pages.di.unipi.it/boerger/Papers/Methodology/BcsFacs0...
My thinking was like this. The complexity of software is synonmyous with us saying we don't know what it will do on given inputs. As complexity goes up, it gets more unpredictable. That's because of the input ranges, branching, feedback loops, etc. So, a decent measure of complexity might be simplifying all that down to purest form that we can still measure.
The ASM's are a fundamental model of computation basically representing states, transitions, and conditionals making them happen. So, those numbers for individual ASM's and combinations of them might be good indicator of complexity of an algorithm. And note that they can do imperative and functional programming.
What you think of that idea?
It's the other way around. Correctness is obviously the goal (and likely performance too, depending on your use case), but the way to achieve it is through simplicity. So simplicity should be prioritized - as it allows you to ensure correctness.
>> Simplicity is a much better goal for the day-to-day work. Because it can be tracked, measured and evaluated for every individual change.
>How does one purport to measure simplicity?
There's 40 years of research into that. And loads of tools to support dev teams.
You can start here: https://en.wikipedia.org/wiki/Cyclomatic_complexity
Also related are costing models: https://en.wikipedia.org/wiki/COCOMO
Have you seen a program that comes with a formal proof of correctness? I have. And boy, they are really simple.
The end result can be complicated. But the program is broken up into small, simple, easy-to-understand pieces that are then composed.
http://shape-of-code.coding-guidelines.com/2018/03/27/mccabe...
http://shape-of-code.coding-guidelines.com/2016/05/19/cocomo...
> if your solution is not simple, it will not be correct or fast.
The point of the article is that "simple" is a prerequisite of "correct" (and "fast").
For every problem there is a solution that is simple, neat - and wrong.I think, however, that the more important part of this quote are the words 'problem' and 'solution'. Until you have an understanding of the problem that is correct, it is unlikely that you will come to a solution at all. Avoiding the introduction of gratuitous complexity is not necessary to reaching that understanding, but it sure helps.
I think he is. Premature optimisation is putting the order: fast, simple, correct.
So although the author doesn't explicitly state it, premature optimisation is something that would be avoided if you followed his advice.
Working, simple, correct, optimized.My approach is usually sending out a PR as soon as I can to a group of reviewers / users and goes in following stages.
1) POC - proof of concept. It does 90% of things, some parts are ugly and messy but validates a hypothesis. The unknown unknowns are discovered. I want to stage this and get this in front of some alpha internal users as soon as I can. First pass reviewers give a on the plan of attack. Lots of sub TODO’s are listed in PR. The goal is to discover edge cases and unknown unknowns.
2) Simple - Go through PR and refactor any existing / new code so it’s readable and DRY. If reviewers don’t understand the “why” of some code, a comment is left. Now 90% of scenarios are covered, probably some edge cases may not work but the edge cases are known. The code is simple and at right layer of abstraction.
3) Correct, Testable - Edge cases are covered, tests are written, internal users have validated that the feature is working as expected.
4) Polish - if it’s slow, then slow internals are swapped out for fast parts. Tests would mostly work as is. Same with UI, css fixes to make it elegant and pretty.
Sometimes the process is a day, sometimes it’s a week.
> Under these conditions it's important that the software be amenable to change.
At the same time, under all conditions, it is important that the software actually works (i.e. correctness), which is why it's more important than simplicity. Irate users who come to us telling us that our program doesn't work will find little comfort as we regale them with how simple it is.
First, make it correct. Then, make it simple. If requirements change what correctness means, then make it correct again, then make it simple again.
He also talks more abstractly about the value of software (as opposed to hardware for instance) being primarily in its "soft"-ness, or ease of changing.
Ultimately this comes from his point of view as an architect, who fights more for system design than say, a PM might for user features. I've encountered the opposite school of thought that says: MVP to deliver features, refactor/rewrite later. I think the strategy to use will depend on the project and team (budget, certainty, tolerance for failure, etc)
Strong disagreement here. A program that isn't kept simple will stop being correct, fast, or any desirable quality over time.
This may be a matter of definitions. It may be worthwhile distinguish between general correctness and full, as close to 100% provable correctness as you can get. That way allows us to dismiss clearly degenerate cases (you can always do a one-statement no-op program that will be simple but do nothing).
General correctness is what I want in most cases. Example: voice dictation. It requires a final read & polish, but errors are infrequent enough to save me a lot of time. Full correctness is usually requested for jet avionics, nuke power plant control, etc.
With that addition one should optimize for general correctness and simplicity as a first goal, full correctness and performance as a very distant second.
When I write software (or build systems) what I end up with is usually significantly different from what I started with; not externally, but under the hood. Keeping designs simple (on large teams being almost militant about it) helps large systems morph as it goes from a proof of concept into an actual thing. My 2c.
Which is the root of the endless back-and-forth in this thread: a program has to do what it says on the tin ("general correctness") before anything else, and then probably be as simple and as "fully correct" as possible. But it's easier said than done for us to posit a distinction between general and full correctness than to actually find exactly where the dividing line lies between the two. A blog post to discuss such a dividing line might have been valuable, but the one we've got here unfortunately just handwaves away all the hard questions.
I’m a novice of sorts. Thanks.
Yes, but what is "Correctness". Its not usually so binary. Get to "good enough" and move onto the next thing.
More info here: https://en.wikiquote.org/wiki/Donald_Knuth
- John Ousterhout
I agree with this.
Interestingly, the post is very simple, and not correct. I prefer posts which are slightly more complex but correct, but those don't get as many upvotes.
Simple correctness is the best way to create beginners that use software to get faster results. Fast isn't all about computation - it's taking the least amount of the user's time as reasonably neccesary.
This is either a great typo, or a hilarious moniker I have somehow missed (almost 40 years in the business). Either way, it's worth recognizing.
Equal parts hilarious and accurate as "/in/con/sultants" are often brought in to play the part of the court jester -- they can speak the hard truths no-one else could, and survive.
>>"Yes, but MY program ACTUALLY WORKS". If the program doesn't have to work, I could make it read 100 cards per second.
I think I wrote a device driver like that, more than once. :( Fast as hell, to the point of outstripping the bitrate of the device it talked to, and about as useful as a sailboat on the moon.
The conclusion I came to personally was always
Accuracy > Maintainability > Performance
in that order
"A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over, beginning with a working simple system." - John Gail
http://www.ppig.org/library/book/psychology-computer-program...
Consider timezones: it's simpler to pretend there's 24 time zones, one for each hour. But the correct assertion is there's 37 time zones (as of this writing). So, the simple solution results in a third of your potential user base having issues.
Other issues to pick: accessibility, cross-browser compatibility, legacy device compatibility... the list goes on.
The problem is always, how to get enough correctness that your customers are happy, but don't spent too much time on it so that rivals won't overtake you.
I meant more for dealing with edge cases like an external API invocation failing. A final “correct” implementation would need to handle failures but an initial simple one would only handle success.
OpenBSD is not bug free at all, it is just security oriented in the implementation.
Windows got traction because it has even better hardware support, a bunch of backroom OEM deals and nice UI features (at the time of 95), then went far on software availability.
Changes are not necessarily easy or even possible to make safely if things are correct but not simple.
"Simple" is a proxy for "can be changed safely" and so IMO is the most important quality to have.
If simplicity helps achieve correctness, then great.
But correctness is not always simple.
Most people think there is a leap year every four years. They are wrong.
Abstraction allows you to hide complexity and make it a simple, reusable part again.
Plus, a complete leap year implementation is already what I would consider simple and most standard libraries already have an implementation for it you can use directly.
In their minds, when things stop being simple, fast trumps correct.
If I'm writing a file backup program, it absolutely must back up all the files without leaving any out or corrupting the data.
But let's say it has a feature that prints progress indicator percentages on the command line, ranging from 0% to 100%. Maybe under certain circumstances (like files added to a directory after the backup starts), it prints 102%. It's not what I had in mind, nor is it something I'd call correct. But if fixing it complicates the code a lot, maybe leaving it that way is the better choice.
(This is a bit of a contrived example because you could just clip the value at 100, but you get the idea.)
In your example, I would take simple to mean "only use UTC". As soon as you need timezones I would say you've moved into correctness territory and need to do them all properly (you would use a good library, of course).
Even "notify me in exactly 24 hours has its own complications. Leap seconds will screw up your day (as will the vague request of "exactly 24 hours").
Corner cases, the bane of simplicity everywhere.
Know your data, and most of your problems get easy.
How about another example? You're building an android application. Let's pretend there's an API in the latest version of Android that reduces a dozen lines of code down to one function call - ShinyNewMethod().
You can use that ShinyNewMethod() call. It's certainly simpler.
But the vast majority of android devices in use are not running the latest OS. So the NewShinyMethod(), while being simple, will cause your app to not work for them.
Hopefully the framework designers figured a way to have this auto-reverse engineer for older devices, but that's not always a guarantee.
That's a flagrant example of "simple and wholly incorrect". If you don't store timezones, your future dates will eventually turn out incorrect when timezone offsets change e.g. create a meeting at 9AM local, store as UTC, country decides to not follow DST that year bam your reminder will ping an hour early or late.
Or a day off when the country decides to jump across the international date line (https://en.wikipedia.org/wiki/International_Date_Line#Samoan...).
The solution isn't incorrect, it is modular.
So you provide an alarm clock which is works as neither a clock nor an alarm.
> The solution isn't incorrect, it is modular.
It's either not correct or not a solution, either way it's useless.
I also like how proponents of "simplicity at all cost" apparently assume/assert the composition of two systems is no more complex than either, and that there is no additional complexity to the composition layer.
Unless restrictions are specified I will assume we're talking about the general case, and for the general case it's just plain wrong.
There are very few applications that need to schedule events into the future, and that is literally the only situation where you have to worry about the timezone.
Btw, keeping the timezone is insufficient as well if you're building a calendar/scheduler. If the user changes the timezone after scheduling the event... do you keep to the old one and alert him whenever, or do you adjust? There are a lot of edge cases with schedulers -- yet as i said before, most applications don't schedule into the future. They're mostly just doing things right now or within the next few minutes and keeping a log of their actions.
> There are very few applications that need to schedule events into the future, and that is literally the only situation where you have to worry about the timezone.
My experience is the exact opposite: there are few applications which only store past dates, and in those said date is usually indicative/barely even relevant and could just as well be part of a freeform comment or removed entirely.
Make it work. Make it work right. Make it work fast.
In that order.
Now it could be argued that "work right" can be read as "make it (work right)", or "(make it work) right", or both, but I think the point of this saying is that the "fast" part should always come later.
If your software doesn't solve the problem, it's useless, no matter how correct or fast it is. Once it solves the problem, then you can work on making it bug free and elegant. Once you're done with that, only then should you look at making it fast.
Note, of course, that "make it fast" refers to gratuitous optimisation. If it's too slow to solve the problem, then it doesn't work, and that needs to be fixed.
A similar adage states the rules of code optimisation:
1) Don't.
2) (For experts only) Do it later.
By the by, is there more than one kungtotte on the Internet? It took me a minute to think why that name was so familiar, but then I remembered watching a few hundred Beaglerush videos.
I've used this handle for a long time though (20 years or so), so it's all over the internet.
If you're into strategy games, XCOM is good, and Long War is matchless. However, Beaglerush is actually surprisingly entertaining even if you don't care for his subject; the girlfriend is still not much into the game, but after the first couple episodes she insisted on watching the other hundred-thirty-odd videos. It's probably not everyone's cuppa, but it could be a thing.
Way back when a bunch of us put our names into a custom name file for XCOM so people could make campaigns featuring ShackTac people instead of generic dudes. I completely forgot about that until you reminded me. It must be five years since I talked to him :)
Once you've got this pretty optimized and it's still taking up the lion's share of your execution time, you have to look elsewhere (probably changing your overall approach or applying some higher level optimisation) to improve things further.
Sometimes it is quicker to start with just that instead of "polishing a turd". You can get it to be shiny but still nowhere near as shiny as gold.
Hope the code is testable and reasonably easy to modify. Otherwise it's going to be a rewrite.
The profile is then useful as a benchmark on real data. If you have enough time, you can turn that into a high level performance test.
The "make it work" implies a level of correctness & performance that is acceptable, which is why any subsequent steps are after thoughts.
If you skip that, you will relatively quickly reach the point of a full rewrite.
I take this to the extreme, I probably wouldn't implement the complicated API / regex chain without 4+ hours of reading documentation and other research. It bothers me that much. If it seems like a simple and common task, I refuse to believe that there isn't a simple API call already to do what I want, I just have to find it. Sometimes, the simple API call really doesn't exist though, and you have to do what you can, with some comments explaining why.
I've noticed some developers will implement the 4 API chain followed by a regex as soon as they find it, and never give a second thought that there might be a simpler way.
in any case, I think it's also worth mentioning that the article is probably talking about "incorrect" as in "accidental bugs", not as in "purposely ignoring the complexity of the problem". with the idea of preventing over-engineering rather than dismissing the specifications of a valid solution
More hand-waving.
It is not possible to store UTC unambiguously on the db server for all future local wall-clock times. (Previous comment about the erroneous assumption of "UTC everywhere" being a "simple solution".[1])
Therefore, redefining "correct" to be "store UTC everywhere" achieves the exact opposite: an incorrect and buggy program. That's because the "universal" in Universal Time Code doesn't apply to governments changing DST and Time Zone rules in the future.
Pure UTC doesn't have enough metadata to encode future unknowns. For correct handling with zero loss of data, one must store the user's intended "wall-clock" time in his local TZ in the db.
There's irreducible complexity when dealing with user-specified appointment times so an uncompromising fixation on programming a "simple" implementation with pure UTC-on-dbserver and localtime-at-only-at-browser-Javascript ... will lead to a broken calendaring program.
Congratulation, your emotional refusal to deal with zoned datetimes has led you to a non-standard ad-hoc reinvention of timezones, your misguided quest for simplicity and obstinate rejection of reality has thus led you to a system which is definitely more complex, probably less correct and likely less performant than if you'd just done the right thing in the first place.
I've commented previously that it's not a good idea to change the rows of UTC times in the database.[1]
Designing "system correctness" to depend on on the reliability of a correctly written SQL statements completing atomic transactions for millions of rows is not a good idea. In addition to batch db updates of UTC being extremely fragile, it's also not simple.
(It's fascinating to note that the multiple programmers independently arrive at the approach to update database rows of UTC times. There's something about it that's cognitively satisfying that attracts repeated reinvention.)
We should store the actual time of an event and update it when the scheduled time changes.
A countdown timer is a runtime concept.
Storing pure UTC and/or intended_localtime_plus_TZ in the database is a static concept of data-at-rest.
A timespan/timer is a different abstraction than a desired point-in-time.
Depending on the use case, the correct timer/timespan value can be derived from pure UTC (e.g. scientific celestial events) -- or -- user_specified_localtime_plus_TZ (recurring yoga class at 5:30pm every Wednesday, or take medication every morning at 7:00am).
For user calendaring and scheduling of social appointments, storing pure UTC will lead to data loss and errors. Instead of complicated mass updates of millions of db rows, it's much more straightforward to take a stored localtimeTZ, and then calculate an up-to-date UTC time at runtime, and then derive a countdown timer from that. The key insight is that the best time to use UTC is when the users need that timer at runtime -- and not when they store the row in the db.
I would love to see some (simple) code which will send a single alert to me at 1:30 am and another at 2:30am. My client registered me as MST (-7) when I set these two alarms in Feburary.
Of particular note for corner cases: Nov 4th and Mar 10, 2019.
The "scheduled time" will change, for many locations, twice yearly.
EDIT: For added fun, instead consider the registration date as May 10th with the same timezone.
Seriously? That, in your opinion, is simpler?
You should probably mention somewhere that you're the author of the blog post under discussion. And it looks like you're going to make a reputation for yourself as the guy who argues that it's more important for software to be simple than for it to function correctly.
Good luck with that.
That assumes your user base is evenly distributed across time zones. The US has 6 time zones, but if you only handled 4, you'd cover 99.3% of people.
"The single most important quality in a piece of software is simplicity. It’s more important than doing the task you set out to achieve. It’s more important than performance. The reason is straightforward: if your solution is not simple, it will not be correct or fast."
It praises a quality that is great as an add-on, not really by itself. Pretty sure everyone prefers a complex thing that "does the task" than a simple one that doesn't.
Simple "helps". Simple never "does". I think the author's values are a bit mixed up.
> The complex problem comes later, and it’ll be better served by the composition of simple solutions than with the application of a complex solution.
Complicated problem domains can be made into simple ones by breaking them down into their constituent components. You can solve time zones by having 5000 Rube-Goldberg-esque lines of if/else-if statements, or you can organize the system into simple components that build on each other.
At any given component or level the problems are clear, simple, and identifiable, and the complexity arises as the components join to form abstractions upon which higher levels operate.
Premature abstraction is a kind of premature optimization, except you're not buying performance.
Bad abstractions tend to stay in for a long time.
What matters is clear delineation between functional components and weak binding, so that internals can change, and that the interfaces are relatively minimal.
>The single most important quality in a piece of software is simplicity.
How panglossian, imagining the best of all possible worlds. Well, the world is intrinsically complex, as Fred Brooks explained in his No Silver Bullet essay from 1986[0].
"The complexity of software is an essential property, not an accidental one."
Sure, there is accidental complexity in most software problems, that can be tackled with skill and experience, and maybe reduced to zero. But then you are left with the essential complexity of the world. And you are done reducing the complexity; you can only manage it from then on. The world is very, very complex and it is a pipe dream to imagine that we can eliminate its complexity just by some bold engineering.
In a sense, this post is simply stating the obvious.
The biggest differentiator of skilled software practitioners is the ability to construct simple systems.
To call this claim panglossian or meaningless is to hold the philistinic line that this skill set doesn't matter, that any complex system is effectively the same as any ol' simple one -- don't worry about cultivating the skill, it doesn't matter anyway...
But simplicity is the single most important thing that matters in any maintaining system other than one-off scripts, hack jobs, etc. -- It's absolute torture to collaborate on a software project with anyone who rejects this premise.
And by that I mean, it has to account for more scenarios or do additional things... unless your software is growing in complexity while you're only removing features...
Bugs in software come from thinking we're simplifying the world in one way though the program, while in reality it receives a slightly different picture.
Faulty data models and system designs -- that aren't fixed -- lead to ever-increasing complexity. But that is the fault of the data model/designer.
I.e., there is a way to build (and grow) systems w/o linear increase in complexity -- but it takes a particular rare skill set.
I would say it's to construct simple enough systems, and the hallmark of skill is a developer's ability to define enough.
_One_ hallmark of developer ability is the discretion/wisdom/experience to know how flexible to make the thing. (How to prioritize and limit feature-creep, etc.)
But this is different than Simplicity. A general purpose programming language or database -- highly flexible/generic systems -- for example, can be built well/simple. But so can highly _specific_ systems.
In both cases though, one can build something that is decoupled and manipulable or one can build something that is coupled and rigid -- and the ability to do so is a function of skill set _not intrinsically_ a function of time. In other words, a skilled developer doesn't have to "take time" to deliver a Simple capability.
And sometimes it is good to have a replaceable head on the hammer.
A Hammer's construction can be Simple. A Knife's construction can be Simple. Or not.
It's possible to have a correct solution that is neither simple nor fast, and it can be worth your while to speed up a correct solution while sacrificing simplicity. So there are trade-offs involved in the relationship between simplicity and speed, but correctness is not negotiable. Acknowledging that all software has bugs is not the same thing as throwing out correctness as your first and primary objective in implementing an algorithm, and accepting that your solution may only be partial or fail with certain inputs is fine if that is acceptably correct for the problem at hand, but ascertaining that still comes first. Preferring simplicity over complexity because it makes debugging, profiling, etc. easier is not a reason to insist that correctness can go out the window in service to simplicity--who cares if you've removed all the bloat from your code if it's wrong?
Sure... but define "correctness".
Suppose my manager comes to me with some incredibly complicated problem. It's going to take six months to solve properly. Suppose in the first three weeks I implement a program that is 98% correct, and let's say it can detect the other 2% and kick it out for a human to solve. But it clearly does not fully and correctly eliminate the problem as brought to me by my manager. Have I solved the problem?
The correct answer is not "no, because your solution is incorrect and there is no such thing as an 'incorrect solution' because all solutions must be correct to even be solutions; you have no professional choice but to spend the next 5 months and a week implementing the correct solution". The correct answer is "the question is underspecified". I need to go to the manager and work with them on the question of what the benefit of just deploying this is, what the benefit of doing it "correctly" according to the original specification is versus the cost, and whether or not there are any other in-between choices. The business may require the full solution, sure. On the other hand, your manager may be inclined to thank you profusely for the 98% solution in a fraction of the time because it was far more than they dreamed possible and is way more than enough to make the remaining 2% nowhere near the largest problem we have now.
"Correctness" is only fully defined in a situation where the spec is completely immutable. Specifications are almost never completely immutable. So for the most part, everyone in this conversation using the word "correct" without being very careful about what they mean are not using a well-defined word.
It's all about costs and benefits, not correctness and incorrectness. For nearly two decades, Python's sort algorithm was technically incorrect: http://envisage-project.eu/proving-android-java-and-python-s... Does this mean that any program that used Python sort was worth "nothing", because it was not correct? Obviously this is absurd (in practice at least), so correctness must be understood in terms of costs & benefits to make any sense. And such an understanding must also be grounded in an understanding of the mutability of requirements as well, to make any sense of the real world.
From this perspective, it honestly isn't even 100% clear to me what prioritizing "correctness" over everything else would even mean. That we are slaves to the first iteration of the spec that comes out, no matter what? (Obviously not, but I can't come up with anything better that it might mean.) Correctness can't be prioritized everything else because it can only be understood holistically as part of the whole process. There is no way to isolate it and hold it up as the top priority over everything else. And there is no way for the correctness of a bit of software to exceed the scope of the specification itself, almost by definition, which in the real world tends to put a pretty tight cap on how correct your software can even be in theory, honestly.
“Simplicity first” means having a minimal skeleton code with glaring weaknesses, unimplemented features, and bugs, but having a simple design that sets you up well to absorb the inevitable shitstorm of changing priorities, pivots, revised performance constraints, feature wishlists, budget, deadlines, etc., and to manage extensibility, integration, or abstraction needs as they arrive in random, ad hoc ways.
Usually project stakeholders don’t care about absolute functional correctness, meeting performance criteria, or completeness until far far later in a project lifecycle, after those requirements have been thrashed around and whimsically changed several times.
Early on, they care about a tangible demo apparatus and solid documentation about the design and tentative plan of implementation. They want to see steady progress towards correctness & performance, but generally don’t care if intermediate work-in-progress lacks these things (often even for early releases or verson 1 of something, they’ll prioritize what bugs or missing features are OK for the sake of delivery).
In terms of interacting successfully with the business people who actually pay you and determine if your project lives on or gets scrapped, “simplicity first” is a total lifesaver, and matters far more than any of the notions of correctness discussed here.
That begs the question (in the original sense) of "unacceptable". If I banged out that 2% solution in an hour, and it lacked other costs that outweighed the benefits, it may still be something we ship! It is unlikely that we'd stop there, just because the numbers as you've given are unlikely to favor it because something else substantial would have to overcome the small amount of the problem we've solved, but to be firmly confident it's "unacceptable" you'd have to define "acceptable" a lot more carefully.
I understand the deep temptation to turn to discussions of the virtues of letting bugs through or something, but the costs/benefits framework completely handles that already. If you ship a buggy piece of "incorrect" shit, well, you've incurred a ton of costs with no benefits. That's wrong, by whatever standards you are measuring costs and benefits by. There isn't a "what if your 98% solution actually has a massive bug in it because you were unconcerned about 'correctness'?" argument to be made, because if it does have a massive bug, it's not a 98% solution.
How about this, for example:
The patient lives.
But I would put $10 down that if I asked you to assert that all medical software in current use that has never killed a patient because of its software issues is therefore "correct", you'd walk back hard. You'd have to be crazy to assert that all such software is "correct".
Unless you are willing to make that assertion, you don't really mean that as a definition.
This is also an example of what I mean in my cousin message about the temptation to turn this into a discussion about attention-grabbing bugs. But my framework already encompasses that. Software that kills patients is software very high on the costs side. There's a complicated discussion to be had about how to exactly quantify probabilities of failure vs. cost, but you can't have that discussion if you're stuck in a "correct or not correct" mindset.
I made no such assertion. That's a straw man.
But I think I can safely assume that if the patient dies as a result of the software's functioning, that software is not "correct".
You may disagree, but I think it's preferable to have a patient kept alive by an overly-complex system than killed by a simple, elegant, incorrectly functioning one.
Not to be intentionally blunt or snarky, but I think Drew DeVault's post was a bunch of rambling, hand-waving nonsense. Until today, I wouldn't have expected anyone to seriously argue that simplicity is more important than correctness. But he comes along and makes that very argument, with a self-assured, authoritative tone, but very little in the way of concrete reasoning, and to my surprise, the number of people on HN who apparently agree with him is non-zero.
"Some compilers allow a check during execution that subscripts do not exceed array dimensions. This is a help … many programmers do not use such compilers because “They’re not efficient.” (Presumably this means that it is vital to get the wrong answers quickly.)" (Page 85)
[0] https://en.wikipedia.org/wiki/The_Elements_of_Programming_St...
Languages like Ada Spark or Rust tend to rarely use or need such runtime checks. (they are available as an option to check unsafe code)
Others like Python and Java do check and give you traces. Not for free though.
And then you probably want something more powerful, such as a virtual machine like Valgrind; full sanitization of Address Sanitizer, etc.
The simple code of present was almost always written by someone who understands the problem domain really well in one or two tries.
This is one of the reasons why I am suspicious about the long-term saliency of so-called "smart contracts" on the blockchain. The immutability of code, while super amazing for digital assets, seems like a horror-show of a liability for dApps.
In my own area of work (database engines), the common mistake is that inexperienced designers do focus on simplicity first, instead of correctness and performance, not understanding that it is at best difficult and sometimes impossible to add correctness and especially performance later. The fast win of "simple" can turn into nearly insurmountable technical debt when you are asked to deliver scale and performance. People often grossly underestimate the minimum amount of initial implementation complexity required for good architecture.
There are many types of software where "simple, correct, fast" is sound advice but it is far from universal.
My definition of simple software is software that I can validate the correctness of using only equational reasoning and the mathematical tools used to carry it out without any specialized knowledge or verification systems.
If I have to learn a new way to reason about a software system in order to understand it then it is complex.
A priori any system written in C fails this litmus test: one must understand and identify the many ways that undefined behavior can enter into their program and be leveraged by their compiler. One cannot reason about a local expression in the presence of global effects and unchecked side-effects. And if it is possible to write a correct C program it takes considerable effort and the use of very specialized verification tools.
There are many reasons to prefer C however; if we're willing to live within some tolerance of "correct" and "incorrect" then we can leverage a tool-chain that can produce highly performant code... but then we're forced to restrain ourselves from introducing complexity instead of spending that effort on other things.
One of the best clarifications of what it means to be Simple, to put it out there, is [1]; but the key point: Simple != Easy.
Simple means minimal coupling, high-cohesion etc etc.
Yet IME many developers do not understand the distinction and mistakenly believe that easy is the same as simple, and are willing to couple the hell out of the world under some false notion of "simplicity"...
As in math, you come up with the "simple" solution of 0.5 only after you've realized that the "complex" solution is, for example, "sin(pi/4) * cos(pi/4)". There might be no other way to discover the simple solution.
> A complex system that works is invariably found to have evolved from a simple system that worked. The inverse proposition also appears to be true: A complex system designed from scratch never works and cannot be made to work. You have to start over, beginning with a working simple system.
A complex system with a good workable and testable architecture will work, starting with passing the tests down to satisfying the user...
Such systems are not designed in detail but in general, and usually start with a single, simple but powerful overarching idea, which is actually quite complex to implement, but ends up working evidently well once even halfway done.
Examples would be message passing architecture, event driven programming, time tracking, microservices, reactors, literate APIs, contract programming, Model-View-* and more... Note how half of those deal with reducing coupling by adding complexity.
I also disagree. I have yet in my life see any programmer crank out a simple solution on the first try for anything that isn't a trivial requirement. The way I think most of us work is to create a complex solution first and to have to refactor at least a couple of times before we get to simple and elegant. That doesn't mean the complex version didn't work.
I've made plenty of code that is bug free according to the requirements. I tend to start with tests and I'm pretty good at figuring out edge cases and other ways to break my code before I've even written it, so what I end up with is pretty robust. But the first version is rarely elegant or simple. By the time I'm done with the first version I understand the problem space so much better and might throw out 90% of my original code in the first refactor. Am I the only one doing this? Sometimes it even takes weeks or months to get to simplicity. I keep understanding the requirements better and better and noticing how I could eliminate code, often after I've noticed some code I'm still not happy with and having slept on it. Sleep does wonders for seeing how to simplify.
If that's what he meant, he's flat wrong. Simplicity is neither necessary nor sufficient for correctness.
I have a simple solution and a complex solution. Does the simple solution meet the requirement(s) before me? If so, I prefer it. Let's move on to the next requirement and consider my options again.
The alternative might be to look at your requirements, but choose a complex solution (over a simple one) because you think it might meet other requirements, either ones that have not yet been identified or ones you think are likely to happen in the future.
Are there times that the more complex solution wins? Probably. Consider you want to write a blog. You know that you can create an HTML (text) file and slap it on a web server and your blog has started. But if you've done this before, you might also know that you can throw WordPress on your server for a little more up front pain. You know you want comments and word clouds and date/time stamps and navigation. So you choose the complex solution. (You also know that you know face potential security implications, upgrades, dealing with users causing trouble with comments, having the PHP/MySQL infrastructure/hosting requirements...) Maybe you just wanted to dump your thoughts to the internet. Maybe the text file approach was better...
It may just be another way to say "avoid gold-plating your software."
But simply meeting requirements is only doing the minimum possible. Now in a government job, that's okay.
But in a real job, if you see where the simple solution is OBVIOUSLY wrong for certain likely cases not considered in the requirement, then THE REQUIREMENTS ARE WRONG, or incomplete and this should be pointed out!
And yet, if you try to compete with Intel with a CPU missing the above optimizations, you will get absolutely creamed in the marketplace. No one, not even those touting the importance of simplicity and correctness, will buy what you're selling.
Today's free market is too complex for these overly simple rules. Choosing between simplicity, correctness and performance, is a complex tradeoff that needs to be made on a case-by-case basis. Trying to find shortcuts to avoid these analyses may feel liberating... but you're ultimately only shooting yourself in the foot.
If you insist on only hiring chauffeurs who drive at 100mph, you can hardly complain when they get into a few accidents.
That being said, consider me lined up to buy one of these CPUs.
If you can find a CPU that has the same number of non-cache[1] transistors as a Intel/AMD chip, but spends them on a larger number of simple (and preferably independent/non-hyperthreaded) cores, rather than squandering them on speculative execution and ten thousand obscure model specific registers, I would absolutely buy several of them.
1: and similar amounts of cache, of course.
Very niche products.
For massively parallel number crunching, GPUs are much better in both performance/watt and performance/dollar. That Xeon Phi 7290 delivers up to 3.45TFlops, costs $3200, and consumes 245W. Compare with GeForce 1080Ti 10.6 TFlops, $700, same 250W.
For general purpose software they don’t work particularly well either. Most IO interfaces is serial, SATA, PCI-X, they have very few wires going to CPU. If you’re IO bound and you don’t have enough single-thread performance you’ll struggle to saturate the bandwidth, doable but very hard.
Also for general-purpose software latency matters. Namely, input to screen latency for desktops and mobiles, or request to response latency for servers. Get Windows or Android tablet with Intel Atom Z8300 (available for $80-100), and see how it performs, it has 4 very similar cores (minus AVX-512), and frequencies are very similar, too.
It isn't simple, it's designed to be incorrect (and even the parts that are supposed to be correct aren't), and I'm not surprised it fails on fast as well.
X86-64, SSE, AVX, AVX-512, AES-NI, etc. Their key selling point is software compatibility.
> Intel does not make [simple cores]
The cores are quite simple by today’s standards; otherwise Intel wouldn’t be able to pack 72 of them on a single chip. IME is unrelated to the cores, it’s a separate piece of silicon.
But if you don’t like the IME and don’t need backward compatibility with x86, maybe you’ll like this: https://www.qualcomm.com/products/qualcomm-centriq-2400-proc... But again, performance benefits of the architecture (48 simple cores) is questionable, GPUs are way faster for parallelizable number crunching, and you need single thread performance for almost everything else.
So it has the ten thousand x86 and x64 registers in addition to the ten thousand ?PCI registers?
> The cores are quite simple by today's standards
That's my point; today's CPUs don't have "ultra-simple" as a option (at modern feature densities).
> IME is unrelated to the cores
Fair point, I probably should have added "and doesn't have technicalities like builtin malware" to my original post.
> https://www.qualcomm.com/products/qualcomm-centriq-2400-proc...
This looks interesting, although I'll need to research a bit more (and "SOC - Features - Integrated management controller" isn't encouraging). Thanks!
Also, there's the case of Intel losing to in-order ARMs in mobile. First with XScale, and later on with the in-order Atoms. (https://appleinsider.com/articles/15/01/19/how-intel-lost-th...)
And yet, if someone tried to sell a server CPU today that was not pipelined, not OOO, and didn't have branch prediction, it would absolutely tank in the marketplace.
I never said that performance optimizations should always be implemented. Just that performance optimizations should sometimes take precedence over simplicity.
You could sell it as a niche product for high security applications, since OOO execution is a nasty side-channel.
The first CPUs were simple as heck. They spent 20 years making them reliable(and faster without compromising simplicity, mostly just node-shrinks), and they've only really been complicating the architecture in the last 30 years.
> I didn't have time to write a simple program, so I wrote a complicated program instead.
This is in my experience more than just a clever turn of phrase: the vast majority of software projects (or features etc.) move from _simplistic_ to complicated, and rarely from there toward simplicity. The end result is exactly what this author describes -- a complex mess that's difficult to reason about and rarely performant.
Few of us (usually myself included) are willing to devote the time and effort required to achieve true simplicity.
This is certainly true for me after 20 years of focussed practice. In fact, I have to go out of my way now to introduce coupling--it takes time for me to do the wrong thing. So when I hear someone say "I don't have time to make it simple / decouple everything / design it correctly", what I really hear is "I don't have the skill set" and, often, "I don't want to do the hard work and patience it takes to require the skill set." It's a philistinic cop out, really.
Please NOTE that I am not saying that I agree or disagree with this article.
http://www.expatsoftware.com/articles/2007/06/getting-your-p...
I came up with Readable as the top priority, followed by Debuggable and Maintainable. I suppose one could combine that into "Simple" if one liked.
But yeah, Fast was already at the bottom of the list. Even back then.
Personally I found the biggest improvement to my own software came from maintaining the same system that I wrote for 4+ years.
If I came back to a part and didn't understand it more or less immediately, then it was time to refactor it. I wrote the code I should understand what it is doing. No excuses that someone else had written bad code.
(Unrelated) When I read the article, the first thing I thought was that all of the simple programs had already been written.
Evidently, they're happy to start auto-forwarding http traffic over to https before finishing the part where https works.
I've flipped it back off for the time being. Thanks for the heads up.
Sure it does. It clearly states that sometimes new features or performance optimisations have to be sacrificed to keep the software simple.
By adding a lot more complex code can I make this blitter 100 times faster? By using this more complex algorithm can I make a "simple" string search amazingly faster?
As for features: Word has more features than Notepad. So why doesn't everyone use notepad?
Reminds me of this: https://github.com/kelseyhightower/nocode
Is there a need to overstate to cut through the noise and get your point across or your message heard? Maybe, but it seems unfortunate that when people have some truth or wisdom to share, there is a felt need to amplify and polarize it.
This article has good things to say about the importance of simplicity in code and implementation. I'm fine with value judgements as long as they convincingly define the values they are judging and show evidence that the facts have been thoroughly weighed. 'Correctness' is an ill-defined villain here and the article would do better to state the benefits of simplicity and experiences the author has had with systems designed without simplicity as a first-order goal.
Then again, perhaps I ask too much. Also, I've never had an article on the front page of Hacker News, so what do I know.
When I first started building TrueJob (job board software), I'd add in all these really cool features that made my app -- and at the time, they felt really useful. But over time, people weren't using them, so I built more features.
But then the old features I had built broke, so I had to fix them. And then they lagged behind the quality of other features I had written them, so I had to update them. And after doing this 5-10 times (as the software evolved and I dramatically increased the complexity of my application), these features that no one used really were painful to keep coming back to, but now enough loud users were using them that I couldn't remove them.
It made me really value the projects where we polished and did just a very few things, but did those very very well -- it lead to higher customer satisfaction, and less pain in the long run for us.
https://www.slideshare.net/chaffeet/how-killer-features-will...
Far more of a problem than code complexity is the lack of systems thinking when applied to programs. Various factors (abstraction, delegation, nicer APIs, solid products, SaaS, "microservice" trends, package managers and bundlers) have encouraged offloading much of the computation and data flow to other products, whose strengths and liabilities become your own if you make use of them. One-liner lambdas might be simple code, but they're often coupled to a maze of other cloud services, and the complexity there is coming from the dependency substrate, whose shape can't even be expressed in an imperative or declarative notation like code.
In truth, code and libraries and services feed into systems, and those systems must be understandable if correctness and maintainability are a goal.
- Occam's razor https://en.wikipedia.org/wiki/Occam%27s_razor
- "Simplicity is the ultimate sophistication" (Leonardo da Vinci)
- "Less is more" (Mies Van Der Rohe)
- "Make everything as simple as possible, but not simpler" (Albert Einstein)
Like saying leap years occur every four years. Simple!
Part of attaining wizard status is not learning how to hold more of the program in your head, but instead learning how to hold _less_. This seems like an excellent step in that direction.
BTW, the quote is usually attributed to Einstein, but it seems he did't say it.
However, the general idea appealed to me, so taking a step back, I tried to post-rationalize a similar thought to the author that I could reconcile with my initial reaction. The thought I then had, is that if a simpler solution can be found that solves a significant subset of the problem being solved, then perhaps it is worth adjusting requirements to go for the simpler solution for the fact that it lets us ship faster and with less risk.
Often times we come up with requirements that aren't really "required": showing business stakeholders that dropping a few requirements could enable you to ship six months faster and with far less risk can be a valuable insight in of itself. In essence we are still putting correctness first, but we are changing our definition of "correct" slightly in order to increase simplicity.
The reason is straightforward: if your solution is not simple, it will not be correct or fast.
This could be reworded in the following ways and the points made in the article would still follow:
If your solution is not correct, it won't be simple or fast. If your solution is not fast, it won't be simple or correct.
This seems like a good opportunity to recommend Rich Hickey's talk "Simple Made Easy": https://www.infoq.com/presentations/Simple-Made-Easy
The obvious solution: Grab a knife, put the bagel on end, and get to slicing.
The commercial solution: Flat and flip. https://www.epicurious.com/expert-advice/best-way-to-cut-a-b...
The mathematician's solution: A Möbius bagel. https://www.youtube.com/watch?v=Ktfo8D3cCr0
The engineer's solution: The bagel jig. http://www.freepatentsonline.com/5228668.pdf http://www.freepatentsonline.com/3347296.pdf http://www.freepatentsonline.com/4807505.pdf http://www.freepatentsonline.com/4747331.pdf
The consumer solution: The bagel guillotine. https://www.surlatable.com/product/PRO-1036557/Sur+La+Table+...
Which is the correct solution? Depends on who you are.
I've been using rspec-inspired testing frameworks to help with this clarity. Whenever I implement some early business logic, I assert that the logic I wrote does what I expect in words using rspec style tests (I've been writing a lot of JS lately so I've been using Jest). The kind that read like sentences: "the tax component applies 2% tax to all purchases above $400." Even if the logic is initially incorrect, being clear in our incorrectness lets engineers and (more importantly) product people quickly identify incorrect assumptions.
The logic to apply that tax in this example may not be simple. Often times, business logic can't be simplified any further and needs to be a bit thorny. In those cases, clarity of purpose is much more beneficial than simplicity, and I've found writing rspec style tests, and forcing myself to translate what the logic is doing into words, helps immensely with clarity. It clarifies my thoughts before shipping code, and it clarifies our business assumptions as a whole when that code is running in production.
The real superstars solve the same problems with simple code.
I recall a fellow student in CS in the 1980s who used to brag about how many lines of code he wrote to solve an assignment. I never understood that mentality. His programs were always 2-3 times longer than mine. But now that I've had many years in the industry, it almost seems that a lot of people believe more code is better.
Correct, simple, fast, in that order.
In other words:
- First get a correct solution working that solves the problem correctly for typical as well as corner cases, hopefully with good test coverage for both typical and corner cases.
- Then improve the solution to make it simpler while preserving correctness.
- Spend time on making it fast when there is actual evidence of performance problems such as data from performance testing with current workload and expected future workloads.
The reason why I like to ensure correctness before simplicity is that many times what might seem like a simple solution initially might turn out to be the wrong idea when all corner cases need to be accounted for. Ensuring correctness first requires me to think through the corner cases well and write test cases early during the development phase. With correctness taken care of and protected reasonably well with test cases, it becomes easier to iterate on the solution and increase its simplicity.
When I'm first building out a feature, I need to make sure that it actually works (i.e. is correct). Once that's out of the way, I'll have a good idea of what it takes to make that functionality actually work correctly, and can begin making it simpler while preserving correctness.
Simplicity is easy if correctness is not a constraint.
Simple software reduces the skill required to intervene.
I once worked on a project that failed because the lead programmers did not want to learn how to use a database. They insisted on using an ORM incorrectly, and almost all of their code needed to be rewritten in order to handle a typical anticipated load.
Granted, the entire codebase was full of "simple" for each loops, but the reality is that if they had started by writing correct database queries, the project would have never failed.
Thus I say, optimize for your budget at the beginning of the project. You should pick design patterns that handle anticipated load. Full optimization can come in later, but if your project can't handle anticipated load at the beginning, then you are misinterpreting what this "simple, correct, fast" mantra really means.
If you really write simple code, you should also be aware of the shortcomings of the simple approach.
But that's the class of problem they were trying to solve. And a DBMS represents vast amounts of energy put into trying to solve that class of problem.
So, the problem was a complex one, but one that had many aspects of it already solved, and rather than building on a good solution (the DBMS) they chose to try and reinvent queries.
> you should also be aware of the shortcomings of the simple approach
Yup, you really can't refuse to learn how databases work. Unfortunately, they didn't know what they didn't know.
Not using a database when the solution calls for one clearly violates the "works" principle. And obviously, using a tool incorrectly (the ORM) trumps anything else. That's a tautology.
For example, if a web application needs to handle 20,000 requests an hour, it's okay if early versions take 10-15 seconds to respond under unusually high load. The optimization phase can bring that down to something more manageable.
Some people take the "optimize last" so far that they ignore their basic scalability requirements; or just assume there are no scalability requirements. That's when a more senior dev needs to step in and demand basic scalability in the design.
Correct comes before fast not only because you should make it correct before making it fast, but because you should not sacrifice correctness in order to make it fast.
Similarly, simple comes before correct because you should not sacrifice simplicity in order to make it correct (or fast). Instead, you should continue looking for different ways to make it correct while maintaining simplicity.
1. The full phase is "complexity is the enemy of reliability".
2. It dates not to the 1980s or 11970s as I'd thought, but the 1950s.
3. It first appeared in print in The Economist newspaper, 18 January 1958, according to Google Books & Ngram viewer.
I'd very much like to have a copy of the article, though its proved resistant to obtaining. HN username at protonmail.com should anyone happen to have access to a PDF.
http://books.google.com/books?id=aDsiAQAAMAAJ&q=%22complexit...
Correctness can be only achieved if the programmer has a good understanding of requirements and posses necessary discipline to write tests. I cannot image system that maintains correctness without tests.
Performance usually is something that only good architecture can bring. I disagree with the article. If you focus on simple too much you will miss important requirements and you will make architecture choices that negatively impact performance. More annoying it the author is trying to create another silver bullet approach.
Simple/Correct/Fast - it depends on the problem you try to solve.
In worst case failing to do that will require write entire project from scratch because basic data structures and structure of code is antithesis for performance and there is not a point that you can optimize. A reasonable default is using vectors over linked lists. A more complex choice is using struct of vectors rather than vector of structs. And neither of those choices are easy to do for data structures that really matter late in the project.
Thinking you understand what you're making before you really know is one of the worst mistakes you can make. It's under that misunderstanding that you'll think you need to add to your program to make it more correct and fast, when really you're over complicating it and making your life worse when you actually need to get it running.
In contrast, when you can run a test as soon as possible, that's where I usually see an alternative way of writing it that may be shorter, more correct or faster
You'll never get to this point of course if it isn't simple in the first place.
1. Correctness
2. Performance
3. Simplicity
4. Consistency
5. Completeness
I think I tend to agree with that order more. While simplicity tends to be helpful with performance and correctness, there are very few cases where you'd sacrifice correctness/performance for simplicity if implementation time/cost were not a factor. Let's not confuse a way of getting to the goal with the goal itself.
> Incorrectness is simply not allowed. It’s pointless to try to get stuff done if you can’t guarantee the result is correct.
For example, think about a game that runs quickly and is playable but crashes occasionally, versus one that lags all the time but never crashes.
> Consistency can be sacrificed for simplicity or performance. Don’t let excessive consistency get in the way of getting stuff done.
I take consistency to mean consistency of results being correct. Wouldn't that make consistency a subset of correctness?
Simple is important, but it's not the most important thing. The overwhelming evidence is that there's plenty of working software, in use, that isn't simple. Users will shape their behaviour and memorize flows around a complex piece of software if there is sufficient motivation.
My own principles for design state this order:
Functional, Simple, Delightful
Does it do the job? Is it simple to use (nothing more than what's required)? Do people get excited to use it?
The first is non-negotiable and the last is optional. There's a lot of software that is functional, but over-complicated. Lots that is delightful, but doesn't work and everything in-between.
The software I admire most (and use daily) has a balance of all three.
Keep it simple. Do what needs to be done now, leave off for later everything else. By the time later arrives, what the project needs will have gone in new and surprising directions. Refactoring complex and coupled code into logical discrete units pays off in multiples down the road. Removing that which is no longer needed is like giving your whole team extra time to breath and new room to think. For all the times that simplifying also solved the two other problems, we used that time for making more cool stuff.
I always stress a hierarchy of Goals, Strategies, Objectives, and Tactics, which is a fundamental and well known way to tackle strategic thinking/planning. When you engage in fuzzy thinking, then it is easy to make the wrong decision. For example: "we must be agile". Well, no, that's not our goal. Our goal is "whatever", and agile might let us meet that goal, or it might hinder it. "Move fast and break things" will (hopefully) never be the motto of Boeing.
So, simple, correct, fast. That conflates different things. It is never my goal that things be simple. A goal might be 'correctness' or 'don't kill people'. A way to achieve correctness is, for example, easily unit testable software, which is a strategy, the objective is a passing unit test, and a tactic might then be the code be simple (low cyclomatic software is easier to exhaustively test, for example).
When you conflate various levels of this hierarchy bad decisions and religion ensure. "Code must be simple". "We must always be agile". "Everything must be documented". "functions must be < 10 lines long" You can see that these are not goals, but often chosen ways to get to your goal. The problem is, goals change, and strategies don't get you to the goals in some edge cases. Because you are focused only on strategies/tactics and haven't clearly articulated the goals you don't notice this and make bad decisions.
For example, short functions are generally a good thing. But, sometimes it takes 50 lines to express a cohesive thought. Splitting that arbitrarily across many functions can just obscure intent, and leave the developer scrolling back and forth, which is proven to reduce comprehension and increase the likelihood of errors.
In short, if you use these three criteria, in this order or any order, I don't think you will end up making very good decisions because you at best only implicitly have stated and understood the problems you are trying to solve and the interrelationships of how your strategies and tactics affect one another/
The fact is retrofit performance into a sufficiently complex system is hard, more often then not the system has to be redesigned and rewritten to achieve it, as we have seen with so many OSS project.
I think with right amount of forethought, all three can be achieved. That doesn’t mean you never need to iterate on your software, because requirement always involves.
Now, while I think some small and contained problems may be solved the second way, I think most complex problems of the kind we solve as programmers will be best solved the first way.
Simple, and still correct, is often the result of doing MORE work to achieve it.
I've never read Gabriel's article in such a way that this "worse" means actually, seriously, worse quality. I always thought it meant "YAGNI", "KISS", and so on.
If you can't solve a problem, maybe you shouldn't try so hard? Find a better problem! I'm confident this attitude helped me a lot in the past. Speaking as a clearly-not-a-genius programmer.
So it was not simple and it was not fast. Nobody wants simple.. ever.
However if the problem space is new for you. I find I usually have to write more complex code to understand it first. Then I can come back and simplify the complex code.
First of all the software needs to be correct, if the software produces incorrect results, then there is no point for it to exists. It needs to be debugged until the results are correct.
Then fast. The point of using software is helping the user to accomplish a task. The better help that the software can provide, the more useful is the software, and performance is often critical to help the user.
Then simplicity, simplicity help future developers to understand how the software work and how to properly maintain it. But if some complicated procedure is needed to produce the correct results, then complicated procedure it is. If a different algorithm needs to be used to speed the software, then the new algorithm it is. It is up to the next developer to study and understand what was done and why.
The purpose of software is the end user. The next developer sits second to the end user.
This way you can easily debug it, replace the logic of 1 class without breaking your whole application.
One of the best advices for me was: your function must do one thing only, and do it good.
I think I need a new job...
"As you see, our main priorities, in order of importance, are simplicity of design, reliable operation, and good performance. For now we're focussing on simplifying our designs and manufacturing process, we'll figure out the rest afterwards."
"In that order?!" screamed another, sitting to the left. "Why you might as well replace the engine with a brick! If it's not able to drive you around, what even makes it a car?! Scrap your ramblings, first we make a car that works, with an engine and all, then we can figure out how to manufacture it."
"Pah," scoffed the engineer across the table, "and when is it in this story that you realize we're in the business of building cars, not mars rovers? If it doesn't get you from A to B faster than a bike nobody is going to give a hoot that it can run for three years without maintenance at the bottom of the Mariana Trench."
"Idiots!", the original butted back in. "If you make it simple first, then changing it to make it fast will be easy, and of course only simplicity begets correctness."
"You think the Mars Rover was simple?!─"
"Ha, because nuclear reactors are the paragon of simplicity─"
"CHERNOBYL IS EXACTLY THE POINT I'M MAKING HERE!─"
"Though wasn't it economic factors that lead to disuse of nuclear power? I hear solar is getting popular, we should really stick to my plan─"
"A car is not a solar panel, you bumble-headed fool─"
"You might as well be though─"
_Ahem,_ sounded the man at the head, drawing the room's attention, "I'm a little lost, so forgive the stupid question, but why have you not just considered... doing them together?"
A lot of people want to jump straight to the auto scaling, self healing, super deluxe cluster + tax edition to handle a billion requests a week before their app even has 1 visitor.
Imagine you have 5 programmers. #1 works in a web development company, writing a CRUD web app in SQL, PHP and JavaScript. #2 is coding C and developing an embedded firmware for a PC component. Also works on the driver for that component in the Linux kernel. #3 writes some COBOL for a half-century old system running in a bank. #4 works in a game studio developing a level editor for yet to be announced videogame. #5 is a researcher working in a university, mostly writing Mathlab but occasionally some Python and R.
The ideal simplicity/correctness/performance tradeoff is totally different between the five. Just like any other tradeoff, or generalization, or methodology, or approach. All 5 are writing code, but the software they’re creating have totally different requirements, expectations, lifetime and budget.
I never saw articles generalizing stuff across all engineers: aircraft, biomedical, marine, telecommunications, etc. However, I saw many articles, this included, that tries to generalize across all software development.
The above is definitely the correct algorithm. Surely you don't think it is wrong! Come at me, bro!
If you aren't trying to make your representation correct, then why bother? Correctness is either the telos or closest of these in the pursuit of the telos (the assumed intrinsic purpose). To make it simple and fast are the instrumental (and heuristical) means to increasingly correct versions of that correctness end(s). You can only ever simplify a representation (hello, Kant!) because you never have complete (hello, Gödel!) access to the thing-in-itself directly.
The representation is the only thing you can consciously (the Daseinic emergent result of the non-conscious [that you know of: problem of other minds, OOO, dualist's hard problem of consciousness, etc.] aspects of your brain using language to talk to itself) attend to; it's the only way to tell and retell these stories to yourself. How correctly can you recursively represent correctness? I can only begin to meaningfully compute by starting with a meaningful notion of correctness as my foundation (however flawed it may objectively be), else it is meaningless. What does it mean to correctly tell yourself about an object if you don't assume the concept of correctness in how you tell yourself about an object?
In a sense, you beg the question of the Ontological Proof (hello Kantian idealist vs Gödelian realist in dialectic!), the reality of the possibility of the goodiest good of your program (and I suggest even beyond), in thinking about the telos of your program (and, clearly, you change your mind about what counts as that).
Simplifying and/or optimizing a representation already begs the question of having something to be correct about. "Simplify" according to what standard? The pursuit of correctness is the necessary precondition to having a reason to take the means. Epistemic justification in coding computers (be they silicon or brains) is inevitably tied to this telic processs and metanarrative. We do not escape the chain of sublations. Be a transcendental coder! I believe in you, folks. I know you care about correctness, deep down. Don't you want to be correct about this code too?
It's dangerous to go alone! Take this: https://plato.stanford.edu/entries/dialetheism
------------------------------
now that I think about it, that would be a good exercise for programming students. after writing a program that does a certain task, make the student rewrite it to be simpler/easier to understand/maintain. then take a look at the best solutions in the class.
Why? Aside from the excellent reasons given by others below, remember that even conceptually simple systems can and often have surprisingly complex behaviours.
The emergent complexity of interacting simple systems is... often breathtaking in the scope of how whacked out the unexpected can be.
tl;dr - "simple" programs do not necessarily have "simple" behaviour or "simple" interactions with other "simple" systems.
Make sure the damn thing works before simplifying it.
I once did a code review for a function that parsed some Linux file for Ethernet stats. It was incredibly convoluted with tons of substring finding and indexing. I told the author to simplify it and he declared he already had and it was as simple as it could get. I then showed him of the existence of regex and his mind was blown.
I had a similar experience (but earlier in his coding process) and managed to change what was about to be a months long effort of adding epicycles and epicycles to code into a one week task. I have most of my library at work and constantly speak of design and theory concepts with the younger folks. Based on more recent code it's paying off.
> The reason is straightforward: if your
> solution is not simple, it will not be
> correct or fast.
A very hand-wavy statement, and not always true.