Что такое синтаксический сахар в программировании
Перейти к содержимому

Что такое синтаксический сахар в программировании

  • автор:

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

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

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

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

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

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

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

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

Примеры:

  • Конструкция new () заменяет new List()
  • Оператор += заменяет конструкцию
    • 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? ExList < get; set; >public string? ExString2 < get; set; >public int ExInt < get; set; >>

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

    Пояснение

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

    . var result = new ExampleClass < . ExList = < 0, 1 >, . >; . 

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

    Но причём тут NRE? А всё дело в том, что заменяет конструкцию .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, и я был бы благодарен за вашу конструктивную критику и обратную связь.

    • синтаксический сахар
    • программирование
    • c#
    • ошибки программистов
    • ошибки и грабли
    • ошибки в коде
    • решения

    О вреде синтаксического сахара

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

    Ситуация здесь как в анекдоте о дорогах, в котором американец хвастается:
    — Я когда еду по дороге, ставлю стакан с водой на капот и гоню со скоростью 100 км/час. Ни одна капля воды не расплескается.
    На что русский говорит:
    — А мы все залезаем на заднее сиденье и всю дорогу в карты режемся, да пиво пьём.
    — А кто же машиной управляет.
    — Да куда она, на хрен, из колеи денется.

    Смешно, но, однако, удобно, я бы даже позавидовал тем ребятам на заднем сиденье если бы у меня не было синтаксического сахара. Колея хороша тем, что она позволяет не думать о том, как ты едешь, синтаксический сахар позволяет не думать о том, как ты пишешь, освобождая мозг для концентрации на задаче. Но что если для решения задачи мне понадобится сделать что-то странное? Съехать с колеи? Проблема даже не в том, что из неё сложно выбраться, она в том, что это колея для наших мыслей — нам просто не придёт в голову, что есть другие способы сделать дело.

    Но довольно теории, пришло время замарать руки. Приведу пример. Допустим, мы пишем ORM и в определённый момент нам понадобилось получить список имен полей модели, чтобы, к примеру, составить SQL запрос. Также, допустим, что поля — это объекты, у которых есть свойство name, содержащее имя поля. Всего-то делов! Надо просто пробежаться по всем полям, вытащить имя и составить список:

    var names = [];
    for ( var i = 0 , l = fields.length; i < l; i ++ ) names.push(fields[i].name);
    >

    Похоже, мы пишем ORM на javascript-е, ну ничего, люди делают куда более странные вещи в наши дни. И кстати, мы попали в первую ловушку синтаксиса — «пробежаться» интерпретировали как цикл, и в результате получили пару переменных, сравнение целых чисел, инкремент и квадратные скобки. Чёрт возьми! Это не то, что я имел в виду, когда говорил «пробежаться»! Попробуем ещё раз:

    var names = fields.map( function (field) return field.name;
    >);

    Короче, симпатичнее и куда ближе к тому, как я описывал алгоритм: пробежаться по полям — fields.map, получить имя — функция, которая получает имя. Н-да, функция, как бы от неё избавиться? Легко, не будем сдерживать себя, перепишем всё на питоне:

    names = [field . name for field in fields]

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

    names = [ » %s . %s » % (alias, field . name) if alias
    else field . name for field in fields]

    Это уже не выглядит так здорово, а что если нам потребуется приписать алияс для поля? И ведь потребуется, не это так что-то ещё. Что ж не будем ждать, когда наш код выйдет из под контроля, отрефакторим его превентивно:

    if alias:
    names = [ » %s . %s » % (alias, field . name) for field in fields]
    else :
    names = [field . name for field in fields]

    Ясность вернулась, но появилось дублирование. Это не случайно, списковые выражения, которые мы используем связывают в одну синтаксическую структуру пробежку и операцию над отдельным элементом. Не проблема, просто должно остаться только одно списковое выражение:

    if alias:
    get_name = lambda field: » %s . %s » % (alias, field . name)
    else :
    get_name = lambda field: field . name
    names = map (get_name, fields)

    … или ни одного — как только мы избавились от запутывания пробежки и операции списковое выражение стало ненужным. Тут есть ещё один интересный момент — мы вернулись к тому, что у нас было в javascript-е. Т. е. отсутствие в языке такого сладкого элемента как списковые выражения, привело к написанию более универсального кода. Неправда ли отличный пример «less is more», товарищи?

    Итак, я «расправился» с циклом for и питоньими списковыми выражениями, пора идти дальше. Вам нравится обращаться к свойствам объекта через точку? Мне — очень, это кратко (кроме самого объекта и требуемого свойства присутствует только маленькая точка) и экспрессивно (даже человек далёкий от объектов легко поймёт в чём тут соль). Это настолько удобно, что мы не вспоминаем об альтернативах, а в том же питоне есть три способа получить атрибут объекта (прямое обращение к __getattr__ и т.п. — чит, не дающий ничего принципиально нового):

    obj . name
    getattr (obj, «name» )
    operator . attrgetter( «name» )(obj)

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

    from operator import attrgetter
    names = map (attrgetter( «name» ), fields)

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

    P.S. Я не пишу ORM на javascript-е.
    P.P.S. Я не пишу ORM и на питоне, хотя временами я копаюсь в ORM Django.
    P.P.P.S. Странная идея, изложенная здесь, пришла ко мне во время чтения Practical Common Lisp. Для тех, кто не в курсе, программа на лиспе представляет собой набор вложенных списков, каждый из которых состоит из “что делать” (имени функции, оператора или макроса) и последующих аргументов, т.е. представляет синтаксическое дерево самой себя. Другими словами в Lisp нет синтаксиса. И как ни странно, это делает программы на нём удивительно гибкими.

    UPDATE. Чтобы ответить на большинство возражений, подойду несколько с другой стороны. Заметим, что map(), который я в конце концов использую, тоже абстракция довольно высокого уровня. На самом деле используемые мной абстракции можно выстроить в иерархию:
    C-style for + абстрагирование от индексирования = for-in
    for-in + возврат результата на каждой итерации = map
    map + lambda = списковое выражение.

    Я начинаю с низкого уровня и дохожу до уровня абстракции, который лучшим образом выражает то, что я пытаюсь сделать. И если мне не нужно обобщать, то здесь я и должен остановиться, но если обобщать приходиться я должен вспомнить, что списковое выражение — это просто map и lambda в одном флаконе или начать дублировать код. Если в языке нет списковых выражений (как в js), то я сразу получу обобщённый код, но он будет более низкоуровневым. Если я забуду о том, что списковое выражение можно разбить, то начну дублировать код.

    Подытоживая:
    1. Отсутствие определённого синтаксиса в языке приводит к написанию более гибкого кода.
    2. Этот более гибкий код будет более низкоуровневым.

    Второе плата за первое,

    Что такое синтаксический сахар в программировании?

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

    Если вы еще не начали карьеру в IT, приходите на наш бесплатный вебинар, чтобы узнать, как начать зарабатывать с помощью зерокодинга и нейросетей!

    Преимущества синтаксического сахара

    • Повышение читаемости кода: замен больших конструкций более понятными дает другим программистам быстрее разбираться в коде.
    • Увеличение производительности разработчика: меньше кода означает меньше возможных ошибок и быстрое написание нового функционала.
    • Сокрытие деталей реализации: он может скрывать сложные детали реализации, делая интерфейс простым для использования.

    Пример в Python: List Comprehension

    python # Обычный способ создания списка с квадратами чисел от 0 до 9 squares = [] for i in range(10): squares.append(i**2) # Использование синтаксического сахара List Comprehension squares = [i**2 for i in range(10)]

    Пример в JavaScript: Arrow Functions

    javascript // Обычная функция в JavaScript function add(a, b) < return a + b; >// Использование синтаксического сахара Arrow Function const add = (a, b) => a + b;

    Пример в Java: Lombok Library

    java // Обычный класс в Java public class User < private String name; private int age; public User(String name, int age) < this.name = name; this.age = age; >// Геттеры и сеттеры public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >> // Использование Lombok для автоматической генерации геттеров и сеттеров import lombok.Getter; import lombok.Setter; @Getter @Setter public class User

    Как правильно использовать синтаксический сахар?

    Правильное его использование важно для поддержания читаемости и поддерживаемости кода. Вот несколько советов, как это сделать:

    Будьте умеренны

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

    Понимайте основы языка программирования

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

    Обучайте команду

    Если вы работаете в команде, убедитесь, что все члены команды знают, как использовать синтаксический сахар. Обучение начинающих и обмен знаниями помогут создать единый стиль написания кода в проекте.

    Проводите код-ревью

    Проводите регулярные код-ревью, чтобы обнаруживать и исправлять потенциальные проблемы с использованием синтаксического сахара. Это поможет поддерживать качество кода на высоком уровне.

    Следите за обновлениями языка

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

    Потенциальные проблемы с использованием синтаксического сахара

    Хотя он дает много преимуществ, существуют и потенциальные проблемы, которые стоит учитывать:

    Сложность отладки

    Иногда использование синтаксического сахара может затруднить отладку программы. Например, в случае проявления ошибки в выражении List Comprehension в Python, сообщения об ошибках могут быть менее информативными, что затрудняет определение проблемы.

    Ограничения в выразительности

    В некоторых случаях его использование может привести к ограничению выразительности кода. Некоторые конструкции могут быть не такими гибкими, как их более длинные аналоги, что может усложнить реализацию определенного функционала.

    Сложность в понимании для начинающих

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

    Зависимость от версий языка

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

    Потеря производительности

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

    Заключение

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

    JavaScript: Синтаксический сахар

    Подобные конструкции index = index + 1 в JavaScript используются довольно часто, поэтому создатели языка добавили сокращённый вариант записи: index += 1 . Такие сокращения принято называть синтаксическим сахаром, потому что они делают процесс написания кода немного проще и приятнее, «подслащивая» его 🙂

    Существуют сокращённые формы для всех арифметических операций и для конкатенации строк:

    • a = a + 1 → a += 1
    • a = a — 1 → a -= 1
    • a = a * 2 → a *= 2
    • a = a / 1 → a /= 1
    • a = a + ‘foo’ → a += ‘foo’

    Задание

    Реализуйте функцию filterString() , принимающую на вход строку и символ, и возвращающую новую строку, в которой удален переданный символ во всех его позициях. Регистр символов важен.

    const str = 'If I look back I am lost'; filterString(str, 'I'); // 'f look back am lost' filterString('zz Zorro', 'z'); // ' Zorro' 

    Упражнение не проходит проверку — что делать? ��

    Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

    В моей среде код работает, а здесь нет ��

    Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

    Мой код отличается от решения учителя ��

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

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

    Прочитал урок — ничего не понятно ��

    Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

    Полезное

    Определения

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *