Cursed Knowledge
Cursed knowledge I have learned over time that I wish I never knew. Inspired by Immich’s Cursed Knowledge. The knowledge is ordered from most to least recently learned.
PHP variables are cursed
PHP’s $ symbol is an operator that looks up a variable by name.
$foo = "hi";
$bar = "foo";
echo $$bar;
//=> hi
$a = "hi";
$b = "a";
$c = "b";
$d = "c";
$e = "d";
echo $$$$$e;
//=> hi
$$$$$e = "bye";
echo $a;
//=> byeThis is cursed because it’s an unnecessary feature that enables writing incomprehensible code.
Credit goes to Stephen Downward for telling me about it!
GitHub Actions’s sleep command is cursed
sleep command is cursedGitHub Actions’s sleep command is implemented as a busy wait.
This is cursed because it can result in 100% CPU usage or even looping forever, but sleeping is supposed to allow other tasks to run.
Credit goes to Hao Wang for telling me about it!
CSS margin collapse is cursed
CSS margins collapse vertically, but not horizontally.
This is cursed because it’s weirdly inconsistent and makes the rules of margin collapse even more confusing than they already are.
Credit goes to Samuel Foster for telling me about it!
C# JsonElement’s TryGet methods are cursed
JsonElement’s TryGet methods are cursedJsonElement has GetByte/TryGetByte, GetDateTime/TryGetDateTime, GetDouble/TryGetDouble, etc. However, GetBoolean and GetString have no corresponding TryGet methods. What gives?
You’d expect Get methods to throw exceptions and TryGet methods to gracefully handle type mismatches, but that’s only half true. For example, the TryGetByte method:
- Returns
truefor JSON numbers that fit in aByte - Returns
falsefor JSON numbers that don’t fit in aByte - Throws an exception for non-number JSON values (e.g. arrays and strings)
The TryGet methods are only graceful after confirming the JsonValueKind matches. This means that TryGetBoolean and TryGetString would behave identically to GetBoolean and GetString, respectively, because they have nothing to validate after the JsonValueKind. The methods don’t exist because they’d be pointless.
This is cursed because the TryGet method names promise graceful error handling, but the methods still throw exceptions. It’s a misleading API that provides no real benefit over wrapping a Get method in a try-catch.
TypeScript readonly properties are cursed
readonly properties are cursedMarking a property as readonly tells TypeScript to disallow writing to it during type-checking.
However, it’s a meaningless guardrail because TypeScript allows assigning a type with a readonly property to a type with a writable property.
type Person = {
name: string
}
type ReadonlyPerson = {
readonly name: string
}
const readonlyPerson: ReadonlyPerson = { name: `Tomer` }
// Cannot assign to 'name' because it is a read-only property.
readonlyPerson.name = `Tumor`
// Typechecks! 😱
const writablePerson: Person = readonlyPerson
writablePerson.name = `Tumor`This is cursed because readonly gives developers a false sense of security while being trivial to bypass, even by accident.
Luckily there’s an open PR that adds a flag for enforcing readonly properties.
Maven dependency mediation is cursed
When multiples versions of a dependency appear in the dependency tree, Maven chooses the version closest to the project root; not the highest version.
This is cursed because it leads to unpredictable dependency resolution that silently downgrades transitive dependencies.
This behavior even caused a bug in the OpenAI Java SDK!
RuboCop is cursed
RuboCop, a popular Ruby formatter and linter, has auto-fixable lint rules known as “cops”. Every time a cop fixes a problem in a file, every other cop reruns on that file.
This is cursed because it takes infinite time to run in the worst case.
Credit goes to Hao Wang for telling me about it!
JavaScript Date’s setMonth method is cursed
Date’s setMonth method is cursedCalling setMonth(month) doesn’t always update the date to the given month. For example, if the date is August 31, then setting the month to September will update the date to October 1. September only has 30 days, so the 31st day “overflows” to the next month.
This is cursed because it violates the fundamental expectation that calling a setter method with a value actually sets that value.
This behavior even caused a bug in Google Docs!
Python default parameter values are cursed
A function’s default parameter values are evaluated once; not on each function call.
This means you shouldn’t use mutable values for a parameter’s default value:
def append_fun(list=[]):
list.append('fun')
return list
print(append_fun())
#=> ['fun']
print(append_fun())
#=> ['fun', 'fun']
print(append_fun())
#=> ['fun', 'fun', 'fun']You have to apply the default in the function body instead:
def append_fun(list=None):
if list is None:
list = []
list.append('fun')
return list
print(append_fun())
#=> ['fun']
print(append_fun())
#=> ['fun']
print(append_fun())
#=> ['fun']This is cursed because it creates invisible shared state between function calls, turning what appears to be a pure function into something stateful.
Java URL’s identity methods are cursed
URL’s identity methods are cursedA call to equals or hashCode may perform a blocking DNS lookup so that two URLs corresponding to the same IP address are considered equal.
This also means that using URL objects as HashMap keys will result in many DNS lookups.
This is cursed because identity methods are supposed to be stateless and performant.