I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to it, like, look, here is the ruleset.
No, there is not. Until there is an iron proof attached, it’s just an opinion (or even rant.) Here is my debrief of some piece of Elixir documentation, bringing mere bits of advice on how to better write an average library, into “Anti-patterns” with my explanation of why all of those are not dogmas.
Thank you for writing this article. My main gripe with the dogma is that spawning processes is wrong. People are afraid of spawning processes but there are so much interesting patterns which involve spawning, even unsupervised, processes. Don’t get me started on monitoring. It’s so powerful yet most of the people I’ve worked with before don’t know about it at all, or at least don’t know how to use it effectively.
If anyone thinks that anti-patterns are rules that should not be violated, you should point them to the second paragraph of their introduction:
The goal of these guides is to document potential anti-patterns found in Elixir software and teach developers how to identify them and their pitfalls. If an existing piece of code matches an anti-pattern, it does not mean your code must be rewritten. Sometimes, even if a snippet matches a potential anti-pattern and its limitations, it may be the best approach to the problem at hand. No codebase is free of anti-patterns and one should not aim to remove all of them.
Some comments on your remarks below:
In complex recursive operations, using throw/1 and catch (or raising a dedicated structural exception rescued at the top-level boundary) acts as a clean, non-local exit.
Aye, throw/catch is exactly what should be used, but throw/catch is not an exception and you must always catch what you throw. I will clarify the current docs, thanks!
While putting arithmetic in a GenServer is obviously silly, using processes as fault-isolation boundaries for computation is a battle-tested OTP pattern. […] If you run that code directly inside the caller’s process and it triggers an out-of-memory error, segfaults, or enters an infinite loop, it takes down the caller.
Processes are not going to protect you from segfaults or out-of-memory errors. Plus the current patterns do support using processes for fault-tolerance and isolation anyway.
Take non-critical background operations: sending an asynchronous telemetry event, flushing an audit log, or dispatching a best-effort metric. If you start these tasks under the main caller’s supervision tree, a failure in the telemetry reporter can crash the supervisor or block the shutdown sequence of the primary application.
Starting an unsupervised process is not a solution to this problem. You can set the tasks to temporary, their default, which means a crash doesn’t count towards the supervisor restart. Worse, by not supervising them, you make much harder to introspect them and guarantee proper shutdown (if ever desired). Those are exactly the type of assumptions the anti-patterns are meant to correct!
The wording “anti-patterns” is so strong, that I believe people think “ok, they put it for a reason, and yet I know Chris wrote this book about macros and whatnot, but quod licet Iovi, non licet bovi.”
Yes, but again, I never said it’s a silver bullet. I said under many circumstances you are good to fire-and-forget unlinked task to send some telemetry and just didn’t do any defensive programming. Like, log("#{Repo.get!(a)} / String.to_integer(b)"). Assuming the meaningful error is handled somewhere else and logged properly.
And that was actually fighting the goal to challenge all the items there, what was most likely the wrong idea.
Consider a library executing third-party NIFs, parsing untrusted image files, or running heavy memory-intensive computations. If you run that code directly inside the caller’s process and it triggers an out-of-memory error, segfaults, or enters an infinite loop, it takes down the caller.
Well, isn’t that exactly what using processes to model runtime properties is all about though!? I might have misunderstood your point, but this has always been my understanding of what fault tolerance/ isolation is.
Besides that, totally agree with the post’s premise; thanks for sharing!
The problem is always wording. For example, in your article (which I like and am generally in agreement with) you’ve taken the term “avoid” to mean “must never.” Many people, myself included, don’t tend to think of “avoid” as “never” but as something we generally try not to do with some exceptions.
The other word like this that shows up quite a bit in documentation, outside of the anti-patterns even (and I’ve just used twice above) is “generally.” While this should unambiguously be taken to mean “usually but not always,” so many people treat it as just meaning “always,” at least in my experience.
EDIT: Ha my logic for my “generally” definition flipped.
Man, I have to look up all the Latin quotes you used. I hope you are not trying to one-up the people who quoted the official guidelines. Joke aside, I agree with everything you said in the post; however, I still think a personal journey through 1, learn the rules, 2 break the rules, 3 come back and appreciate the rules, can be skipped.
I think its important to understand that declarations of anti-patterns is an anti pattern in itself, as most of them are opinionated and not based on hard science. There is nothing wrong with opinions as long as they are always questions and never taken as gospel unfortunately history has shown again and again that we humans tend to leverage these kind of declarations politically. It would have been better for our community to stick with hard science regardless of how hard some people find “readability” to be. This is even more true in this day and age with AI.
Readability will be important as long as there are people who want to work on the code, with or without AI.
And with the sad fact happy LLM agents are to forever generate code and never tidying it up, unless you very strongly insist on that with them (which still fails, part of the time, maddeningly), readability as a requirement, as annoyingly non-deterministic a concept it might be, is not going anywhere. Its important is even going up in the age of LLMs, at least if one hopes to be able to understand the codebases they are working on.
Wise men learn by other men’s mistakes; fools by their own. (See? I didn’t use a dead-language proverb here :-P)
On the other hand, I am pretty sure that while failing miserably breaking the rules, the wise man gains a ton of great experience, which is not verbally shareable, specifically taking into account that mileages do always vary.
That said, this journey makes a person wiser. The cost of this training is out of scope though.
I am not so sure. I love rules for this clinking sound they do when you break them. The humanity could not (arguably) survive without the tablets, covered with 10 anti-patterns. If Leonardo stuck with the hard science, his heritage would have narrowed to a couple of murals.
In 2026, the Newton’s laws of motion are not hard science anymore, but I do still estimate when I’m to arrive at the destination without applying the relativistic adjustment.
We should not stick at all. They write the “anti-patterns” paper, I do write my rant, we talk, and everyone benefits from the argument.
Truth springs from argument amongst friends — David Hume
There’ll always be fuzzy at the edges of such guidelines, but usually someone starting out (without the tools of making their own tradeoffs yet) is unlikely to operate just in that fuzzy realm. In the past I’ve had my own experiences – before such guidelines existed – of trying to do something like ecto does it just for @michalmuskala to tell me ectos architecture is not where you want to start. The issues with it becomes really obvious once you start looking at the hoops ecto has to jump through with Repo.put_dynamic_repo - apis, which can be avoided if you do not have lagacy to maintain.
There however comes the time where you land on the fuzzy portion as well, but with the guidelines in mind you might be able to layer e.g. global behaviour over overridable per call/per process options instead of leaving the global switches as the only option.
While I agree with some points and don’t agree with the others in the post, I think that it highlights the bigger problem behind it: Official language documentation is not a place for coding recommendations. Here are my points why it should be changed or removed:
This can be just a regular documentation. Instead of saying “avoid using application env”, I think that Application module doc should be extended to something like “application env is global, so it is not applicable in cases where different configurations may be used simultaneously”. And this point is more important than it seem: documentation must be easily discoverable, and when engineer learns about application env, they must learn about the possible drawbacks of it in the documentation about application env. Engineer won’t randomly stumble across some doc with guidelines.
This is a poor band aid. Imagine you stumble across the nuclear plant and you find the big red button which says “Immediate self-destruction. Do not press it under any circumstances”. Why would a plant have this button in the first place if it’s so bad and wrong?
Coming back to Elixir, you can change the language to make wrong things inexpressible or just make sure that the developer explicitly acknowledges the drawbacks. For example, add the mix new --library which would generate a template for library without config/ directory and no connection to config/ in mix.exs. Or rename the Application.get_env to Application.GlobalEnv.get. Or for supervised processes, add the Task.Supervisor in the generated root supervisor code. Or for exceptions in control flow: deprecate handling exceptions without stacktrace and make sure that developer must always explicitly state that their rescue is handling the stacktrace, thus developer is acknowledging that this is not just a control flow, it is an exception which collects the stacktrace into a list, which affects performance.
These examples are not important and may be wrong, I am just trying to bring attention that the language can be self-contained and it can lead engineers to correct solutions without any recommendations. “Good” things can be easier to express than “bad” things.
This is opinionated. Again, I can list many thing I personally find more important than any of those present in the guidelines. For example, “Try to avoid using Application and instead expose the root supervisor which can be started independently”. Or “avoid singleton processes with global names”. Or “dont install any executables or download any binaries in runtime”, etc. We can’t fit all our experience in this guideline, it will always be incomplete, insufficient.
It would make a great blog post, but the official language documentation brings too much authority. I’ve been participating in discussions during code reviews (defending not only my own code) where this doc and the infamous “code smells” doc were used as the arguments appealing to the highest authority: the language documentation itself. And you may say that these guidelines are only guidelines, not strict rules, but that’s misleading. First, almost every other line in documentation is a strict rule, and I would argue that documentation must be as strict and as precise as possible. Second, it is just the weight of authority, it is really hard to argue with the opinion of the core team member or the language creator (as it should be), but the language documentation is the ultimate boss and I think that it is too much for just recommendations.
However, these code smells and guidelines are good in a sense that they highlight the problems the language has. And having recommendations articles in docs won’t help. These problems require proper solutions which include changing the language or improving the regular documentation.
In the age of LLMs, these anti-patterns can and will be replaced with various homemade or community linters – based on ast-grep, or just your own Elixir AST analyzer (Claude Fable rolled a primitive version of that for me in 2h).
I tend to agree that anything that people spot as “anti-patterns” in official docs loses all nuance immediately and people start following it unconditionally, with zero critical thought. I’ve been on the receiving end of “Why are you questioning Jose Valim, do you know better than him?” and my response of “I likely don’t but he’s not here now and he does not know the intricacies of our projects, requirements and priorities” absolutely did not land even when I presented an alternative. On that basis alone, I dislike the “official anti-patterns” and have been vocal about it in the past. Dogma is rarely good.
On the other hand, if that leads to little bit less crappy code out there, it would be a win. I have my doubts but in the grand calculus of the entire Elixir area, it’s probably still a net gain.
I’m not sure this analogy holds. I’d rather compare anti-patterns to Freon, which is by many measures a great thing but one whose side-effects weren’t discovered until much later. Quite often we have to deprecate things after learning they’re bad after lots of trial and error.
At which point I’m sure you politely pointed out that the document itself says to not take it as dogma, and that a particular anti-pattern may indeed be the best approach to the problem at hand.
If someone does not have the confidence to make a judgment call over whether a particular “anti-pattern” is justified given the circumstances, then they’ve pretty much declared themselves incompetent. If we simplify things a bit (perhaps too much) our whole industry is about adapting our particular requirements into something machines can work with, and we’d be entirely redundant if that could be done by following the documentation without reflection. Someone would’ve turned that into a program that does it for us by now, and to the extent that Claude Code et al can do it, we’re still needed for grounding the result in reality.
Hence, I’d say the fault lies not with the documentation but with the folks who take recommendations as gospel and/or wield them like a cudgel.
Thats exactly the problem, obviously you could just say that whatever place that person work at is toxic and they should find a new place to work ASAP. But that would miss the whole point of this, that people might be in a place where they don’t have that much control, and should we really make it easier for incompetent people to do the wrong thing?
There are better ways to help people then writing a declaration.
Just to add my 2c, the anti-patterns section has helped me more than it has caused issues when I was starting out.
Also, trying to pander to the lowest denominator, i.e. those who are not able to interpet the “this isn’t gospel” part, is a losing battle. If it isn’t this, then there’d be something else to latch onto. If the anti-patterns were taken out from the Elixir docs and Jose hosts it on his own or dashbit’s blog, you’re going to still run into the same exact problem with those people at work but now newcomers would likely not see it.
It’s really a people problem of that workplace, not the language maintainers’, in my opinion. Obviously not to discount the difficulty of that problem.
Absolutely. And sadly that’s exactly what is happening: they wield the antipatterns like a cudgel.
Conversely, adding such documentation they perceive as being handed a cheap and easy-to-use cudgel. It’s a two-pronged problem.
I’ll agree that we can’t ban having rocks in the city just because somebody can use them as a murder weapon though.
A lot of people just want to get something done and move on; I sympathise with that in a startup setting – but a nuance often missed is that “moving fast and breaking things” while keeping customers satisfied hits a plateau, hard, sooner than they expect. Just a mere 6 months later your CEO is in talks with 4 more customers and now you suddenly don’t have only one customer anymore and lo and behold, half your system has hardcoded business rules inside the code, global state everywhere (how people allow more than the absolute very minimum amount of Application.get_env calls in their code remains a mystery to me), sync tests (in Elixir of all PLs!), an app full of timeline processing people still uses DateTime.utc_now and not a easy-to-mock shim, etc.
And then the classic problem: “Why can’t you just do X? We moved so quickly before!”. If I hear that one more time this year, I might actually go retire to farm tomatoes and potatoes.
The problem with good practices is that people wisen up about them 6-12 months down the line. The long tail of negative consequences of “winging it” and “do you know better than Jose Valim?” is not visible in the meetings where your experience gets overruled by vibes and impatience. It’s always later.
The amount of times I was called by former colleagues (CEOs included) telling me “You were right, Dimi” might sound like a covert brag but with the years this began giving me bitterness much more than satisfaction.
Agreed on the premise and you’re both crushingly correct. The nuance here is that often people can’t just leave a bad workplace, especially today when the illusion that “devs are easy to get rid of and get a new one” is still in full swing for a lot of businesses, and many of them have severely reduced their hiring. Add to that the fact that Elixir, much to my regret, remains niche, and you get a picture of “well maybe I shouldn’t leave because my experience yet again got overruled and I know people will call me in a year telling me I was right”.
Let’s not forget a lot of people are with families and just leaving and finding a new job might be anything but “just leave and find a better place”; kids need clothes, school tuition, and a thousand other things.
So very often one has to stay and fight a doomed battle with people who made up their minds three years ago.
But I don’t fully detest that. When I was in such less privileged positions, I partially learned to navigate stubborn people. And as mentioned above, not all are truly unreasonable; some are just overwhelmed and rushing. Sitting them down and trying to dissect a problem works, part of the time, especially when you show them that you all are fighting the same problems and have the same mission.
And I agree with @mudasobwa; framing and phrasing matters a lot. But admittedly, I have no better idea. I’d not put those antipatterns in the official docs in the first place – but that ship has sailed.