Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Ruby
Code

Основы рефакторинга кода с запашком на Ruby/Rails. Часть 02

by
Length:LongLanguages:
This post is part of a series called Ruby / Rails Code Smell Basics.
Ruby/Rails Code Smell Basics 01
Ruby/Rails Code Smell Basics 03

Russian (Pусский) translation by Marat Amerov (you can also view the original English article)

file

Это вторая часть небольшой серии статей о «коде с запашком», и возможных рефакторингах. Целевая аудитория, для которой я это писал, — это новички, которые слышали об этой теме, и те кто, возможно, откладывал погружение в эту тему. В следующей статье рассматриваются «Завистливые функции», «Стрельба дробью» и «Расходящиеся модификации».

Темы

  • Завистливые функции
  • Стрельба дробью
  • Расходящиеся модификации

То, что вы быстро поймете работая с «кодом с запашком», состоит в том, что некоторые из них являются очень близкими родственниками. Даже их рефакторинг иногда связан — например, Встраивание класса и Извлечение класса не так уж и отличаются.

Например, при встраивании класса, вы извлекаете весь класс, в процессе очищения исходно. Таким образом, это что-то типа извлечение класса с небольшим завихрением. То, что я пытаюсь сделать, — это то, что вы не должны себя чувствовать ошеломленным от количества запашков и рефакторингов, и конечно же, не быть обескураженными от их искусных имен. Такие названия, как «Стрельба дробью», «Завистливые функции» и «Расходящиеся модификации», могут показаться фантастическими и запугивающими для людей, которые только начали погружение. Может быть, я ошибаюсь, конечно.

Если вы слегка погрузитесь в эту тему, и пробуете различные паттерны рефакторинга «кода с запашком», вы быстро обнаружите, что они очень похожи. Множество паттернов рефакторинга — это просто разные стратегии, для достижения точки, где у вас есть классы, которые являются лаконичными, хорошо организованными и сосредоточены на небольшом объеме обязанностей. Я думаю, будет справедливым сказать, что если вы этого добьетесь, вы будете в большей степени опережать большинство — не то, чтобы быть впереди других это так важно, но, такой дизайн классов часто отсутствует в коде разработчиков, которые не считаются экспертами.

Так почему бы вам не попасть в эту лигу раньше и создать конкретную основу для дизайна вашего кода. Не верьте тому, что может быть вашим собственным предубеждением — что это продвинутая тема, которую вы должны отложить на некоторое время, пока не будете готовы. Даже если вы новичок, если вы делаете небольшие шаги, вы можете заняться рефакторингом «кода с запашком» намного раньше, чем вы могли бы подумать.

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

Завистливые функции

Вернемся к примеру из предыдущей статьи. Мы извлекли длинный список параметров для #assign_new_mission в объект-параметр через класс Mission. Пока что это круто.

M с завистливой функцией

Я кратко упомянул, как мы можем еще больше упростить класс M, переместив метод #assign_new_mission в новый класс для объекта-параметра. Я не обращал внимания на то, что у M была легко излечимая завистливая функция. М был слишком много знал об атрибутах Mission. Иначе говоря, он задавал слишком много «вопросов» об объекте миссии. Это не только плохой случай микроменеджмента, но и очень распространенный «код с запашком».

Позвольте мне показать вам, что я имею в виду. В M#assign_new_mission M «завидует» данным в новом объекте-параметра и хочет получить к нему доступ по всему месту.

  • mission.mission_name
  • mission.agent_name
  • mission.objective
  • mission.licence_to_kill

Кроме того, у вас также есть объект-параметр Mission, который отвечает только за передачу данных — это еще один «запашок», Класс Данных.

Вся эта ситуация говорит вам в основном о том, что #assign_new_mission должен быть где-то в другом месте, а M не нуждается в подробностях о том, как назначаются миссии. В конце концов, почему бы не возложить на миссию ответственность за назначение новых миссий? Помните, лучше всегда держать вместе сущности, которые также меняются вместе.

M без завистливых функций

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

Завистливые функции порождают запутанность — я не имею в виду хороший вид, который позволяет информации путешествовать быстрее света — я говорю о том, что со временем может подвергнуть ваш импульс развития размалывать все более приближающуюся остановку. Нехорошо! Почему так? Эффекты пульсации во всем коде будут создавать сопротивление! Изменение в одном месте затрагивают много чего во всем проекте. (Хорошо, немного чересчур драматично, но я даю себе B+).

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

