Съставяне на софтуер: Въведение

Димни арт кубчета за пушене - MattysFlicks - (CC BY 2.0)
Забележка: Това е част от поредицата „Compose Software“ (сега книга!) За изучаване на функционални програми и техники за композиционен софтуер в JavaScript ES6 + от самото начало. Поддържайте настройките. Има много повече от това, което предстои!
Купете книгата | Индекс | Напред>
Състав: „Актът за комбиниране на части или елементи, за да образуват едно цяло.“ ~ Dictionary.com

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

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

Интервюирал съм стотици разработчици. Това, което научих от тези сесии е, че не съм сам. Много малко работещи разработчици на софтуер добре разбират същността на разработката на софтуер. Те не знаят най-важните инструменти, с които разполагаме, нито как да ги използваме. 100% са се мъчили да отговорят на един или двата най-важни въпроса в областта на софтуерната разработка:

  • Какво е състава на функцията?
  • Какво представлява обектната композиция?

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

Целият свят работи днес за софтуер. Всеки нов автомобил е мини суперкомпютър на колела, а проблемите със софтуерния дизайн причиняват реални злополуки и струват реални човешки животи. През 2013 г. журито определи екипа на Toyota за разработка на софтуер за виновен за „безразсъдно пренебрежение“, след като разследването на злополука разкри кода за спагети с 10 000 глобални променливи.

Хакерите и правителствата складират грешки, за да шпионират хората, да откраднат кредитни карти, да впрегнат изчислителни ресурси, за да стартират атаки за разпределено отказване на услуга (DDoS), да напукат пароли и дори да манипулират избори.

Трябва да се справим по-добре.

Вие съставяте софтуер всеки ден

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

Процесът на разработка на софтуер разгражда големи проблеми на по-малки проблеми, изгражда компоненти, които решават тези по-малки проблеми, след което съставя тези компоненти заедно, за да образува цялостно приложение.

Съставяне на функции

Функционалният състав е процесът на прилагане на функция към изхода на друга функция. В алгебрата, имаща две функции, fand g, (f ∘ g) (x) = f (g (x)). Кръгът е операторът на композицията. Обикновено се произнася "съставено със" или "след". Можете да кажете това на висок глас като "f, съставено с g, равно на f на g на x", или "f след g е равно на f на g на x". Казваме f след g, защото първо се оценява g, след това неговият изход се предава като аргумент на f.

Всеки път, когато пишете код по този начин, вие съставяте функции:

const g = n => n + 1;
const f = n => n * 2;
const doStuff = x => {
  const afterG = g (x);
  const afterF = f (afterG);
  връщане следF;
};
doStuff (20); // 42

Всеки път, когато пишете верига за обещания, вие съставяте функции:

const g = n => n + 1;
const f = n => n * 2;
const изчакване = време => ново обещание (
  (разрешаване, отхвърляне) => setTimeout (
    разреши,
    път
  )
);
чакам (300)
  . тогава (() => 20)
  .След (ж)
  .След това (е)
  .then (value => console.log (value)) // 42
;

По същия начин, всеки път, когато свързвате метода на масив от масиви, методи за задаване, наблюдаеми (RxJS и т.н. ...), вие съставяте функции. Ако веригите, вие композирате. Ако предавате възвръщаеми стойности в други функции, вие съставяте. Ако извиквате два метода в последователност, вие съставяте това като входни данни.

Ако веригите, вие композирате.

Когато умишлено композирате функции, ще го направите по-добре.

Умишлено съставяйки функции, можем да подобрим функцията си doStuff () до обикновен еднолинейник:

const g = n => n + 1;
const f = n => n * 2;
const doStuffBetter = x => f (g (x));
doStuffBetter (20); // 42

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

const doStuff = x => {
  const afterG = g (x);
  console.log (`след g: $ {afterG}`);
  const afterF = f (afterG);
  console.log (`след f: $ {afterF}`);
  връщане следF;
};
doStuff (20); // =>
/ *
"след g: 21"
"след f: 42"
* /

Първо, нека абстрактно, че "след f", "след g" влиза в малка полезна програма, наречена trace ():

const track = label => value => {
  console.log (`$ {label}: $ {value}`);
  възвръщаема стойност;
};

Сега можем да го използваме така:

const doStuff = x => {
  const afterG = g (x);
  track ('след g') (afterG);
  const afterF = f (afterG);
  track ('след f') (afterF);
  връщане следF;
};
doStuff (20); // =>
/ *
"след g: 21"
"след f: 42"
* /

Популярни библиотеки за функционално програмиране като Lodash и Ramda включват помощни програми, за да улеснят състава на функциите. Можете да пренапишете горната функция по този начин:

