Замена магического числа символьной константой
В коде используется число, которое несёт какой-то определённый смысл.
Решение
Замените это число константой с человеко-читаемым названием, объясняющим смысл этого числа.
double potentialEnergy(double mass, double height)
static final double GRAVITATIONAL_CONSTANT = 9.81; double potentialEnergy(double mass, double height)
double PotentialEnergy(double mass, double height)
const double GRAVITATIONAL_CONSTANT = 9.81; double PotentialEnergy(double mass, double height)
function potentialEnergy($mass, $height)
define(«GRAVITATIONAL_CONSTANT», 9.81); function potentialEnergy($mass, $height)
def potentialEnergy(mass, height): return mass * height * 9.81
GRAVITATIONAL_CONSTANT = 9.81 def potentialEnergy(mass, height): return mass * height * GRAVITATIONAL_CONSTANT
potentialEnergy(mass: number, height: number): number
static const GRAVITATIONAL_CONSTANT = 9.81; potentialEnergy(mass: number, height: number): number
Причины рефакторинга
Магические числа — это числовые значения, встречающиеся в коде, но при этом неочевидно, что они означают. Данный антипаттерн затрудняет понимание программы и усложняет её рефакторинг.
Дополнительные сложности возникают, когда нужно поменять определённое магическое число. Это нельзя сделать автозаменой, так как одно и то же число может использоваться для разных целей, а значит, вам нужно будет проверять каждый участок кода, где используется это число.
Достоинства
- Символьная константа может служить живой документацией смысла значения, которое в ней хранится.
- Значение константы намного проще заменить, чем искать нужное число по всему коду, при этом рискуя заменить такое же число, которое в данном конкретном случае использовалось для других целей.
- Убирает дублирование использования числа или строки по всему коду. Это особенно актуально, если значение является сложным и длинным (например, -14159 , 0xCAFEBABE ).
Полезные факты
Не все числа являются магическими.
Если предназначения чисел очевидны, их не надо заменять константами, классический пример:
for (i = 0; i
Альтернативы
- Иногда, магическое число можно заменить вызовом метода. Например, если у вас есть магическое число, обозначающее количество элементов коллекции, вам не обязательно использовать его для проверок последнего элемента коллекции. Вместо этого можно использовать встроенный метод получения длины коллекции.
- Магические числа могут быть использованы для реализации кодирования типа. Например, у вас есть два типа пользователей, и чтобы обозначить их, у вас есть числовое поле в классе, в котором для администраторов хранится число 1 , а для простых пользователей — число 2 . В этом случае имеет смысл использовать один из рефакторингов избавления от кодирования типа:
- замена кодирования типа классом
- замена кодирования типа подклассами
- замена кодирования типа состоянием/стратегией
Порядок рефакторинга
- Объявите константу и присвойте ей значение магического числа.
- Найдите все упоминания магического числа.
- Для всех найденных чисел проверьте, согласуется ли это магическое число с предназначением константы. Если да, замените его вашей константой. Эта проверка важна, так как одно и тоже число может означать совершенно разные вещи (в этом случае, они должны быть заменены разными константами).
Устали читать?
Сбегайте за подушкой, у нас тут контента на 7 часов чтения.
Или попробуйте наш интерактивный курс. Он гораздо более интересный, чем банальный текст.
Скидки!
Этот рефакторинг — малая часть интерактивного онлайн курса по рефакторингу.
- Премиум контент
- Книга о паттернах
- Курс по рефакторингу
- Введение в рефакторинг
- Чистый код
- Технический долг
- Когда рефакторить
- Как рефакторить
- Раздувальщики
- Длинный метод
- Большой класс
- Одержимость элементарными типами
- Длинный список параметров
- Группы данных
- Операторы switch
- Временное поле
- Отказ от наследства
- Альтернативные классы с разными интерфейсами
- Расходящиеся модификации
- Стрельба дробью
- Параллельные иерархии наследования
- Комментарии
- Дублирование кода
- Ленивый класс
- Класс данных
- Мёртвый код
- Теоретическая общность
- Завистливые функции
- Неуместная близость
- Цепочка вызовов
- Посредник
- Неполнота библиотечного класса
- Составление методов
- Извлечение метода
- Встраивание метода
- Извлечение переменной
- Встраивание переменной
- Замена переменной вызовом метода
- Расщепление переменной
- Удаление присваиваний параметрам
- Замена метода объектом методов
- Замена алгоритма
- Перемещение метода
- Перемещение поля
- Извлечение класса
- Встраивание класса
- Сокрытие делегирования
- Удаление посредника
- Введение внешнего метода
- Введение локального расширения
- Самоинкапсуляция поля
- Замена простого поля объектом
- Замена значения ссылкой
- Замена ссылки значением
- Замена поля-массива объектом
- Дублирование видимых данных
- Замена однонаправленной связи двунаправленной
- Замена двунаправленной связи однонаправленной
- Замена магического числа символьной константой
- Инкапсуляция поля
- Инкапсуляция коллекции
- Замена кодирования типа классом
- Замена кодирования типа подклассами
- Замена кодирования типа состоянием/стратегией
- Замена подкласса полями
- Разбиение условного оператора
- Объединение условных операторов
- Объединение дублирующихся фрагментов в условных операторах
- Удаление управляющего флага
- Замена вложенных условных операторов граничным оператором
- Замена условного оператора полиморфизмом
- Введение Null-объекта
- Введение проверки утверждения
- Переименование метода
- Добавление параметра
- Удаление параметра
- Разделение запроса и модификатора
- Параметризация метода
- Замена параметра набором специализированных методов
- Передача всего объекта
- Замена параметра вызовом метода
- Замена параметров объектом
- Удаление сеттера
- Сокрытие метода
- Замена конструктора фабричным методом
- Замена кода ошибки исключением
- Замена исключения проверкой условия
- Подъём поля
- Подъём метода
- Подъём тела конструктора
- Спуск метода
- Спуск поля
- Извлечение подкласса
- Извлечение суперкласса
- Извлечение интерфейса
- Свёртывание иерархии
- Создание шаблонного метода
- Замена наследования делегированием
- Замена делегирования наследованием
- Введение в паттерны
- Что такое Паттерн?
- История паттернов
- Зачем знать паттерны?
- Критика паттернов
- Классификация паттернов
- Фабричный метод
- Абстрактная фабрика
- Строитель
- Прототип
- Одиночка
- Адаптер
- Мост
- Компоновщик
- Декоратор
- Фасад
- Легковес
- Заместитель
- Цепочка обязанностей
- Команда
- Итератор
- Посредник
- Снимок
- Наблюдатель
- Состояние
- Стратегия
- Шаблонный метод
- Посетитель
- C#
- C++
- Go
- Java
- PHP
- Python
- Ruby
- Rust
- Swift
- TypeScript
Что такое магические числа в программировании и как снять это заклятие
Магические числа — пример плохих практик в программировании. Из этой статьи вы узнаете, почему от них надо избавляться и как это делать.
- Что такое магические числа
- Как избавиться от магических чисел
Что такое магические числа
В программировании магическими называют числа в коде, смысл которых сложно понять. Взгляните на пример.
// показываем пользователю окончательную цену товара const showBruttoPrice = (nettoPrice) => const bruttoPrice = nettoPrice * 1.20; return bruttoPrice; >;
Это пример магического числа, потому что невозможно однозначно ответить на вопрос, почему для вычисления bruttoPrice нужно умножить nettoPrice на 1.20 . Смысл числа 1.20 приходится восстанавливать по контексту. Но при работе с большими приложениями в реальной разработке это не всегда возможно.
Магические числа не ломают код. В примере выше пользователь увидит окончательную цену, то есть программа отработает. В чём же проблема?
Код с магическими числами сложно понять без контекста. Это может стать проблемой для разработчиков, которые впервые видят приложение. Да и автор кода может забыть, почему использовал именно это число.
Как избавиться от магических чисел
Это можно сделать с помощью константы с понятным названием. То есть название константы должно передавать смысл числа.
// указываем ставку НДС const vatRate = 1.20; const showBruttoPrice = (nettoPrice) => const bruttoPrice = nettoPrice * vatRate; return bruttoPrice; >;
Очевидное указание ставки НДС делает код более понятным.
Код с магическими числами сложно поддерживать и расширять. Например, если магазин продаёт товары людям из разных стран, магические числа приводят к дублированию:
// в разных странах разные ставки НДС const showBruttoPrice = (nettoPrice, country) => let bruttoPrice; if (country === 'Russia') bruttoPrice = nettoPrice * 1.20; > if (country === 'Germany') bruttoPrice = nettoPrice * 1.19; > // стран может быть 5, 20 или 50 // для каждой придётся использовать // своё магическое число // . return bruttoPrice; >;
Проблему решает избавление от магических чисел:
const getVatRate = (country) => // получаем ставку НДС для конкретной страны из // базы данных или из внешнего источника >; const showBruttoPrice = (nettoPrice, country) => // определяем ставку НДС вместо того, // чтобы указывать её с помощью магических чисел const vatRate = getVatRate(country); // определяем окончательную цену const bruttoPrice = nettoPrice * vatRate; return bruttoPrice; >;
- Магические числа — плохая практика в программировании
- Обычно магические числа не ломают код, а делают его менее понятным
- Чтобы избавиться от магических чисел, достаточно использовать константы или переменные с понятными названиями
Чистое зло Python
Темные силы не дремлют. Они пробираются в дивное королевство Python и используют черную магию, чтобы осквернить главную реликвию — чистый код. Однако опасны не только злые чары.
Сегодня я расскажу о страшных чудовищах, которые, возможно, уже обжились в вашем коде и готовы устанавливать свои правила. Здесь нужен герой, который защитит безмятежный мир от злобных тварей. И именно вы станете тем, кто сразится с ними!
Всем героям, однако, нужно магическое снаряжение, которое верой и правдой послужит им в грандиозных битвах. К счастью, с нами будет линтер wemake-python-styleguide. Он станет тем самым острым оружием и надежным соратником.
Все готово, выступаем в поход!
Пожиратели пространства
А вот и первые проблемы. Жители стали замечать, как нечто лакомится пробелами, а операторы обретают причудливые формы:
x = 1 x -=- x print(x) # => 2 o = 2 o+=+o print(o) # => 4 print(3 --0-- 5 == 8) # => True
Эти странные операторы состоят из совершенно обычных и дружественных нам -= и — . Посмотрим, сможет ли наш линтер найти их:
5:5 E225 missing whitespace around operator x -=- x ^ 5:5 WPS346 Found wrong operation sign x -=- x ^ 10:2 E225 missing whitespace around operator o+=+o ^ 14:10 E225 missing whitespace around operator print(3 --0-- 5 == 8) ^ 14:10 WPS346 Found wrong operation sign print(3 --0-- 5 == 8) ^ 14:11 WPS345 Found meaningless number operation print(3 --0-- 5 == 8) ^ 14:12 E226 missing whitespace around arithmetic operator print(3 --0-- 5 == 8) ^ 14:13 WPS346 Found wrong operation sign print(3 --0-- 5 == 8) ^
Настала пора обнажить меч и принять бой:
x = 1 x += x o = 2 o += o print(3 + 5 == 8)
Враг повержен, и сразу вернулись прежние чистота и ясность!
Загадочные точки
Теперь жители сообщают о появлении странных глифов. О, смотрите-ка, вот и они!
print(0..__eq__(0)) # => True print(. __eq__(((. )))) # => True
Что же здесь происходит? Кажется, там замешаны типы данных float и Ellipsis , но лучше удостовериться.
21:7 WPS609 Found direct magic attribute usage: __eq__ print(0..__eq__(0)) ^ 21:7 WPS304 Found partial float: 0. print(0..__eq__(0)) ^ 24:7 WPS609 Found direct magic attribute usage: __eq__ print(. __eq__(((. )))) ^
Ага, теперь понятно. Действительно, эти точки — краткая запись значений типа float (в первом случае) и Ellipsis (во втором). И в обоих случаях происходит обращение к методу, также через точку. Давайте посмотрим, что же скрывалось за этими знаками:
print(0.0 == 0) print(. == . )
На этот раз все обошлось, но впредь не сравнивайте константы друг с другом, дабы не накликать беду.
Кривая дорожка
А между тем у нас новая напасть — значения из некоторых веток в функции никогда не возвращаются. Давайте разберемся, в чем дело.
def some_func(): try: return 'from_try' finally: return 'from_finally' some_func() # => 'from_finally'
Функция не возвращает значение ‘from_try’ из-за закравшейся в код ошибки. «Как ее исправить?» — изумленно спросите вы.
31:5 WPS419 Found `try`/`else`/`finally` with multiple return paths try: ^
Оказывается, wemake-python-styleguide знает ответ: никогда не возвращайте значение из ветки finally . Послушаемся совета.
def some_func(): try: return 'from_try' finally: print('now in finally')
Мрачный СИ-луэт прошлого
Древнее существо пробуждается. Уже несколько десятилетий никто не видел его, но теперь оно вернулось.
a = [(0, 'Hello'), (1, 'world')] for ['>']['>'>'>'], x in a: print(x)
Что тут происходит? Известно, что в циклах можно распаковывать разные значения: почти любые валидные в Python выражения.
Правда, многое из этого примера нам не следовало бы делать:
44:1 WPS414 Found incorrect unpacking target for ['>']['>'>'>'], x in a: ^ 44:5 WPS405 Found wrong `for` loop variable definition for ['>']['>'>'>'], x in a: ^ 44:11 WPS308 Found constant compare for ['>']['>'>'>'], x in a: ^ 44:14 E225 missing whitespace around operator for ['>']['>'>'>'], x in a: ^ 44:21 WPS111 Found too short name: x for ['>']['>'>'>'], x in a: ^
Теперь разберемся с [‘>’][‘>’>’>’] . Похоже, что данное выражение можно просто переписать как [‘>’][0] , поскольку у выражения ‘>’ > ‘>’ значение False . А False и 0 — одно и тоже.
Метки Темного Колдуна
Насколько сложным может быть выражение на Python? Наверняка такие конструкции — происки злых сил. Это Темный Колдун оставляет свои замысловатые метки во всех классах, к которым прикасается:
class _: # Видите эти четыре метки? _: [(). ()] = <((). ()): <(). ()>>[((). ())] print(_._) # и этот оператор выглядит знакомо # =>
Что же за ними скрывается? Похоже, у каждой метки свое значение:
- Объявление и указание типа: _: [(). ()] = .
- Определение словаря, где значение — набор данных: = < ((). ()): > .
- Ключ: [((). ())] .
В мире людей подобная запись не имеет никакого смысла и безвредна, однако в королевстве Python она может стать оружием в злых руках. Давайте ее уберем:
55:5 WPS122 Found all unused variables definition: _ _: [(). ()] = <((). ()): <(). ()>>[((). ())] ^ 55:5 WPS221 Found line with high Jones Complexity: 19 _: [(). ()] = <((). ()): <(). ()>>[((). ())] ^ 55:36 WPS417 Found non-unique item in hash: () _: [(). ()] = <((). ()): <(). ()>>[((). ())] ^ 57:7 WPS121 Found usage of a variable marked as unused: _ print(_._) # и этот оператор выглядит знакомо ^
Теперь, когда мы удалили или зарефакторили это выражение (со значением 19 по метрике сложности Jones Complexity), от метки Темного Колдуна в бедном классе не осталось и следа. Очередные ростки зла уничтожены.
Метамагия
Однако теперь наши классы связались с какими-то дурными типами, и те оказывают на них пагубное влияние.
Сейчас классы выдают очень странные результаты:
class Example(type((lambda: 0.)())): . print(Example(1) + Example(3)) # => 4.0
Почему 1 + 3 равно 4.0 , а не 4 ? Чтобы это выяснить, рассмотрим поближе часть с type((lambda: 0.)()) :
- (lambda: 0.)() просто равно 0. , а это просто иная запись 0.0 .
- type(0.0) возвращает тип float .
- когда мы пишем Example(1) , это значение преобразуется в Example(1.0) внутри класса.
- Example(1.0) + Example(3.0) = Example(4.0) .
Давайте убедимся, что наш линтер-клинок по-прежнему остр:
63:15 WPS606 Found incorrect base class class Example(type((lambda: 0.)())): ^ 63:21 WPS522 Found implicit primitive in a form of lambda class Example(type((lambda: 0.)())): ^ 63:29 WPS304 Found partial float: 0. class Example(type((lambda: 0.)())): ^ 64:5 WPS428 Found statement that has no effect . ^ 64:5 WPS604 Found incorrect node inside `class` body . ^
Со всем разобрались, теперь наши классы в безопасности. Можем двигаться дальше.
Иллюзии
Иногда на пути встречаются выражения такие похожие, но такие разные. Вот и мы столкнулись с таким примером в коде. Выглядит, как самое обычное выражение-генератор, но на самом деле это кое-что совсем другое.
a = ['a', 'b'] print(set(x + '!' for x in a)) # => print(set((yield x + '!') for x in a)) # =>
Это одно из хтонических чудовищ Python — да, они все-таки существуют и тут. Учитывая, что в python3.8 такая конструкция приведет к SyntaxError , yield и yield from следует использовать только в функциях-генераторах.
А вот и отчет об инциденте:
73:7 C401 Unnecessary generator - rewrite as a set comprehension. print(set(x + '!' for x in a)) ^ 76:7 C401 Unnecessary generator - rewrite as a set comprehension. print(set((yield x + '!') for x in a)) ^ 76:11 WPS416 Found `yield` inside comprehension print(set((yield x + '!') for x in a))
И давайте перепишем обработку, как нам предлагают.
print()
Эта задачка была сложна, но и мы не лыком шиты. Что же дальше?
Злобный двойник email
Если нужно записать адрес электронной почты, то используем строку, ведь так? А вот и нет!
Для решения обычных задач существуют необычные способы. А у обычных типов данных есть злые двойники. Сейчас мы выясним, кто есть кто.
class G: def __init__(self, s): self.s = s def __getattr__(self, t): return G(self.s + '.' + str(t)) def __rmatmul__(self, other): return other + '@' + self.s username, example = 'username', G('example') print(username@example.com) # => username@example.com
Разберемся, как это работает.
- в Python @ — это оператор, который можно переопределить с помощью магических методов __matmul__ и __rmatmul__ .
- выражение .com означает обращение к свойству com ; переопределяется методом __getattr__ .
Этот пример значительно отличается от остальных тем, что он-то на самом деле корректный. Просто вот такой необычный. Вероятно, пользоваться им мы не будем, но в бестиарий запишем.
Сила заблуждений
В нашем королевстве настали смутные времена. Тьма наделила жителей сомнительными способностями. Это раскололо содружество разработчиков и привело к серьезным разногласиям.
Способности эти воистину страшные, ибо теперь вам дано программировать в строках:
from math import radians for angle in range(360): print(f' ') print(th) # => angle=0 (th:=radians(angle))=0.000 # => 0.0 # => angle=1 (th:=radians(angle))=0.017 # => 0.017453292519943295 # => angle=2 (th:=radians(angle))=0.035 # => 0.03490658503988659
Что происходит в этом примере?
- f’ — это способ записи f’angle= в новых версиях (python3.8+).
- (th:=radians(angle)) — это операция присваивания значения; да, теперь можно так делать и внутри строки.
- =:.3f указывает на формат вывода: возвращается значение, округленное до третьего знака
- метод print(th) отрабатывает, так как (th:=radians(angle)) имеет локальную область видимости в части кода, где находится вся строка.
Стоит ли использовать f-строки? Как хотите.
Стоит ли определять переменные в f-строках? Ни в коем случае.
А вот дружеское напоминание о том, что еще можно (но, наверное, не нужно) написать с помощью f -строк:
print(f"
") # => posix Всего лишь импортируем модуль внутри строки, ничего такого, идем дальше.
К счастью, в реальном коде наше оружие сразу почует неладное и засветится, аки знаменитый меч Жало:
105:1 WPS221 Found line with high Jones Complexity: 16 print(f"
") ^ 105:7 WPS305 Found `f` string print(f" ") ^ 105:18 WPS421 Found wrong function call: __import__ print(f" ") ^ 105:36 WPS349 Found redundant subscript slice print(f" ") ^ И еще кое-что: f -строки нельзя использовать как переменные docstrings :
def main(): f"""My name is /!""" print(main().__doc__) # => None
Заключение
Мы сразились со многими жуткими монстрами, расплодившимися в коде, и сделали королевство Python прекраснее. Вы герой, гордитесь собой!
Это было невероятное приключение. И я надеюсь, что вы узнали что-то новое для себя, что поможет в грядущих сражениях. Мир нуждается в вас!
На сегодня все. Удачи на тракте, пусть звезды ярко освещают ваш путь!
Полезные ссылки
- Python code disasters
- wtf, python?
- wemake-python-styleguide
А вы, вольные жители Python королевства, встречались с подобной черной магией в вашем коде? Удалось ли справиться с ней? Или битва еще не завершена (или вовсе проиграна)? Если вам нужна помощь бывалых магов и чародеев Python, то приходите к нам на Moscow Python Conf++ 27 марта 2020 года. У нас будут проверенные рецепты по борьбе с плохим и старым кодом от Владимира Филонова (доклад + 2 часа практики), Кирилла Борисова и Левона Авакяна.
- python
- open-source
- wemake-python-styleguide
- moscow python conf++
- Блог компании Конференции Олега Бунина (Онтико)
- Open source
- Python
- Программирование
- Совершенный код
Python: Магические числа
Возьмем пример программы, которая считает курс валют:
euros_count = 1000 dollars_count = euros_count * 1.25 # 1250.0 rubles_count = dollars_count * 60 # 75000.0 print(rubles_count)
С точки зрения профессиональной разработки, такой код не соответствует «лучшим практикам» — best practices.
В этом примере сложно понять, что значат числа 60 и 1.25 . Представьте, что вам придется разбираться в этом коде через месяц или через год — это будет сложно. Также сложно будет программисту, который не видел код ранее.
В нашем примере контекст легко восстановить, потому что переменные названы грамотно. Но в реальных проектах код значительно сложнее, поэтому догадаться до смысла чисел зачастую невозможно.
Проблема кроется в «магических числах» — magic numbers. Это числа, происхождение которых невозможно понять с первого взгляда — приходится глубоко вникать в то, что происходит в коде.
Чтобы предотвратить проблему, нужно создавать переменные с правильными именами. Так все встанет на свои места:
dollars_per_euro = 1.25 rubles_per_dollar = 60 euros_count = 1000 dollars_count = euros_count * dollars_per_euro # 1250.0 rubles_count = dollars_count * rubles_per_dollar # 75000.0 print(rubles_count)
В этой программе:
- Используется именование snake_case
- Две новые переменные отделяются от последующих вычислений пустой строчкой. Эти переменные имеют смысл и без вычислений, поэтому такое отделение уместно, потому что повышает читаемость
- Получился хорошо именованный и структурированный код, но он длиннее прошлой версии. Так часто бывает — это нормально, ведь код должен быть читабельным
Магические числа и непонятные именования переменных не ломают код, но делают его менее читабельным.
Нужно понимать, что компьютер в любом случае выполнит заданное вычисление. Однако другой программист будет читать код и ничего не поймет — это усложнит работу. Правильное именование переменных — половина успеха анализа кода.
Задание
Вы столкнулись с таким кодом, который выводит на экран общее количество комнат во владении нынешнего короля:
king = "Rooms in King Balon's Castle:" print(king) print(6 * 17)
Как видите, это магические числа: непонятно, что такое 6 и что такое 17. Можно догадаться, если знать историю королевской семьи: каждый новый король получает в наследство все замки от предков и строит новый замок — точную копию родительского.
Эта странная династия просто плодит одинаковые замки…
Избавьтесь от магических чисел, создав новые переменные, а затем выведите текст на экран.
Rooms in King Balon's Castle: 102
Названия переменных должны передавать смысл чисел, но должны при этом оставаться достаточно короткими и ёмкими для комфортного чтения.
Помните: код будет работать с любыми названиями, а наша система всегда проверяет только результат на экране, поэтому выполнение этого задания — под вашу ответственность.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное