Рефакторинг неструктурированной устаревшей кодовой базы
Эта статья написана так, чтобы быть доступной для читателей с любым уровнем опыта работы с интерфейсными веб-технологиями. Это часть серии.
Как оформлена эта статья:
- Обзор проекта
- Выбор фреймворка
- Возникшие проблемы
- Используемые решения
- Выводы
Обзор проекта:
Когда я начал работать над 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 для управления состоянием.
Я доволен тем, что научился использовать эти технологии в таком порядке.