Маркетинг в Telegram по-узбекски

Думаю, многие из тех, кто занимался продвижением в Телеграм, слышали об «узбекских каналах». Это словосочетание часто используют не только в прямом, но и в переносном смысле. И в этом случае «узбекский» значит «низкокачественный». Хочу рассказать вам свою историю рекламы в одном из таких каналов и поделиться полученным (горьким) опытом.



«Узбекскими» чаще всего называют каналы с низким качеством контента и подписчиков. Рост аудитории таких каналов стремителен, реклама дешевая и часто предлагается сразу пакетом: пуликация в 5 — 20 каналах по цене одного.

Мой небольшой опыт рекламы в Телеграм показывает, что значительная часть русскоязычных каналов — это «узбекские каналы». Это станет откровением, как только вы попытаетесь закупиться рекламой для собственного уютненького канальчика.

Углубляться в суть этого феномена я не буду. Упоминание о нём встречаются, на просторах интернета. А я сразу перейду к истории провижения на одном из таких каналов.

Первый звоночек

Желание купить рекламу в подобном канале появилось после того, как я увидел их преложение в одной из групп покупки/продажи рекламы.


Не реклама биржы, скорее фотоотчет​.

Конечно, меня, как фраера, которого обычно губит жадность, привлекла цена — 500р за 1/24 в канале с аудиторией 10к+. После личного общения мне даже сделали «специальное предложение»: размещение 2/24 за те же деньги. Правда, по итогу запись продержалась в топе около 40 минут.

Для надежности, прежде чем согласиться на заманчивое предложение, я сходил проверить статистику канала на tgstat и ничего подозрительного не заметил. Статистика нормальная.

Соглашаюсь на сотрудничество. И вот тут — тот самый первый звоночек: оплатить рекламу мне предложили через Qiwi на номер с кодом страны +998 — Узбекистан.

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

Скажу честно, на тот момент я уже читал о том, что реклама в подобных каналах неэффективна и, возможно, опытный телеграм-маркетолог отклонил бы предложение уже на этом шаге, но я решил попробовать.

Публикация и первые подписчики

Публикация была сделана в 18:00, и я начал следить за количеством просмотров моего рекламного поста и ростом подписчиков.

Первые 20 минут: довольно активно пошли подписки. Пришло порядка 10 человек. Я был приятно удивлен и уже предвкушал долгожданный рост своего канала.

Следующие 10-20 минут: здесь мне пришлось отвлечься и заняться насущными делами. Вернувшись через 20 минут, я обнаружил, что число подписчиков выросло на 120 человек.

Ничего себе, подумал я, вот это удача!

Но радость от успеха прошла, как только пришло осознание того, что меня обманули.

Сразу поделюсь дальнейшей статистикой подписчиков в день публикации и на следующий день:

В дальнейшем значительного прироста подписчиков не наблюдается, на следующий день начались отписки​.

Так что же произошло?

Произошла накрутка. Как именно, я не знаю, да и отследить источник, как мне кажется, невозможно. Но ни для кого не секрет, что способов много: можно накрутить и ботов, и живых незаинтересованных подписчиков. Стоит это совсем недорого.

Первое, что я заметил, — это серьезное падение ERR%, коэффициента вовлеченности аудитории. Значительная часть новой аудитории не смотрит посты.


коэффициент вовлеченности аудитории упал

Затем я решил проверить подписчиков. Для администратора телеграм-канала есть еще один полезный инструмент — просмотр последних действий канала. Захожу в «Recent Actions» и смотрю, кто же подписался на мой канал (без скриншота, чтобы не показывать ники):

  1. Около 10 Deleted Account среди новых подписчиков
  2. Значительная часть подписчиков с никнеймами из арабской вязи (с повторами). Они, конечно, могут быть реальными, но вряд ли это та аудитория, которую я искал для канала с мемами, делая рекламу в русскоязычном канале
  3. Много пользователей с повторяющимися и не слишком уникальными именами вроде «cool boy»
  4. Пользователи с «мусорными» никами, которые содержат только цифры и знаки препинания

Выводы

Оценить качество аудитории канала не так просто, как кажется на первый взгляд. Мой опыт показал, что одной проверки кривых роста подписчиков, просмотров и вовлеченности (ERR) недостаточно. Для себя я выделил 2 новых правила:

  1. Остерегайтесь иностранных номеров при оплате. Это, конечно, может быть ложное предубеждение, но как минимум проверьте такой канал дважды.
  2. Не поленитесь зайти в список рекламных публикаций интересующего вас канала на tgstat (секция «Channels quoted by»). Изучите, какие каналы рекламировались в нем ранее. Проверьте их почасовую статистику. Убедитесь, что реклама не привела к аномальному росту подписчиков. А если одновременно с ростом этого показателя происходит резкий спад вовлеченности аудитории (ERR), то, скорее всего, канал просто накручивает клиентам подписчиков.

Вот пример результата другой рекламы на том же канале, где публиковался я:

Реклама этого канала вышла 21-го числа. Картина в точности такая же, как и у меня. Но проверил я это, конечно же, после покупки.

