Лучшее решение, которое я нашел, — это языки, которые предлагают Nullability для встроенных типов, которые не могут быть нулевыми. Например, есть как string, так и Null<string>. При определении параметров метода можно явно указать обычную версию. Затем компилятор предотвратит передачу nullOrString этому методу.

Это решает проблему. Вместо того, чтобы требовать от метода проверки всех параметров, он передает проблему клиенту. т.е. возведение ответственности на то место, которое вызовет проблему.

Это также решает другую проблему. Вот как избежать проверки всех входных параметров в каждом методе.

MethodA(Null<string> a) {
  if (a == null) { return; }
  MethodB(a);
}
MethodB(Null<string> b) {
  if  (b == null) { return; }
  console.log(b);
}
MethodA(null);

Чтобы убедиться, что ваши входные данные действительны, вам нужно выполнить проверку в двух местах. В одной библиотеке/сервисе вы, скорее всего, будете выполнять проверку нулей только в одном месте (потому что так проще), но это в конечном итоге вызовет проблемы, когда есть MethodC, что на самом деле нормально, передавая null.

Это решило бы эту проблему

MethodA(string a) {
  MethodB(a);
}
MethodB(string b) {
  console.log(b);
}
let a = null;
if (a == null) { return; }
MethodA(a);