Технология CORE

в 17:05, , рубрики: 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});
    },
    ...
};

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

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

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

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

Автор: okneigres

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js