Рефакторинг неструктурированной устаревшей кодовой базы

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

Как оформлена эта статья:

  • Обзор проекта
  • Выбор фреймворка
  • Возникшие проблемы
  • Используемые решения
  • Выводы

Обзор проекта:

Когда я начал работать над Goldstone, мой менеджер создал впечатляющий и полностью работающий прототип JavaScript. Его навыки инженера охватывают несколько дисциплин и не ограничиваются одним языком программирования. У него не было большого опыта работы с JavaScript, но он смог применить основы дизайна и знание основных концепций языка для создания надежного прототипа.

Выбор фреймворка:

Меня наняли сразу после окончания 12-недельного интенсива по JavaScript в Hack Reactor, и передо мной была поставлена ​​задача модульности кода внутри фреймворка и добавления средства запуска тестов.

Мы обсудили возможность использования Backbone или Angular, а также плюсы и минусы каждого из них, насколько мы их поняли. Мы решили использовать Backbone якобы из-за того, что по сравнению с Angular создавалось впечатление, что между интерфейсом и внутренней работой намного меньше «волшебства». Любой, кто потратил время на просмотр исходного кода любой из них, вероятно, согласится, что Backbone легче понять. Решение также было принято примерно в то время, когда команда Angular получила широкую известность за выпуск серьезных критических изменений в API, а Backbone был известен своей стабильностью.

Возникшие проблемы:

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

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

Следующие определения функций…

function addOne(n) {
  return n + 1;
}
andOne(2) // => 3
function addTwo(n) {
  return n + 2;
}
andTwo(8) // => 10
function addThree(n) {
  return n + 3;
}
andThree(4) // => 7

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

function addSome(n, base) {
  return n + base;
addSome(4, 2) // => 6

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

Так что пример с рефакторингом был довольно очевиден, но как насчет:

function addOneAndDrum(n) {
  playDrumroll();
  return n + 1;
}
function addTwoAndFlute(n) {
  playFlute();
  return n + 2;
}
function addThreeAndCalliope(n) {
  playCalliope();
  return n + 3;
}

Теперь у вас есть 3 функции, как и предыдущие 3, за исключением того, что на этот раз у вас также есть дополнительный побочный эффект внутри тела каждой функции, с которым нужно иметь дело. Можно ли полагаться на ту же абстракцию, что и раньше?

function addSomeDoSomething(n, base, sideEffect) {
  sideEffect();
  return n + base;
}
addSomeDoSomething(1, 2, climbRope) // => 3 ( climbRope was also invoked );

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

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

from this:
function addSomeDoSomething(n, base, sideEffect) {
  sideEffect();
  return n + base;
}
to this:
function addSome(n, base) {
  return n + base;
}
function doSomething(sideEffect) {
  sideEffect();
  return;
}

Теперь мы вернулись к тому, с чего начали с функции addSome, и разобрали функцию doSomething. Это указывает на то, что исходные функции, которые мы заменяли, были плохо продуманы с точки зрения повторного использования кода.

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

В случае с проектом, над которым я работал, прототип, рефакторинг которого я проводил, был выполнен старшим инженером с отличным пониманием основ компьютерных наук и объектно-ориентированного программирования, но ограниченным опытом работы с JavaScript и его экосистемой. Это компромиссы, на которые приходится идти при разработке прототипа в сжатые сроки в мире стартапов. «Быстро, дешево, точно: выберите два». Взяв своего рода «кредит» у Accurate, организация может использовать его для быстрого выпуска проекта. Это также приводит к накоплению так называемого технического долга. В конце концов совокупная неточность, которая позволила создать прототип, который был бы быстрым и дешевым, компенсируется сложностью системы и скоростью итераций. Каждый инженер в какой-то момент испытывает эту боль, и в идеале этот опыт дает ценные уроки о плюсах и минусах конкретной стратегии проектирования.

Используемые решения:

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

  • Backbone.js (инфраструктура JavaScript)
  • D3.js (библиотека визуализации данных)
  • DataTables (плагин для форматирования таблиц jQuery)
  • Moment.js (библиотека даты/времени)
  • Bootstrap (отзывчивая среда дизайна)
  • Раннер задач
  • Карма-испытатель
  • Casper (библиотека для сквозного тестирования)
  • Jed (утилита интернационализации JavaScript GETTEXT. Мы внедрили японский язык в дополнение к английскому)

Выводы:

В проекте Goldstone Backbone.js был идеальным фреймворком для модуляризации неструктурированной кодовой базы и предоставлял широкие служебные функции для реализации архитектуры MV* с надежным API событий и надежным встроенным маршрутизатором. В дополнение к своей полезности, это отличная основа для изучения и применения принципов объектно-ориентированного проектирования. Мало что в нем работает по волшебству. Весь исходный код относительно лаконичен и доступен в аннотированной версии.

Для моего последнего проекта с этой компанией я провел исследование React и Redux во время одного из наших спринтов. Услышав и читая об этом все чаще и чаще, увидев, что это все чаще обсуждается на конференциях и переговорах, я выступил за то, чтобы моя команда поддержала меня в рассмотрении этого как жизнеспособной основы для нашего следующего проекта. Я был невероятно впечатлен опытом прохождения ряда руководств и создания нескольких тестовых проектов, и для моего следующего проекта я полностью владел клиентским микросервисом, который был развернут вместе с серверными технологиями нашего проекта. К сожалению, этот проект не был открытым исходным кодом, поэтому я не могу ссылаться на него. Это было приложение React, скомпилированное с помощью webpack и использующее Redux для управления состоянием.

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

Эта статья является частью серии