Python и Go имат различни свойства и затова могат много добре да се допълват взаимно.
Съществува често срещано погрешно схващане, че простото и лесното са едно и също нещо. В края на краищата, ако един инструмент е лесен за използване, вътрешното му устройство би трябвало да е лесно за разбиране, нали? А обратното също е вярно, нали така? Всъщност е точно обратното. Макар че по дух двете понятия указват едно и също нещо (отстрани крайният резултат изглежда лесен), на практика тази повърхностна лекота се постига с огромна сложност „под капака“.
Да разгледаме Python. Добре известно е колко нисък е прагът за навлизане в този програмен език; именно затова Python е фаворит като първи език за програмиране. В училищата, университетите, изследователските институти и многобройните компании по света Python е предпочитан именно защото всеки може да го разбере, независимо от нивото си на образование, а академична подготовка обикновено изобщо не се изисква. За да работите с Python, рядко се налага да прибягвате до теорията на типовете или да разбирате къде, какво и как се съхранява в паметта, в кои нишки се изпълнява този или онзи фрагмент от кода и т.н. Освен това чрез Python можете да се запознаете с някои от най-обширните библиотеки, предназначени за научни изчисления и системно програмиране. Когато се окаже, че разполагате с такава мощ, дори един-единствен ред сорс код убедително демонстрира защо Python се е превърнал в един от най-популярните езици за програмиране на планетата.
Ето тук се появяват нюансите – оказва се, че лекотата, с която можете да изразите всичко на Python, си има цена. Под капака на Python се крие тежък интерпретатор и дори за да се изпълни един ред код, трябва да се извършат много операции. Ако някога сте чували да се казва, че Python е „бавен“ програмен език, то знаете, че тази привидна „бавност“ се дължи на редица решения, които интерпретаторът трябва да вземе по време на изпълнението. Но според мен дори и това не е основният проблем. Сложността на цялата среда за изпълнение на Python и неговата екосистема, заедно с някои произволно взети решения за управлението на пакетите в този език, са причините за изключителната крехкост на тази среда. Поради тази крехкост често възникват случаи на несъвместимост и откази по време на изпълнение. Често се случва да се оттеглите от някое приложение на Python за известно време, да се върнете към него след няколко месеца и да установите, че екосистемата се е променила толкова много, че старото ви приложение вече дори не може да се изпълнява.
Разбира се, това е грубо, дори прекалено голямо опростяване: днес дори децата знаят, че контейнерите могат да решават подобни проблеми. Наистина, с Docker и другите подобни инструменти е възможно да се „заключат“ трайно зависимостите в кодовата база на Python, така че тя да работи практически вечно. Но на практика това е просто прехвърляне на отговорността и изхвърляне на цялата сложност върху инфраструктурния слой на операционната система. Не е краят на света, но не можете да гледате на този проблем през пръсти и да го подценявате.
От лекотата към простотата
Ако се заемем с проблемите, които съществуват в Python, ще получим нещо подобно на Rust – език, който е изключително продуктивен, но е известен с високия си праг за навлизане. По мое мнение Rust съвсем не е лесен за използване и дори нещо повече – изобщо не е прост. Макар че днес Rust е на най-високата вълна на популярност, аз с целия си опит (програмирам от 20 години, като първите ми стъпки бяха направени на C и C++) не мога да погледна откъс от код на Rust и да го разчета с лекота и увереност.
Но преди около пет години открих Go, точно когато работех със система, базирана на Python. Въпреки че не успях да се справя със синтаксиса на Go от първия път, веднага ми стана ясно колко семпли са идеите зад него. Go е проектиран така, че да бъде лесен за разбиране от всеки в организацията, независимо дали става въпрос за джуниър, току-що излязъл от института, или за висш софтуерен мениджър, който просто е хвърлил един поглед върху сорс кода. Ще ви кажа още, че при цялата простота на езика Go, неговият синтаксис много рядко се актуализира. Последната голяма промяна беше генериците, добавени във версия 1.18, и то след десетилетие на сериозни дискусии. В повечето случаи, ако погледнете на код на Go, написан дори преди пет дни или пет години, той ще изглежда много разпознаваем и би трябвало просто да работи.
Но простотата изисква дисциплина. На пръв поглед езикът Go може да изглежда ограничаващ и дори леко ретрограден. Особено ако сравните кода на Go с кратки изрази като списъци или речници в Python:
temperatures = [
{"city": "City1", "temp": 19},
{"city": "City2", "temp": 22},
{"city": "City3", "temp": 21},
]
filtered_temps = {
entry["city"]: entry["temp"] for entry in temperatures if entry["temp"] > 20
}
За написването на същия код на Go са необходими много повече натискания на клавиатурата, но в идеалния случай той ще има едно ниво на абстракция по-малко от Python, който зависи от своя интерпретатор под капака:
type CityTemperature struct {
City string
Temp float64
}
// ...
temperatures := []CityTemperature{
{"City1", 19},
{"City2", 22},
{"City3", 21},
}
filteredTemps := make(map[string]float64)
for _, ct := range temperatures {
if ct.Temp > 20 {
filteredTemps[ct.City] = ct.Temp
}
}
Въпреки че можете да напишете подобен код на Python, в програмирането има едно негласно правило: ако даден език предлага по-лесен (разбирай по-кратък, по-елегантен) вариант, програмистите са склонни да го използват. Но „лекотата“ е субективно понятие, а „простотата“ трябва да е лесна за всеки. Когато едно и също действие може да бъде извършено по няколко начина, това води до развитието на различни стилове на програмиране, а често пъти многото стилове могат да бъдат комбинирани в една и съща кодова база.
Да, Go може да бъде описан като многословен и „скучен“, но когато се работи с него, има още един плюс: компилаторът на Go трябва да свърши много малко работа при съставянето на изпълнимия файл. Компилирането и стартирането на приложенията на Go често е толкова бързо, колкото и в Python (където все пак трябва да се стартира интерпретатор) или Java (където трябва да се стартира виртуална машина), или дори по-бързо от тях. Никак не е изненадващо, че най-бързият изпълним файл е нативният изпълним файл. Той не е толкова бърз, колкото неговите аналози на C/C++ или Rust, но е няколко пъти по-опростен на ниво изходен код. Готов съм да пренебрегна този малък „недостатък“ на Go. И като предястие: двоичните файлове на Go са статично свързани. Това означава, че можете да ги компилирате където и да било и да ги стартирате на машината, която искате. Няма да има никакви зависимости, свързани със средата за изпълнение или библиотеките. За по-голямо удобство все още опаковаме нашите приложения, написани на Go, в Docker контейнери. Тези приложения обаче са миниатюрни и консумират само малка част от паметта и процесорното време в сравнение с аналогичните приложения, написани на Python или Java.
Как да комбинираме Python и Go в наша полза
Най-прагматичното решение, до което стигнахме в нашата работа, е да съчетаем най-добрите характеристики на лекотата на Python и простотата на Go. Считаме, че Python е чудесен полигон за изпробване на прототипи. Това е мястото, където се раждат идеите, където се приемат или отхвърлят научните хипотези. Python е създаден за науката за данните и машинното обучение и тъй като в работата си постоянно се сблъскваме с тези области, едва ли има смисъл да изобретяваме колелото на друг език за програмиране. Освен това Python е в основата на фреймуърка Django, чийто девиз е бързата разработка на приложения (едва ли има равни на него, но за да е пълна картината, разбира се, ще спомена Ruby on Rails и Phoenix for Elixir).
Да предположим, че даден проект се нуждае от минимален потребителски контрол, а администрирането на данните трябва да бъде организирано вътре в приложението ( при нас повечето проекти са именно такива). В този случай правим скелетно приложение в Django, защото то има вграден Admin, фантастично полезно нещо. След като един грубо подготвен пробен модел на Django започне да прилича на продукт, разглеждаме каква част от този модел се поддава на пренаписване на Go. Тъй като в приложението на Django вече има дефинирана структура на базата данни и се разбира как изглеждат моделите на данните, е доста лесно да се напише код на Go, който да замени Django. След няколко итерации достигаме до симбиоза, при която двете половини съжителстват мирно върху една и съща база данни, а комуникацията между тях се осъществява чрез най-опростената система за съобщения. В крайна сметка „шелът“ на Django се превръща в оркестратор – т.е. той отговаря за администрирането и изпълнява тези задачи, които след това отиват за обработка в областта на приложението, написано на Go. Частта, написана на Go, отговаря за всичко останало – от клиентските API и крайни точки до бизнес логиката и задачите за обработка, възникващи в машинния интерфейс.
Тази симбиоза все още не ни е подвела и се надяваме, че няма да ни подведе и в бъдеще. Някой ден ще напиша статия, в която ще обсъдя по-подробно очертаната тук архитектура.
Благодаря ви за вниманието!
Всичко важно от света на технологиите, директно в пощата ти.
С абонирането приемате нашите Условия и Политика за поверителност. Може да се отпишете с един клик по всяко време.
Коментирайте статията в нашите Форуми. За да научите първи най-важното, харесайте страницата ни във Facebook, и ни последвайте в Google Новини, TikTok, Telegram и Viber или изтеглете приложението на Kaldata.com за Android, iPhone, Huawei, Google Chrome, Microsoft Edge и Opera!

