Антипаттерны в написании учебных пособий или откуда растут ноги плохого кода?

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

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

Важно: пример из статьи не является вымыслом или шуткой!

Давайте сразу к делу: проанализируем первое попавшееся в поисковике решение квадратного уравнения на листочке.

Источник: https://hotcore.info/algebra/298548
Источник: https://hotcore.info/algebra/298548

Уже на таком простом примере видно, что для таких решений характерно следующее:

  • это сложно тестировать

  • низкая наглядность — сплошная простыня символов, которая будет нечитабельной при большом объеме

  • сложно разбить на маленькие функции блоки из которых итоговый алгоритм можно было бы собрать как из «кирпичиков»

  • если закралась ошибка/опечатка — её тяжело найти.

В основном это минусы не математики, а бумажного носителя, но вишенкой на торте всё же являются переменные. В математике имена переменных всегда из одной буквы, часто используются нижние индексы: x1, x2, …, xn, за что в среде программистов, напротив, применяют самые жёсткие санкции. В примере выше переменных не так много, но какая чехарда начинается в более крупных примерах? Большинство решений представляют собой длинный код шифрованный текст. Без раздумий никто не скажет, чем x4 важней x7 и что содержит каждая из них. Оппоненты возражают: индексированные переменные не требуют различий между собой, а в сложных решениях сквозную нумерацию индексов для всего решения вообще никто не применяет. Хорошо, а что тогда хранится в условной переменной bm и как она связана с условной h?

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

А теперь краткое содержание этой статьи одним абзацем: здесь приведены два варианта оформления математического упражнения, где «хороший» вариант наглядней и примерно в 160 раз короче «плохого», хотя и там и там был идентичный алгоритм. Плохой пример навеян увиденной методичкой для студентов некоего тюменского вуза. Статья написана после того, как я спустя почти 15 лет столкнулся с похожей в одном из екатеринбургских…

Плохой вариант является фантастическим антипаттерном и, к сожалению, он всё-таки из жизни, а не искусственно придуман «шутки для». На текущий момент это самая плохая публикация, которую я видел в печати.

Методичка, кстати, получилась такой по очень грустной и банальной причине.

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

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

Я знаю, что формулы почти никто не читает и даже есть утверждение: каждая формула уменьшает количество читателей вдвое. Поэтому я спрятал оба решения под спойлеры. Для понимания статьи нет необходимости читать оба примера от буквы до буквы, но желателен хотя бы беглый просмотр. Намекну, что формулы есть только в первом решении, а второе приведено в крайне сокращённом виде и без них. Знайте, что прочтение содержимого спойлеров сделает вашу совесть чистой и шелковистой!

В конце статьи приведена таблица сравнения учебной литературы с программированием.

Пример короткий, пример хороший

Упражнение: найти первообразную функции:

y = \int ln(x) \cdot (3x + 2) \mathrm{d} x
Решение

Для краткости весь этот интеграл мы так и будем называть — y. Самое простое решение — это интегрирование по частям. Для тех, кто забыл или не знал, что это такое — это формула производной от произведения, только наоборот. В школе мы писали её так:

