When I do a new project I always try to focus on a tech stack where - if i need to replace a part - i could easily do so. All these components are part of the decision
This paragraph really gets to the idea of why I think discussing someone’s taste is basically useless in an engineering context. This “predictably broken compass” person stands out like a sore thumb, and you can just hold a 20min behavioural interview to filter them out.
A far more dangerous engineer is the “partially broken compass”, which appears at first sight to be working because it spins around like you’d expect, but is actually 127degrees off at all times.
1. Someone who has only ever written code by tutorial, and has no idea of the architecture, performance considerations or usability implications of the code they're writing.
2. Someone who has got to a point in their career LLM Coding and is unable to write code without it because they don't understand what they're doing.
The problem occurs when one of these people:
- Is required to be good at the things they are not yet
- Proceeds as they always have because they are unaware of their skill gap
- (Optionally) gets promoted while things are still _just_ working
IME you hit these silent inflection points as a system begins to scale beyond the experience of the people involved. They survive for a while (Coyote time / the compass is still pointing north) until something happens (the whole thing falls off a cliff / starts to go south).
func isEven(s string) bool {
num, _ := strconv.Atoi(s)
return num % 2 == 0
}
becomes: func isEven(s string) (string, error) {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return "invalid", err
}
num := int(f)
if num%2 == 0 {
return "even", nil
}
else if num % 2 != 0 {
return "odd", nil
}
return "invalid", nil
}
Which is.... technically correct, instead of func isEven(num int) bool {
return num % 2 == 0
}
func isEvenSafe(s string) (bool, error) {
num, err := strconv.Atoi(s)
if err != nil {
return false, err
}
return isEven(num), nil
}There are a lot of ways to accumulate tech debt so fast you’d think you’re in a code casino.
But limited editor support, and some implementations have poor performance.
https://old.reddit.com/r/ChatGPTCoding/comments/15s62yl/save...
There are some domains where the word “taste” can still properly be applied, for example “vi vs emacs” comes down to individual taste. But then, “emacs people have poor taste” is something that only a narcissist would say. (The “narcissism of small differences” is a well-studied phenomenon).
Or perhaps one uses this choice of words because one feels some sympathy for people who say this in other domains, like “This room, filled with IKEA furniture and film memorabilia, was decorated in poor taste”
… either way, the red flag seems to stick.
The reason it's worth mentioning is that the notion seems to be catching on, and I've seen it applied, for example, in hiring decisions, where I think it's quite dangerous and counterproductive. It lends itself to rationalizing hiring only like-minded people, even where there is no objective ground for preferring one candidate to another.
e.g. I might decide that some clothes, although well made and possibly even very fashionable, are not my taste. The superiority/inferiority of taste is something that insecure people focus on IMO. A tasteless thing would be something that doesn't seem to show any overall philosophy of design or something which is bombastic - it goes to town on some aspect at the expense of all others - there's a lack of balance. Even then, who cares?
If I wear a bright tomato-coloured suit because I like colour, why should that make me a bad person? It's only when other people have to accept your tastes because they work with you that they're going to moan about them.
Lacking an objective way to predict that, we turn to taste.
There are plenty of subjective preferences that we can make comparative claims about without any risk of narcissism.
Hence the author's main point: a good taste is one that fits with the needs of the project. If you can't align your own presuppositions with the actualities of the work you're doing then obviously your subjective measures going forward will not be very good.
Something might not be to my taste but it can be good and workable nonetheless. It has taken some decisions that lead to solving a problem in a certain way and I can see that that way works and can be extended but it might not be a way that pleases me. Perhaps this is because it forces me to think in a mode that I am not generally accustomed to.
The same applies to picking vendors, asking questions like "will they extort me next contract renewal" and "what options do I have if they extort me".
it really depends on the problem domain tho, doesnt it?
Would you call the fast inverse square root[0] code good taste?
[0] https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overv...
I highly recommend checking out the clothing and fur stores to accessorize if you're in an Inuit town with nothing but "purely practical" Western clothing. A fashionable design will improve both the look and the cold weather performance.
This sometimes gets taken to the extremes of high fashion, like this design by an Inuit woman:
https://vafashion.ca/pages/the-ukiaksaq-collection-nyfw-2020
It does no I/O and has no action-at-a-distance. The float-long-float casts are the worst part.
If a coworker brought it to me with a performance justification and some unit tests I'd be pretty happy to pass it.
Writing code that looks so simple, everyone else says "pshaw, anyone could have written that!" is good taste.
Not all dogs are golden retrievers, etc
The starting approximation is just magic.
As an old programmer, I would write "FastInverseSquareRoot()".
It took a looong time for the penchant to write identifiers adhering to the FORTRAN limit of 6 characters to fade away.
(Yes, I know Q_rsqrt is 7.)
Just because something is simple doesn't mean anyone has the wherewithal to understand it.
And then there's a class of people that overcomplicate the architecture out of boredom, perceived possible problems (like scaling) or cargo cult, and they introduce stuff like microservices or lambdas instead of just solving the problem at hand with proven and simple solutions.
But you won't find good developers if your job listing is "looking for a java developer for a straightforward CRUD application".
For example, consider the instruments. They run off of 5 volts, while the system voltage of the car is anywhere from 10-18 volts. How to get 5 volts? It has a sort of buzzer which is an electromagnet. When the voltage is above 5 volts, it breaks the circuit, when it is below 5 volts, it closes the circuit. It happens fast enough that the result is a rough 5 volts.
Many restorers of these cars decide to replace it with an electronic voltage regulator, that gives it a rock solid 5 volts.
Then they discover the instruments don't work.
It turns out that the (mechanical) instruments tend to get stuck. The roughness of the 5 volts keeps them from getting stuck. So now the the electronic voltage regulator needs another circuit added to make the 5 volts rough.
That little, simple voltage regulator hidden inside a can is a marvel of simplicity and effectiveness.
The solution was simple and pure genius.
The nozzle was composed of tubes welded together. The liquid oxygen was run through the tubes, which pre-heated the oxidizer and cooled the nozzle. But that still wasn't enough. Then the engineers simply drilled tiny holes in the tubes, so some of the oxygen would leak out into the nozzle. The gas would form a barrier between the flame and the nozzle, and would carry away the heat. (This is known as boundary layer cooling.) If you ever get a chance to look at a rocket nozzle, you'll see the tubes and the holes.
The mechanical-averaging voltage regulator worked for the design because it only had to work in the context of the specific model of car it was going into. It didn't have to produce 5v for any application that needs 5v; it just had to produce "5v" for the instruments in the '72 Dodge Challenger. That makes it a pretty terrible 5v regulator, but a pretty great part for the system it was designed to fit into if the mechanical-averaging version is more reliable or cheaper or more robust than the fancy electronic versions.
But if I'm designing a 5v regulator to be sold as a 5v regulator, well... I don't know what system its getting installed into so I won't have much luck selling one that, over a long enough time span, averages out to supplying 5v of power when its supplying power. So I have to design in tight tolerances, and everyone integrating my regulator has to design for tight tolerances, etc.
The good news is that now anyone can buy my regulator and get a reliable 5v - interchangeable parts! But the bad news is now every system on both sides has additional complexity for the sake of complying with our standard.
We see this _all the time_ in software, especially comparing old software to new. Why is Roller Coaster Tycoon so much more elegant and efficient than a modern game written on Unity? Sure, good tastes probably factor in - but that taste from the author was allowed to shine because it was designed as a complete system, not a bunch of component subsystems from different teams and vendors stitched together.
In 1972, the electro-mechanical regulator was also likely far cheaper than a solid state one. The car didn't have a single transistor in it outside of the (rather expensive) radio. (There was an upgrade available for the ignition to make it "electronic" - it had one transistor!) The alternator of course had diodes in it.
The rough 5V also enabled the instruments to be made cheaper.
Those mechanical voltage regulators wore out and had to be replaced periodically. They weren't great at maintaining consistent voltage so consumers had to accept loose voltage tolerances. They made noise and generated electrical interference.
Modern cars are more reliable, despite their complexity. I'll take solid state.
I would hope so, after 55 more years of development!
People will watch a ballet and remark "they make it look so easy and effortless!"
The reality is, it is easy and effortless to them, because they practiced it 10,000 times.
Strange how absent the customer or underlying business always is in this discussion.
I've seen a LOT of software that could have literally just been a spreadsheet on a file share or a simple SQL ETL job. When reviewing the actual business requirements, we will often find that we don't even need a goddamn web interface. It's just assumed we're gonna build some full stack slop so we never bother to stop and ask why.
I'm watching a client (despite my advice) self destruct on yet another rewrite of a piece of software that doesn't need to exist in the first place. Looking forward to having this exact same conversation IRL in a few months.
I think the heart of bad taste comes from obsessing over tools and techniques and not ever getting meaningful things built and shipped to real customers. Being exposed to unfiltered criticism of the people who actually use your software is the fastest way to drive the noob behavior out of a developer. It's amazing how quickly you drop weird principled takes when your monkey brain senses it is disappointing others.
I was working on this project and I kept suggesting it. It was seen as inferior and yet the system we were going to produce was far inferior in every way.
Having done at least one migration from "could have literally just been a spreadsheet" applications into proper applications, I'm very careful with recommending spreadsheets. It's great how flexible they are, but that's also their major down side. If you want to enforce anything within a spreadsheet, you have few options and it's very easy for something to break. For example someone deleting a formula, a formula not including the full range, or copy & pasting breaking the references. Not to mention the performance issues you'll run into once you've collected a few rows.
Not all code can be like that, sometimes you need to write clever code, but it is an ideal to strive for.
BTW this is why egoless programming is so hard. Not only you have to accept criticism and let go of the idea of ownership of "your" code - you also have to write the code in a way that strokes ego the least.
I did not take the job.
Why not both?
In an interview, you need to impress, and that's true for both the job-seeker and the hiring team. So yeah, you need to talk a bit about your accomplishments, and it's hard to do that without being a little braggy. But you can let your work be measured on its own. If you directly measure your accomplishments against the other person's, and especially if you put their accomplishments down, you're not selling them. You're showing off your personality flaws, even if you "win" the pissing contest.
I also agree that you sensed the environment and avoided it and were probably right. When looking for a job this can be very dispiriting but then you occasionally do interviews where people are more friendly and secure and it reminds you that it is possible to find a reasonable place.
This is not as easy as it sounds. Who are those "new engineers", juniors? 10 years of experience? 30 years? What's your requirement?
"Readability" is such a wildcard, with a whole range of acceptable levels from zero to infinity. Readability is a non-concept really. Maxwell's famous equations are readable to some and absolutely impenetrable to the rest of us.
So when someone says "code should be readable", to whom exactly?
Some code is not readable by _anyone_. That's not readable code.
Some code is readable by its author only (be it AI or a human). That's also not readable one.
Saying readability is not a concept is really strange.
The question comes down to being reasonably readable and we are back to square one: "reasonable" is very relative. In my early days I could read 8086 binary code (in hex) and understand what it does, it was literally at the very edge of readability but it wasn't unreadable.
print("Hello World")
vs ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
¹: more readable means easier/faster to read for most human beings that know the languageI can read Assembly. I can, in some cases, figure out what that Assembly is doing. I can not, however, work productively with it. I can read Assembly but would not consider it readable.
If somebody has spent lots of time in specific patterns he/she'll find them natural to read and mentally process.
To others, they'll be unreadable.
Developers coming from functional programming and developers coming from C programming, for instance, have very different definitions of "readable", and neither is obviously wrong.
Similarly, developers used to channel-based, async-based or mutex-based concurrent programming will all have very different criteria for "readable" code, again none of them obviously wrong.
Yet it's pretty easy to find people who consider that `map()` or `filter()` are simply not readable – or, on the other side of the aisle, that having a loop variable is detrimental to readability.
And of course, these criteria change with time, industry and programming language used.
That is to say, if you target "readable to the majority of engineers with 3-4 years of experience, without them getting confused" then you've hit the mark.
I'm sorry this is a very naive take, I presume (I could be wrong) coming from someone with just a few years of experience.
If the new engineer is well versed in mapping and filtering he/she'll have an easier time onboarding a codebase that's rather void of manual loops.
Yes, it's all subjective, and depends on the reader's expertise and existing familiarity with the codebase. But arguing that code readability isn't at thing, because it's subjective, is an absurd take. Would you claim that Joyce's Ulysses is equally readable as Seuss's The Cat in the Hat?
Yes.
It's not a non-statement. Rich Hickey explains it well, readability is not about the subjective factors, it's mostly about the objective ones (how many things are intertwined? the code that you can read & consider in isolation is readable. The code that behaves differently depending on global state, makes implicit assumptions about other parts of the system, etc - is unreadable/less readable - with readability decreasing with number of dependencies).
"to most developers who are most likely to interact with this code over its useful lifetime."
This means accounting for the audience. Something unfamiliar to the average random coder might be very familiar to anyone likely to touch a particular piece of code in a particular organization.
Oh, I completely disagree here. Take obfuscation for example, which you can carry on into things like minimized files in javascript. If you ever try to debug that crap without an original file (which happens far more than one would expect) you learn quickly about readability.
Big companies may actually have an answer to that: "since we require at least 2 years of experience in the area from new hires, all code should be readable at that level".
However startups may prioritize something else over readability at that level, for example: move fast, produce the most minimalist code that would be easy to maintain for people like you.
My point being, "code should be readable" should always come at least with a footnote that defines the context.
Usually that means something less than "perfect" from the perspective of the writer. Applying to much DRY, SOLID, DI and other "best practices" will make it very hard to understand. Pretend you have about 20 less IQ points than you actually have when writing the code - you will thank yourself when you come back to it.
> Reading -- and understanding -- code requires twice the brainpower of writing code. So if you used every bit of your intelligence to write 'clever' code, you won't be able to maintain it because it requires twice your intelligence to read it again.
Einstein's words are oh so suitable as well:
> Everything should be made as simple as possible, but not simpler.
Following the same patterns across large parts of the codebase is what makes the codebase as a whole readable. Those patterns may even be complex, as long as they are used over and over without too much deviation and flag-explosion the codebase will be readable.
In short local isolated code can be as bad to read as it wants, as long as it doesn't infect the codebase as a whole (like through the use of shared mutable state or through a bad API).
When we talk about a language's readability we're typically talking about 'accidental complexity', to use Brooks' term, [0] and not the 'essential complexity' of the problem being solved. For a hairy enough algorithm, even pseudocode can be difficult to understand.
Readability applies in mathematics too, as a bad notation may make formulae unnecessarily difficult to comprehend.
> So when someone says "code should be readable", to whom exactly?
I'll have a go: to another competent engineer familiar with the general problem domain but not familiar with your specific work. This includes yourself in 2 years time.
This seems rather like the question of readability for scientific writing. Research papers should be readable to other researchers in the field, but they aren't generally expected to be readable to a general audience.
I was hoping the article would go in that direction - what subjective combination is a software engineer deciding on that you can argue is truly a matter of taste and not just a technical decision about a trade-off.
I would say this this article itself may be an example of bad taste. It meanders across a couple of disparate topics in software engineering, independently each section is competently written but as a whole they really don't sell the "look" the article was aiming for.
(I don't mean to discourage future writing by the author - I think it's a potentially excellent choice of topic. I'm just giving my two cents here on the execution.)
And fashion is a lot about tradeoffs too. Not just in the production, but also in the day to day wearing and mix-and-matching part.
Im sure you've seen examples of this yourself - you can absolutely sport a Ray Ban in good taste and you've almost surely seen someone believe themselves to be fashionable because they are wearing a Ray Ban.
Also, I'm not suggesting fashion as a whole is about combing outfits - merely that being able to combine varying pieces of clothing into a cohesive whole is an expression of good taste.
The great majority of technical things are not cool to normal humans, they’re geeky. Programming languages are not cool. In programming one has to therefore start from “not cool” and move down the scale:
Uncool: Rust, C++, most languages
Painfully uncool: anything functional and weird. Bash, Linux, etc.
First off, the concept of "good taste" is much, much broader than only applying to clothing based fashion. You can have good taste in practically any field that involves any amount of creativity/choice: cooking, painting, writing, music, programming, video game design, etc, the list is practically infinite.
As such, the idea that most "engineers and technical persons" don't "understand good taste" is incredibly silly. It's entirely possible, perhaps even probable that the average programmer lacks good taste in terms of fashion, but that says nothing at all about good taste in other areas.
Secondly, having good taste and being able to apply it is also wildly different. I can recognize what looks good in fashion or paintings without being myself able to achieve that.
Thirdly, there's really no such thing as a "normal human". The longer you live, assuming you're willing to actually examine your experiences, the more you'll learn that the trite expression "everyone is unique" really is accurate.
Just as a semi-random example, it might be tempting to think of watching football (nfl) as "normal". According to a quick google, the average nfl game gets like 17-20million viewers, and even assuming that's accurate/all unique people/etc, it's a very large number in absolute terms but its less than 10% of the population of america.
So if you took a random group of 100 americans, something like 6-7 would have watched a football game last week[1]. Now that's still many times larger than the number of people who wrote code last week, but it's not some kind of overwhelming majority that "everyone" does.
[1] Yes these numbers are extremely imprecise, it's rhetorical
Furthermore, taste in one area is more likely to manifest in other areas. Somebody that has good taste in fashion would likely have good taste in interior decoration or art.
> Secondly, having good taste and being able to apply it is also wildly different. I can recognize what looks good in fashion or paintings without being myself able to achieve that.
Ok, but applying it is the interesting part.
> Thirdly, there's really no such thing as a "normal human". The longer you live, assuming you're willing to actually examine your experiences, the more you'll learn that the trite expression "everyone is unique" really is accurate.
The things that make individuals truly unique are often irrelevant in the greater scheme. One could draw a line across continents and ages to connect quite similar people.
I put down to taste many things that I believe are technical decisions about trade-offs but that I cannot absolutely verify or which I believe the trade off is so very small that it doesn't actually matter.
What's interesting is that software engineering starts in social science where most choices are made subconsciously or at least not discussed with other people.
Christopher Alexander studied this deeply for building architecture, and his ideas had influenced many thinkers of software architecture; Alexander asserts that there is a thing to objective beauty. Alexander’s keynote to the OOPSLA conference is worth reading, as is Roy Fielding’s dissertation. The “values” mentioned in this article is organized as “architectural properties” in Fielding’s dissertation.
The person with the good taste.
If you want to go more concrete than that, you need additional parameters, otherwise you risk ending up with a broken compass that only points north in organizations similar in size and other properties to the ones you've worked in before.
If we can seperate good taste from best practice, it's that good taste is commonly associated with restraint and economy. Hence, no one can say that Liberace or Elvis had good taste. On the other hand best practice is primarily governed by efficiency and is driven by commonly practiced principles.
I was hoping we moved beyond this "clean code"-ish nonsense of functions having to be short.
Quality of a software design, maintainability, etc, have virtually no relation to length of functions and the most respectable software out there contain functions hundreds if not thousands of lines of code long without being impacted by its own weight.
Readability, while subjective, plays a large role in software maintenance. Developers will be more reluctant to change code they don't understand, and more likely to introduce bugs. "Long" functions require following large blocks of code that work within the same context, and relying on comments to explain functionality, which could be wrong or outdated, rather than descriptive function names.
Also, "long" functions are typically difficult to test. They either require complex setup and mocking, or are only tested under very specific conditions in end-to-end tests, if at all. Chances are that only users are actually testing them, which is not a good place to be.
We can argue whether "AI" tools help with this or not, but while humans are still reading and writing code, following standard conventions of keeping functions relatively "short", "clean" (whatever those terms mean for you and your team), and with a single purpose, makes maintenance and testing easier, and, in turn, produces more robust and higher quality software.
Having to jump out of the code you're reading comes with its own downsides and tends to compromise maintainability where you are increasing the shallowness of your code (same functionality, but higher api surface).
You break up things when there are benefits to breaking them and Unix provides a very sensible reference to this topic where plenty of syscalls run generously in the thousands or tens of thousands of lines.
Stanford professor Jon Ousterhout (among other things the author of the Tcl language, the Tk framework, the Raft consensus algorithm and many other things) has an entire paragraph in his book "A philosophy of software design"[1], on why the argument "functions should be short" is short sighted and should never be taken at face value.
[1] https://www.amazon.com/Philosophy-Software-Design-John-Ouste...
I never claimed otherwise.
> Having to jump out of the code you're reading comes with its own downsides and tends to compromise maintainability where you are increasing the shallowness of your code (higher api surface).
I don't buy this argument. The code you're reading should do one thing according to what it says on the tin (the function name). When the code does something else, you navigate to that other place (easily done in most IDEs), and change contexts. This context change is important, since humans struggle with keeping track of a lot of it at once. When you have to follow a single long function, the context is polluted with previous functionality, comments, variables, and so on, not unlike the scope of the program at that point. If you're changing the code, it becomes easier to shadow a previous variable, or to change something that subsequent code depends on. Decomposing the large function into smaller ones avoids all of this.
As well as aiding in testability, which you conveniently ignored from my previous comment.
The criteria for determining what is "short" and "long" is subjective, of course, and should be determined by whatever the team collectively agrees on. But there should be some accepted definition of these.
> Stanford professor Jon Ousterhout
Eh, I'm not swayed by arguments from authority. Jon's opinion is as valid as mine or yours.
John has spent his entire life to learning and teaching general principles of good software design and their limits.
His opinion definitely has a different weight than ours.
https://web.stanford.edu/~ouster/cgi-bin/cs190-winter21/inde...
Arguments from authority lead no where here. Because these "authorities" also disagree.
Fowler has written *nothing* anybody ever cared for. Nothing.
Ousterhout has taught Software Design at Stanford where he's had students implement the most diverse software and finding the limits of his own theories and having them questioned rigorously year after year.
Ultimately, you're going to revisit this code to make the change after some time passes. Is it easy to follow the code and make the change without making mistakes? Is it easy for someone else on the team to do the same?
Sometimes optimizing for "easy to understand and change" means breaking something apart. Sometimes it means combining things. I've read that John Carmack would frequently inline functions because it was too hard to follow.
So, rather than whether something is big or too small, I would ask whether it would be easy to understand/change when coming back to it after a few months.
Put another way: why not optimize for the actual thing you care about rather than an intermediate metric like LOC?
You're misunderstanding. Code is not broken up because it's "long". It's broken up because it is difficult to comprehend and maintain, and its length is one criterion that might signal that to be the case. Another sign is cyclomatic complexity, which is another arbitrary number left for teams to decide how to use best.
The main topic, and why it is so widely argued, is that readability and maintainability are entirely subjective concepts that are impossible to quantify. This is why we need some specific guidelines that could point us in certain directions.
This doesn't mean that these guidelines should be strictly enforced. I've often decided to silence linters that warn me about long functions or high cyclomatic complexity, if to me the function is readable enough, and breaking it up would be more problematic. This is open to interpretation and debate during code reviews, but it doesn't mean that these are useless signals that developers should ignore altogether.
You seem to be the one misunderstand it.
It's just not. Function length is not a useful metric, at all. The probability of some problems increase with length, but even then it's not the length that will tell you if your code has a problem or not.
If you have length guidelines, your guidelines are bad.
And, yeah, cyclomatic complexity is almost as useless as function length. If you have warnings bothering people about those, you are reducing your code quality.
The good thing is LLMs try to optimize for information theoretic measures of language so they naturally generate better scoped more inline code. LLMs might help us win this battle :-)
That said...
> I will always distrust engineers who justify decisions by saying “it’s best practice”. No engineering decision is “best practice” in all contexts! You have to make the right decision for the specific problem you’re facing.
There are indeed general best practices that are applicable to most, if not all, situations. E.g. version control and testing (arguably even the testing approach) are practices that every competent developer would agree are pretty much requirements.
Context does matter, but within specific contexts there are practices that can be generally considered "best", because applying them delivers more value than not, and their drawbacks are manageable.
For example, after a certain team size, implementing a CI pipeline is a best practice. Yes, it takes time and effort to implement and maintain, but the benefits far outweigh the drawbacks. After a certain scale, implementing a robust and efficient infrastructure management and deployment solution is pretty much a requirement. Relying on ClickOps to provision infrastructure, and deploying with shell scripts over SSH, while technically "simple", can only get you so far. And so on.
These practices become more evident with experience, and deciding when to vouch for them, even strongly, becomes more evident with wisdom. So I wouldn't necessarilly always distrust engineers who want to promote best practices. I would listen to them, discuss it with them, and make a decision along with the rest of the team. If they do push back after a decision has been made, that would be a bigger issue. Not necessarilly with that specific engineer―it could also be a sign of communication problems, egos, politics, etc. Unfortunately, there are many dysfunctional teams and companies with toxic environments, but I suppose that's just human nature.
IOW it's a short circuit to not justifying something. If something is best practise you can explain why it suits the current situation and if you can't explain why then it doesn't matter.
So, in some cases, it's more beneficial for the company to adopt a "best" practice, than to have engineers engage in arduous discussions, which can cause resentment and further problems within the team.
Needless to say, it's a delicate balance, which is why I wouldn't want to be a manager. :)
The most simple and easy one is to auto-format code. That kills so many ridiculous arguments. I wish all problems could solved that simply.
Compare:
* I apply the dependency inversion principle to keep my code testable
* I minimise the scope and mutability of all my variables
With:
* These methods do what they say they do
* This code is good because it's declarative
* These functions have the right number of lines
Even the best actionable principles can be incorrect given a certain set of circumstances. If in those cases you choose to uphold your priciples, rather than choosing what is "right" for the project you would fall into the camp of "bad taste".
That is at least how I interpreted the article.
If they are principles, the discussion around whether to apply them can at least be fruitful. "Taste" is bound to devolve into "I like this" vs "I like that".
I don't buy into the "everything has its upsides and its downsides" advice given in the article for the same reason. It's a useless truism. It's a taste:-
I have 1 new feature ticket in my backlog, 3 support tickets, 2 failing tests, and 2 performance regressions. "Premature optimisation is the root of all evil" informs me about the feature work, as does "Make it work, make it right, make it fast". "Reproduce locally" will be my north star for the support, the test failures, and the performance work. Add "Find and measure the bottleneck(s)" for the performance work, as well as "make sure the new code is actually faster than the old code" before checking it in.
I don't need to invoke the maturity of any particular coder for any of this.
Another problem with letting "taste" into the discussion is that you can cheapen principles: you think this code needs tests? "Well, there are upsides and downsides with that", "You're just being inflexible, which is immature". Neither tasteful reply will help you answer whether the code needs tests, and it stirs up shit in the team because it makes it about people, not work, so egos will get inflamed.
>> Personally, I feel like code that uses map and filter looks nicer than using a for loop
I'm not going to argue the person, I'm going to argue the principle. I use map and filter in my business logic because I can do so without mutability. My business logic should reflect the requirements and customer expectations - deterministically. The principle of making the source code pretty is a distant second to the principle of making the code deterministic. If the requirements change from "apply the correct tax rules" to "apply the correct tax rules, if the system is in the right state", then I might well bring in a bunch of mutations to make that happen.
>> is more straightforward to extend to other iteration strategies (like taking two items at a time).
Nope, items.pairs.map((x,y) => ..). Didn't need to discuss maturity.
It's less about what you like or dislike and more about aligning a collection of practices you've seen work well to the situation and constraints, which is why variety of experience helps
I'm not sure why stirring up shit or inflaming egos would necessarily happen with such conversations. Skilled engineers often start a solution proposal by explicitly outlining what they are optimising for, known limitations etc which all help create a baseline to describe "taste"
The language matters a lot here too:
> For instance, map and filter typically involve pure functions, which are easier to reason about, and they avoid an entire class of off-by-one iterator bugs.
That's not how "for loops" work in many programming languages! Python being the most obvious, and by some measures the largest programming language of all.
If you can articulate why certain code is better with convincing justifications, I would say that's "good taste". Coding styles are often examples of these.
But if you can't articulate or don't have a convincing reasoning for preferring certain coding patterns, isn't it just "opinionated"?
- software should do one thing and do it well - code should be understandable, easy to build, easy to port - dependencies should not be a problem - the chosen language must not be a burden - resources are scarce: if you say "unused memory is wasted memory" you are part of the problem
(I know, I'm a horrible person)
> (I know, I'm a horrible person)
You made a throwaway just for that? I'd hope most people are capable of differentiating the engineering ideology from the (ironic?) nazi reference and tiki torch march ideology bit.
So... is that not a nazi reference? Why else would you preempt being "horrible".
Actually my country had a fascist dictatorship until the end of the 70s. I'm old enough to have been hit by nazis in the early 90s. I have seen fascists in a parade and policemen doing nothing.
IMHO, software philosophy does not have anything to do with some developer philosophy. IMHO again, that is like comparing journaling filesystems and B+ trees as apology of gender-based violence because ReiserFS was one of the first mainstream filesystems that implemented it.
I think those are platitudes: they sound good but aren't specific enough that anyone disagrees with them, so they're not saying anything. Imagine:
> - software should do one thing and do it well
No one ever says "let's do a bit of everything, badly".
And there's tons of wiggle room in "one thing". Imagine a few, very different huge codebases. Let's say Linux, Photoshop, Rust. I think their proponents would say they're each focused on one thing (kernely-stuff, photo editing, foundational software). Their detractors would disagree: use a microkernel instead, separate out the filters or something, people are using it for scripting. Who's right?
> - code should be understandable, easy to build, easy to port > - dependencies should not be a problem > - the chosen language must not be a burden
Again, no one ever says differently, even if the result they end up with is none of these things.
> - resources are scarce: if you say "unused memory is wasted memory" you are part of the problem
I think this is the most interesting one, because it's contrasted with something I have actually heard people say (maybe even said myself on occasion). But still, I generally haven't heard people say it for no reason. There's some other attribute they're getting for that RAM, whether it's a compute-vs-RAM trade-off or the ability to use a (believed-to-be) simpler programming language or some such. This one might be more interesting if prioritized relative to "code should be understandable".
About the code should be understandable and the efficiency, I'm thinking of the old times java. I've seen people in systems department having to profile java stuff because they were OOMing their tomcats. Eventually we had to ask for the code, debug it and send it patched with just some '=null' because the GC wasn't doing its job in the best way. In the early 2000 the philosophy already was "Good hardware is cheaper than expert developers" :(
People have asked the systemd folks about this, and their answer is that these are separate things because systemd-resolver is a distinct, optional binary that does one thing well. Is that enough to say they're totally separate things? Opinions obviously differ!
It is really hard to come up with rules of taste that are generally applicable yet specific enough to that people interpret them in the same way to make the same decisions. I don't really have any such rules of my own to offer. I've written coding standards for multiple projects but I definitely can't copy'n'paste one to another and have it still make sense.
> About the code should be understandable and the efficiency, I'm thinking of the old times java. I've seen people in systems department having to profile java stuff because they were OOMing their tomcats. Eventually we had to ask for the code, debug it and send it patched with just some '=null' because the GC wasn't doing its job in the best way. In the early 2000 the philosophy already was "Good hardware is cheaper than expert developers" :(
Yeah, there are plenty of examples of poorly written applications around. [1] But I don't think someone said "let's write this code really badly and just buy bigger machines". Could be wrong, just never heard anyone say that. On the other hand, I do almost universally see people prioritize the next "critical" feature over clearing the bug tracker. So if you said for example "fixing customer-reported memory leaks always takes priority over new features" that'd be making a real, controversial taste choice.
[1] ...and my guess is this was a poorly written application—keeping around reachable references too long, thus forbidding GC from cleaning them up, rather than a flawed Java GC algorithm that didn't detect the parent was also unreachable in a timely way.
I have come across this although I think quite experienced engineers can suffer from this kind of immaturity.
Aeons ago I used to help friends with their Computer Science assignments. I remember the temptation to rewrite their code because I didn't like the way it worked. I would start to do so and then, eventually, think that it was going to take ages and be unrecognisable to them. How could I help them by rejecting everything they thought?
I'd think about it again and realise that they weren't idiots - their approach probably could work with a couple of adjustments. I helped them make those adjustments and they were happy because they could understand the result.
After that I would find that my own way of thinking about the problem had changed and my own code would get much better from having seen the problem from a different angle. I should really have been the one thanking them.
I am still like this - still prejudiced - but in the back of my mind I know it and when I'm being sensible I remember to try to give the other viewpoint a proper chance and be happy when it really turns out to have more merit.
Principles are a bit subjective and if you lean on them all the time without thought it's a sort of laziness - you're not really examining the situation and what it merits.
Your way of thinking is how I like to handle my direct hires. And people on a project that may not be my direct hires.
Recognize that there are different ways to get things done. Not everything needs to please my specifics, process, etc. if the end result or goal is met
In short though, it's hard to have good coding taste when you are new to the field. (But, as you point out, it's not true that experience guarantees you will acquire taste — you have to both be looking out for it, recognize it, and be able to to mirror it.)
In the end, all code comes with tradeoffs, so I'm guessing we're really talking about good/bad tradeoffs, rather than the actual code itself.
I'm definitely more of a gardener myself, and I've always considered the architect software engineers of the world to be silly and unfun and prone to locking themselves into an over-engineered architecture before they even understood the problem properly, but they really are just different approaches to software development and each of them has their strengths.
In indie game development, the requirements are often unclear, and I am still exploring what the game idea might be, still trying to "find the fun" of a game, and so the gardening approach works quite well.
In other fields, where you have clear requirements figured out from day one and the consequences for not meeting those requirements is much higher, the architect's approach has its wisdom.
Years ago, I worked at a company where my time was split between working on a bespoke platform for an older client and a generic platform that would be used for all clients going forward. The bespoke platform was essentially being used to fund the development of the generic platform.
The contrast between the two was stark: the bespoke platform was legacy in all its weird glory, and the generic platform was considerably higher quality. Seeing that contrast up close, and flipping between working on each one, was immensely useful to me. It made it clearer why I favour certain approaches over others, and it made concepts easier to explain to others.
Since then, I'm of the opinion you should aim to work with both the good and the bad to clarify the "why" of things.
It all depends on what field you are in, the business requirements, the team and so many other factors.
What is the most important aspect: Easy to extend, easy to read or most optimal performance?
Is your team more comfortable with the functional style of programming or OO?
Do you follow existing conventions your team has established or current best practices?
Do you implement everything yourself or do you use external libraries?
Do you write code that makes you look smart using tech that looks good on your resume or do you use the simplest method to solve the program?
The is no one right way to program.
> The is no one right way to program.
Those two statements taken together makes me suspect you think there is.
Experience does not always result in maturity. It is actually pretty common for engineers to become more set in their ways as they gain experience, especially if they've seen some success, because that success has convinced them that the way they do things is the right way.
I was 12 years into my career before the ideas in this article started filtering into my mind.
As an engineer and a strong generalist, I find that simply knowing that another angle or solution exists is enough to change my view of a problem and come up with solutions that are better than my first instinct.
Any code can be made perfect, but what could you help the person across the table with to make it good enough and also improve their understanding given their priors. Making the code perfect will simply be a bridge too far for them (and generally just means coding it up to your level of incompetence anyway.)
I think there’s a very important developmental stage for programmers where they buy in to a set of principles past the point of all reason. It can be maddening to work with them at this stage. They’re all in on type systems or OOP or hexagonal architecture or what have you, and everything looks like a nail for that hammer. But eventually, some of them see the shortcomings of the one true way and come out better programmers for it.
Have your 10 mins back.
I suppose the most important matter of “taste” is how deterministic the flow control is by just reading the code. It doesn’t have to be pretty. When this is absent I know the author values something other than maintenance or extension. When it’s hidden behind cultural conventions, like a framework, I know the author values those learned conventions more than a utility goal or objective criteria.
Since these observations reflect behavior they apply equally to many other non-code contexts in the real world.
Us humans are not very good at recognizing it, but we all model and understand the world slightly differently.
So that even if you fundamentally agree with someone's values, you might prefer other code to that person because it fits better in the way a problem is modelled in your brain than in someone else's.
In addition to that, all of the real people with great taste I meet, are not out there talking about it. They confidently spread it.
Also now having done the same thing 95% ai coding.
Good taste is good architecting. Knowing what to use; how to use it and when. How to maximize KISS. Simplicity is key.
When is it right to keep it very simple and load it to a dict? When to upgrade to a panda/numpy dataframe? When to go sqlite3? When to go full on DB(postgresql?). Do you need to go that extra step in order to stay with your current design; will the code become complicated and ugly to do that? What are the upsides and downsides?
good taste is the comfort to try a new library that looks like it'll do a better job than what you know how to use now.
Does your script run in memory for long? Is that ok?
Recently I learnt about Python Slots and use them now. You look at: https://www.w3schools.com/python/python_datatypes.asp
And for some reason they dont even mention slots. but omg so much better!
I believe that is what builds taste: varied experience.
Let's say I have a plan for a new feature and a way of building it, if I show it to someone with good taste, they will be better at telling me if it's going to work out, and how valuable it will be to users, than someone with bad taste.
Viewed this way, it's almost quantifiable, and at the very least: good developers can usually agree on who has good taste. You can tell based on their actions not words. They will go out of their way to get feedback from people with good taste. IME the people with the best taste for a product are power users and evangelists.
It's hard to defend that taste to someone who doesn't have it. They say, "Yeah, well, that's just, like, your opinion, man". And in fact they're right. But it's an opinion honed by a fair amount of real-world experience, of seeing how fine-sounding ideas fail to work out in the long run.
Go is nice when working with other languages at the same time as there are few language specific things to remember. However, they really should add common things that exist in almost every other language (I'd argue map / filter would be good to have for instance). It isn't really easier if it is "simple" but unique to the language.
Usually I would expect an engineer to be able to gauge roughly "what values" are most important in a given practical context. Let's call those the "hard constraints" of the problem being solved. Let's set the hard constraints aside and consider "taste" only in the context of the remaining degrees of liberty.
In other words, we often impose on ourselves additional constraints that are not strictly necessary. An artist in a given context may technically be required to use oil paint and a 4x6 canvas. But the work product will be judged on what additional constraints (or lack thereof) the artist plays by.
As a rule of thumb, I'll stick my neck out and say that good software engineering taste, while notionally similar to artistic taste, is unique in that it is 1) aesthetically minimalist, and; 2) maximalist in self-restraint.
I think the notion of taste still eludes this description, but whenever I've encountered something I found truly in bad taste, it usually went counter to these principles for no obvious reason: in other words it was sacrificing something without any benefit. Oftentimes bad taste is taking a loss and calling it a tradeoff: in my experience this often occurs when people confuse simplicity for familiarity.
> Which design decisions you feel really good about, and which ones are just fine? It's not just a feeling, it comes from reasoning.
Good taste == good skill isn't it?
When someone chooses a functional lang over an object oriented one - is this because of better taste? They’ve learned, through experience, what kinds of problems map more cleanly to immutability and composition versus encapsulation and state. Their "taste" for one paradigm over another is just a reflection of the skills they’ve built up in recognizing tradeoffs, debugging pain points, and anticipating long-term maintainability. What looks like taste is really just expertise shaping intuition.
I get why the author feels a team member who doesn't align with the team's goals has bad taste. But it's really just this - the opinions they have formed from their experience don't match yours.
I don't think this makes sense. Firstly because engineering values aren't the same thing as engineering requirements; I don't want someone's "values" to end up making a product unsafe or unreliable (these aren't "taste", they're non-negotiable requirements). But secondly because, in engineering at least, your personal opinions shouldn't matter.
Taste is all about opinion and self-expression. Your taste is different than my taste? That's fine. Your engineering is different than my engineering? That's not fine. The same building, with the same architectural plans, same location, same everything, shouldn't be built 6 different ways depending on the "taste" of the engineer. Trusses and struts aren't to your "taste"? Tough luck, buddy. Engineering is about precision, science, math. It's not a plate of spaghetti.
A PR could be said to be a "review of taste", that nit-picks in a PR are just judgements of personal taste. But that's not what PRs are supposed to be. They're actually a replacement for QA. In normal products, a Quality Assurance process is a secondary one that verifies products are built correctly. But in software engineering today, PRs take the place of QA (and design reviews). This means a PR isn't about taste, but about quality.
Higher quality implies good taste, but they aren't the same thing. Style, fit, context, etc are as much a component of "taste" as quality is. Therefore I think "taste" in software engineering should be limited to the optional or irrelevant parts. All the things that you care about, but don't actually affect the engineering quality of the end result. And if that's true, then it means we probably need to have hard definitions of what affects engineering quality.
For example: most people would argue that variable names are very important. That how you name a variable affects its "maintainability". But in reality, the names could be random, and the program would operate the same way. That doesn't mean that variable names are irrelevant. But it does mean there's a categorical difference in what it is, and why it's important. That needs to be codified and taught to new CS students.
Which is why software development, even if the field has decided to grace itself with the title of "Engineering", just isn't that. Say what you will, Software development is NOT about science and math the same way that civil engineering is. Otherwise, we'd have loads more data handbooks and ISO norms that we have a legal requirement to fulfill.
Here's a simple programming language - combinatory logic with S and K combinators, and you can create a new combinator by naming any expression of combinators, and use it.
What constitutes a "tasteful" or "readable" program in this language?
Great dimension to look at!
The “values” mentioned in this article maps to the architectural properties (versatility, scalability, observability, readability, maintainability, etc.), but I have found that understanding architectural constraints is even more important because that tells you where anti-patterns are, and how an architecture can evolve (or not evolve).
While many people program only for the salary, this lacks the obsession that good taste requires.
We can often recognize a person's taste by the content they share over their feeds.
The author's definition of taste as a prioritization of various engineering values is one we can understand based on experience.
"How do you develop good taste? It’s hard to say, but I’d recommend working on a variety of things, paying close attention to which projects (or which parts of the project) are easy and which parts are hard. You should focus on flexibility: try not to acquire strong universal opinions about the right way to write software. "
Absolutely! But also be careful not to over-engineer a solution for a situation that never appeared
The reverse is not true; you cannot really exercise good taste whether you have good engineering or shitty engineering.
Bad taste can hamper engineering but not make it impossible; bad engineering renders taste moot.
Engineering serves taste, mainly.
Consider that something well-engineered can be entirely unnecessary and unwelcome.
Technologies: Erlang, Smalltalk(s), Lisp, Clojure, Query By Example, Programming By Demonstration, Logo
Hardware: Canon Cat, Xerox Alto/Star, Sketchpad, PDP-11, PERQ Workstation, SGI workstations, OLPC
Books: Computers As Theatre Second Edition, Computer Lib, "As We May Think" by Bush, Engelbart's 62' paper, Mindstorms by Seymour Papert.
Links:
https://worrydream.com/refs/Papert_1980_-_Mindstorms,_1st_ed...
https://archive.org/details/humaneinterfacen00rask
https://www.dougengelbart.org/mousesite/EngelbartPapers/Cont...
https://courses.cs.umbc.edu/471/papers/turing.pdf (for the AGI fuckheads)
1) There are objectively bad decisions that you can make regardless of "taste" or principles. If you search a list in O(n) for an items key, it is objectively worse than using a dictionary with O(1) search in most cases. It is not about taste or readability, there is a right way and a wrong way (or multiple right ways and multiple wrong ones).
2) Everything else is a matter of trade-offs. Map reduce or a loop? It depends entirely on performance requirements, what reads better in a specific scenario, maybe browser compatability or whatever but as long as the trade-offs are considered, I won't get bitchy to another Dev who decides that one is better than the other although I might disagree.
If something is wrong or the trade-offs haven't been considered though, that is a question of maintenance: do we care enough, is the performance bad enough, is the code visited enough to change it? In a lot of cases, the app will be deleted before it becomes a problem but it is still a question of trade-offs.
As someone said below, as long as someone has considered the "why" then its all fair game. I'm not sure that any of this is "taste" though.
[0] Of Hamming distance fame
[1] Here's a link to the Wikipedia article about the book: https://en.wikipedia.org/wiki/The_Art_of_Doing_Science_and_E...
This is definitely not a taste thing. They are both useful in different scenarios.
For example if using async/await, you can use Promise.all with a map to execute some code on multiple elements in parallel. But if you want to execute the code on them sequentially, you NEED a for loop.
There are many situations where the second approach may be better for example you are calling an API with rate limiting and want to spread out your calls, one at a time, intentionally.
Sometimes I use both; if the amount of data is large; I break it up into batches. I use a for loop on the outside to execute each batch in series but then inside the loop I use Promise.all with a map to execute the items in the batch in parallel... And I make the batch size configurable depending on the capacity of the server.
How one defines pleasure? Depends. Simplicity is certainly a dimension.
kingkongjaffa•4mo ago
The point about varying levels of skill vs taste is accurate in my experience.
I think I fall into the more taste than skill camp, although neither are particularly large in an absolute sense.
I switched to product management quite early in my career and pivoted to learning design and product “taste” more than software engineering.