От удаленных аккаунтов я свой канал уже очистил, остальные, надеюсь, отпишутся сами.
Учитесь на чужих ошибках и удачи в продвижении.

П.С.: линк на мой уютный канальчик

ссылка на оригинал статьи https://habr.com/ru/post/499818/

Вы знаете английский? Узнайте, насколько далеко вы могли бы отправиться в прошлое

Язык — это пластичная система, которая постоянно изменяется. Но эти изменения незаметны обычному человеку — для них требуются десятилетия и даже века.

Мы решили показать, насколько сильно менялся английский язык на протяжении веков. Но не в скучной околонаучной статье, а в почти интерактиве. Узнайте, насколько далеко вы могли бы отправиться в прошлое со знанием современного английского и при этом понимать своих собеседников.

Эксперимент будем проводить в пределах Британских островов. Готовы? Поехали!


Что такое современный английский?

История английского языка насчитывает более 1500 лет. И за это время он успел кучу раз измениться до неузнаваемости.

Современный английский — это глобальный язык. По разным данным, на нем говорят от 1,5 до 1,75 млрд людей во всем мире. Но даже сегодня английский нельзя считать статичным.

К примеру, британский и американский английский — это два совершенно разных ответвления языка, которые сегодня развиваются отдельно друг от друга.

В 1997 году в Великобритании вышла книга Джоан Роулинг «Гарри Поттер и Философский камень». Но для того, чтобы опубликовать книгу в США, ее адаптировали под американский английский. Хоть суть и оставалась та же, но многие слова и словосочетания заменили на более привычные для американцев. Пример показательный, потому что у детей словарный запас меньше, чем у взрослых, и они в большинстве своем знают только американские варианты слов, но могут не знать британские.

Британский английский — американский английский — перевод на русский
Philosopher’s Stone — Sorcerer’s Stone — Философский Камень
car park — parking lot — парковка
sherbet lemon — lemon drop — лимонный леденец
cooker — stove — кухонная плита
mummy — mommy — мамуля
cinema — movies — кинотеатр
jumper — sweater — свитер
ice lolly — ice pop — фруктовое мороженое
football — soccer — футбол
trolley — cart — тележка
trainers — sneakers — кроссовки
sweets — candy — конфеты
changing room — locker room — раздевалка
mad — crazy — сумасшедший

Рассматривать диалекты более детально мы не будем и остановимся на британском английском. Американский английский стал активно распространяться только после первой мировой войны.

Для начала уйдем неглубоко в прошлое. Рассматривать будем на примере английской литературы.

XIX век — Чарльз Диккенс

«Дэвид Копперфильд», самый известный роман Чарльза Диккенса, был впервые опубликован в 1849 году. Написан он на новоанглийском.

И знаете что? Язык в нем практически ничем не отличается от современного английского, на котором мы говорим в 2020.

I was born with a caul, which was advertised for sale, in the newspapers, at the low price of fifteen guineas.

Whether sea-going people were short of money about that time, or were short of faith and preferred cork jackets, I don’t know; all I know is, that there was but one solitary bidding, and that was from an attorney connected with the bill-broking business, who offered two pounds in cash, and the balance in sherry, but declined to be guaranteed from drowning on any higher bargain.

***

Я родился в сорочке, и в газетах появилось объявление о ее продаже по дешевке – за пятнадцать гиней.

Но либо в ту пору у моряков было мало денег, либо мало веры и они предпочитали пробковые пояса, – я не знаю; мне известно только, что поступило одно-единственное предложение от некоего ходатая по делам, связанного с биржевыми маклерами, который предлагал два фунта наличными (намереваясь остальное возместить хересом), но дать больше, и тем самым предохранить себя от опасности утонуть, не пожелал.

Как видим, все грамматические конструкции абсолютно идентичны современным. Лексика полностью понятна. Единственное, немного тяжеловат стиль, но это уже вопросы к самому Диккенсу как к писателю.

Поэтому да, современный человек, знающий английский, смог бы поговорить с Диккенсом вообще без проблем.

«Born with a caul» — очень любопытное двусмысленное выражение. Давайте разберем его детальнее.

Caul в прямом переводе — это околоплодный пузырь. То есть, мембрана, в которой находится плод во время развития. Во время родов она рвется и ребенок рождается без нее. Но иногда пузырь бывает очень упругим, и ребенок рождается прямо внутри него.

Одно из значений «Born with a caul» — это как раз рождение ребенка в такой мембране. Почти медицинский термин.

Второе значение «Born with a caul» — «удачливый». Наиболее точный русский эквивалент — «родился в рубашке». Но есть нюанс при переводе. «Родился в рубашке» имеет сегодня только переносное значение, хоть изначально фраза также ссылалась и на мембрану, которую называли «рубашкой».

В «Дэвиде Копперпильде» фраза одновременно несет оба смысла. То есть, ребенок физически родился в мембране и одновременно с этим был удачлив. Но в русском первый смысл пропадает и вся фраза целиком превращается в метафору, когда на самом деле это прямое описание событий.

XVI-XVII век — Уильям Шекспир

