Null safety

Photo Credit: WeGraphics

Null safety

Несколько лет назад видел такой подход к написанию кода. Думал, что больше не увижу. Ошибался.

Есть в разработке такой термин, как null safety. Идеалогия борьбы такова: не допустить появление нулей (null). Ведь нули ведут нас к NPE, а NPE - это АйАйАй. Мне очень нравится подход Kotlin в данном направлении: Kotlin Null Safety

One of the most common pitfalls in Java programming is accessing a member of a null reference, that results in a NullPointerException,

Но разговор сегодня о другой крайности. Итак, у нас есть метод:

String doSmth(String str, Integer times) {
	String out = str;
	for i in range(times) {
		out = doSmthInternal(out);
	}
	return out;
}

Что-то тут не так. А, да. Конечно! Что же будет, если одним из параметров придет null? Все же упадет! Решение!

String doSmth(String str, Integer times) {
	if(times == null) {
		return null;
	}
	String out = str;
	for i in range(times) {
		out = doSmthInternal(out);
	}
	return out;
}

Вот. Обезопасились. Теперь код будет работать. #Стоп Что мы только что сделали! Мы предусмотрели случай нарушения контракта метода!? Сигнатура метода говорит нам, что метод принимает String и Integer и возвращает String. Если клиент, вызывает метод, и неудовлетворяет требования контракта, то мы втихаря отвечаем гадостью на гадость. Типа: “Ты нам фигню на вход, а мы тебе в ответ - фигню на выход”. И ладно бы данное решение использовалось для методов, аля String.isEmpty или в языках с ограниченными механизмами обработки ошибок. Но при использовании данного подхода во ВСЕМ КОДЕ! Это слишком.

А, ладно с контрактом. Давайте посмотрим на проблему с другой стороны. Дебаг. Он великолепен.

/**
 * Extracts digits from string
 * returns digits string or null if nothing found
 */
@Nullable String getDigits(String str) {
  return getDigitsInternal(str)
}

/**
 * Multiplies string by 2
 */
String dobleString(String str) {
  if(str != null) {
    return str + str;
  }
  return null;
}

String toUpperCase(String str) {
  if(str != null) {
    return str.toUpper();
  }
  return null;
}

public static void main() {
  String str = "abc"; // <-- contains no digits
  String digits = getDigits(str);
  sendWithTcp(toUpperCase(doubleString(digits))); // <-- NPE in send() method
}

Стектрейс мы увидем на методе sendWithTcp. Ошибка была при вызове doubleString. Но toUpperCase проглатил ерунду на вход и выдал ерунду на выход.

Смысл таких выкрутасов? “Наш код не падает, но выдает ерунду?”. Для меня это самая неприятная ситуация. Я хочу, чтобы программа показала, где ошибка. Я не хочу их скрывать. Откладывать ошибки до последнего? Не, не вариант.

Ребята, NPE это не плохо. NPE - это маркер ошибки. Он должен указывать максимально точно, где проблема.

Одна из моих любимых статей в блоге QT - Apology of the NULL pointer. Мнение NPE о людях ;)

Главное, чтобы котята не умирали.