«Суворий» JavaScript: навіщо і кому це треба

Олена Жукова , підприємець і Frontend developer, на VinnytsiaJS виступила з доповіддю «Strict JavaScript» і на його основі написала статтю для DOU. JavaScript вважається динамічним мовою, але все частіше використовуються інструменти, які додають йому статичної типізації. Google, Facebook і Microsoft пропонують свої рішення. Чому так відбувається і чи варто це робити?

Дані з GitHub за останні 3 роки показують істотне зростання кількості проектів, написаних за допомогою інструментів, які додають в JavaScript статичної типізації. Звичайно, можна припустити, що використання, наприклад, TypeScript сприяв вихід фреймворку Angular2, який настійно рекомендував використовувати саме TypeScript, проте існує чимало проектів, які використовують зовсім інший фреймворк — ReactJS, при цьому написані на TypeScript. До того ж Facebook розробив власний інструмент для статичної типізації JavaScript — Flow, яким рекомендує користуватися для свого фреймворку ReactJS.

Таке широке поширення статичних надбудов над динамічним JavaScript-му змушує задуматися: «Навіщо і кому це треба?».

Невеликий екскурс в історію появи JavaScript

Ми всі знаємо, що JavaScript був створений в 1995 році Бренданом Эйхом для браузера Нетскейп навігатор. Автор говорив, що на його роботу вплинули такі популярні в той час мови програмування як Scheme, Self, Java і Smalltalk. Примітно, що всі ці мови мають строгу типізацію. Так чому ж JavaScript вийшов таким слабо типізованих?

Можливо, цитата автора допоможе це зрозуміти:

«HTML потребував „мовою сценаріїв“, мовою програмування, який був би простий у використанні любителями і новачками, де код міг бути написаний безпосередньо у вихідній формі як частину розмітки веб-сторінки. Ми прагнули надати „мова-прошарок“ для веб-дизайнерів і програмістів-аматорів, які будують веб-контент з таких компонентів, як зображення, плагіни і аплети Java. Ми розглядали Java як „компонентний мова“, використовується більш досвідченими програмістами, тоді як програмісти прошарку — розробники веб-сторінок — збирали б компоненти і автоматизували б їх взаємодії з допомогою JS».

Брендан дуже б здивувався, якби йому тоді сказали, що мине 20 років, і всі браузери відмовляться від використання Java аплетів, а JavaScript стане найпопулярнішою мовою.

JavaScript залишався несерйозним мовою для веб-дизайнерів до 2004 року, коли Google, використовуючи технологію AJAX, випустили в масове споживання Gmail. Для свого поштовика Google вже не використовували чистий JS. Вони придумали набір інструментів Google Web Toolkit , який перетворював Java в JavaScript. Основною метою GWT заявляв кросбраузерність, свободу від вивчення JavaScript, а також загальний код для бекенду і фронтенда (single code base).

Щороку яка-небудь компанія придумувала свої інструменти перетворення інших мов в JavaScript. Їх більше сотні , і їх об'єднують цілі і завдання, які ставив перед собою і GWT.

Всі ці рішення об'єднувала одна особливість — вони були створені для програмістів, які більшу частину часу працювали з іншими мовами і не хотіли переходити на JavaScript.

У 2011 році з'явився TypeScript — відносно самостійний мову (під сильним впливом C#) спеціально для JavaScript-розробників, головною задачею якого була саме статична типізація для JS. У 2014 подібне рішення — Flow — представили і в Facebook.

Аргументи «за»

Так чому ж гіганти індустрії Google, Microsoft і Facebook займаються однією справою — додають статичної або суворої типізації JavaScript?

Тут можна вказати на кілька причин.

Знайти помилки на етапі компіляції

JavaScript не вимагає компіляції, а слабка динамічна типізація робить практично неможливим аналіз коду для IDE. З цієї причини ми можемо зіткнутися з помилкою, пов'язаною саме з типами даних, тільки у певних випадках під час роботи програми. Це ускладнює процес налагодження продукту.

Навіть самий досвідчений розробник JavaScript не завжди може сказати, яким буде результат виконання наступних операцій:

/*
* JavaScript coercion
*/
var objArr = {}+[];

var arrObj = []+{};

var arrNum = []+1;

var arrNum = []*1;

var arrNum1 = [2]*2;

var arrNum2 = [2,1]+2;

var arrNum3 = [2,1]-2;

var arrs = [1]+[2];

У таких випадках хотілося б отримувати повідомлення про помилку вже в процесі написання коду навіть в самому простому редакторі, як це дозволяє TypeScript або Flow з безкоштовним редактором Atom.

Більш читабельний код

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

Ось простий приклад коду. Досить простий, щоб розробник вирішив, що тут все гранично зрозуміло: є продукт, у нього є категорія, навіщо тут вказувати типи?