Светило британской поэзии Уильям Шекспир писал на ранненовоанглийском. И хоть язык уже был похож на современный, но некоторые принципиальные отличия в нем были. А если поискать, то таких отличий было достаточно много.

Давайте сразу на примере «Отелло» Шекспира.

RODERIGO

Tush! Never tell me.
I take it much unkindly
That thou, Iago, who hast had my purse
As if the strings were thine, shouldst know of this.

IAGO
‘Sblood, but you’ll not hear me!
If ever I did dream of such a matter, abhor me.

RODERIGO
Thou told’st me
Thou didst hold him in thy hate.

Наиболее сильно бросается в глаза наличие местоимения второго лица единственного числа — «thou». То есть, это то самое недостающее «ты», которого нет в современном английском. Местоимение множественное число второго лица выглядело как «ye», а не «you»

Небольшой нюанс. Современное значение слова «you» зависит в большей мере от контекста и интонации говорящего. Но иногда нужно отдельно подчеркнуть, что человек говорит большому количеству людей, а не кому-то конкретному.

Для этого часто используют фразу «you all» или ее аналоги «guys», «fellows» и другие.

Что еще более странно для современного читателя, то в ранненовоанглийском частично сохранилось сопряжение глаголов. Оно крайне похоже на сопряжение глаголов в современном немецком — технология абсолютно та же, даже некоторые формы соответствуют.

I go — я иду
thou goest — ты идешь
he, she, it goeth — он, она, оно идет
we go — мы идем
you go — вы идете
they go — они идут

В целом речь Шекспира довольно понятна. Есть большое количество слов, которые пишутся не так, как мы привыкли, но которые при этом имеют практически идентичное произношение.

Да, над значением оборотов нужно подумать, но в этом и есть стиль Шекспира. Так что если вдруг окажетесь в XVI веке, то сможете объясниться с людьми. Современный английский будет звучать неграмотно из-за упрощения грамматики, но в целом большинство слов и грамматических конструкций вы сможете понять без проблем.

XIV век — Джеффри Чосер

Отправимся еще дальше, в XIV век. В качестве примера рассмотрим одно из самых известных английских произведений средневековой литературы — «Кентерберийские рассказы» Джеффри Чосера. Среднеанглийский язык во всей красе.

Для удобства мы привели адаптацию на современный английский сразу же, чтобы была понятна огромная разница в текстах.

1. Whan that Aprill with his shoures soote
When April with its sweet-smelling showers
2 The droghte of March hath perced to the roote,
Has pierced the drought of March to the root,
3 And bathed every veyne in swich licour
And bathed every vein (of the plants) in such liquid
4 Of which vertu engendred is the flour;
By which power the flower is created;
5 Whan Zephirus eek with his sweete breeth
When the West Wind also with its sweet breath,
6 Inspired hath in every holt and heeth
In every wood and field has breathed life into
7 The tendre croppes, and the yonge sonne
The tender new leaves, and the young sun
8 Hath in the Ram his half cours yronne,
Has run half its course in Aries,
9 And smale foweles maken melodye,
And small fowls make melody,
10 That slepen al the nyght with open ye
Those that sleep all the night with open eyes
11 (So priketh hem Nature in hir corages),
(So Nature incites them in their hearts),
12 Thanne longen folk to goon on pilgrimages,
Then folk long to go on pilgrimages,
13 And palmeres for to seken straunge strondes,
And professional pilgrims to seek foreign shores,
14 To ferne halwes, kowthe in sondry londes;
To distant shrines, known in various lands;
15 And specially from every shires ende
And specially from every shire’s end
16 Of Engelond to Caunterbury they wende,
Of England to Canterbury they travel,
17 The hooly blisful martir for to seke,
To seek the holy blessed martyr,
18 That hem hath holpen whan that they were seeke.
Who helped them when they were sick.

Среднеанглийский показывает читателю всю тщетность бытия. Здесь куда больше непонятной лексики, чем знакомых слов. В это время на английский сильно влияла латынь и старофранцузский, поэтому в лексике много заимствований из этих языков. Очень много глаголов, которые сегодня пишутся абсолютно по-другому.

Еще можно обратить внимание на формирование предложений. Встречается обратный порядок слов «дополнение — сказуемое — подлежащее».

Чтобы прочитать Чосера в оригинале, знаний английского языка как иностранного уже не хватит. Можно понять общий смысл и отдельные фразы, но вникнуть во все нюансы текста уже не получится, даже если ваш английский находится на уровне fluent. Соответственно, если вы окажетесь в Британии XIV века, среднеанглийский будет звучать для вас как совершенно чужой язык, в котором угадываются только отдельные знакомые слова.

VIII — X век — Беовульф

«Беовульф» — самая известная памятка литературы Англии ранних христианских времен. Она занимает примерно 10% всей англосаксонской литературы, дошедшей до наших дней.

Написан «Беовульф» на древнеанглийском языке. А если точнее, на смеси уэссекского и мерсийского диалектов — то есть, диалектов центральной и южной части Англии.