тръба за внос от „lodash / fp / flow“;
const doStuffBetter = тръба (
  д,
  следа („след g“),
  F,
  trace ('след f')
);
doStuffBetter (20); // =>
/ *
"след g: 21"
"след f: 42"
* /

Ако искате да опитате този код, без да импортирате нещо, можете да определите тръбата така:

// тръба (... fns: [... функция]) => x => y
const тръба = (... fns) => x => fns.reduce ((y, f) => f (y), x);

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

pipe () създава тръбопровод от функции, предавайки изхода на една функция към входа на друга. Когато използвате pipe () (и неговия близнак, compose ()) Нямате нужда от посреднически променливи. Функциите за писане без споменаване на аргументите се наричат ​​безточков стил. За да го направите, ще извикате функция, която връща новата функция, вместо да декларира изрично функцията. Това означава, че няма да имате нужда от функционалната ключова дума или синтаксиса със стрелки (=>).

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

Има няколко ползи от намалената сложност:

Работна памет

Средният човешки мозък има само няколко споделени ресурси за дискретни кванти в работната памет и всяка променлива потенциално консумира една от тези кванти. Докато добавяте повече променливи, способността ни точно да припомним значението на всяка променлива се намалява. Моделите на работната памет обикновено включват 4–7 дискретни кванти. Над тези числа честотата на грешките рязко се увеличава.

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

Сигнал за съотношение на шума

Кратък код също подобрява съотношението сигнал / шум на вашия код. Това е като слушане на радио - когато радиото не е настроено правилно на станцията, получавате много смущаващ шум и е по-трудно да чуете музиката. Когато го настроите на правилната станция, шумът отшумява и получавате по-силен музикален сигнал.

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

Повърхностна площ за бъгове

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

По-малко код = по-малка повърхност за грешки = по-малко грешки.

Съставяне на обекти

„Предпочитан състав на обекта над наследяването на класове“ Групата от четири, „Шаблони на дизайн: елементи на обектно ориентиран софтуер“
„В компютърните науки комбиниран тип данни или сложен тип данни е всеки тип данни, който може да бъде конструиран в програма, използвайки примитивните типове данни на езика за програмиране и други съставни типове. […] Актът за изграждане на композитен тип е известен като композиция. ”~ Уикипедия

Това са примитиви:

const firstName = 'Клод';
const lastName = 'Debussy';

И това е съставна част:

const fullName = {
  първо име,
  фамилия
};

По същия начин, всички масиви, набори, карти, слаби карти, TypedArrays и т.н. ... са съставни типове данни. Всеки път, когато изграждате каквато и да е непримитивна структура на данни, изпълнявате някакъв вид обектна композиция.

Обърнете внимание, че групата на четирите дефинира модел, наречен композитен модел, който е специфичен тип рекурсивна композиция на обекта, която ви позволява да третирате отделни компоненти и агрегирани композити по еднакъв начин. Някои разработчици се объркват, мислейки, че съставният модел е единствената форма на обектната композиция. Не се обърквайте Има много различни видове композиция на обекти.

Бандата на четирите продължава: „Ще видите обектната композиция, прилагана отново и отново в дизайнерски модели“, след което те каталогизират три вида композиционни връзки на обекта, включително делегиране (както се използва в състоянието, стратегията и моделите на посетителите), запознаване (когато даден обект знае за друг обект чрез препратка, обикновено се предава като параметър: a use-a отношение, напр., мрежов манипулатор на заявка може да бъде препратен към логър, за да регистрира заявката - обработващият заявката използва логър), и агрегиране (когато дъщерните обекти формират част от родителски обект: a-отношение, например, DOM деца са компонентни елементи в DOM възел - DOM възелът има деца).

Унаследяването на класове може да се използва за конструиране на композитни обекти, но това е ограничителен и крехък начин да го направите. Когато групата на четирите казва „предпочитайте композицията на обекти над наследяването на класове“, те ви съветват да използвате гъвкави подходи към съставянето на композитни обекти, а не твърдия, плътно свързан подход на наследяване на клас.

Ще използваме по-общо определение на обектния състав от „Категорични методи в компютърните науки: С аспекти от топологията“ (1989):

„Композитните обекти се образуват чрез обединяване на обекти, така че всеки от последните да е„ част от “първия.“

Друга добра справка е „Надежден софтуер чрез композитен дизайн“, Glenford J Myers, 1975. И двете книги отдавна не са напечатани, но все още можете да намерите продавачи в Amazon или eBay, ако искате да проучите темата за обектната композиция в повече техническа дълбочина.