Стрельба дробью

Название звучит немного глупо, не так ли? Но в то же время это довольно точное описание. Звучит как серьезный бизнес, и это так! К счастью, это не так сложно понять, но, тем не менее, это один из неприятных «запашков». Почему? Потому что он порождает дублирование, как никто другой, и легко упустить из виду все изменения, которые вам нужно будет сделать, чтобы исправить ситуацию. Что происходит во время стрельбы дробью, вы делаете изменения в одном классе/файле, и вам также нужно коснуться многих других классов/файлов, которые необходимо обновить. Надеюсь, это не то что вам нужно.

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

Если у вас есть спектр с сухим кодом на одной стороне, то код, который часто требует стрельбы дробью, в значительной степени находится на противоположном конце. Не ленитесь и позвольте себе войти на эту территорию. Я уверен, что вы предпочтете открыть один файл и применить там свои изменения и закончить это. Это такая лень, к которой нужно стремиться!

Чтобы избежать этого «запашка», вот краткий список симптомов, на которые вы можете обратить внимание:

  • Завистливые функции
  • Тесная связь
  • Длинный список параметров
  • Любая форма дублирования кода

Что мы имеем в виду, когда говорим о коде, который связан? Допустим, у нас есть объекты A и B. Если они не связаны, вы можете изменить один из них, не затрагивая другого. В противном случае, вам чаще всего придется иметь дело с другим объектом.

Это проблема и «Стрельба дробью» является симптомом для тесной связи. Поэтому всегда следите за тем, как легко вы можете изменить свой код. Если это относительно легко, это означает, что ваш уровень связи приемлемо низкий. Сказав это, я понимаю, что ваши ожидания были бы нереалистичными, если вы ожидаете, что сможете избежать связанности все время любой ценой. Этого не произойдет! Вы найдете веские причины, чтобы принять решение против этой принудительной замены условий с полиморфизмом. В таком случае, немного мутаций, стрельбы дробью и сохранения API объектов в синхронизации, стоят того чтобы избавиться от тонны операторов case через Null Object (подробнее об этом в более поздней части).

Чаще всего вы можете применить один из следующих рефакторингов для лечения ран:

  • Перемещение поля
  • Встраивание класса
  • Извлечение класса
  • Перемещение метода

Давайте посмотрим на некоторый код. Этот пример представляет собой фрагмент того, как приложение Spectre обрабатывает платежи между их контрагентами и злыми клиентами. Я упростил платежи, немного, за счет стандартных сборов, как для подрядчиков, так и для клиентов. Поэтому не имеет значения, поручено ли Spectre похитить кошку или вымогать целую страну: плата остается прежней. То же самое касается того, что они платят своим подрядчикам. В редком случае операция идет на юг и другая Nr. 2 должна буквально перепрыгнуть акулу, Spectre предлагает полный возврат, чтобы держать злых клиентов счастливыми. Spectre использует какой-то проприетарный гем, который является заполнителем для любого вида платежного процессора.

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

Пример с запашком стрельбы дробью:

Когда вы смотрите на этот код, вы должны спросить себя: «Должен ли класс EvilClients действительно беспокоиться о том, как платежный процессор принимает новых злых клиентов и как они взимаются за операции?» Конечно нет! Это хорошая идея, чтобы распределить различные суммы, чтобы заплатить повсюду? Должны ли отображаться детали реализации платежного процессора в любом из этих классов? Скорее всего, нет!

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

В этом примере должно быть легко видно, что изменения в способах принятия и передачи платежей создают эффект пульсации во всем коде. Кроме того, если вы хотите изменить сумму, которую вы взимаете или передаете своим подрядчикам, вам понадобятся дополнительные изменения повсюду. Основные примеры стрельбы дробью. И в этом случае мы имеем дело только с тремя классами. Представьте себе свою боль, если речь идет о более реалистичной сложности. Да, это из-за кошмаров. Давайте посмотрим на пример, который будет более разумным:

Пример без запаха стрельбы дробью и извлеченного класса:

Мы здесь применили API PaymentGem в нашем классе. Теперь у нас есть одно центральное место, где мы применяем наши изменения, если решим, что, например, SpectrePaymentGem будет лучше для нас работать. Больше не нужно касаться внутренних взаимосвязей с несколькими платежами, если нам нужно адаптироваться к изменениям. В классах, связанных с платежами, мы просто создавали экземпляр PaymentHandler и делегировали необходимые функции. Легко, стабильно, и нет причин для изменения.