1. Hwæt. We Gardena in geardagum,
Now! We Spear-Danes, in ages gone,
2. þeodcyninga, þrym gefrunon,
Days of the clan-kings, knew glory.
3. hu ða æþelingas ellen fremedon.
How those princes did mighty deeds.
4. Oft Scyld Scefing sceaþena þreatum,
Shield Sheafson seized mead-benches
5. monegum mægþum, meodosetla ofteah,
From many a man, among his enemies;
6. egsode eorlas. Syððan ærest wearð
That terror of warriors flourished later,
7. feasceaft funden, he þæs frofre gebad,
After his first rescue as foundling,
8. weox under wolcnum, weorðmyndum þah,
Waxed under heaven, grew in honour,
9. oðþæt him æghwylc þara ymbsittendra
Till near tribes, over the whale-road,
10. ofer hronrade hyran scolde,
Had to yield to him, forced to submit,
11. gomban gyldan. þæt wæs god cyning.
Offer him tribute. That was a fine king!

Появляется логичный вопрос: «Это чё, английский?». Как ни странно, но да, он самый.

Древнеанглийский — это удел исключительно лингвистов, которые специализируются на этой теме. Даже носители с идеальным знанием языка и большим словарным запасом смогут распознать только отдельные слова.

Древнеанглийский стал той нестабильной опорой для будущего языка, которая постоянно изменялась. Ведь переход от рунного письма к латинскому алфавиту по сути заставил изобретать письменный язык заново.

В этот период видно, что язык пошел от протогерманского (это видно на префиксах «ge-» — «gefrunon», которые и сейчас есть в современном немецком); но на него значительно повлиял староскандинавский (к примеру, из него взята буква торн «þ» и большинство слов, в которых она есть). Также на язык оказала сильное влияние латынь и древние кельтские диалекты.

По сути древнеанглийский язык — это бурлящий котел, в котором смешали диалекты разных народов Британских островов и добавили специй в виде заимствований из других языков. И прошло несколько веков, прежде чем орфография и фонетика языка немного устоялась и обрела хоть какую-нибудь стабильную форму.

Так что если вы вдруг окажетесь в Англии X века, то лучше бы вам побыстрее настраивать свою машину времени и лететь назад. Потому что понять вы не сможете абсолютно ничего.

И если объективно сравнивать современный английский с его более древними предками, то учить его — одно удовольствие. А если учить грамотно — то еще лучше.

Онлайн-школа EnglishDom.com — вдохновляем выучить английский через технологии и человеческую заботу

Только для читателей Хабра первый урок с преподавателем по Skype бесплатно! А при покупке занятий получите до 3 уроков в подарок!

Получи целый месяц премиум-подписки на приложение ED Words в подарок.
Введи промокод oldenglish на этой странице или прямо в приложении ED Words. Промокод действителен до 30.04.2021.

Наши продукты:

ссылка на оригинал статьи https://habr.com/ru/company/englishdom/blog/499806/

Беспроводная точка доступа vs роутер: в чем различия?

В 9:00 утра: Вы проводите видеоконференцию в офисе через ноутбук. 9:00 вечера: Вы смотрите прямую трансляцию на своем мобильном телефоне дома. Подождите минуту, вы когда-нибудь задумывались о том, какие беспроводные устройства работают в вашей беспрепятственной сети? Конечно, вы слышали, как окружающие вас люди время от времени говорят о роутере. Как насчет беспроводных точек доступа (точек доступа)? Это так же, как роутер? Абсолютно нет! Ниже мы поможем вам отличить два разных беспроводных сетевых устройства.

Что такое беспроводной роутер?

Роутер — это сетевое устройство, которое может передавать данные проводным или беспроводным способом. Как интеллектуальное устройство, роутер может эффективно направлять входящий и исходящий трафик в сети. Традиционно роутер был подключен к другим устройствам локальной сети (LAN) через кабели Ethernet для проводной сети. Со временем беспроводные роутеры, которые обеспечивают удобную установку без проводки, постепенно становятся любимыми во многих домах и небольших офисах.

Беспроводной роутер относится к сетевому устройству, которое выполняет функции роутера посредством беспроводного подключения устройств, поддерживающих WiFi (таких как ноутбуки, смартфоны и планшеты). Для корпоративных роутеров они поддерживают услуги IPTV/цифрового телевидения и могут использоваться для Voice over IP (VoIP). Кроме того, они также имеют брандмауэр и защиту паролем для защиты от потенциальных угроз за пределами локальной сети.

01-The connection scenario of a wireless router.jpg

Рисунок 1: сценарий подключения беспроводного роутера

Что такое беспроводная точка доступа?

Беспроводная точка доступа (также называемая беспроводной AP или WAP) представляет собой сетевое аппаратное устройство, которое добавляет возможности Wi-Fi к существующей проводной сети, соединяя трафик от беспроводной станции к проводной локальной сети. Беспроводная точка доступа может выступать в качестве независимого устройства или компонента роутера.

Вообще говоря, беспроводная AP позволяет устройствам без встроенного подключения Wi-Fi получать доступ к беспроводной сети через кабель Ethernet. Другими словами, сигнал от роутера к точке доступа преобразуется из проводного в беспроводной. Кроме того, в случае увеличения требований к доступу в будущем WAP также можно использовать для расширения зоны охвата существующих сетей.

