Лесно ли е да направите такъв, да го направите така, че да може да се удължи, без да се простреляте в крака? Това не е толкова просто

Писането на CLI (интерфейс на командния ред) инструмент през 2022 г. с помощта на Node.js вече не е истинско предизвикателство, тъй като вече има безброй пакети, които ви помагат да правите точно това.

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

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

Какво правим тогава?

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

По-специално, проектът, който създадох, предоставя 3 команди за работа с низове:

  • Можете да го помолите да раздели низ на думи или да презапише поведението по подразбиране и да посочи друг разделител.
  • Можете да го помолите да напише низ с главни букви. Просто, нищо изискано.
  • И можете също да го помолите да преброи колко думи има на низ. Или ако искате да замените поведението по подразбиране, можете да преброите колко пъти една дума (посочена от вас) се появява в низа.

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

Ето екранна снимка на нашия инструмент, работещ, показващ помощния екран по подразбиране:

Обърнете внимание на 3-те команди (split, upper и wc) и допълнителната команда „help“, която ще предостави подробности за всяка от другите.

Инструментите, които ще използваме

Това няма да е прекалено сложна компилация, както споменах в началото на статията, 2022 г. е, има много библиотеки, които можем да използваме. И по-специално, ще използвам библиотеката Commander, която предоставя всичко необходимо за това.

Разбира се, това ще бъде изградено с помощта на Node.js с обикновен JavaScript, но определено можете да добавите TS или друг допълнителен слой, който искате. Просто ще ви покажа основите.

Какво ме накара да отида с командира? Две неща:

  1. Неговият набор от функции. Както ще видим, добавянето на команди, атрибути и тяхното внедряване е доста лесно. Изисква нулев модел.
  2. Изглежда доста популярен и активно поддържан. Според NPM той се изтегля над 86 милиона пъти седмично и е актуализиран през последните два месеца (по време на писането на това). Така че проверява всички квадратчета, от които се нуждая, за да му се доверя.

И накрая, последният „инструмент“, който ще използвам, не е непременно част от кода, а модел на проектиране, всъщност един от любимите ми: командният модел, подходящо наречен. Нека бързо да го разгледаме, за да разберете защо го използвам.

Разбиране на модела Command

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

По-конкретно за нашия случай на използване, аз преследвах модуларизацията и разширяемостта, така че нека да разгледаме модела Command и да видим дали можем да извлечем това, от което се нуждаем.

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

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

Ако сте повече визуален мислител като мен, ето основна UML диаграма, показваща връзките между включените класове:

Класът „клиент“ е фалшив потребител на нашите команди. В идеалния случай във вашия проект, който ще бъде заменен от всеки код, извикващ командите. Основната идея зад тази структура е, че:

  1. Всички команди имат една и съща „форма“, в смисъл, че всички имплементират един и същ интерфейс (ако имате такава конструкция, както при TS) и разширяват един и същ общ клас. Тази последна точка е свързана с повторната употреба на кода, всички команди ще имат подобно поведение в смисъл, че могат да бъдат изпълнени, те вероятно ще генерират някакъв вид изход и структурно, те най-вероятно ще споделят едни и същи основни характеристики (т.е. в нашия случай, всички те ще имат име, описание, набор от атрибути и опции).
  2. „Клиентският“ код, изпълняващ командата, не трябва да знае точно коя команда изпълнява. Всички те трябва да могат да се извикват от едно и също място и това място може да бъде навсякъде в клиентския код. Това е, което изолира тези „капсули“, които споменах по-рано, от контекста около тях.

По отношение на внедряването, ще видим, че това е доста просто, особено в JS, където няма типове, за които да се притеснявате. Въпреки това, ако работите с TS, всичко, което трябва да направите, е да внедрите интерфейс за класа Command, който да внедрите, и след това да се уверите, че вашият клиентски код импортира този интерфейс като тип на всяка команда.

Както и да е, това е достатъчно теория за тази статия, нека да разгледаме действителното изпълнение на тази команда, за да можем да разберем как тя всъщност ще ни помогне.

Ако сте харесали това, което сте прочели досега, помислете дали да не се абонирате за моя БЕЗПЛАТЕН бюлетин „„Бродянето на един стар разработчик““ и редовно получавайте съвети за ИТ индустрията директно във входящата си поща.

Първо внедряване на клиентския код

