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;
//=> bye
This 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
true
for JSON numbers that fit in aByte
- Returns
false
for 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.