02-The connection scenario of a wireless access point.jpg

Рисунок 2: сценарий подключения беспроводной точки доступа

Беспроводная точка доступа vs роутер: в чем различия?

Как точки беспроводного доступа, так и беспроводные роутеры поддерживают сетевые подключения Wi-Fi и играют аналогичную роль. Так что была путаница. На самом деле эти два сетевых устройства больше похожи на двоюродных братьев, а не на близнецов. Различия между ними будут объяснены ниже.

03-AP vs Router.jpg

Рисунок 3: AP vs роутера

Функция

Как правило, большинство беспроводных роутеров сочетают в себе функции беспроводной точки доступа, Ethernet роутера, базового межсетевого экрана и небольшого Etherne коммутатора. Беспроводные точки доступа

обычно являются встроенными компонентами устройств, такими как роутеры или удлинители сети Wi-Fi. Короче говоря, беспроводные роутеры могут действовать как точки доступа, но не все точки доступа могут действовать как роутеры.

Нет никаких сомнений в том, что беспроводной роутер, играющий роль Ethernet-концентратора, помогает создать локальную сеть, связывая и управляя всеми подключенными к нему устройствами. Однако точка доступа является вспомогательным устройством в локальной сети и обеспечивает доступ только к сети, установленной роутером. Поэтому, если вы являетесь администратором сети, вы можете использовать беспроводной роутер для изменения настроек сети, но беспроводная точка доступа не имеет этой функции.

Соединение

Режим роутера vs режим AP, способ подключения отличается. Беспроводная точка доступа не может подключиться к модему. Обычно коммутатор или роутер будут использоваться в качестве посредника. беспроводной роутер имеет функцию широкополосного коммутируемого доступа и может быть напрямую подключен к модему для доступа в Интернет.

Покрытие

Беспроводные роутеры являются наиболее распространенным сетевым оборудованием сегодня. Но если роутер не может покрыть сигнал Wi-Fi, он будет слабым или не будет сигнала. И наоборот, беспроводные точки доступа могут быть добавлены в местах с плохими сетевыми условиями, что устраняет мертвые зоны и расширяет беспроводную сеть.

Приложение

Как правило, беспроводные роутеры могут обслуживать жилые помещения, рабочие среды SOHO и небольшие офисы или организации и могут легко удовлетворить потребности фиксированного и среднего доступа. Очевидно, что такие роутеры нельзя расширить, чтобы отразить растущий спрос на предсказуемые будущие сети. Что касается беспроводных точек доступа, они в основном используются для крупных и средних предприятий и организаций, включая несколько беспроводных точек доступа для поддержки нескольких пользователей. В отличие от предыдущей ситуации, сетевые администраторы могут добавлять другие точки доступа в соответствии с ростом спроса для охвата более широкой физической области.

Согласно опыту, более производительные продукты могут удовлетворить больше потребностей. Проще говоря, конечный эффект необходимо учитывать. Если вы хотите использовать беспроводную сеть дома только для удовлетворения потребностей членов семьи, тогда достаточно беспроводного роутера. Однако, если вы хотите построить более надежную беспроводную сеть, которая приносит пользу большому количеству пользователей, беспроводная точка доступа является более подходящей.

Заключение

Беспроводные роутеры и точки беспроводного доступа — все зависит от ваших потребностей. Для будущей архитектуры Wi-Fi необходимо учитывать некоторые ключевые факторы: физический размер объекта, покрытие сети, текущее количество пользователей Wi-Fi и даже ожидаемые требования к доступу. В качестве первого выбора для многих пользователей беспроводные роутеры необходимы практически для любого дома и малого бизнеса. После появления беспроводных точек доступа современные крупные предприятия стремятся использовать их для охвата больших площадей или поддержки большего количества пользователей в более крупных локальных сетях.

ссылка на оригинал статьи https://habr.com/ru/post/499822/

Распределённый АД

Полгода назад в третьем выпуске подкаста «Сушите вёсла» мы немного предсказали будущее. Точнее, в эфире мы обсудили удалённую работу распределённых команд, а сейчас это оказалось одной из горячих тем в ИТ-мире.

Вместе с разработчиками и проектным менеджером ребята в студии поговорили о плюсах, минусах и «подводных граблях» в работе распределённых команд.

А ещё попробовали понять, как нужно строить коммуникацию с удалёнными разработчиками и как, в случае такого формата работы, провести корпоратив. Ну и бонусом потренировали принятие факта, что часто для зарубежных компаний программисты — это простая рабочая сила (почти как сборщики дешёвых игрушек, только тссс).

В общем, провели время с пользой и записали подкаст внезапно сильно актуальный на сегодня. Слушайте, где удобно, и заглядывайте в полезные материалы по теме.

Полезные материалы

Предыдущие выпуски подкаста «Сушите вёсла»

Архитектор ПО: зачем он нужен и в чём его проклятие
Источник правды: как аналитик учит менеджера и разработчика работать вместе