Класовото наследяване е само един вид композитна конструкция на обекти. Всички класове произвеждат композитни обекти, но не всички съставни обекти се произвеждат от класове или наследяване на класове. „Композиция на предпочитания обект над наследяване на класове“ означава, че трябва да формирате съставни обекти от малки съставни части, вместо да наследявате всички свойства от прародител в йерархията на класа. Последното причинява голямо разнообразие от добре познати проблеми в обектно-ориентирания дизайн:

  • Проблемът с плътното свързване: Тъй като детските класове зависят от прилагането на родителския клас, наследяването на класове е най-плътното свързване, налично в обектно ориентирания дизайн.
  • Проблемът с крехкия базов клас: Поради плътното свързване промените в базовия клас могат потенциално да нарушат голям брой низходящи класове - потенциално в код, управляван от трети страни. Авторът може да наруши код, за който не е запознат.
  • Проблемът с негъвкавата йерархия: С единичните таксономии на предците, предвид достатъчно време и еволюция, всички класови таксономии в крайна сметка грешат за нови случаи на използване.
  • Проблемът с дублирането по необходимост: Поради нестабилните йерархии, новите случаи на използване често се прилагат чрез дублиране, а не от разширение, което води до подобни класове, които неочаквано се разминават. След като дублирането се включи, не е очевидно от кой клас трябва да произхождат новите класове или защо.
  • Проблемът с горилата / банана: „… проблемът с обектно ориентираните езици е, че имат цялата тази имплицитна среда, която носят със себе си. Искаше банан, но това, което имаш, беше горила, която държи банана и цялата джунгла. ”~ Джо Армстронг,“ Кодери на работа ”

Най-често срещаната форма на композиция на обекти в JavaScript е известна като обединяване на обект (известен още като миксинов състав). Работи като сладолед. Започвате с предмет (като ванилов сладолед) и след това смесвате в желаните от вас функции. Добавете малко ядки, карамел, шоколадов вихър и завършвате с ядков карамелен шоколадов вихър сладолед.

Строителни композити с класно наследяване:

клас Foo {
  конструктор () {
    this.a = 'a'
  }
}
клас бар разширява Foo {
  конструктор (опции) {
    супер (опции);
    this.b = 'b'
  }
}
const myBar = нов Bar (); // {a: 'a', b: 'b'}

Строителни композити с миксинов състав:

const a = {
  a: 'a'
};
const b = {
  b: 'b'
};
const c = {... a, ... b}; // {a: 'a', b: 'b'}

По-късно ще проучим по-подробно други стилове на композицията на обектите. Засега вашето разбиране трябва да бъде:

  1. Има повече от един начин да го направите.
  2. Някои начини са по-добри от други.
  3. Искате да изберете най-простото, най-гъвкаво решение за задачата.

заключение

Тук не става въпрос за функционално програмиране (FP) спрямо обектно-ориентирано програмиране (OOP) или един език срещу друг. Компонентите могат да бъдат под формата на функции, структури от данни, класове и т.н. ... Различните езици за програмиране обикновено предлагат различни атомни елементи за компоненти. Java предлага класове, Haskell предлага функции и т.н. ... Но без значение какъв език и каква парадигма предпочитате, не можете да се измъкнете от съставянето на функции и структури от данни. В крайна сметка това е всичко.

Ще говорим много за функционалното програмиране, защото функциите са най-простите неща, които трябва да се съставят в JavaScript, а общността на функционалното програмиране инвестира много време и усилия за формализиране на техники за композиция на функции.

Това, което няма да направим, е да кажем, че функционалното програмиране е по-добро от обектно-ориентираното програмиране или трябва да изберете едно от друго. OOP срещу FP е фалшива дихотомия. Всяко истинско Javascript приложение, което видях през последните години, смесва FP и OOP широко.

Ще използваме обектна композиция за производство на типове данни за функционално програмиране, а функционално програмиране за производство на обекти за OOP.

Без значение как пишете софтуер, трябва да го съставите добре.

Същността на разработката на софтуер е композицията.

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

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

Време е да научите как да съставяте софтуер.

Купете книгата | Индекс | Напред>

Научете повече на EricElliottJS.com

Видео уроци с предизвикателства с интерактивен код са на разположение за членове на EricElliottJS.com. Ако не сте член, регистрирайте се днес.

Ерик Елиът е експерт по разпределени системи и автор на книгите „Съставяне на софтуер“ и „Програмиране на JavaScript приложения“. Като съосновател на DevAnywhere.io, той обучава разработчиците на уменията, от които се нуждаят, за да работят отдалечено и да приемат баланс между работа и живот. Той създава и съветва екипи за разработка на крипто проекти и е допринесъл за софтуерни преживявания за Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC и най-добрите звукозаписни артисти, включително Usher, Frank Ocean, Metallica и много други.

Той се радва на отдалечен начин на живот с най-красивата жена в света.