tef

bad poster & mediocre photographer

  • they/them

ask programmer for very general advice, they might say "learn to avoid complexity" or "embrace simplicity".

the only problem? it's really bad advice. it stinks, actually. absolutely terrible advice.

it's not because "experience is the best teacher" or "beginners don't have enough knowledge to apply this advice," but that in eschewing complexity, you often create something worse in your quest to eliminate it.

not to be all "conservation of energy" here but when someone tells you they've eliminated complexity, it usually means the complexity is now someone else's problem.


ask a programmer to write a "simple tool" and sure enough, you might get back something with only a few lines of code, and maybe just a handful of features—but you'll also get back a tool that takes eighteen command line switches and maybe a yaml interpreter. if you tell them you might change your mind later, the code will have a plugin engine, too.

similarly, you can write a EULA in basic english but that does not make it any more readable, or any more enjoyable to read.

building a simple tool doesn't mean building an easy to use tool, and building something out of simple parts doesn't make for simple results, either. in practice, there aren't really any examples of simple code: there's only code that has no responsibilities, or code that doesn't manage its responsibilities. code in the real world does not have either luxury.

a lot of the time, code ends up looking that way because it has to—we call it an abstraction when we like it, and indirection when we don't. meanwhile, simple, easy, clean, good, these are words that describe how the person feels about the code, often how much pride they take, and not much else.

ultimately, complexity it isn't some inherent property of code in isolation, but how that code interconnects with the other parts of your system, human or computer—something can be complex to one person and simple to another.

when someone says "that code looks complicated" they usually mean "i wouldn't have written it that way" (and when they say "this code is simple" they often mean "please buy my cloud database product")

that said, so, so much complexity has been created in the name of building "simple reusable code". for some reason, many programmers believe the simplest solution involves writing a framework and yaml interpreters. i don't know who to blame.

in the end, the only complexity you can avoid is the complexity you add yourself. it's pretty easy to avoid: you err on the side of under abstracting until you make a mess, then err on the side of abstracting again, when the mess gets a little more manageable, and move on with your life. it doesn't matter how complex something is if you never have touch it.

be warned: changes get made in the easiest place to make them, and if you don't tidy up after yourself, you'll quickly run out of easy things to change.

as for asking "is this simple", or "is this complex?", you'll be better off asking "will this make anyone happy?" and occasionally adding "really?" just to be sure.

talk about what needs you have and if the code will meet them. talk about how much time you can afford to spend on the problem, and how much you're willing to spend. talk about responsibilities and if any of them can be shifted elsewhere. talk about the dev team, too.

so, my advice to beginners?

add some words to that Readme. make the error messages a bit nicer. maybe reject bad inputs earlier. worry about documentation. meaningful tests that help find bugs. try limiting the impact of the cheap hacks. oh and don't put all the util functions in one file, they always get tangled.

don't worry about complexity. worry about your users.

remember: beginners can create an absolute mess, but only an expert can get paid for it.


You must log in to comment.

in reply to @tef's post:

i strongly agree with the conclusions here, but some of the logic on the way there does not parse for me

"code ends up looking that way because it has to" seems to be easily falsifiable, so i think perhaps i am parsing it wrong, or may i am having a reaction because it sounds too much like "there is no point in trying to make things better" even though that cannot be what you mean

i have written a lot of code which does not present its thesis in a straightforward fashion and trips up other people as well as future me, restructuring that code to flow in a way that makes it easier for humans to make sense of can drastically change the way the code looks, operates, and often (but not always) results in a reduction of cyclomatic complexity (or branching operations, or total steps to accomplish the same user goal, etc)

in any case this sounds a lot like the principle of focusing on the impact

getting caught up in the implementation details of anything often results in circular metrics and empty successes

I think you're referring to what the next paragraph in the post calls "the complexity you add yourself". Like over-indexing on "don't repeat yourself" and making a deeply nested tree of helper functions and classes that is a nightmare to keep in your head. I suspect you may actually still agree with the post here.

I think there's a difference between "simple" meaning "each part has clear inputs and outputs" and "simple" meaning "it's terse" and maybe "simple" meaning "the 'business logic' lacks edge cases".

For example, when working in Clojure, most code is simple in the first sense: a function performs some transformation, the inputs and outputs are obvious and there's no gotchas like mutating state. But it isn't simple in the second or third sense. The code calls many other functions, or it has a high branching count so it's unclear when or why certain code paths will be followed.

