Наскоро попаднах на поучителна поредица от статии от Ерик Елиът, обясняващи двата стълба на JavaScript. Тези две основни концепции, които коренно променят начина, по който мислим за JavaScript програмите, според него са:

  • Предпочитане на прототипно наследяване срещу класически механизъм за наследяване


  • Писане на код, който е по-лесен за управление чрез принципите на функционалното програмиране


Част 1 наистина ми проговори, защото току-що бях завършил рефакторинг на бекенда на OO модел на данни на голяма корпоративна платформа за управление на обучението. Цялата основа на тази катедрала, ако щете. Въпреки че не смятам, че писането на програми, използващи базирано на класове или класическо наследяване, трябва непременно да се чувства като преминаване през деветте пръстена на ада на Данте, със сигурност се чувствах така, докато променях хиляди редове код, за да отговоря на промените в бизнес изискванията. По време на преработването ми мислех, че съм жертва на ограничения, присъщи на този дизайн като: възможността да се разширява само от един родителски клас и да се справя с тясното свързване, което съществува между родителски и дъщерни класове; но това беше моя собствена работа, опитвайки се да вместя квадрат в кръг.

Винаги ни учат на свободно свързване - висока кохезия,и безспорно е, че ковалентната връзка, която съществува между дъщерен клас и неговия родител, чрез супер, е най-силното ниво на свързване, което някога можем да изградим. Това може да доведе до много твърди структури на обекти, които са трудни за разделяне, но когато се направи правилно, това не е непременно лошо - дъщерните класове трябва да се разглеждат като единични модулни единици; в противен случай „Бандата на четиримата” препоръчва да се използва композицията пред наследяването (повече за това по-късно).

Като казах това, аз наистина вярвам, че проблемът не е в класическото наследяване, а в начина, по който го използваме или злоупотребяваме според случая. Това обикновено се случва, когато наивно сме склонни да налагаме наследствени връзки върху модел на домейн, който няма естествена или органична таксономия (като моя случай). Следователно в крайна сметка се опитах да поддържам модел на данни, който се е разраснал както вертикално, така и хоризонтално... наследяване на една таблица, наследяване на конкретна таблица или комбинация от двете? Истинският проблем, с който се сблъсках, беше да се опитвам да наложа връзка IS-A при някои обстоятелства, когато HAS-A, композиция, беше по-подходяща.

Тъй като преминаваме към по-модулни и слабо свързани типове, както казва Ерик, трябва да спрем да пишем JavaScript, сякаш е Java, и да предпочитаме прости класове, които съставят, вместо сложни класове, които наследяват всичко — напълно съм съгласен. Правейки това, ние също сме склонни да изоставим използването на this, което може да означава много различни неща в език като JavaScript. Това сегуей е част от втория стълб на JavaScript, част 2 от поредицата, и една от моите страсти, Функционалното програмиране.

Споделям мнението на Ерик за важността на FP за език като JavaScript.

От моята книга „Функционално програмиране в JavaScript (Manning)“:

Функционалното програмиране се отнася до декларативната оценка на чисти функции за създаване на неизменни програми чрез избягване на външно наблюдавани странични ефекти.

На високо ниво преминаването от OO към функционално изисква да:

  • Предпочитайте композицията пред наследството (задължително)
  • Помислете за неизменните данни
  • Третирайте функциите като работни единици, а не като класове
  • Приемете принципа на заместване на Лисков (донякъде следствие от първата точка)

С композицията можете да обърнете внимание, наред с други неща, на основното ограничение на класическото наследяване: повторна употреба със свободна връзка. Но композицията също играе много по-голяма роля от простото композиране на типове с други типове: композиране на функции.

Това, което точката и запетая е за процедурни изрази, композицията е за функционални изрази

Композицията е (самата по себе си функция, функтор или функционален комбинатор) как организирате изпълнението на вашите функции във функционална програма. Докато типична императивна JavaScript функция, която търси и записва някои данни в някаква HTML таблица, може да изглежда така:

function addToTable(key) {
  if(key !== null) {
     key = key.replace(/^\s*|\-|\s*$/g, '');
     if(key.length === 0) {
        throw 'Invalid key!';     
     }
     var record = db.get(key);
     if(student !== null) {
       $('#table tr:last').after(`<tr>${record}</td>`);
       return $('#table tr').length - 1;
     }
     else {
       throw 'Record not found!';
     }
  }
  else {
    return 0;
  }  
}

Функционалните програми ви принуждават да разложите тази монолитна програма на независими, по-прости части (използвайки добре принципа на сингулярността), които след това се сглобяват отново като Лего. Написване на еквивалентната функция на JavaScript (като се изключат простите подробности за отделните функции):

var addToTable = compose(
   map(appendToTable('#table')),
   IO.of,                          
   map(findObject),
   checkLength,
   Maybe.fromNullable(cleanInput) 
);
addToTable(key);

Без капка съмнение, този код е не само по-управляем (чисти функции), но също така е по-четим (декларативен) и тестван (модулен). Освен това ви принуждава да разделите конкретна задача на по-прости части и да ги слепите заедно на интерфейсно ниво, което удовлетворява принципа на програмиране към интерфейс.

Този код също е много по-конфигурируем и адаптивен, тъй като мога лесно да сменя всеки компонент на тази програма (стига да се подчинява на същите интерфейси) с друг. Така че, вместо да отпечатам обекта в таблица, мога също толкова лесно да го отпечатам в конзолата:

var addToTable = compose(
   map(printToConsole),  // swapped function here
   IO.of,
   map(findObject),
   checkLength,
   Maybe.fromNullable(cleanInput)
);

Няма да отделям твърде много време за гмуркане в отделните детайли на FP (за това написах цяла книга), но с един поглед можете да видите, че декларативният характер на тази програма описва какво прави, а не как го прави.

Това е възможно само благодарение на централния стълб на функционалното програмиране, чисти функции, функции, които не причиняват видими мутации извън своя обхват. Разширявайки тази идея допълнително, можете да създавате функции, които изчисляват своите резултати въз основа единствено на входните данни, които им се дават, подобно на математиката, концепция, която Ерик нарича идемпотентност, известна също като референтна прозрачност.

Въз основа на тези здрави основи, изграждането на цели катедрали може да се извърши с много по-малко структурни проблеми и дефекти. Не е чудно, че JavaScript е най-влиятелният функционален език, създаван някога!