function Product(){
}

Product.prototype.setCategory = function (category) {
 this.category = category;
};

Однак коли колега автора цього коду або сам автор через деякий час вирішує написати функцію пошуку по категорії продукту:

function searchProductsByCategory(products, category) {
 return products.filter(product=>{
 return //???
})
}

Тут виникає проблема: «А що таке категорія? І як написати порівняння?». Може бути, що категорія — це рядок або число, або об'єкт...

TypeScript або Flow дозволяють нам явно вказати тип параметра і тим самим однозначно вирішити подібні питання.

interface Category{
 name: string;
 id: string;
}

class Product{
 private category:Category;
setCategory(category:Category){
 this.category = category;
}
}

Код, яким легше керувати

Зміни в коді — звичайна рутина для програміста. Дуже важливо, щоб ця робота відбувалася максимально безболісно, але JavaScript — не той випадок. При достатньо великому обсязі коду, особливо розділеному на кілька файлів, або занадто загальних назвах змінних зміни в коді можуть бути дуже нетривіальною завданням. Рефакторинг стає значно простіше, якщо використовувати TypeScript або Flow. Ваш редактор може автоматично змінити назву змінної або методу у всіх місцях, де ви їх використовуєте.

Аргументи «проти»

Зазначених трьох причин достатньо для того, щоб значно полегшити життя розробника, проте є й інший погляд на це питання. Є аргументи проти використання статичної типізації для JavaScript.

У передмові до книги Кайла Сімпсона «Ви не знаєте JavaScript: типи та граматика» Девід Волш пише:

«JavaScript — єдина мова, яку розробники не вчать, перед тим як почати використовувати».

Противники суворого JavaScript кажуть, що люди користуються інструментами для статичної типізації просто тому, що вони не знають, як користуватися JS .

Наприклад, Ерік Елліот каже :

«В динамічно типизированном мові немає необхідності в конструкторах типів... Замість цього розробники можуть використовувати duck typing і, можливо, виконувати перевірки типи часу виконання».

В якості прикладу Елліот пропонує таку функцію для перевірки наявності обов'язкових параметрів:

const fn = (fn, {required = []}) => (params = {}) => {
 const missing = required.filter(param => !(param in params);

 if (missing.length) {
 throw new Error(`${ fn.name }() Missing required parameter(s):
 ${ missing.join(', ') }`);
}

 return fn(params);
};

const createEmployee = fn(
({
 name = ",
 hireDate = Date.now(),
 title = 'Worker Drone'
 } = {}) => ({
 name, hireDate, title
}),
{
 required: ['name']
}
);

console.log(createEmployee({ name: 'foo' })); // works
createEmployee(); // createEmployee() Missing required parameter(s): name

Крім того, що ця функція об'ємна і складна для читання, неймовірно дивно, що вона покликана вирішити завдання, з якою TypeScript або Flow справляється без зайвих зусиль з боку програміста, тобто просто повідомити про відсутність необхідного параметра.

Дуглас Крокфорд, автор знаменитої книги «JavaScript. Сильні сторони», так висловлюється про статичних типах в JS:

«Я знайшов у своїй роботі, що помилки, які виявляє сувора перевірка типів, не є помилками, про які я турбуюся. З іншого боку, я знаходжу вільну типізацію ліберальної. Мені не потрібно створювати складні ієрархії класів. І мені ніколи не доводиться перетворювати або боротися з системою типів, щоб отримати те поведінка, яке я хочу».

Для прикладу, Крокфорд вважає, що код, як цей, не відповідає духу JavaScript:

var answer = 42.5 | 0;

І пропонує таку конструкцію для того, щоб перетворити число з крапкою (float) в ціле число (int):

Number.method('integer', function ( ) {
 return Math[this < 0 ? 'ceil' : 'floor'](this);
});

При всьому бажанні важко прийняти подібні альтернативи для розв'язання проблем, пов'язаних з відсутністю статичної або суворої типізації в JavaScript. Тим більше, що сучасні популярні інструменти TypeScript і Flow не позбавляють JS його знаменитої гнучкості, при цьому додають корисної організованості як кодом, так і розробника.

Опубліковано: 10/10/17 @ 10:05
Розділ javascript

Рекомендуємо:

Конкурс «Кексомания». 250к рублів
.NET дайджест #20: як влаштована аутентифікація і авторизація в ASP.NET 2.0., нововведення в ASP.NET Core, огляд GraphQL vs REST
Школа Партнеркина. Робимо сітку музичних порталів
QA дайджест #30: справжня історія терміна Bug, перезавантаження ПК під час тесту, навантажувальне тестування з Gatling з нуля
Кращий тестувальник року — про кар'єру, досягнення і про те, як стати професіоналом у QA