On the other hand, working with Vue in javascript, it's generally simple in the second sense: I don't have to write a lot of code to get nice results. However, it's not simple in the first. Vue is a magical framework that hides a ton of underlying complexity and uses a truly annoying DSL.

Not to be a Rich Hickey fanboy, but I like his differentiation between "simple" and "easy". I feel like a lot of your statements are really about things being presented as easy: "just write some yaml!"

i'm reminded of the multiple times a dev team has told me they've implemented a really simple service to do X, but then said that they then need an really involved way of deploying it — in one case they gave me a "deployment script" that did more than the service

On the other hand I think avoiding complexity is great general advice for programming (and just creating systems in general).

My impression after reading what you wrote is that you often come across already well-made systems, with necessary trade-offs, and others around you make the mistake of assuming a different approach is better because they don't have the foresight to know of - or don't care about - the trade-offs in their hypothetical solution.

I have yet to have the pleasure of viewing this problem unfold quite as often. A lot of the time I'll run into what is just a poorly thought-out solution. A system with 10 unique points of failure and decisions that still stump me 2 years into working alongside it. Sometimes I'll have that moment where I finally understand why the previous person did something in such a way, and while I wouldn't do it that way, I definitely see the logic behind it. But the majority of the time it's just a needlessly complex solution; likely because the needs of the system were still a moving goalpost until after it had already been frankensteined together.

Getting hyperfixated on the 'optimal' way to program something is definitely a waste of time - there's so much value in accepting something is 'good enough'. But I think celebrating simplicity is great advice, especially for a beginner. I've run into just as many people obsessed about the 'right way' to code something as I've met people that hack together technical debt and walk away.

I'm not sure if I disagree or am just misreading you. Obviously, there's nothing simple about YAML, as anyone who's ever browsed the spec(s) can attest (bleh!), and if someone's idea of "simplicity" is to add a YAML parser, I don't know what to tell you, they don't use the English language the way I do.

But to your other points: Yes, sometimes the complexity becomes someone else's problem, but it's not at all uncommon to find that complexity in business logic should simply be eliminated altogether. Especially when automating a manual process for the first time, it's common to find complexity that's just there because "that's how we've always done it", with nobody being able to articulate a reason for the complexity. Streamlining processes before automating them is an essential part of software engineering, and failing to do so often leads to overly complicated and buggy software.

This applies doubly when the complex logic isn't perfect. A program with complex logic that's 99% accurate can easily be worse than a program with dumb logic that's only 80% accurate, because users can reason about the dumb logic and act accordingly, but will be mystified by those 1% errors.

i would generally agree with "avoid needless complexity" but also i once managed to get myself past a complete lack of motivation when writing the state machine for a simple port of a poetry-generating program by instead writing an abstract framework for defining and transitioning within state machines. because obviously that task is more fun than just implementing the logic i needed to.

and like, no-one was harmed, the code worked fine & as far as i know that poetry generating machine ran in a museum for years without issue. i am pretty sure no-one else has ever looked at that code, and in fact the framework did turn out to be worth reusing on similar future projects.

but i'm sure if i was feeling in a defensive mood, i'd easily be able to justify creating the framework. think of all the duplicated code i avoided writing! think of how easy it would be to add additional states and make sure i called the correct clean up code when exiting them!

but also, i don't think you can talk people out of making self-interested decisions and then justifying them to themselves by giving them generalised advice. i think you can probably solve the "justifying themselves" bit with a sense of psychological safety, team culture, etc etc. but the self interested decisions bit is probably impossible to solve for.

(but obv yr more specific advice is more useful, not least because they are harder to weasel around)

When I talk about "minimizing complexity," I'm mostly worried about the number of ways of accomplishing similar tasks, and I'm attempting to write code that is easy for my team to maintain. I want consistently-formed APIs. I want everyone to program in the same language, and to use the same frameworks. I disagree with "use the best tool for the job;" I want everyone to have a depth of experience with the tools that they're using, to know their faulure modes, and to apply that experience in their next project. If everyone is doing things in the same way, anyone can pretty quickly get up-to-speed on someone else's code.

I think code review is one of the best tools for encouraging this. On my team, every PR is reviewed by at least 2 other devs, regardless of who wrote it. It is super important to me to have junior devs reviewing senior engineers' code. They'll pick up a lot of technique passively, and also, if they don't understand what the code is doing, it's a pretty good sign that the senior dev needs to refactor it, or add documentation. Code review isn't so much to catch bugs (though that's a nice benefit), it's there to make sure that the code that makes it to prod is going to be something that at least 3 people are comfortable maintaining.