Tomer Aberbach

Cursed Knowledge

Published ·

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 permalinkPHP 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 <code>sleep</code> command is cursed permalinkGitHub Actions’s sleep command is cursed

GitHub 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 permalinkCSS 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# <code>JsonElement</code>’s <code>TryGet</code> methods are cursed permalinkC# JsonElement’s TryGet methods are cursed

JsonElement 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:

  1. Returns true for JSON numbers that fit in a Byte
  2. Returns false for JSON numbers that don’t fit in a Byte
  3. 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 <code>readonly</code> properties are cursed permalinkTypeScript readonly properties are cursed

Marking 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 permalinkMaven 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 permalinkRuboCop 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 <code>Date</code>’s <code>setMonth</code> method is cursed permalinkJavaScript Date’s setMonth method is cursed

Calling 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 permalinkPython 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 <code>URL</code>’s identity methods are cursed permalinkJava URL’s identity methods are cursed

A 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.