(U \cdot V)^{'} = U^{'} \cdot V + U \cdot V^{'}

Если проинтегрировать левую и правую части этого равенства, то будет уже готовая формула интегрирования по частям (хотя пишут её чуть-чуть в другом виде):

U \cdot V = \int U^{'} \cdot V + \int U \cdot V^{'}

Оба интеграла в правой части у неё очень похожи структурой на интеграл у: они тоже состоят из двух сомножителей. Поэтому можно подставить y вместо любого и них и формула станет короче. Либо так:

U \cdot V = у + \int U \cdot V^{'}

Либо так:

U \cdot V = \int U^{'} \cdot V + у

Какой вариант выбрать и как? Опытным путём. Конечная цель — это чтобы второй из оставшихся интегралов был проще, чем исходный y (принцип: «упрощаем сложное»). Сравним оба варианта подстановки. Первый:

y = \int U^{'} \cdot V \implies \left [ \begin{array}{c} U^{'} = \ln( x ) \\ V = ( 3 \cdot x + 2 ) \end{array} \right ]y = \int U \cdot V^{'} \implies \left [ \begin{array}{c} U = \ln( x ) \\	V^{'} = ( 3 \cdot x + 2 ) \end{array} \right ]

Лично мне бросается в глаза то, что в первом варианте прямо сейчас придётся искать интеграл от логарифма, что легко, но всё же сложней, чем не искать его вовсе. Поэтому рискнём остановиться на втором варианте, избавимся от знака производной в его комментарии и посмотрим, что получится:

V^{'} = ( 3 \cdot x + 2 ) \implies V = 3 \cdot \frac{x^{2}}{2} + 2 \cdot x

Теперь вернёмся к формуле интегрирования по частям и подставим в неё этот комментарий:

\begin{array}{c} U \cdot V = \int U^{'} \cdot V + y \implies \\ \ln(x) \cdot ( 3 \cdot \frac{x^{2}}{2} + 2 \cdot x )  = \int \frac{1}{x} \cdot (3 \cdot \frac{x^{2}}{2} + 2 \cdot x) \mathrm{d} x + y \end{array}

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

  1. перенесём y в одну сторону, а всё остальное — в другую

  2. раскроем скобки внутри оставшегося интеграла

  3. решим наконец этот последний интеграл

Итак, первое:

y = \ln(x)( 3 \cdot \frac{x^{2}}{2} + 2 \cdot x ) - \int { \frac{1}{x} \cdot (3 \frac{x^{2}}{2} + 2 \cdot x)} \mathrm{d}x

Теперь второе:

y = \ln(x) \cdot ( 3 \cdot \frac{x^{2}}{2} + 2 \cdot x ) - \int 3 \cdot \frac{x}{2} + 2 \mathrm{d} x

Наконец, третье:

y = \ln(x) \cdot ( 3 \cdot \frac{x^{2}}{2} + 2 \cdot x ) - \frac{3}{2} \frac{x^{2}}{2} - 2 \cdot x

Вот и ответ (плюс константа).

Резюме: написанное мной решение уместилось (почти) на страницу А4. Если увеличить шрифт формул, добавить заголовок и два пояснения, то выйдет около двух страниц. Возьмём эту версию решения как образец, с которым будем сравнивать следующий пример.

Пример длинный, пример плохой

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

Здесь нечто!

Том (!) первый, введение, отрывок:

В современном мире не только лишь все могут осознанно воспринимать возложенные на них обязанности, что негативно сказывается на процессе коммуникации более старшего инициатора, являющегося A posteriori ответственным за постановку задачи, и исполнительным субъектом, который Actum ut supra осуществит подконтрольные действия в форме, пригодной для вышестоящей оценки.

Солидное начало? Это для упражнения, в котором условие умещается в 60 символов и меньше 20 символов в уравнении! Идём дальше — отрывок темы 3.2.2.1.2.3 параграфа 3.2.2.1.2 главы 3.2.2.1 части 3.2.2 раздела 3.2 третьего тома:

...Полученная формула является следствием формулы А, рассмотренной нами ранее в теме 2.3.2.1.1.4 параграфа 2.3.2.1.1 главы 2.3.2.1 части 2.3.2 раздела 2.3 второго тома, путём подстановки вместо второго слагаемого правой части выражения, приведённого в теме 1.1.1.2.1.1 параграфа 1.1.1.2.1 главы 1.1.1.2 части 1.1.1 раздела 1.1 первого тома с учётом преобразований, выполненных в теме 2.1.1.1.1.2 параграфа 2.1.1.1.1 главы 2.1.1.1 части 2.1.1 раздела 2.1 второго тома. …

Описанный нами результат мы будем использовать при получении ответа в будущей теме 3.1.1.1.1.1 параграфа 3.1.1.1.1 главы 3.1.1.1 части 3.1.1 раздела 3.1 этого тома.

На этом можно было бы закончить цитирование, но для тех, кому мало, я припрятал примерно воспроизведённое мной оглавление (с сокращениями!), которое фигурировало в первоисточнике. Обратите внимание на то, что индексы в оглавлении почти никогда не превышают единицу, ведь в каждой теме/параграфе/главе в основном одно-два предложения, которые и так исчерпывающе их описывают, что развить мысль, дописав что-то новое просто невозможно. Ближе к концу оглавления этот эффект «единичности нумерации» только усиливается.

Примерно таким является оглавление, не судите за сумбурность пересказа

Том 1. Проблематика формулирования рассматриваемого проекта решения.
Введение
Раздел 1. Математические выражения — основа описательной части передаваемой на исполнение работы
Часть 1. Уравнения
Глава 1. Линейные уравнения
Глава 2. Квадратные уравнения
Часть 2. Преобразование выражений
Глава 1. Элементарные арифметические преобразования
Параграф 1. Раскрытие скобок
Тема 1. Перестановка слагаемых
Параграф 2. Умножение уравнения на константу
Часть 3. Системы уравнений
Глава 1. Системы линейных алгебраических уравнений
Раздел 1.2. Представительность решений, стандартизация практик и оформления сопроводительной документации
Часть 1.1.1. Графическое представление документации
Глава 1.1. Графики функций
Параграф 1.1.1. Точки перегиба.
Часть 1.2. Сопроводительная литература и информационные источники
Глава 1.1. Выбор авторитетных источников литературы

Том 2. Интегральное и дифференциальное исчисление.
Введение
Раздел 2.1. Дифференцирование математических выражений и доступный математический аппарат
Часть 1. Таблица производных
Глава 1. Производные от алгебраических функций
Глава 2. Производные от тригонометрических функций
Часть 2. Дифференцирование сложных выражений
Раздел 2. Интегрирование функций и теоретико-методический комплекс приёмов интегральных вычислений
Часть 1. Таблица интегралов
Часть 2. Неалгоритмические особенности вычисления интегралов
Раздел 3. Взаимосвязь дифференцирования выражений и интегрирования функций
Часть 1. Интегрирование по частям

Том 3. Рационализация математического инструментария
Введение
Раздел 1. Точки внимания
Часть 1. Трудновыполнимые вычисления в интегралах
Раздел 2. Стратегия последовательных выборов хода решения
Часть 1. Временные затраты на вычисления выражений
...

Теперь давайте сравним оба решения

В первом решении 11 формул, незначительные упущения, есть пояснения и... всего 263 слова. Во втором — три тома, в каждом из которых около 80 страниц формата А5. В среднем на странице умещается 180 слов на русском языке. Итого: около 43 тысяч слов и уже неустановленное число формул.

Если провести аналогию первого решения с листингом программы, то в нём одно подключение внешней библиотеки, 7 строчек присвоения и один if … else. Комментарии, на мой взгляд, многословны, но по характеру хотя бы разделены на те, которые показывают:

  • что делает этот «код»

  • почему именно такой ход мысли

В решении нет ничего, что могло бы напоминать циклы и многочисленные зависимости, операторы goto и много чего ещё — оно примитивное и последовательное. Большинство предложений — короткие, очень мало вводных конструкций, деепричастных оборотов и вот этого вот всего. Допускается изменённый, но всё ещё приемлемый вариант, в котором будет около 20 формул и примерно на 80% больше слов.

Второе же решение оформлено его автором как целый курс. Это не тот масштаб для решения одного упражнения!

Что именно (чисто технически) делает второй пример таким плохим? Целый ворох «особенностей»:

  1. большое оглавление

  2. перегруженное оглавление

  3. перенасыщенная детализация оглавления

  4. многословность оглавления (да, именно так — переменные в уравнениях делаем однобуквенными, а в оглавлении отрываемся по полной)

  5. абстрактно сформулированные пункты оглавления

  6. иногда непоследовательное содержание

  7. бесконечные ссылки на «туда» и «там»

В решении много «сервисного» содержимого, бессодержательные комментарии, перекрёстные ссылки (аналог goto), что занимает большую часть текста. За таким объёмом данных не видно само упражнение. Вы уже сами отчётливо представили, что каждая страница такой методички в основном состоит из предложений, содержащих одни и те же слова, которые даже не пересекаются с математикой вообще и этим упражнением в частности.

А вот с точки зрения автора написанное пособие идеально — в нём всё досконально разложено по полочкам, а несмотря на обилие повторяющихся фраз «как было написано ...», «как было уже сказано ...» и пр., ни в одном параграфе нет повторяющейся информации. Почти каждая новая мысль хронологически выстроена верно и нет ни одной логической ошибки. Ветвистое оглавление имеет непротиворечивую (почти) структуру.

Как это связано с плохим кодом?

Плохим влиянием. Решение не только не полезно, но и вредно — оно отнимет у учащегося время и опыт, который он мог бы получить с другой методичкой, которая, кстати, есть в этой же библиотеке. Также у человека формируется ощущение «нормальности» плохого способа изложения, из-за чего вредный стиль может частично перейти в привычку и затем вылиться на первом же месте работы в виде плохого кода.

Вы замечали, как некоторые начинающие разработчики защищают свой первый код во время ревью? Это тоже является следствием обильной теории и скудной практики: «Я видел похожее на лабораторных, меня именно так учили и значит это правильно, критиковать такое нельзя».

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

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

Некоторые типичные ошибки таких изданий, у которых можно провести аналогию с работой программистов:

Стиль в учебной литературе

Стиль в работе программиста

Изложение материала не опирается на уже имеющиеся знания читателя. Факты описываются каждый раз заново.

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

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

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

Безликие имена переменных

Пресловутые a, c, tmp, rez, n, var1, ii, i2

Стремление выносить в оглавление каждое предложение.

Написание комментария к функции/методу, который больше, чем сам код.

Неуместное обобщение контекста прикладной задачи до фундаментальных основ теории (принцип: «от частного к общему»)

Написание функции, работа которой сильно разнится от контекста её вызова и предварительной настройки передаваемых параметров.

Избирательные ссылки на предыдущие и даже будущие утверждения в тексте.

Использование операторов безусловного перехода goto.

Введение «авторских» понятий и терминов, в контексте которых нужно читать весь материал.

Магические числа, глобальные переменные, захардкоженные текстовые константы, ip-адреса и т. п.

Попытка разместить на графиках часть промежуточных вычислений, которые были опущены ранее.

На примере MVC: запросы к базе данных из представления, детали бизнес-логики в контроллерах.

Попытка дополнить ответ чем-то, что подгоняет полученный результат под желаемый.

Написание костылей, которые мне даже стыдно перечислять.

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

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

Математика — это обязательный предмет в любой учебной программе: и школы, и техникума, и вуза. Значит каждому из нас хотя бы в лёгкой форме уже встречалась некачественная форма подачи математической нотации. Ознакомление с ней при отсутствии альтернативы действительно может повлиять на разработчика, который не привыкает думать о чистоте кода. Для него будет вполне естественно писать код, который нужно кропотливо расшифровывать, а не просто читать.

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

Что стоит почитать на тему хорошего написания кода? Лидер моего личного рейтинга — это всё-таки «Чистый код. Создание, анализ и рефакторинг» за авторством Роберта Мартина. Убеждён, что подобная книга обязана быть в библиотеке каждой команды, так как ожидать её наличие в вузе не стоит.

Как бы это можно было бы решить? Для медленно меняющихся (=фундаментальных) знаний — классической механики и основ высшей математики — легко. Для них следует использовать проверенные временем учебники и пособия, отработанные годами, с отточенными до совершенства и вылизанными до чистоты предложениями. К счастью, второй закон Ньютона в наш век не сильно-то поменялся. Фундаментальные знания — это не оперативные Stackoverflow или Q&A, а тем более не каталог парфюмерии, который для сохранения актуальности важно переписывать каждый год с нуля.

Масла в огонь подливает «план публикаций». Именно выполнение плановых показателей порождает тексты посредственного качества. Каждое учебное заведение непременно хочет иметь свой перечень учебной литературы, свою кузницу знаний. Выходит, что в действительности учебная литература ему нужна не лучшая, а только своя. Строгая модерация таких (и вот таких!) публикаций могла бы уменьшить негативный эффект.

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