Фундамент масштабованості javascript програми

"Якщо хочеш йти швидко — йди один. Якщо хочеш йти далеко — йдіть разом." ©
З цієї ліричної рядка в даній статті я буду розмірковувати про те, як правильно організувати код в вашому додатку, щоб воно могло рости в висоту і в широчінь. Якщо ви хочете, щоб продукт вашої мозкової активності був потужнішим, ніж у ваших конкурентів, то вам неминуче доведеться запрошувати нових програмістів у вашу команду. А якщо не покласти вектор масштабованості, то пориви ентузіазму через рік перетворяться в локшину-код і командна робота перетворить кожного співробітника від злості в маленького сатану.
Так ось… Для того, щоб ваші бійці відчували себе комфортно разом в одному проекті, треба щоб вони не заважали один одному і писали свої букви в різних не перетинаються ділянках коду. Для цього їм потрібно буде писати "Самостійні" компоненти.
"Самостійні" — це такі компоненти, які самі керують своєю поведінкою, орієнтуючись на події зовнішнього середовища. При знанні про те, як працює ваш додаток і які події в ньому протікають, можна легко писати такі "самостійні" компоненти, не зачіпаючи старі і чужі ділянки коду.
"Несамостійні" — компоненти, які нічого не знають про зовнішньому середовищі, але у них є дуже розгорнуте api. Цьому компоненту потрібно пояснити, як себе вести у вашому додатку. Такі компоненти, на відміну від "самостійних" пишуться заради багаторазового використання у вашому публічному проектах, як наприклад відкриті бібліотеки на github та ін
Як визначити, які компоненти потрібні у вашому додатку? Дуже просто. Якщо компонент застосуємо тільки до однієї задачі і не багаторазовий, то його потрібно писати так, щоб він був "самостійним".
Ось наприклад, розглянемо компонент втілює поля вводу повідомлення у стрічці чату. Швидше за все таке поле вводу в вашому додатку ви будете використовувати тільки за прямим призначенням і не будете його використовувати, скажімо, у формі введення нікнейму або пароль при авторизації, бо там у компонентів буде своя специфіка.
Не будемо тягнути кота за те, що не варто відтягувати і розберемо конкретний приклад. Нехай це буде імітація чатика.
Уявімо, що у вас два програміста в команді. Петька і Толік. І у них є ядро масштабованого програми. Просте, як два пальця у двупалого людини. В ядрі є мережевий транспорт, сховище стрічки повідомлень у вигляді масиву(в цьому прикладі не будемо виділяти його в окремий файл з методами) і event emitter, який у цьому випадку є запорукою масштабованості.
В якості event emitter в цьому прикладі я взяв Backbone.Events, хоча цим і обмежимося у використанні Backbone, щоб показати приклад як можна простіше.
<html>
<head>
<script src="http://underscorejs.org/underscore-min.js">
<script src="http://backbonejs.org/backbone-min.js">
<script src="connection.js"/>
<script src="app.js"/>
</head>
<body>
<div id="header" style="padding:10px; border:1px solid #ddd"></div>
<div id="container" style="margin-top:20px;"></div>

<script>
window.app = new App();
app.init();
app.on('new_message', function(text){
console.info('new message', text);
console.info('messages length', app.messages.length);
});
</script>
</body>
</html>

//app.js
var App = function(){
var app = _.extend({
init: function(){
this.connection.connect();
}
}, Backbone.Events);
app.connection = new Connection(),
app.messages = [];

app.connection.on('connected', function(){
console.info('App connected!');
});

app.connection.on('incoming_message', function(text){
app.messages.push(text);
app.trigger('new_message', text)
});

return app;
}

//connection.js
var Connection = function(){
return _.extend({
connect: function(){
/*просто імітуємо те, що наш мережевий транспорт приймає повідомлення від сервера і віддає якісь сигнали кожну секунду в зовнішнє середовище*/
var i=0;
setInterval(_.bind(function(){
i++;
var text = 'message_' + i;
this.trigger('incoming_message', text);
},this),1000);
this.trigger('connected');
},
}, Backbone.Events);
}