И не только мы держали все в одном файле. В классе PaymentsHandler есть только одно место, которое нам нужно поменять местами и ссылаться на возможный новый процессор платежей — в initialize. Конечно, если у новой платежной службы есть совершенно другой API, вам нужно настроить тела нескольких методов в PaymentHandler. Это крошечная цена, которую можно заплатить по сравнению с полной стрельбой дробью — это больше похоже на операцию для небольшого осколка в вашем пальце. Хорошая сделка!

Если вы не будете осторожны, когда пишете тесты для такого платежного процессора, как этот, — или любой внешний сервис, на который вы должны положиться, — вы, возможно, испытываете серьезные головные боли, когда меняете свой API. Конечно, они «страдают от изменений». И вопрос не в том, изменит ли он свой API, только тогда.

Благодаря нашей инкапсуляции мы находимся в гораздо лучшем положении, чтобы заглушить наши методы для процессора платежей. Зачем? Потому что методы, которые мы заглушаем, являются нашими собственными, и они меняются только тогда, когда мы хотим их. Это большая победа. Если вы новичок в тестировании, и это вам не совсем понятно, не беспокойтесь об этом. Не торопитесь, эта тема может быть сложной вначале. Поскольку это такое преимущество, я просто хотел упомянуть об этом ради полноты.

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

Если вы не совсем довольны этим классом, и видите возможности для рефакторинга, я приветствую вас, и я рад в этом признаться. Хорошее начало может иметь дело с тем, как вы находите payments_ids. Сам класс тоже немного переполнился...

Расходящиеся модификации

Расходящиеся модификации, в некотором роде, противоположны стрельбе дробью — там, где вы хотите изменить одну вещь, и вам нужно произвести это изменение через кучу разных файлов. Здесь один класс часто изменяется по разным причинам и по-разному. Моя рекомендация состоит в том, чтобы идентифицировать части, которые меняются вместе и извлечь их в отдельный класс, который может сосредоточиться на этой единственной ответственности. Эти классы, в свою очередь, также должны иметь не более чем одну причину для изменения, если нет, другой «запашок» «Расходящиеся модификации», скорее всего, ждет вас.

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

Не поймите меня неправильно: частые изменения это не «запашок». Это полезный симптом. Другим очень распространенным и более явным признаком является то, что этот объект должен манипулировать более чем одной ответственностью. Принцип единой ответственности SRP является отличным руководством для предотвращения этого запаха кода и для написания более стабильного кода в целом. Это может быть сложно, но, тем не менее, стоит обратить на это внимание.

Давайте посмотрим на этот скверный пример ниже. Я немного изменил пример стрельбы дробью. Blofeld, голова Spectre, может быть известен микроматериалам, но я сомневаюсь, что он был бы заинтересован в половине того, с чем связан этот класс.

Класс Spectre имеет слишком много разных вещей, о которых он знает:

  • Назначение новых операций
  • Плата за их грязную работу
  • Печать назначение миссий
  • Убийство безуспешных призрачных агентов
  • Работа с внутренними компонентами PaymentGem
  • Оплаты агентам/подрядчикам Spectre
  • Он также знает о сумме денег для начисления и выплаты

Семь различных обязанностей по одному классу. Нехорошо! Вам нужно изменить способ удаления агентов? Один вектор для изменения класса Spectre. Вы хотите обрабатывать выплаты по-другому? Другой вектор. Вы получаете дрейф.

Хотя этот пример далеко не реалистичен, он по-прежнему рассказывает историю о том, как легко излишне использовать поведение, которое необходимо часто менять в одном месте. Мы можем сделать лучше!

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

Вы хотите обрабатывать платежи по-разному? Теперь вам не нужно прикасаться к классу Spectre. Вам нужно платить или платить иначе? Опять же, нет необходимости открывать файл для Spectre. Печать назначение операций теперь является операцией — там, где она принадлежит. Это оно. Не слишком сложно, я думаю, но, безусловно, один из наиболее распространенных запашков, с которыми вам следует познакомится пораньше.

Последние мысли

Надеюсь, вы дошли до того момента, когда чувствуете себя готовым попробовать эти рефакторинги в своем собственном коде, и вам легче определить, какой «запашок» вокруг вас. Мы только начали, но вы уже взялись за пару крупных запашков. Думаю, это было не так сложно, как вы когда-то могли подумать!

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.