Признаки необходимости рефакторинга

Главная Форумы Программирование Технология программирования Признаки необходимости рефакторинга

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 3 мес., 2 нед. назад.

  • Автор
    Сообщения
  • #4033

    Эта статья будет посвящена не менее важному вопросу, чем само понятие рефакторинга. – Когда его нужно проводить. На блоге уже поднимался этот вопрос в статье “SOLID принципы. Рефакторинг“, однако принципы эти направлены в большей части на правильное использование механизмов объектно-ориентированного программирования. Случаев же когда необходим рефакторинг значительно больше – по этим темам написаны целые книги. В частности, Роберт Мартин в книге “Чистый код” описывает множество случаев улучшения кода, а затем приводит большой пример последовательного рефакторинга, в результате которого сложный, непонятный и тяжело тестируемый проект стал хорошим во всех отношениях. Более строго эти вопросы описывает Мартин Фаулер в книге “Рефакторинг. Улучшение существующего кода”. Мы же сейчас рассмотрим моменты, которые наиболее часто встречаются в коде.

    Есть набор признаков, по которым можно легко определить, что путь к совершенствованию кода простирается на часы работы и километры переписанного кода. Конечно, я утрирую, но бывает всякое))

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

    Дублирование кода

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

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

    Слишком объёмные методы

    Правило, которое часто забывается, когда увлекаешься процессом кодинга, и радует уже то, что процесс идёт, работа движется, и всё как бы получается, гласит: КАЖДЫЙ МЕТОД ДОЛЖЕН ВЫПОЛНЯТЬ ОДНУ ЗАДАЧУ.

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

    Слишком большой класс

    Ещё один признак задуматься о рефакторинге – объёмные классы. Тогда в первую очередь нужно подумать, почему их определение получилось настолько объёмным. Не слишком ли много атрибутов у класса? И если да, то все ли они действительно являются характеристиками объекта данного типа или мы по ошибке наделили класс чужими атрибутами.

    Не слишком ли много методов определяется в классе? И если да, то все ли они являются поведенческими характеристиками этого типа данных или мы поторопились отдать классу функционал, которым должны обладать какие-то другие типы данных?

    “Деревянный” код

    Часто получается вследствие недостатка планирования архитектуры приложения. Код получается как бы монолитный, его структура не рассчитана ни на какие изменения, расширения, так сказать одноразового использования. Дальше того вида, который получился, пойти невозможно даже при большом желании. Все классы жёстко связаны друг с другом, не применяется абстракция. При первой же попытке внести какие-то изменения в любой части кода – всё поломается. Хочешь – пользуйся, не хочешь – перепиши всё заново.

    Решение – обязательно прорабатывать архитектуру в плане её гибкости и возможности расширения – не забывать о принципах ООП, в частности, абстракцию и полиморфизм.

    Нецелесообразные функции

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

    Тогда возникает предположение, что, скорее всего, дело не в данных, которые нужно взять неведомо откуда, а в том, что сама функция размещена не в том классе, поэтому для решения её задачи данных и не хватает.

    Что делать – перенести функцию в другой класс, где она будет уместнее.

    Авось пригодится

    Когда написание кода начинается не с планирования архитектуры и продумывания алгоритма, а прямо с написания кода (знакомо, да?), трудно определиться сразу наверняка, какие типы данных будут определены и какие данные и функции где будут реализованы. Поэтому начинается хаотичное кодирование, когда какие-то классы определяются вообще пустыми, потому что так и не решили, что именно там будет, но точно потом пригодится. Поля классов создаются на всякий случай, когда неясно, пригодится ли в дальнейшем такое поле или нет (но лучше сразу написать, чтобы не забыть).

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

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

    Группирование данных в типы

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

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

    Комментарии

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

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

    Теперь ответим на вопрос, какие из вышеперечисленных пунктов свидетельствуют о необходимости проведения рефакторинга. Правильно, все, кроме первого. А почему? Потому что они указывают на наличие изъянов кода.

    Щедрое наследство

    Этот недостаток кода встречается не только у новичков, но и у профессионалов. Он заключается в том, что у базовых типов (в частности классов) слишком много лишней информации для наследников. Переменные и методы базового класса, которые не используются и вряд ли будут задействованы классами – наследниками. Это нарушение одного из принципов SOLID – принципа разделения интерфейса.

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

    Решение – создание “тонких” интерфейсов для наследования.

    Якоря

    Этот недостаток кода заключается в жёстком указании адресов файлов (например, изображений, аудио-файлов, видео, баз данных), не являющихся встроенными ресурсами.

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

    string path = @”D:\Images\MyPhoto.jpg”;

    или так:

    string path1 = @”C:\Users\admin\Documents\Visual Studio 2010\Projects\CoolApp\Images\MyPhoto.jpg”;

    В первом случае указывается адрес некоего файла, который даже не находится в каталоге самого приложения, во втором же – всё таки в подкаталоге приложения. Чем плох такой подход? Тем что файл, находящийся вне приложения, может быть случайно или намеренно удалён или перемещён, каталог может быть переименован, и тогда адрес, указанный в переменной path, становится неактуальным. Во втором случае актуальность теряется, если мы, например, переместим сам каталог проекта в другое место.

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

    Решение – определять универсальный формат адреса.

    Универсальный и бесполезный

    Данный случай имеет место, если в погоне за универсальностью и расширяемостью мы получаем тип, перенасыщенный всякими интерфейсами, делегатами, но ничего очевидного, по сути, не выполняющий. То есть наши стремления имеют обратный эффект, когда так называемая “универсальность” рассеивает назначение класса. Когда смотришь на этот класс, возникает вопрос: а что он делает? Авторы кода с обидой в голосе скажут: он всё умеет!!!! Он умеет работать с любыми типами данных, передаваемых через параметры указанного интерфейсного типа, он умеет выполнять любое вычисление, которое будет задано через нужный делегат!!!!

    Другими словами – ничего конкретного…

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

    Решение: классы, как другие типы (интерфейсы, структуры, делегаты, перечисления) , должны создаваться в большинстве случаев под конкретную цель (я не говорю, что обязательно под конкретное приложение).

    Архитектурная чушь

    Это следствие одержимости шаблонами проектирования, и данной теме у меня на блоге посвящена отдельная статья, которая так и называется “Злоупотребление шаблонами проектирования, или код с душком”.

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

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

    Операторы switch

    Основным недостатком оператора switch является его нерасширяемость: сколько вариантов значения указано – столько и будет всегда. А если предположить, что каждый case привязан к какому-то варианту типа данных, то получается типичный “хардкод”. И хочется спросить, а что будет, если появятся другие варианты или какие-то из указанных станут неактуальными? Придётся переписывать метод, а этого делать нельзя.

    Пример, когда switch или его вариант – составной if нужно заменить на внедрение полиморфизма в код продемонстрирован в моей статье, посвящённой Принципу открытости-закрытости

    “Недофункция”

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

    Для чего это делается – трудно сказать. Скорее – это слишком буквально воспринятое правило о том, что каждая функция выполняет строго одну задачу. Под словом “задача” может подразумеваться алгоритм или вычисление, которое выполняется часто, соответственно, где-то повторяется , а соответственно, проще выделить его в отдельную функцию и вызывать где нужно, чем копировать из раза в раз.

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

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

    На этом я пока остановлюсь, хотя причин для рефакторинга значительно больше, чем здесь перечислено, а причина в том, что пределов идиотизму нет ))) шучу, конечно.

Для ответа в этой теме необходимо авторизоваться.