Ну ось, у нас є додаток, в якому поки жодної в'юхи і роботу якого можна протестувати через консоль браузера. До речі, якщо з вашого додатки видалити всі допоміжні компоненти і в'юхи, і воно зможе працювати через консоль, то це дуже добре. Значить у вас досягнута в якійсь мірі слабка зв'язаність між компонентами та код можна покрити автоматизованими тестами. Погнали далі.
Тепер обізнана в стратегічних планах людина ставить завдання Петькові і Дещицю, мовляв, треба, щоб додаток показувало стрічку повідомлень, а в шапці був лічильник всіх повідомлень з стрічки. У вас може виникнути питання… кому взагалі потрібен цей, блін, лічильник повідомлень в шапці в реальному житті? Це просто для прикладу.
Добре, думають Петька і Толік, ок. Вони вирішують одночасно написати два різних компонента для програми.
Петька взяв на себе завдання по стрічці повідомлень
Але він не чув про те, як програмувати масштабоване додаток і почав писати код:
//list-view.js - "несамостійний" компонент
var ListView = function(container){
var el = document.createElement('div');
container.appendChild(el);

return {
addMessage: function(text){
var row = document.createElement('div');
row.innerHTML = 'message:' + text;
el.appendChild(row);
}
}
}

//app.js зміна коду
var App = function(){
var app = _.extend({
init: function(){
connection.connect();
}
}, Backbone.Events);
app.connection = new Connection(),
app.messages = [];
//додав код
app.listView = ListView(document.getElementById('container'));

app.connection.on('connected', function(){
console.info('App connected!');
});

app.connection.on('incoming_message', function(text){
app.messages.push(text);
app.trigger('new_message', text);
app.listView.addMessage(text); //додав код
});

return app;
}

Петя створив компонент, яким доводиться керувати за допомогою методів на більш високому рівні і, як наслідок, крім простого оголошення компонента, довелося копирсатися в коді app.js і додати рядки в обробник incoming_message. Тепер ви не зможете просто закоментувати рядок "app.listView = .." так, щоб ваш додаток не зламалося. Бо app.listView.addMessage(text); видасть Exception. Додаток почало обростати зв'язаністю. Ядро початок залежати від view.
Подивимося, як впорався Толік з завданням по лічильнику повідомлень в шапці
Він знає, як писати код так, щоб не заважати іншим:
//header-view.js
var HeaderView = function(container) {
var el = document.createElement('div'),
span = document.createElement('span'),
view = {
setCounter: function(num){
span.innerHTML = num;
}
}

el.innerHTML = 'К-ть повідомлень: ';
el.appendChild(span);
container.appendChild(el);
view.setCounter(0);

app.on('new_message', function(){
view.setCounter(app.messages.length);
});

return view;
}

//app.js зміна коду
...
app.connection = new Connection(),
app.messages = [];
//додав код
app.headerView = HeaderView(document.getElementById('head'));
...

Що зробив Толік за межами свого компонента — це тільки оголосив компонент в області змінних app і все. Компонент залишається також доступним для ручного тестування через консоль або для модульного тестування, так як він все ж повертає api.
Зона відповідальності за код Дещиця обмежується по суті всього одним файлом header-view.js і ці правки легше ревьюить, адже треба дивитися все в один файл.
Писати "самостійні" компоненти вигідно
Якщо б Толик теж написав несамостійний компонент, то в app.js він торкнувся б ті ж шматки коду, що і Петя. Складно мержить, зв'язаність між компонентами збільшується. На такому маленькому прикладі може цього не сильно помітно, але якщо у вас сумарно багатотисячний код і пишуться великі складні системи, то це можна буде добре відчути.
В процесі написання коду завжди буде вибір, або керувати компонентом на більш високому рівні ієрархії, або дати компоненту управлятися самостійно.
Розділяйте і властвуйте господа, пишіть для своїх програм "самостійні" компоненти.
p.s. Хоча приклади коду в даній статті були написані на голому JS без використання фреймворків, дана філософія слабкою пов'язаності справедлива і при їх використанні, будь то Backbone або React з хитрими методологіями ізоморфних додатків типу Flux і Redux, чи ще якихось інших фреймворків.
Завжди прагніть обмежувати зону відповідальності в коді ваших програмістів, коли вони пиляють нові фічі. Якщо вам дали такий гайковий ключ, як React, то їм потрібно закручувати гайки, а не бити себе їм на пальцях.
Команда розробників JivoSite.ru бажає вам чистого і зрозумілого коду.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.