Проблемы рефакторинга
Начну с того, что в случае, когда мы изучаем какую-нибудь новую технологию, которая увеличивает нашу продуктивность, сложно увидеть, когда эта технология не работает. Потому что мы ее только еще изучаем и не знаем всех аспектов. Обычно мы изучаем ее на примере какого-нибудь небольшого проекта, а это дает нам мало пищи для размышлений.
Например, возьмем объекты из ООП. Раньше, когда они только-только появились, было сложно представить случаи, когда их нежелательно использовать. Были видны в основном только преимущества.
Сейчас то же самое происходит с рефакторингом: мы знаем его преимущества, мы знаем, что рефакторинг может внести ощутимую пользу в нашу работу. Но, мне кажется, мало кто пытается осознать, какие недостатки у него есть. А таковые есть и мы сейчас поговорим о них. Ведь чем больше мы изучаем технологию, тем больше ее проблем мы сможем решить, тем больше проблем мы узнаем, которые не поддаются решению вообще.
Базы данных
Базы данных - одна из проблемных областей в рефакторинге. Многие программы туго связаны с определенными схемами баз данных. Это одна из причин непростого изменения базы данных. Другая причина - перемещение данных. Даже если Вы грамотно спроектировали Вашу систему для уменьшения зависимостей между структурой БД и объектной моделью программы, изменение структуры базы данных вынуждает разработчика перемещать данные из старой структуры в новую, а это длительный и нудный процесс.
Чтобы решить эту проблему, можно поставить некий промежуточный слой между объектной моделью и базой данных, который будет отвечать за связь между ними и изолировать изменения этих двух моделей. Т.е. если вы измените одну модель, другую менять не нужно будет. Нужно будет просто изменить промежуточный слой. Такой слой конечно усложнит программу, но придаст ей некоторую гибкость. Даже если не считать рефакторинга, это можеть быть полезно в случаях, когда мы не можем контролировать БД.
Но необязательно начинать с промежуточного слоя. Вы можете создать временный слой как часть объектной модели. Таким образом Вы получите хорошую возможность для изменений.
Объектные базы данных с одной стороны помогают, но с другой - затрудняют работу. Некоторые объектно-ориентированные БД могут автоматически перемещать данные из одной версии объекта в другую. Это уменьшает трату сил, но все еще занимает часть Вашего времени. Но если такой возможности автоматической миграции данных нет, Вам придется это делать руками, что опять же отнимает много сил. Поэтому здесь нужно быть осторожным: нужно семь раз подумать, прежде чем менять структуру классов. Вы можете легко изменять поведение класса, но при перемещении полей нужно быть осторожным. Нужно использовать временные поля, чтобы создать эффект того, что данные перемещены (но на самом деле они еще пока на месте). А когда Вы полностью уверены, где должны быть данные, Вы их перемещаете окончательно. При этом потребуется изменить только временные поля - это поможет избежать ошибок.
Изменение интерфейсов
Одно из преимуществ объектов в том, что они позволяют изменять реализацию программного модуля независимо от интерфейса. Т.е. Вы можете изменить что угодно в объекте, не волнуясь об остальных. Но! Измените интерфейс - и все полетит к чертям.
Есть множество приемов рефакторинга по изменению интерфейса. О них мы конечно будем говорить в дальнейших статьях. Один из таких приемов, например, - изменение имени метода в классе.
Вы без проблем можете изменить имя метода, если имеете доступ ко всему коду, который вызывает этот метод. Но если этот метод используется каким-то кодом, к которому Вы не имеете доступа, это проблема. Интерфейс, используемый таким кодом, можно назвать общим интерфейсом (т.е. интерфейс, доступный и используемый множеством пользователей, которых Вы, возможно, даже не знаете). Однажды создав такой интерфейс, Вы уже не можете его менять. Здесь уже нужно искать какой-то более сложный способ.
Говоря коротко, при изменении интерфейса Вам придется поддерживать обе его версии: старую и новую. По крайней мере до тех пор, пока все пользователи старого интерфейса не перейдут на новый. Можно сделать так, что старый интерфейс продолжит работать: например, пусть все методы старого интерфейса просто вызывают переименованные методы нового. Заметьте: именно вызывать новый метод из старого, а не копировать код старого метода в новый. Иначе этот код получится в итоге продублированным. При этом, если есть возможность, нужно отметить метод со старым именем устаревшим (в документации или прямо в коде) - так называемый deprecated-метод. Это даст возможность пользователям интерфейса знать, что метод устарел и побудить их к переходу на новый.
Это конечно решение, но это в принципе головная боль. Из всего сказанного выше следует, что общие интерфейсы лучше не создавать. А уж если и создаете - то максимально продумывайте его, ведь он создается скорее всего раз и навсегда. Это фундамент, который очень сложно менять потом. А иногда вообще невозможно (попробуйте изменить фундамент 9-ти этажного жилого дома - придется сносить дом).
Изменения проектных решений, которые сложно рефакторить
Можете ли Вы застраховать себя от ошибок в проектировании или задумать проект таким образом, что его рефакторить потом вообще не нужно будет? Я думаю, вряд ли, потому что изначально мы зачастую не обладаем полной информацией о том, что точно должно делать наше проектное решение.
Когда мы думаем над каким-то проектом, мы имеем в голове (или на листе бумаги… где угодно) несколько вариантов, которые можем использовать для создания этого проекта. И при этом мы должны спрашивать себя: а насколько сложно будет потом изменить эту схему вот на эту? Если кажется, что изменить будет просто, то создаем такое решение, котором может быть и не покрывает всех требований, но простое. А чем проще решение, тем его легче потом изменять. Но если мы не видим простых решений, придется покрепче и подольше подумать над этим (семь раз отмерь - один раз отрежь). Но сложных решений все-таки нужно избегать.
Когда мы не должны рефакторить вообще?
Бывают такие ситуации, в которых рефакторить не имеет смысла. Например, если код настолько запутанный, что его очень трудно понять, то его лучше написать заново. А если код еще к тому же имеет большое количество багов, который Вы не можете стабилизировать, то тут нет никаких сомнений, что легче будет его переписать.
Помните: код перед проведением рефакторинга должен работать!
Но есть и другой вариант: для большой системы - это выделение всего кода в отдельные модули и рефакторинг каждого модуля по отдельности. Особенно это касается старых систем.
Еще один случай, когда мы не должны рефакторить - если время завершения проекта подходит к концу (deadline). Если Вы не успеете закончить рефакторинг, Вы как бы залезете в долг. Вы должны будете завершить проект. И может это даже покажется Вам интересным занятием и сможете перенести этот долг. Но если этот долг окажется большим, это будет для Вас разорением.
Но с другой стороны, зачастую, нехватка времени означает, что над кодом нужно провести рефакторинг, чтобы потом этот код отнимал меньше времени.
А в следующий раз мы противопоставим рефакторинг и производительность.
До встречи!