Six months ago, I wrote a post about a programming technology invented by me (
habrahabr.ru/post/163881 ), which helped me to accelerate strongly (and not only me) and to do my job better.
Whereas last time was devoted to practice and comparison with the usual development model, this time I want to talk about the theoretical foundations of technology.
To simplify the explanation from the Context-Object-Request-Event system, I’ll throw out contexts, and we’ll talk about setting tasks and how they are related to objects, events, and requests.
')
Task setting and flowing abstractions
In programming, we often have to deal with flowing abstractions. When, for example, our code does not take into account that the connection to the database may fall off, this leads to problems.
There is a more fundamental problem with flowing abstractions, and it concerns the modeling of complex behavior. It often happens that when additional requirements are added to the task, the code that has just been written becomes unusable, and everything has to be redone. This is also a flowing abstraction: we modeled the solution of the problem not in terms of the problem, but in terms of the programming language — variables, lists, references, objects, design patterns. With a small change in the requirements, the whole code turned out to be unusable.
This is because programming languages are not similar to the language of the description of problems that we use in the formulation.
I argue that most practical tasks come down to two patterns: “when A is done B” and “it is necessary for C. to be done. Let us do it in the way of D”.
We introduce the following notation: A is an event, B is a reaction to an event, C is an action request, D is a method of execution.
Examples for the first case:
- when the user clicks on this button, let's show him the following animation (hmm, just like at the meeting)
- when designer Anya draws a prototype of the interface, I want to look at it and express my ideas for improvements
- let's collect statistics on each click
- every fifth run show banner
For the second:
- Need to draw a design for this idea. Let it make Anya. Well, or, Vova.
- save the current account value to the database
Notice this: the essence of the task in the first case is that B was done when A. happened. It does not matter exactly what A is, what particular context it came from, and so on. And this task is completely separate from the task where event A takes place, it is dedicated to other goals. in the second case - the opposite. it does not matter in principle how task C. will be done. It is important that it be done somehow, in any suitable way.
Why is it important to understand? Consider the following code for an abstract game (or anything else) on js:
addMoney: function(amount) { this.balance+=amount; if(this.balance > 250) { $('.you_win!').animate({css:{opacity: 1}}) } }
This code is very bad. why? because in it the logic of money is mixed with the logic of the show. But worse:
$('.coin').click(function(){ this.balance+=15; if(this.balance > 250) { $('.you_win").animate({css:{opacity: 1}}) } })
In prototypes such an example is very frequent. Touch on any aspect of the task - design, money counting, animation - all around, it will quickly turn into a mess and will be buggy. How would it be done normally? Just describe what was in the problem:
- when the user clicked on a coin, add it to the balance
- when the balance has become more than 250, show the banner that we won
We divide the task into three objects, one of which will be responsible for the display and the UI, the second for the state of the account, the third for determining the winning or losing user:
var UI = { handleCoinClick: function() { .... }, showWinAnimation: function() { .... }, ... } var Balance = { addCoins: function() { ... }, ... } var WinWatcher = { watchForWin: function() { .... } ... }
UI here is only responsible for the display and clicks - user interaction.
Now these components need to be connected somehow. It will be bad if we call them from each other, therefore we will connect them with events and requests
var UI = { handleCoinClick: function() {
Now you need to link the pieces of code where the comments "call, when ..." and "throw here / request." But here we are confronted with the very proceeding abstractions. If you call the Balance and WinHandler methods directly from the UI, then we may need to collect statistics, or some other complication, and calls related to other tasks will be added to the UI method. The method will cease to be clean.
Therefore, we will try to make the method simple. Let's provide the resolution to the event dispatcher.
Core.js
Last time I promised to make an open-source implementation. Currently there is an implementation for javascript
github.com/okneigres/corejsThe library works both in the browser and under Node.js
<script src="core.js"></script> var Game = { };
Now it’s easy to do anything with this code: add new functionality:
Refactor UI (divided into two objects - UI and 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}); }, ... };
Now that the code is written in strict accordance with the logic, working with the code is easy.
Instead of conclusion
Work in such a paradigm greatly simplifies the design and content of the project. We can re-model as many times as necessary, but the logic of the problem will remain the same. Why not create code from it? And if you practice a little, to work in such a paradigm is easier than ever, because we, in fact, just have to describe the task in the words in which we think about it, that's all.
In my experience, this greatly simplifies the design of interfaces, and even server applications. To contain code in the level of abstraction that the task requires is possible and necessary.