Хочу поднять эту тему для всеобщего обсуждения. Не уверен, что это в Управление Проектами, потому не обижусь, если модераторы переместят нить.
1. Что есть спагетти-код?
Под этим термином понимается локально переусложненный и локально непонятный код. Классические примеры — активное использование goto, особенно вверх по коду, а также функции огромного размера.
Сюда же относится и известная болячка в виде обработки большого количества частных случаев, причем на уровне отдельных строк кода и групп строк, также частных случаев, ломающих парадигму окружающего кода — например, добавление дополнительных условий вместо дополнительных значений переменной "состояние" к конечному автомату.
В некоторых случаях спагетти возникает от того, что к нему располагает окружающая среда платформы, как, например, в файловых системах Windows, где в один обработчик приходит и cached, и noncached, и noncached paging IO, которые существенно отличаются в мелочах.
Ниже эта болячка будет называться "спагетти в узком смысле слова".
2. Чем плох спагетти код?
Высокой сложностью его развития и поддержки, особенно при передаче от человека человеку, а также при возврате к тому же человеку через полгода его занятий чем-то другим (равносильно передаче другому).
Высокой вероятностью внесения крупных багов мелкими правками, связанной с возможным неучетом совершающим правку человеком частных случаев в "спагетти в узком смысле слова", а также перегруженности объема внимания человека синтаксисом языка, мультипарадигмальностью или же просто неряшливым написанием кода. Это есть следствие того, что правильное понимание такого кода требует значительных интеллектуальных усилий — зачастую больших, чем его создание.
Ситуация особо усугубляется тем, что частные случаи могли быть ранее внесены ради правки некоего бага — например, возникшего в граничных условиях. Слом таких случаев неаккуратными последующими правками может привести к повторному возникновению ранее исправленного бага.
3. Является ли спагетти абсолютным злом?
Нет.
Спагетти может быть почти идеально отлажен, реализовывать требования, в т.ч. по производительности.
4. Зависит ли вероятность появления спагетти от языка?
Слабо. Пожалуй, особого рассмотрения заслуживают только мультипарадигменные языки с крайне тяжелым для человека синтаксисом — Perl есть прекрасный пример. Смешение парадигм добавляет нагрузку на интеллект и объем внимания читателя кода, повышает возможность превышения некоего порога, после чего человек обязательно что-то упустит важное. Это верно не только для Perl-образного синтаксиса, но и для более легкого для восприятия, но тоже мультипарадигмального языка Си++ — смешением парадигм можно и на нем сделать спагетти.
"Спагетти в узком смысле слова" от языка не зависит.
5. Общий паттерн, наблюдаемый в спагетти.
Функция DoSomething выполняет действия, существенно зависящие от неких внешних условий. При этом отслеживание этих внешних условий делается в ней в виде if( условие ) две-три-четыре строки операторов. С некоей концептуальной точки зрения функция выполняет два или более различных действий, но все они равномерно перемешаны в ней самой, и не сразу понятно, что в ней к какому виду действия относится.
Такие вещи тяжело поддаются рефакторингу, ведь речь идет по сути либо о делении функции на несколько похожих, либо же о переработке всей архитектуры функции/объекта с его делением на несколько объектов, связанных полиморфными интефейсами.
6. Причины возникновения спагетти.
Злоупотребление мультипарадигмальностью обычно возникает от разудалого желания девелопера продемонстрировать свое владение сложными возможностями языка, или же от его же желания потренироваться в применении этих возможностей.
Со "спагетти в узком смысле слова" дело чуть другое. Как правило, такие вещи возникают — и накапливают негативные явления — в условиях быстрых правок кода под лозунгом "реализуем фичу самым минимальным возможным усилием, минимально трогая оттестированное". Мелкие правки и добавление частных случаев к имеющемуся коду действительно самые дешевые и по разработке, и по затрагиванию уже оттестированно. Но именно они накапливают "макароны".
7. Архитектор и спагетти.
Паттерн "1 умный архитектор — много глупых кодеров" предрасполагает к появлению спагетти. Да, крупноблочных архитектурных ошибок такой архитектор не сделает, но спагетти накапливается на уровне строк (редко десятков строк) кода. Витающий в облаках архитектор, оторванный от кода, этого и не заметит. Кроме того, в таких командах роль кодера принижена до уровня исполнителя распоряжений, а человеку в такой ситуации свойственно сделать побыстрее и получить бонусы, а не сделать по уму, задержав сроки, а то и затронув чужой код.
Помогает не столько архитектор, сколько лид-девелопер с функциями играющего тренера, а еще лучше — вся команда, составленная из толковых девелоперов без жесткого прессинга по срокам имплементации конкретных фич.
8. Неизбежно ли спагетти?
В какой-то мере, видимо, да. Его может быть больше или меньше, но, думаю, что какой-то процент такого кода есть в любом старом, долгоживущем проекте.
9. Проблемы, не имеющие отношения к спагетти.
Abstraction overhead. Спагетти возникает от слишком низкого уровня абстрагирования, оверхед же — от слишком высокого.
Abstraction inversion. Эта проблема мне кажется более серьезной, чем спагетти (именно она зачастую и имеется в виду в тех случаях, когда от кода хочется материться), и возникает от элементарного недоразвития одного из абстрактных слоев. При этом уровень абстрагирования и деления на слои может быть достаточным для качественного кода. Правильное решение проблемы — добавление функционала к бедному абстракному слою, а не обход этих недоделок с использованием побочных эффектов в абстракциях более высокого уровня.
10. Меры, снижающие вред от спагетти.
Массовое написание комментариев, при поддержании их адекватности коду. Лишними комментариями ничего не испортишь, а массовость повысит шансы на то, что наскоро добавленные частные случаи будут по крайней мере откомментированы, т.е. не будет загадочного кода. Это сильно снижает вред от спагетти.
Ослабление ротации девелоперов с подпроекта на подпроект, и ослабление текучести кадров. Чем реже передается владение кодом — тем меньше шанс, что спагетти превратится в реальный баг.
Административный отсев некоторых парадигм программирования, имеющихся в некоторых языках.
Уход от паттерна "1 Умник — много рабов" к паттерну, предполагающему больший интеллект рядовых исполнителей и большую приближенность лучших мозгов команды к непосредственно коду.
Увеличение вложений времени каждого исполнителя в продумывание перед кодированием. Траты времени на продумывание микроархитектуры незначительны по сравнению с тратами времени на поиск багов в спагетти и их правку (без внесения новых багов).
Использование полиморфизма и паттернов типа "страгетия" и "декоратор", вынесенных в отдельные объекты с четко определенными интерфейсами. Добавление частного случая в такой ситуации сводится к написанию еще одной имплементации того же интерфейса. Естественно, что, чтобы отследить исполнение вот этой идеи, нужен достаточно высокий уровень людей, работающих непосредственно с кодом.
В случаях, когда к спагетти предрасполагает окружающая среда, как в ядре Windows — имеет смысл от нее абстрагироваться прокладками, и в дальнейшем эту абстракцию не нарушать.
Роадмапы, т.е. перспективные планы требований к продукту. Чем лучше они проработаны — тем больше информации поступает "на вход" к архитектору или микроархитектору, и тем меньше шансов на то, что новое требование приведет к добавке точечного частного случая, что накапливает спагетти.
Отдельно стоит вопрос о дублировании кода. Если вместо 1 функции, делающей три разных, отличных в мелочах вещи, создается три функции, дублирующие друг друга на 90% — то это зачастую менее болезненно, чем спагетти. Например, функция ReadWrite с булевским параметром Direction имеет намного больший потенциал к развитию спагетти, чем 2 функции Read и Write.