Нещо, което наистина обичам да правя, е да започна да прилагам нещата с подход отгоре надолу. Това означава, че първо ще внедря клиентския код, ако приемем, че вече имам готови „капсули“ и следвам модела до съвършенство, и това ще ми помогне да „видя“ API, който трябва да внедря в бъдеще.

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

Така че нека да погледнем и да видим какво имам предвид:

Това е целият клиентски код, от който ще се нуждаем. Така че нека го разделим на секции:

  1. Първото нещо, което правим, е да импортираме както пакета Commander, така и нашия списък с команди (ще разгледаме този след минута).
  2. След това преглеждаме нашия списък с команди и за всяка от тях дефинираме команда, която Commander да разбира. Ето как работи библиотеката, вие дефинирате командата и след това я карате да анализира аргументите на командния ред, за да определи кой трябва да бъде изпълнен.
  3. След това анализираме входа и решаваме метода action на коя команда да извикаме. Това се прави в последния ред на кода, чрез извикване на метода parse на командира.

Това е командният модел в целия му блясък. Прегледахме списък с команди, сякаш бяха един и същ обект (и не са такива, каквито ще видим след минута). За всеки от тях поискахме едни и същи атрибути и никой от тях не се оплаква от това. И накрая, дефинирахме изпълнението на тяхната бизнес логика за всеки един (редове 23–26), извиквайки техния action метод, и познайте какво? Никой от тях не каза: „Чакай, не, не знам за какъв метод говориш“. Това е така, защото всички те споделят една и съща „форма“, или с други думи, всички те разширяват един и същ общ клас.

Нека сега да разгледаме този общ клас, който, както предстои да видите, няма истинска магия вътре:

По същество това е фиктивен клас. Той определя кои атрибути ще имат всички команди и след това ще дефинира метода action, който всички те трябва да внедрят. Ако имахме достъп до абстрактни методи или класове в JS, този би бил перфектният кандидат за него, тъй като никога няма да създадете директно екземпляр на класа Command, той е само там, за да се уверите, че всички команди следват едни и същи стандарти.

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

Така че една обичайна команда ще изглежда така:

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

  1. Конструкторът. Както можете да видите, нашата команда не приема никакви параметри, вместо това ги дефинира предварително сама. Което означава, че тази команда е независима от своя контекст и всеки, който я извика, ще има същия резултат, защото вече знае точно как да се дефинира.
  2. Методът action. Той определя бизнес логиката за нашата команда. Предназначено е да презапише изцяло този, дефиниран в класа Command, поради което не извикваме другия чрез super или нещо подобно. И винаги ще получава 2 атрибута, низа, с който имаме работа, и опциите, посочени от потребителя. Това е стандартно и определено от Commander, така че можем да му се доверим. Въпреки това, ако искахме да бъдем още по-изолирани от външния свят, бихме могли просто да очакваме обект „полезен товар“ и всяка команда да се справи с него, както е необходимо.
  3. И накрая, двата спомагателни метода, _countWords и _countSingleWord тук представляват „частни“ методи. Частта от командата, която се използва само вътрешно и в идеалния случай, няма да бъде достъпна от външния свят.

Ако искате да видите как са имплементирани другите (по-прости) команди, ето пълното репо за вас, с което да си поиграете.

Правейки го лесно разширяем

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

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

Но какво се случва, ако трябва да ДОБАВИМ нова команда? Обратно към фрагмента на клиентския код можете да видите, че импортираме списъка с команди от папката „commands“. Но как събираме този списък? Твърдо кодиран ли е или динамичен?

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

Ето как изглежда файлът index.js в папката commands:

Както можете да видите, всичко, което правим, е да преглеждаме всички файлове в папката (папката commands) и ако те завършват с „Command.js“, тогава ги импортираме и ги инстанцираме.

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

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

Магия!

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

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

Използвали ли сте командния модел в миналото? Какво ще кажете за всеки друг модел на дизайн? Кой е вашият любим? Споделете го в коментарите и аз ще споделя моя!

Създавайте композируеми уеб приложения

Не изграждайте уеб монолити. Използвайте Bit, за да създавате и съставяте отделени софтуерни компоненти — в любимите си рамки като React или Node. Създавайте мащабируеми и модулни приложения с мощно и приятно изживяване за разработчици.

Доведете екипа си в Bit Cloud, за да хоствате и да си сътрудничите по компоненти заедно и да ускорите, мащабирате и стандартизирате разработката като екип. Опитайте компонентни интерфейси с Система за проектиране или Микро интерфейси или изследвайте компонентни интерфейси с сървърни компоненти .

Опитайте →

Научете повече