Это перевод статьи Sandi Metz The Wrong Abstraction.
Я размышляю о последствиях «неправильной абстракции». Мой доклад с RailsConf 2014 «all the little things» включал раздел, в котором я высказала такое мнение:
дублирование значительно дешевле, чем неправильная абстракция
А заключение я подытожила советом:
выбирайте дублирование вместо неправильной абстракции
Эта небольшая часть довольно длинного доклада спровоцировала удивительно сильную реакцию. Несколько человек предположили, что я сошла с ума, но большинство выразило свои чувства примерно так:
This, a million times this! “@BonzoESC: “Duplication is far cheaper than the wrong abstraction” https://twitter.com/pims/status/442010383725760512
Сила эмоций заставила меня понять, насколько широко распространена и неразрешима проблема «неправильной абстракции». Я начала задавать вопросы и пришла к следующему шаблону:
Программист A натыкается на дублирование.
Программист A извлекает дублирование и как-то его называет.
Это создает новую абстракцию. Что-то вроде нового метода или, возможно, даже нового класса.
Программист А заменяет дублирование новой абстракцией.
Ах, код совершенен. Программист А довольно сваливает.
Проходит время.
Появляется новое требование, для которого текущая абстракция почти идеальна.
Программисту B поручают реализовать это требование.
Программист B чувствует почётное обязательство сохранить существующую абстракцию, но поскольку для каждого случая она — не совсем то же самое, чтобы принять параметр, код меняют, а затем добавляют логику, чтобы сделать всё правильно в соответствии с условными конструкциями и основываясь на значении этого параметра.
То, что когда-то было универсальной абстракцией, теперь ведёт себя по-разному в разных случаях.
Появляется ещё одно новое требование.
Программист X. Ещё один дополнительный параметр. Ещё одна условная конструкция. Зацикливание до момента, пока код не станет слишком сложным для понимания.
Тут в истории появляетесь вы, и ваша жизнь резко делает драматический поворот в худшую сторону.
Существующий код оказывает сильное влияние. Само его наличие доказывает, что он правильный и необходимый. Мы знаем, что код — это приложенные усилия, и сильно мотивированы сохранить ценность этих усилий. К сожалению, печальная правда в том, что чем сложнее и непостижимей код, т. е. чем массивней вклад в его создание, тем больше мы чувствуем давление сохранять его («ложные выводы о необратимых затратах»). Как будто наше бессознательное говорит нам: «Боже, тут всё так запутано — наверно, потребовалась вечность, чтобы сделать всё как надо. Конечно, это действительно очень, очень важно. Грех, если все эти усилия пропадут впустую.»
Когда в этой истории появляетесь вы, как в пункте 8, давление может заставить вас продолжать, то есть реализовать новое требование, изменяя существующий код. Но это будет жестоко, скорее всего... Код больше — не единая общая абстракция, а нагруженная условиями процедура, которая чередует ряд неясно связанных идей. Его трудно понимать и легко сломать.
Если вы окажетесь в такой ситуации, избегайте влияния необратимых затрат. Когда вы сталкиваетесь с неправильной абстракцией, самый быстрый способ продвинуться дальше — вернуться. Действуйте в такой последовательности:
- Восстановите дублирование, вернув абстрагированный код туда, где происходит вызов.
- Внутри каждого вызова, используйте передаваемые параметры, чтобы определить подмножество объединённого кода, который этот конкретный вызов исполняет.
- Удалите части, которые не нужны для данного вызова.
Это устранит и абстракцию, и условные конструкции, а также сократит каждый вызов и оставит в нём только необходимый код. Когда вы перематываете решения таким способом, обычно обнаруживается, что, хоть каждый вызов, вроде как и обращался к общей абстракции, выполняемый ими код, был довольно уникальным. Как только вы полностью удалите старую абстракцию, вы можете начать заново, повторно изолировать дублирование и повторно извлечь абстракции.
Мне встречались случаи, когда люди пытались уверенно продолжать работать с неправильной абстракцией, но у них это слабо получалось. Добавление новых фич было невероятно сложным, а каждый положительный результат ещё больше усложнял код, что делало добавление следующей фичи ещё сложнее. Когда они поменяли свою точку зрения с «мне нужно сохранить своё вложение в этот код» на «этот код имел смысл какое-то время, но, мы, возможно, извлекли из него максимум пользы», и позволили себе переосмыслить свои абстракции, с точки зрения текущих требований, всё упростилось. После того, как они объединили код, стало очевидно куда двигаться дальше, а добавление новых фич стало быстрее и проще.
Мораль этой истории? Не попадайте в ловушку ложных выводов о необратимых затратах. Если вы заметите, что передаёте параметры и добавляете условные пути через общий код, абстракция неверна. Возможно, было правильно с этого начинать, но тот день прошёл. Как только ошибочность абстракции доказана, лучшая стратегия — восстановить дублирование и оно покажет, что правильно. Хоть иногда и имеет смысл сохранить несколько условных конструкций, чтобы получить представление о том, что происходит, чем раньше вы откажетесь от неправильной абстракции, тем меньше вы пострадаете.
Когда абстракция ошибочна, самый быстрый шаг вперёд — это возврат. Это не значит, что вы сдаётесь, это движение в сторону улучшения. Поступайте так. Вы сделаете лучше свою жизнь и жизнь всех, кто будет следовать вашему опыту.