Технология CORE

от автора

Полгода назад я написал пост о придуманной мною технологии программирования ( habrahabr.ru/post/163881/ ), которая сильно мне помогла ускориться (и не только мне) и делать своё дело более качественно.

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

Для упрощения объяснения из системы Context-Object-Request-Event я выкину контексты, и мы поговорим о постановке задач и как они связаны с объектами, событиями и запросами.

Постановка задач и протекающие абстракции

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

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

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

Я утверждаю, что большинство практических задач сводится к двух паттернам: «когда A сделать B» и «нужно, чтобы было сделано C. Давайте мы сделаем это способом D».
Введём обозначения: A — это событие, B — реакция на событие, C — запрос действия, D — способ исполнения.

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

Для второго:
— Нужно отрисовать дизайн для этой идеи. Пусть это сделает Аня. Ну, или, Вова.
— сохраним значение текущего счёта в базу данных

Заметьте вот что: суть задачи в первом случае в том, чтобы было сделано B, когда произошло A. Неважно конкретно, что за A, к каком конкретно контексте оно возникло, и так далее. И эта задача совершенно отдельна от задачи, где происходит событие A, она посвящена другим целям. во втором случае — наоборот. принципиально не важно как будет сделана задача C. Важно чтобы она хоть как-то была сделана, любым подходящим способом.

Почему это важно понять? Рассмотрим следующий код абстрактной игры (или чего угодно другого) на js:

 addMoney: function(amount) {     this.balance+=amount;     if(this.balance > 250) {         $('.you_win!').animate({css:{opacity: 1}})     } } 

Этот код очень плох. почему? потому что в нём логика денег смешана с логикой показа. А вот ещё хуже:

 $('.coin').click(function(){     this.balance+=15;     if(this.balance > 250) {         $('.you_win").animate({css:{opacity: 1}})     } }) 

В прототипах такой пример очень част. Затронь любой аспект задачи — дизайн, подсчёт денег, анимацию — всему каюк, это быстро превратится в кашу и будет глючить. Как было бы сделать по нормальному? Просто описать то, что было в задаче:
— когда пользователь кликнул на монетку, добавить её на баланс
— когда баланс стал больше 250, показать баннер, что мы выиграли

Делим задачу на три объекта, один из которых будет отвечать за отображение и UI, второй за состояние счёта, третий за определение выигрыша или проигрыша пользователя:

 var UI = {     handleCoinClick: function() {        ....     },     showWinAnimation: function() {        ....     },     ... }  var Balance = {     addCoins: function() {         ...     },     ... }  var WinWatcher = {     watchForWin: function() {        ....     }     ... } 

UI здесь отвечает только за отображение и клики — взаимодействие с пользователем.

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

 var UI = {     handleCoinClick: function() {         // вызвать, когда происходит DOM Init, или другое событие, которое оповещает о генерации карты         ....         $('.coin').click(function(){             // здесь бросить событие клик на монетку Event_CoinClick             .....         });     },     showWinAnimation: function() {         // вызвать, когда потребуется показать пользователю что он выиграл Request_ShowUserWin         $('.you_win').animate({opacity: 0});     },     ... }  var Balance = {     addCoins: function() {        // вызвать, когда будет событие «клик на монетку» Event_CoinClick        this.balance+=15;        // здесь бросить событие, что баланс счёта изменён Event_BalanceChanged     },     ... }  var WinHandler = {     watchForWin: function(balance) {        // вызвать, когда произошло событие, что баланс изменён Event_BalanceChanged        if(balance > 250) {            // запросить показ пользователю, что он выиграл Request_ShowUserWin        }     }     ... } 

Теперь нужно связать кусочки кода там, где комментарии «вызвать, когда …» и «здесь бросить/запросить». Но тут мы сталкиваемся с теми самыми протекающими абстракциями. Если вызывать из UI методы Balance и WinHandler напрямую, нам потом может понадобиться сбор статистики, или ещё какое-нибудь усложнение, и в метод UI добавятся ещё вызовы, связанные с другими задачами. Метод перестанет быть чистым.

Поэтому постараемся сделать метод простым. Предоставим разруливание зависимости диспетчеру событий

Core.js

В прошлый раз я обещал сделать open-source реализацию. На данный момент есть реализация для javascript github.com/okneigres/corejs

Библиотека работает как в браузере, так и под Node.js

 <script src="core.js"></script>  var Game = { }; //определяем неймспейс  Game.UI = {     CoinClickEvent: new Core.EventPoint,     handleCoinClick: function() {         Core.CatchEvent(Event.DOM.Init);         $('.coin').click(function(){             new Game.UI.CoinClickEvent();         });     },     showWinAnimation: function() {         Core.CatchRequest(Game.WinHandler.ShowUserWinRequest);         $('.you_win').animate({opacity: 0});     },     ... }  Game.Balance = {     ChangedEvent: new Core.EventPoint,     addCoinsOnClick: function() {        Core.CatchEvent(Game.UI.CoinClickEvent)        this.balance+=15;        new Game.Balance.ChangedEvent;     }     ... }  Game.WinHandler = {    ShowUserWinEvent: new Core.EventPoint,    ShowUserWinRequest: new Core.RequestPoint,     watchForWin: function(balance) {        Core.CatchEvent(Game.Balance.ChangedEvent)        if(balance > 250) {                   new Game.WinHandler.ShowWinRequest;        }     }     ... }  Core.processNamespace(Game);  

Теперь с этим кодом легко делать всё, что угодно: добавлять новый функционал:

 // отправляем клики и выигрыши на сервер Game.GatherStat = {     sendClick: function() {         Core.CatchEvent(Game.UI.CoinClickEvent);         $.post('/stat/gather/ajax', {click: 1, date: new Date});     },     sendWin: function() {         Core.CatchEvent(Game.WinHandler.ShowUserWinEvent);         $.post('/stat/gather/ajax', {win: 1, date: new Date});     } } 

Рефакторим UI (разделяем на два объекта — UI и UIWin):

 Game.UI = {     CoinClickEvent: new Core.EventPoint,     handleCoinClick: function() {         Core.CatchEvent(Event.DOM.Init);         $('.coin').click(function(){             new Game.UI.CoinClickEvent();         });     } };  Game.UIWin = {     showWinAnimation: function() {         Core.CatchRequest(Game.WinHandler.ShowUserWinRequest);         $('.you_win').animate({opacity: 0});     },     ... }; 

Теперь, когда код написан в чётком соответствии с логикой, работать с кодом легко.

Вместо заключения

Работа в такой парадигме сильно упрощает проектирование, и содержание проекта. Мы можем перемоделировать сколько угодно раз, но логика задачи останется той же. Почему бы и не создавать код от неё? А если немного потренироваться, работать в такой парадигме — проще простого, потому как мы, фактически, просто должны описать задачу в тех словах, в которых мы о ней думаем, и всё.

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

ссылка на оригинал статьи http://habrahabr.ru/post/185372/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *