Должен ли C# выдавать предупреждения на использование пустых ссылок

Как вы наверное знаете, компилятор языка C# анализирует константы для поиска недостижимого кода. В следующем методе компилятор предупреждает о том, что вызов метода является недостижимым.

const object x = null;
void Foo()
{
if (x != null)
{
Console.WriteLine(x.GetHashCode());
}
}

Предположим, мы уберем “if” и оставим лишь вызов метода.

const object x = null;
void Foo()
{
Console.WriteLine(x.GetHashCode());
}

Компиляторне предупредит вас о разыменовывании нулевой ссылки! И очень часто задают вопрос,почему?

И, как обычно, ответ состоит в том, что нам не нужно никакого оправдания на то, почему мы не реализовали какую-то возможность. Разработка любой возможности требует средств, времени и усилий, а также забирает средства, время и усилия от других вещей, которые были бы более полезными для пользователя, так что любая новая возможность должна оцениваться с точки зрения отношения цена/качество. Давайте подумаем об этом с такой точки зрения.

При анализе некоторой новой возможности я задаю себе следующий вопрос «является ли эта возможность частным случаем более общей задачи? » По сути, предложенная возможность предназначена для определения того, что некоторое исключение всегда будет сгенерировано. Хотим ли мы определять ситуации, когда некоторое исключение будет сгенерировано в любом случае, и выдавать об этом предупреждение? Как мы уже обсуждали, полное решение этой проблемы означает решение Проблемы Остановки (Halting Problem). Но даже без этого, мы не хотим выдавать предупреждение каждый раз, когда точно знаем, что исключение должно быть сгенерировано; тогда следующий фрагмент кода будет приводить к предупреждению:

int M()
{
throw new NotImplementedException();
}

Основная цель генерации этого исключения –четко сказать о том, что эта часть программы не работает; предупреждение, которое будет говорить о том, что она не работает контрпродуктивно; предупреждения должны говорить о том, чего вы еще не знаете.

Итак, давайте подумаем о возможности определения разыменования константы null. А вообще, как часто такое происходит? Иногда я создаю константы, равные null; например, чтобы написать что-то типа: «if (symbol == InvalidSymbol)», где InvalidSymbol – это константа, равная null; это позволяет читать код, как будто он написан на естественном языке. И тогда, если кто-то случайно напишет «InvalidSymbol.Name», то компилятор выдаст предупреждение о попытке разыменовывания null.

Мы уже поднимали подобные вопросы. Более того, я создал список вопросов, которые я при этом себе задаю. Так что давайте пройдемся по нему.

Эта возможность является разумной; приведенный код кажется допустимым и явно ошибочным, и мы довольно просто можем это определить. Однако, область действия такого предупреждения чрезвычайно мала; в основном я использую такие константы не для доступа к их членам, а для сравнения с ними. Кроме того, проблема всплывет при тестировании кода.

Тогда может быть, мы можем обобщить эту возможность в каком-то другом виде? Возможно, компилятор мог бы анализировать любые разыменования нулевой ссылки, а не только разыменования констант. Полное решение этой задачи, опять-таки, сводится к решению проблемы остановки, что, как мы знаем, невозможно, но мы можем использовать некоторые умные эвристики для получения разумного результата.

На самом деле даже сейчас компилятор языка C# содержит некоторые такие эвристики; они используются для оптимизации арифметических операций над nullable-типами. Если во время компиляции мы знаем, что результатом выражения всегда будет null, то компилятор генерирует более эффективный код. (Как мы это делаем, является отличной темой для будущих статей.) Однако текущие эвристики являются чрезвычайно «локальными»; например, не анализируется вариант, когда локальной переменной присваивается null, а потом она используется до переприсваивания. Так что, опять-таки, количество мест, где генерируется такое предупреждение, будет небольшим, возможно, совсем небольшим.

Для полноценной реализации этой возможности нам придется модифицировать алгоритм анализатора, определяющий, что переменная является проинициализированной, на алгоритм, который будет анализировать, что до разыменования переменной присвоено не нулевое значение. Это значительно сложнее реализовать; от него будет больше пользы, но и потребует больших затрат.

Но главным является последний пункт моего списка. Да, значительно лучше найти ошибку во время компиляции, но ошибки разыменовывания нулевых ссылок, которые могут быть гарантированно найдены компилятором, очень легко находятся во время исполнения, поскольку они обязательно проявятся при тестировании кода. Так что существуют и возражения против реализации этой возможности.

Итак, по сути, данная возможность предполагает разработку некоторого сложного детектора, который будет определять во время компиляции некоторое маловероятное условие, которое и так всегда будет проявляться при первом запуске кода. Таким образом, эта возможность является не лучшим кандидатом для расходования на нее бюджета; так что она никогда не будет реализована. Это отличная возможность и я был бы очень рад иметь ее в компиляторе, но она не является настолько полезной, чтобы тратить средства на ее разработку, реализацию и тестирование, когда у нас есть масса других более высокоприоритетных задач.

Кроме того, вы могли заметить, что такие инструменты, как Code Contracts (выпускаемый с .NET 4), ReSharper и другие аналогичные инструменты содержат возможности статического анализа для определения возможного или гарантированного доступа к нулевым ссылкам. Это еще раз доказывает, что эта возможность реализуема. Но это также говорит против добавления такого предупреждения в компилятор. Как я уже писал, если уже есть инструмент, отлично делающий некоторую работу, то зачем дублировать ее в компиляторе? Компилятор не должен быть единственным и всезнающим инструментом анализа кода; на самом деле, мы создаем Roslyn именно для того, чтобы другие компании могли разрабатывать подобные инструменты. Мы не можем реализовать все полезные возможности для анализа кода, но мы можем сделать эту работу легче для кого-то другого!

Оригинал статьи