Pull to refresh

Организация кода в больших AngularJS и JavaScript приложениях

Reading time5 min
Views40K
От переводчика: Думаю, что статьи по архитектуре приложения и организации кода наиболее важны на начальном этапе, т. к., в отличие от всего остального, основу приложения поменять очень трудно. [Оригинал статьи]

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

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

Стопки на полу


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

  • css/
  • img/
  • js/
    • app.js
    • controllers.js
    • directives.js
    • filters.js
    • services.js
  • lib/
  • partials/

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

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

Ящик для носков


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

Давайте представим, что делаем простой сайт электронной коммерции с формой логина, каталогом товаров и интерфейсом корзины. Также мы определили новые сущности для моделей (бизнес-логики и состояния) и сервисов (прокси для HTTP/JSON терминалов), а не свалил их в одну кучу «сервисы». Наш яваскрипт-каталог будет выглядеть следующим образом:

  • controllers/
    • LoginController.js
    • RegistrationController.js
    • ProductDetailController.js
    • SearchResultsController.js
  • directives.js
  • filters.js
  • models/
    • CartModel.js
    • ProductModel.js
    • SearchResultsModel.js
    • UserModel.js
  • services/
    • CartService.js
    • UserService.js
    • ProductService.js

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

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

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

Модульность


Будем надеяться, что банальные метафоры не были слишком утомительными, и вот резюме:

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

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

  • cart/
    • CartModel.js
    • CartService.js
  • common/
    • directives.js
    • filters.js
  • product/
    • search/
      • SearchResultsController.js
      • SearchResultsModel.js
    • ProductDetailController.js
    • ProductModel.js
    • ProductService.js
  • user/
    • LoginController.js
    • RegistrationController.js
    • UserModel.js
    • UserService.js

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

В Ангуляре мы можем взять этот шаг за основу и создать модуль с соответствующим кодом:

var userModule = angular.module('userModule',[]);
 
userModule.factory('userService', ['$http', function($http) {
  return new UserService($http);
}]);
 
userModule.factory('userModel', ['userService', function(userService) {
  return new UserModel(userService);
}]);
 
userModule.controller('loginController', ['$scope', 'userModel', LoginController]);
 
userModule.controller('registrationController', ['$scope', 'userModel', RegistrationController]);

Если затем поместить UserModule.js в пользовательскую папку она становится «явным» объектом, используемым в этом модуле. Также было бы разумно, добавить некоторые директивы для загрузчика RequireJS или Browserify.

Советы по общему код


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

  1. Если объекты вашего модуля требуют прямой доступ к нескольким «общим» объектам, создайте один или несколько фасадов для них. Это поможет сократить число нахлебников для каждого объекта, так как наличие слишком большого числа нахлебников, как правило, показывает, что код протух.
  2. Если ваша папка «общие» становится большим модулем, разделите его на подмодули для решения конкретных функциональных задач или проблем. Убедитесь, что прикладные модули используют только те «общие» модули, в которых они нуждаются. Это «Принцип разделения интерфейса» от SOLID.
  3. Добавьте служебные методы в $rootScope, чтобы они могли быть использованы в дочерних областях. Это избавит от внедрения одной и той же зависимости (например, «PermissionsModel») в каждый контроллер в приложении. Обратите внимание, что это нужно делать вдумчиво, чтобы избежать загромождения глобальной области и не сделать зависимости не очевидными.
  4. Используйте события, чтобы разделить компоненты, которые не требуют явной ссылки друг друга. В Ангуляре это делается с помощью методов $emit, $broadcast и $on в области видимости объекта. Контроллер может сгенерировать событие для выполнения некоторых действий, а затем получить уведомление, что действие завершено.

Небольшое замечание по ресурсам и тестам


Думаю, что можно более гибко подойти к организации HTML, CSS и изображений. Поместив их в папку «assets» вложенную в папку модуля, что, вероятно, улучшит баланс между ресурсами и зависящими от них модулями и не сделает структуру слишком громоздкой. Так же, думаю, будет разумным разделить папки верхнего уровня на папки для контента, содержащие структуру папок, отражающую структуру пакета приложения. Думаю, что это так же хорошо подойдет для тестирования.
Tags:
Hubs:
+35
Comments21

Articles