Attractive nuisances in software design

In tort law, there is a doctrine called attractive nuisance, where a property owner can be held liable for injuries that happen to children if the injury was caused by something on the property that is likely to attract them, even if the children are trespassing . The idea is that kids don't fully understand boundaries and they also don't have enough life experience to understand the risks of their actions, so if there's something accessible on your property that looks fun to play with but is actually dangerous, kids are going to ignore any No Tresspassing signs you put up and hurt themselves on it.

I believe that a similar problem arises when designing software. Most of your users are not going to read your documentation from back to front and fully grok all of the concepts before using your software — they are going to approach your software attempting to solve a problem, then grope around looking for the easiest solution to that problem and implement it. It's important to keep this in mind when designing your interfaces, because it makes it very easy to accidentally create attractive nuisances; if you have two ways to accomplish a task and one is a simple way that looks like the right thing but is subtly wrong, and the other is correct but more complicated, the majority of people will end up doing the wrong thing. Many of the articles on my blog are after-the-fact attempts to educate people about these attractive nuisances, like my articles on pytz and utcnow() and utcfromtimestamp().

A particularly insidious type of attractive nuisance occurs when you have a feature that may have legitimate niche use cases, but also provides a solution to a particularly common XY Problem. Imagine that 0.1% of all your users would find feature X useful, but it will induce 5% of your users into using an anti-pattern — by adding the feature, you would get a 50:1 misuse to use ratio. This isn't even so bad when the misuses are easily detectable (e.g. pandas' SettingWithCopyWarning), but sometimes the anti-pattern is something that causes problems only at scale, or only way down the road.

As a maintainer, it's easy to feel that you are doing the right thing by adding the feature in this scenario; as Alex Gaynor has written, every feature has a constituency, and well-meaning people will show up in your GitHub issues making the case for why their feature is useful. If you do add the feature, people who are negatively affected will generally not realize that what they are doing is wrong, so they won't show up to complain until much later, if they show up at all. You will likely realize your mistake years later when someone writes an article imploring people to never use it, or not to use it in most circumstances.

One strategy that I like to use for avoiding this type of attractive nuisance is to provide complex solutions to niche problems — for example, one can keep a very minimal interface for common tasks, but also provide a full-featured plugin system. Users with a common XY problem will find that it is possible to accomplish X, but it involves writing your own plugin — this provides a hint that using your software to do X is actually a very uncommon thing, and when they are searching for a solution they are likely to encounter someone advising them to do Y instead.[1]

Footnotes

[1]A concrete example of this is supporting file: for install_requires in setup.cfg; a good fraction of people will create a requirements.txt file for their program, and then think, "Hey, I have a list of dependencies, I should re-use it in my install_requires". However, these two fields represent different concepts, and populating install_requires from requirements.txt is an overuse of the DRY principle. In that issue, though, we had one rare but legitimate use case for DRY-ing your install_requires from a requirements.in file, but luckily it was already possible to accomplish this by creating a setup.py and reading your requirements file there. By keeping file: unsupported in setup.cfg, we were signalling to people that maybe including a requirements file is not a common pattern that you should be using. Alas, this anti-feature was added over my objections.