CTO всея стартапа
QA для начинающих: как протестировать ракету или самолёт
Очередь в backend: за чем стоим и с чего начать свой путь?

Слушайте нас там, где удобно: Soundcloud, Apple, Google Podcasts
Забегайте обсудить выпуск в Telegram-чат

ссылка на оригинал статьи https://habr.com/ru/company/redmadrobot/blog/499826/

Почему мы выбрали Kotlin одним из целевых языков компании. Часть 2: Kotlin Multiplatform

Продолжаем цикл статей о внедрении языка Котлин в наш процесс разработки. Первую часть ищите здесь.

В 2017 году увидел свет амбициозный проект от компании Jetbrains, предлагающий новый взгляд на кросс-платформенную разработку. Компиляция кода на kotlin в нативный код различных платформ! Мы же в Домклике в свою очередь всегда ищем способы для оптимизации процесса разработки. Что может быть лучше переиспользования кода, подумали мы? Правильно — не писать код вообще. И чтобы всё работало так, как хочется. Но пока так не бывает. И если есть решение, которое позволило бы нам, не затрачивая слишком больших усилий, использовать единую кодовую базу для разных платформ, почему бы не попробовать?

Итак, всем привет! Меня зовут Геннадий Васильков, я андроид разработчик в компании Домклик и сегодня я хочу поделиться с вами нашим опытом разработки на Kotlin Multiplatform для мобильных устройств, рассказать с какими трудностями мы столкнулись, как решали и к чему в итоге пришли. Тема наверняка будет интересна тем, кто хочет попробовать Kotlin MPP (Multiplatform projects), либо уже попробовал, но не довёл до продакшена. Либо довёл, но не так как хотелось бы. Я попробую донести наше видение того, как должен быть устроен процесс разработки и доставки разработанных библиотек (на примере одной из них расскажу начало нашего пути становления в Kotlin MPP).

Желаете историй как у нас всё получилось? Их есть у нас!


Вкратце про технологию

Для тех, кто ещё не слышал либо не погружался в тему, завис в мире Java и только переходит в мир Котлин (или не переходит, но подглядывает): Kotlin MPP — технология, которая позволяет использовать один раз написанный код на множестве платформ сразу.

Разработка ведётся, что не удивительно, на языке Котлин в IntelliJ IDEA или Android Studio. Плюс в том, что все андроид разработчики (у нас по крайней мере) знают и любят как язык, так и эти замечательные среды разработки.

Также большой плюс в компиляции получившегося кода в нативные для каждой платформы языки (OBJ-C, JS, с JVM все понятно).

То есть всё круто. На мой взгляд Kotlin Multiplatform идеально подходит для вынесения бизнес логики в библиотечки. Полностью написать приложение тоже возможно, но пока это выглядит слишком экстремально, а вот разработка библиотек подходит именно для таких больших проектов, как наш.

Немного технического, для понимания, как устроен проект на Kotlin MPP

  • Системой сборки является Gradle, он поддерживает синтаксис на groovy и kotlin script (kts).
  • В Kotlin MPP есть понятие targets — целевые платформы. В данных блоках настраиваются необходимые нам операционные системы, в нативный код которых и будет компилироваться наш код на котлине. На картинке ниже реализовано 2 таргета, jvm и js (картинка частично взята с оф.сайта):

    Аналогичным образом реализуется поддержка остальных платформ.
  • Source sets — собственно из названия понятно, что здесь хранятся исходные коды для платформ. Есть Common source set и платформенные (их столько, сколько в проекте таргетов, за этим следит IDE). Здесь нельзя не упомянуть механизм expect-actual.

    Механизм позволяет из Common модуля обращаться к платформозависимому коду. Мы объявляем expect декларацию в Common модуле и реализуем в платформенных. Ниже пример использования механизма для получения даты на устройствах:

    Common: internal expect val timestamp: Long  Android/JVM: internal actual val timestamp: Long      get() = java.lang.System.currentTimeMillis()  iOS: internal actual val timestamp: Long      get() = platform.Foundation.NSDate().timeIntervalSince1970.toLong() 

    Как видно для платформенных модулей доступны системные библиотеки Java и iOS Foundation соответственно.

Этих пунктов достаточно для понимания того, о чём пойдёт речь дальше.

Наш опыт

Итак, в какой-то момент мы решили, что всё, принимаем Kotlin MPP (тогда он ещё назывался Kotlin/Native) как стандарт и начинаем писать библиотеки, в которые выносим общий код. Вначале это был код только для мобильных платформ, в какой-то момент добавили поддержку для jvm backend. На андроиде разработка и публикация разработанных библиотек проблем не вызывала, на iOS же на практике столкнулись с некоторыми проблемами, но успешно их решили и выработали рабочую модель разработки и публикации фреймворков.

Время что-нибудь покодить!

Мы вынесли разнообразные функциональности в отдельные библиотеки, которые используем в основном проекте.

1) Аналитика

Предыстория появления

Не секрет что все мобильные приложения собирают кучу аналитики. Событиями обвешаны все значимые и не очень события (например, в нашем приложении собирается более 600 разнообразных метрик). А что такое сбор метрики? По-простому это вызов функции, которая отправляет событие с определённым ключом в недра движка аналитики. И дальше уже это уходит в разнообразные системы аналитики вроде firebase, appmetrica и другие. Какие с этим есть проблемы? Постоянное дублирование одного и того-же (плюс-минус) кода на двух платформах! Да и человеческий фактор никуда не деть — разработчики могли ошибиться, как в названии ключей, так и в передаваемых с событием набором мета-данных. Такое однозначно нужно писать один раз и использовать на каждой платформе. Идеальный кандидат для вынесения общей логики и проверки технологии (это наша проба пера в Kotlin Mpp).

Как реализовывали

Мы перенесли в библиотеку сами события (в виде функций с зашитым ключом и набором обязательных мета-данных в аргументах) и написали новую логику обработки этих событий. Формат событий унифицировали и создали движок-обработчик (шину), в которую сыпались все события. А для разнообразных систем аналитик написали слушатели-адаптеры (очень полезное решение оказалось, при добавлении любой новой аналитики мы можем легко перенаправить все, либо выборочно каждое событие в новую аналитику).

Интересное в процессе реализации

Из-за особенностей реализации работы с потоками в Kotlin/Native для iOS нельзя просто взять и работать с котлиновским object как с синглтоном и в любой момент записывать туда данные. Все объекты, которые передают своё состояние между потоками, должны быть заморожены (для этого есть функция freeze()). Но библиотека помимо прочего хранит состояние, которое может изменяться во время жизни приложения. Есть несколько способов разрешения этой ситуации, мы остановились на самом простом:

Common:  expect class AtomicReference<T>(value_: T) {     var value: T }  Android:  actual class AtomicReference<T> actual constructor(value_: T) {     actual var value: T = value_ }  iOS:  actual typealias AtomicReference<V> = kotlin.native.concurrent.AtomicReference<V> 

Такой вариант подходит для простого хранения состояний. В нашем случае хранится конфигурация платформы:

object Config {     val platform = AtomicReference<String?>("")     var manufacturer = AtomicReference<String?>("")     var model = AtomicReference<String?>("")     var deviceId = AtomicReference<String?>("")     var appVersion = AtomicReference<String?>("")     var sessionId = AtomicReference<String?>("")     var debug = AtomicReference<Boolean>(false) } 

Зачем это нужно? Часть информации мы не получаем на старте приложения, когда модуль уже должен быть загружен и его объекты уже находятся в состоянии frozen. Для того чтобы добавить эту информацию в конфиг и потом её можно было читать из разных потоков на iOS и требуется такой ход.

Также перед нами встал вопрос публикации библиотеки. И если для Андроида с этим никаких проблем не возникло, то предложенный на тот момент официальной документацией способ подключать собранный фреймворк напрямую в iOS проект нас по ряду причин не устраивал, хотелось получать новые версии максимально просто и прозрачно. Плюс чтобы поддерживалось версионирование.
Решение — подключать фреймворк через pod файл. Для этого сгенерировали podspec файл и сам фреймворк, положили их в репозиторий и подключать библиотеку к проекту стало очень просто и удобно для iOS разработчиков. Также, опять же для удобства разработки, мы собираем единый артефакт для всех архитектур, так называемый fat framework (на самом деле жирный бинарник, в котором уживаются вместе все архитектуры и добавленные мета-файлы, сгенерированные kotlin плагином). Реализация всего этого дела под спойлером:

Кому интересно как мы сделали это до выхода официального решения

После того как kotlin плагин собрал для нас кучу разных фреймворков под разные архитектуры, создаём вручную новый universal фреймворк, в который копируем мета-данные из любой (в нашем случае взяли arm64):

//add meta files (headers, modules and plist) from arm64 platform task copyMeta() {     dependsOn build      doLast {         copy {             from("$buildDir/bin/iphone/main/release/framework/${frameworkName}.framework")             into "$buildDir/iphone_universal/${frameworkName}.framework"         }     } } 

Далее, как я уже сказал, необходимо раскормить бинарник, сделать его жирным. Для этого используется утилита lipo, собираем все архитектуры вместе:

//merge binary files into one task lipo(type: Exec) {     dependsOn copyMeta      def frameworks = files(             "$buildDir/bin/iphone32/main/release/framework/${frameworkName}.framework/$frameworkName",             "$buildDir/bin/iphone/main/release/framework/${frameworkName}.framework/$frameworkName",             "$buildDir/bin/iphoneSim/main/release/framework/${frameworkName}.framework/$frameworkName"     )     def output = file("$buildDir/iphone_universal/${frameworkName}.framework/$frameworkName")     inputs.files frameworks     outputs.file output     executable = 'lipo'     args = frameworks.files     args += ['-create', '-output', output] } 

Следующий шаг требуется, так как мы копируем мета-файлы из одной архитектуры и там нам Kotlin MPP в файле Info.plist указывает ключ UIRequiredDeviceCapabilities, который нам не нужен от слова совсем (мы же генерируем универсальный фреймворк). Здесь нам на помощь приходит утилитка PlistBuddy:

//workaround //remove UIRequiredDeviceCapabilities key from plist file (because we copy this file from arm64, only arm64 architecture was available) task editPlistFile(type: Exec) {     dependsOn lipo      executable = "/bin/sh"     def script = './scripts/edit_plist_file.sh'     def command = "Delete :UIRequiredDeviceCapabilities"     def file = "$buildDir/iphone_universal/${frameworkName}.framework/Info.plist"      args += [script, command, file] } 

edit_plist_file.sh:

/usr/libexec/PlistBuddy -c "$1" "$2" 

Остальное проще, создаём zip архив из нашего универсального фреймворка (необходимо для подключения через podspec файл):

task zipIosFramework(type: Zip) {     dependsOn editPlistFile     from "$buildDir/iphone_universal/"     include '**/*'     archiveName = iosArtifactName     destinationDir(file("$buildDir/iphone_universal_zipped/")) } 

Генерируем сам podspec файл:

task generatePodspecFile(type: Exec) {     dependsOn zipIosFramework      executable = "/bin/sh"      def script = './scripts/generate_podspec_file.sh'      def templateStr = "version_name"     def sourceFile = './podspec/template.podspec'     def replaceStr = "$version"      args += [script, templateStr, replaceStr, sourceFile, generatedPodspecFile] } 

Скрипт generate_podscpec_file.sh делает только подстановку параметров в шаблон:

sed -e s/"$1"/"$2"/g <"$3" >"$4" 

Ну и простым curl закидываем всё что получилось в любой подходящий репозиторий:

task uploadIosFrameworkToNexus(type: Exec) {     dependsOn generatePodspecFile      executable = "/bin/sh"     def body = "-s -k -v --user \'$userName:$password\' " +             "--upload-file $buildDir/iphone_universal_zipped/$iosArtifactName $iosUrlRepoPath"     args += ['-c', "curl $body"] }  task uploadPodspecFileToNexus(type: Exec) {     dependsOn uploadIosFrameworkToNexus      executable = "/bin/sh"     def body = "-s -k -v --user \'$userName:$password\' " +             "--upload-file $generatedPodspecFile $iosUrlRepoPath"     args += ['-c', "curl $body"] } 

Ну и весь процесс выше запускается вот такой тасочкой:

task createAndUploadUniversalIosFramework() {     dependsOn uploadPodspecFileToNexus     doLast {         println 'createAndUploadUniversalIosFramework complete'     } } 

Profit!

На момент написания статьи существуют официальные решения для создания fat framework и генерации podspec файла (и вообще интеграции с CocoaPods). Так что нам остаётся только закинуть созданный фреймворк и podspec файл в репозиторий и точно также подключать в iOS проект как и раньше:

Приятности

После реализации такой схемы с единым местом хранения событий, механизмом обработки и отправки метрик наш процесс добавления новых метрик свёлся к тому, что один разработчик добавляет в библиотеку новую функцию, где описывает ключ события и необходимые мета-данные, которые обязан передать разработчик для конкретного события. Далее разработчик другой платформы просто выкачивает себе новую версию библиотеки и использует в нужном месте новую функцию. Намного проще стало поддерживать консистентность всей системы аналитики, также намного проще теперь менять внутреннюю реализацию шины событий. Например нам в определённый момент для одной из наших аналитик было необходимо реализовать собственный буфер событий, что и было сделано сразу в библиотеке, без необходимости менять код на платформах. Profit!

Мы продолжаем переносить всё больше кода в Kotlin MPP, в следующих статьях я продолжу рассказ о нашем приобщении к миру кросс-платформенной разработки на примере ещё двух библиотек, в которых были использованы сериализация и база данных, работа со временем. Расскажу об ограничениях, которые встретились при использовании нескольких kotlin фреймворков в одном iOS проекте и каким образом это ограничение получается обойти.

А сейчас кратко о результатах наших изысканий

+ Всё стабильно работает в продакшене.
+ Единая кодовая база бизнес логики сервисов для всех платформ (меньше ошибок и расхождении).
+ Сократили расходы на поддержку и уменьшили время разработки под новые требования.
+ Увеличиваем экспертизу разработчиков.

Ссылки по теме статьи

Сайт Kotlin Multiplatform: www.jetbrains.com/lp/mobilecrossplatform

З.Ы.:
Не хочу повторяться про лаконичность языка, а следовательно фокусировку на бизнес-логике при написании кода. Могу посоветовать несколько статей, раскрывающие эту тему:

«Kotlin под капотом — смотрим декомпилированный байткод» — статья описывающая работу компилятора Kotlin и покрывающие основные структуры языка.
habr.com/ru/post/425077
Две части статьи «Kotlin, компиляция в байткод и производительность», дополняющая предыдущую статью.
habr.com/ru/company/inforion/blog/330060
habr.com/ru/company/inforion/blog/330064
Доклад Паши Финкельштейна «Kotlin — два года в продакшне и ни единого разрыва», который основывается на опыте использования и внедрения Kotlin в нашей компании.
www.youtube.com/watch?v=nCDWb7O1ZW4

ссылка на оригинал статьи https://habr.com/ru/company/domclick/blog/499820/