Типы рефакторинга
Feb. 2nd, 2024 04:49 pmЕсть два осмысленных типа рефакторинга. (Я говорю про настоящий рефакторинг, когда функциональность не меняется от слова "совсем", не "лучше алгоритм", не "большее покрытие вариантов", а когда идёт чистое переименование и реструктуризация кода без какого-либо влияния на поведение).
Первый тип: мы готовимся к новому изменению, и нам надо подвинуть терминологию. Допустим, раньше это называлось "preparation", теперь нам надо добавить в preparation несколько подэтапов, первый шаг, переименовать существующий preparation в seeding, с той мыслью, что seeding будет одним из этапов нового preparation.
Мы берём понятную абстракцию с понятным именем и меняем это имя. Это рефакторинг вида S₁ -> S₂ (semantic -> semantic). В принципе, перемещение кода между модулями можно считать S -> S.
Второй тип интереснее, и я бы сказал, это то, что обычно подразумевают под "расчисткой".
Мы берём непонятное "нечто", у которого нет имени, и начинаем эти имена придумывать. Может быть мы меняем несколько непонятных имён, которые остались от предыдущих итераций, но больше не имеют смысла (допустим, у нас были whitelabels, но самого понятия в продукте больше нет, а есть whitelabel[0], который считается дефолтным хранилищем настроек, и whitelabel[2], который содержит в себе настройки для bootstrapping'а (я специально придумываю имена, чтобы дать почувствовать тут боль). Но whitelabel[2] часто лазит в whitelabel[0], хотя по архитектуре не положено, и даже пишет туда. По архитектуре не положено, но так как у нас осталось всего два, то это было быстрое решение заткнуть проблему. Теперь пришло время это исправлять.
Итак, мы берём нечто, что является самопротиворечивым, набор терминов, которые не отражают текущее видение, и ... И у нас нет того, во что мы хотим превратить. У нас нет слов. Слова из кода нам мешают, потому что являются thick concept, которые направляют нас по смыслу не туда, куда нам надо.
Это и есть самый настоящий жирный тяжёлый рефакторинг, ради которого стоит думать днями. Обычно я пытаюсь выкинуть слова, спуститься на слой ниже (или даже два) и описать какие фактические эффекты мы имеем и из чего (не используя слова вышестоящих уровней).
После этого для преобразований состояний можно пытаться придумывать новые слова. Появляется некий набросок, который в целом, правильный, но не учитывает все нюансы.
В этом месте, казалось бы, надо переименовать и подвигать весь код, но... Но это будет ошибкой, потому что преобразование ¬S -> S (из бессмыслицы в осмысленное) не может делаться в высокоуровневых терминах (мы пытаемся их создать!), то есть на практике такой рефакторинг представляет собой новую бессмыслицу (¬S ∨ S, то есть "всё что попало"), и единственный метод сохранить контекст в мозгу, это оперировать малыми участками кода и малыми шагами.
Для этого приходится придумывать временные костыли (абстракции), которые сохранятся только на время рефакторинга, но которые отдалённо подходят по смыслу для ¬S и ближе к S. "ближе" понятие субъективное, так что периодически приходится откатываться назад, иногда на несколько таких "мостиков".
Финалом успешного рефакторинга становится код, который не выразим в старых терминах (для него он "¬S"), но который более соответствует текущему использованию кода и свежим requirement.
... Во всей этой идилли есть только один смертельный момент: в момент такого рефакторинга невозможно ребейзиться с конфликтами. Невозможно. Если возникает ситуация конфликта, то надо выкидывать весь рефакторинг и начинать его заново. Невозможно разрешить конфликт между изменениями из множества несовместимых абстракций, неописуемых в старом ¬S, и всё ещё не в S.
... Симметрии ради можно рассмотреть рефакторинги вида ¬S->¬S, но смысл? Была фигня, фигня осталась.
Ещё есть S -> ¬S, но это либо git revert, либо откровенный саботаж. Более того, я искренне верю, что ни один органический мозг не может написать работающий ¬S по заданному S так, чтобы оно напоминало ¬S до рефакторинга вида ¬S->S. Это невозможно, поскольку не является мыслительной деятельностью.
Первый тип: мы готовимся к новому изменению, и нам надо подвинуть терминологию. Допустим, раньше это называлось "preparation", теперь нам надо добавить в preparation несколько подэтапов, первый шаг, переименовать существующий preparation в seeding, с той мыслью, что seeding будет одним из этапов нового preparation.
Мы берём понятную абстракцию с понятным именем и меняем это имя. Это рефакторинг вида S₁ -> S₂ (semantic -> semantic). В принципе, перемещение кода между модулями можно считать S -> S.
Второй тип интереснее, и я бы сказал, это то, что обычно подразумевают под "расчисткой".
Мы берём непонятное "нечто", у которого нет имени, и начинаем эти имена придумывать. Может быть мы меняем несколько непонятных имён, которые остались от предыдущих итераций, но больше не имеют смысла (допустим, у нас были whitelabels, но самого понятия в продукте больше нет, а есть whitelabel[0], который считается дефолтным хранилищем настроек, и whitelabel[2], который содержит в себе настройки для bootstrapping'а (я специально придумываю имена, чтобы дать почувствовать тут боль). Но whitelabel[2] часто лазит в whitelabel[0], хотя по архитектуре не положено, и даже пишет туда. По архитектуре не положено, но так как у нас осталось всего два, то это было быстрое решение заткнуть проблему. Теперь пришло время это исправлять.
Итак, мы берём нечто, что является самопротиворечивым, набор терминов, которые не отражают текущее видение, и ... И у нас нет того, во что мы хотим превратить. У нас нет слов. Слова из кода нам мешают, потому что являются thick concept, которые направляют нас по смыслу не туда, куда нам надо.
Это и есть самый настоящий жирный тяжёлый рефакторинг, ради которого стоит думать днями. Обычно я пытаюсь выкинуть слова, спуститься на слой ниже (или даже два) и описать какие фактические эффекты мы имеем и из чего (не используя слова вышестоящих уровней).
После этого для преобразований состояний можно пытаться придумывать новые слова. Появляется некий набросок, который в целом, правильный, но не учитывает все нюансы.
В этом месте, казалось бы, надо переименовать и подвигать весь код, но... Но это будет ошибкой, потому что преобразование ¬S -> S (из бессмыслицы в осмысленное) не может делаться в высокоуровневых терминах (мы пытаемся их создать!), то есть на практике такой рефакторинг представляет собой новую бессмыслицу (¬S ∨ S, то есть "всё что попало"), и единственный метод сохранить контекст в мозгу, это оперировать малыми участками кода и малыми шагами.
Для этого приходится придумывать временные костыли (абстракции), которые сохранятся только на время рефакторинга, но которые отдалённо подходят по смыслу для ¬S и ближе к S. "ближе" понятие субъективное, так что периодически приходится откатываться назад, иногда на несколько таких "мостиков".
Финалом успешного рефакторинга становится код, который не выразим в старых терминах (для него он "¬S"), но который более соответствует текущему использованию кода и свежим requirement.
... Во всей этой идилли есть только один смертельный момент: в момент такого рефакторинга невозможно ребейзиться с конфликтами. Невозможно. Если возникает ситуация конфликта, то надо выкидывать весь рефакторинг и начинать его заново. Невозможно разрешить конфликт между изменениями из множества несовместимых абстракций, неописуемых в старом ¬S, и всё ещё не в S.
... Симметрии ради можно рассмотреть рефакторинги вида ¬S->¬S, но смысл? Была фигня, фигня осталась.
Ещё есть S -> ¬S, но это либо git revert, либо откровенный саботаж. Более того, я искренне верю, что ни один органический мозг не может написать работающий ¬S по заданному S так, чтобы оно напоминало ¬S до рефакторинга вида ¬S->S. Это невозможно, поскольку не является мыслительной деятельностью.