Как стать автором
Обновить

Как синтаксический сахар может сыграть с вами злую шутку

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров29K

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

Сразу стоит отметить, что история, которую я опишу ниже, произошла с разработчиком из моей команды, а я лишь выступал в качестве наблюдателя.

Синтаксический сахар в C#

В нашей команде используется язык C#, поэтому мой рассказ будет про его синтаксис.

Синтаксический сахар заменяет длинные и часто используемые конструкции кода, делает их более короткими и быстро читаемыми, облегчает и ускоряет разработку.

Здесь стоит отметить и другое мнение: существуют люди, которые считают, что синтаксический сахар только усложняет код, делая его менее читаемым.

Я придерживаюсь данного мной определения, хотя опыт показал, что не всё так однозначно. Разберем на примерах:

Примеры:

  • Конструкция new () {1, 2} заменяет new List() {1, 2}

  • Оператор += заменяет конструкцию

    • number += 1 =>

    • number = number + 1

  • А оператор ??= вообще имеет долгую историю

    • foo ??= "Строка была равна null" =>

    • foo = foo ?? "Строка была равна null" =>

    • foo = foo is null ? "Строка была равна null" : foo =>

    • if (foo is null)
      {
      foo = "Строка была равна null";
      }

Эти примеры лишь малая часть всех тех конструкций, которые иcпользуются в C#. С каждой новой версией языка появляется всё больше новых конструкций и нужно пристально следить, чтобы ничего не упустить.

Неожиданные Null Reference Exception

Итак, перейдем к истории. Всё началось с того момента, когда в нашем проекте начали "выстреливать" ошибки NRE. По стек-трейсу было ясно, что код "падает" при инициализации класса. Ниже представлен фрагмент кода, приближенный к тому, что был у нас в проекте.

...
var result = new ExampleClass
{
    ExString = "Тут не должно быть взрыва",
    ExList = { 0, 1 },
    ExString2 = "И тут",
    ExInt = 24
};
...

NRE выбрасывалось на строке var result = new ExampleClass, которая, в теории, не должна вызывать такую ошибку. Отсюда стало очевидно, что следует обратить внимание на то, как инициализируются поля объекта.

Вот как выглядел класс, который мы пытались инициализировать:

public class ExampleClass
{
    public string? ExString { get; set; }
    public List<int>? ExList { get; set; }
    public string? ExString2 { get; set; }
    public int ExInt { get; set; }
}

Наверное, я оставил очень прозрачные подсказки в коде, чтобы вы смогли догадаться, из-за чего выбрасывалась ошибка. Но в моменте было совсем не ясно, особенно когда ты пытаешься быстро разобраться в ситуации из-за ошибок на продакшене.

Пояснение

Можно обратить внимание на весьма странный синтаксис в коде.

...
var result = new ExampleClass
{
    ...
    ExList = { 0, 1 },
    ...
};
...

Выше я приводил пример того, как конструкция new() {0, 1} заменяет new List() {0, 1}. Можно заметить, что {0, 1} уж очень похоже на new() {0, 1}.
Первый раз, глядя на эту конструкцию, в голову совсем не приходит мысль о том, что тут что-то не так. Скорее возникает мысль: "Наверное, это новый синтаксис". После 10 минут интенсивного просмотра кода было принято решение заменить {0, 1} на new() {0, 1}. И, о чудо, ошибки исчезли.

Но причём тут NRE? А всё дело в том, что{0, 1} заменяет конструкцию .Add() И вот эти два блока кода оказываются идентичными:

// 1
var result1 = new ExampleClass
{
    ExList = { 1, 2 },
    ExString = "Привет",
    ExInt = 24
};

// 2
var result2 = new ExampleClass
{
    ExString = "Привет",
    ExInt = 24
};
result.ExList.Add(1);
result.ExList.Add(2);

Так как ExList при инициализации равен null, то при попытке вызывать null.Add() мы получаем NRE.

Почему такое произошло?

Лично я вижу здесь несколько причин. Во-первых, в нашей команде и вокруг неё этот синтаксис не так популярен, с ним редко кто сталкивается. Во-вторых, IDE никак не подсвечивает такую простую NRE, хотя поле было помечено как nullable, и такое можно было бы подсветить.

Как этого избежать?

Самое главное — быть внимательным и проверять код. Можно попробовать следить за актуальными конструкциями, но, по моему опыту, это сделать весьма проблематично. Как ни странно, хоть IDE в этом случае подвело, но оно все равно умеет замечать большинство таких проблем. Именно поэтому стоит прислушиваться к его предупреждениям.

Заключение

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

P.S.

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

Теги:
Хабы:
Всего голосов 36: ↑30 и ↓6+24
Комментарии52

Публикации

Истории

Работа

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург