Журнал

Задачи: делить или не делить? (или "У меня бомбит от скрама")

Disclaimer: многие вещи намеренно гиперболизированы. Структура заметки стремная и похожа на поток токсичного сознания.

Плюсы

В последнее время есть тренд на деление задач на максимально мелкие подзадачки. См., например, доклад. С одной стороны, выглядит довольно логично: слона-то надо есть по частям. Плюсов довольно много, мелкую задачку:

  • проще сформулировать
  • проще ревьюить
  • проще мержить
  • проще оценить
  • проще понять
  • можно кинуть на новенького
  • легче параллелить между разными людьми
  • да и вообще, конвеер получается, проще менять шестеренки!

С другой стороны, сейчас разработчик во многих компаниях снова становится кодером (или просто продолжает?). В типичном скраме задачка рождается в недрах разума "подставок для бифштекстов", фильтруется "продуктом", жуется аналитиком и выплевывается в Jira-таск: накодь-ка мне, пожалуйста.

Потом другой разраб посмотрит (при хорошем раскладе), и дальше поедет через "леди баг" на "шило". Все, что не касается собственно кодирования — в топку, делай, что говорят, но потом спросят на "смотре выступлений": "а как ты повлиял на наши бизнес-OKR и KPI года по выручке". Чтобы сильно не вякали, можно дать щедрых 20% времени, незанятого встречами, на "техдолг" (но только если не будет задач поважнее, и "техдолг" может быть таким, что это фича).

Минусы

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

(картинка немного баян, но осознал я это слишком поздно, shame on me)

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

Кстати, про цельную картинку: где архитектура в Скраме? Ответ в рифму, и она "появляется сама", с учетом того, что скрам это про то, как быстро сделать как-нибудь, чтобы потом переделать нормально (когда-нибудь через никогда). Можно, конечно, завести отдельную задачку "на архитектуру", но, во-первых, это "не настоящий Скрам", во-вторых, маловероятно, что будут достаточно проработаны все требования (это же не водопад, чтобы заранее знать стратегический план, "вижена" вперед на одну-две итерации хватит).

Разбиение задачи на кучу мелких не защитит от рисков. Во-первых, о чем-то можно тупо забыть. Во-вторых, многое возникает на середине пути (если это не совсем спинно-мозговые задачи). Поэтому да, сами мелкие задачи проще оценить, но это не значит, что большая задача будет оценена точнее.

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

Мелкие задачи приводят к тому, что разработчик разучивается мыслить. В задаче все уже максимально разжевано — зачем напрягаться? Вот только когда попадается задача на исследование, в которой надо делать что-то необычное или просто высокая степень неопределенности, то она превращается в Большую и Страшную Задачу на Много Сторипоинтов, Которую Надо Поделить (даже если это бессмысленно). И делать ее после кучи мелких действительно тяжело, тяжко говорить на дейли "занимался своей Задачей". Хочется закончить с ней побыстрее, а не покачественнее. А поскольку обычно не разработчик делает тикет, то и навыки декомпозиции тоже страдают. В том числе нормальное разбиение на коммиты. Тяжело ревьюить не большие задачи, а большие коммиты-свалки.

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

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

Кстати, мелкие задачи не мотивируют отписываться в тикетах. В большинстве мелких задач это не нужно. Но вот в более-менее крупных задачах имеет смысл писать о неудачных решениях, возникших проблемах и т.п. Это поможет в будущем понять, почему было сделано "именно так", а не "иначе". Обычно эта информация передается из уст в уста на дейли (разумеется, завтра это уже никто не вспомнит).

Разбиение на мелкие таски в пределе — это создание тикетов, которые дольше писать, чем делать. Чтобы он полежал в бэклоге, чтобы его прогрумили (если есть такой процесс, конечно), приоритезировали, оценили "вэлью", оценили в "сторипоинтах" планнинг-покером... Хотя это мог бы быть просто коммит.

Наконец, от мелких задач нет чувства достижения. Работать в команде — это хорошо, но хочется и самостоятельно что-то из себя представлять. Что говорить на итогах года? Сделал 100500 тикетов, которые попались по очереди? Писал код? Как следствие — легче "забить" на некоторые шероховатости: код-то общий, даже если ты его вылижешь, мало кто это оценит.

Может, в скраме отказались от инженерного проектирования ПО и вся индустрия это просто "авантюра с кодом"?

И чо?

Безусловно, можно делать хорошую работу и в скраме с мелкими задачами, особенно если команда классная (хотя скорее вопреки, да и процесс надо модифицировать под свои обстоятельства).

Но я бы смотрел на вопрос по-другому. Основная цель разработки — это все-таки получение работающего (надеюсь, что слово "качественно" здесь подразумевается) решения. Фокус, соответственно, должен быть на достижении результата, а не на том, как его достичь.

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

Может, это я уже старый дед и не хочу учиться новым трюкам, но все-таки хочется делать цельные задачки, а не кусочки...

СсылкаКомментировать

Про продуктивность

По моим ощущениям, IT живет в культе продуктивности и эффективности. Это в целом неплохо, но тут очень легко перегнуть палку.

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

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

Бизнес зачастую тоже хочет максимальную утилизацию ресурса, да и его понимание agile тоже к этому подталкивает. Разработчик, сидящий без задачи — преступление. Нечего сказать на ежедневном митинге — стыдно, гоните его, насмехайтесь над ним. Что? Какой еще техдолг, там бизнесу надо 100500 новых фич, у них есть просчитанная миллионая ценность, их ждут с прошлого года, а этот ваш долг подождет, не в приоритете. RnD? Не, рисково, мы используем только проверенные технологии. Когда будет 100500 специалистов с этим навыком, тогда может попробуем, мы не лохи сырое использовать. Лучше поучаствуй на этом митинге, где мы будем говорить ртом, потому что нам некогда читать.

В общем, не надо тратить каждую минуту полезно. Это касается как работы, так и жизни.

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

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

Я заметил, что раньше у меня для этого единения с собой был транспорт, обед (когда надо ножками топать в жральню), душ, отпуск и дача, где не было интернета. Переход на удаленку — это хорошо и экономия кучи времени, но такая "эффективность" может быть ложной: это время может заполнится и чем-то не тем. Транспорт перестал быть актуальным, да и рабочий телефон искушает наличием интернета. Ходить обедать теперь никуда не надо — еда уже дома. На одной даче появился интернет (работать надо), в отпуске тоже вечером залипаю в интернете, гостиница без WiFi — это моветон. Даже в ванной стал лежать с планшетом. А были времена, когда к интернету надо было подключаться осознанно и по необходимости — было чем заняться и без него.

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

Что иронично, пишу я эту заметку в воскресенье, на даче, с рабочего ноута, будучи подключенным через интернет с рабочего смартфона. Чтобы "эффективно использовать" свое время, пока насос качает воду из подвала. Не надо так:)

P.S. Некоторые ситуации в первой половине этой заметки я намерено утрировал, чтобы ярче выразить мысль. Но "основано на реальных событиях", как говорится.

СсылкаКомментировать

Впечатления от F#

... на основе обзорного тура и аж одной программы. Ну ладно, еще есть паттерн railway oriented programming, с которым я познакомился довольно давно, и в котором примеры на этом языке. При этом с С# у меня опыта чуть побольше, правил наш замечательный толстый клиент и в студенчестве даже делал приложение для Windows Mobile (в те времена, когда использовался термин "коммуникатор"). Так что не стоит ждать от этой заметки чего-то глубокого или интересного: уровень гораздо ниже, чем у хабровской стрельбы в ногу с Kotlin.

Если есть желание быстренько ознакомиться с синтаксисом, но лень читать даже обзорный тур, то есть F# за 60 секунд.

Установка и среда разработки

Сам dotnet устанавливается через snap, особых проблем не возникло. Напряг момент с телеметрией, которая по умолчанию собирает много чего. К счастью, можно ее отключить через переменную окружения (или отключится только уведомление, это надо еще проверить:) ) , но первые N запусков с ней уже были. И узнать о ней можно либо из документации (которую я разумеется прочел уже потом) или после первого запуска. В общем, в бесплатном приложении товар — это пользователь, классика.

Дальше начались пляски с VS Code. Вроде как из каждого утюга хвалят, да и альтернатив не особо много. Скачал, поставил, установил рекомендуемые плагины. Сгенерировал hello world и успешно запустил его. Решил поправить немного — а мне VS Code такой: а у тебя F# не установлен.

Ладно, чешу репу, использую 2 стандартных приема... И все равно не могу запустить даже стандартный hello world. Оказывается, надо было воспользоваться "простой" инструкцией и отредактировать руками пару файлов проекта VS Code. Я, конечно, все понимаю, это блокнот с наворотами и все такое, но для обычного шарпового приложения пришлось бы пройти через такой же путь, а это продукты одной компании! Вроде в 2021 году живем, чтобы руками файлы билдов править — спасибо хоть, что они не в формате Makefile.

Изучал все не в один присест и когда после перерыва продолжил писать код — запуск опять перестал работать, но по другой причине: VS Сode не мог понять конфигурацию coreclr. Предлагал поставить расширение, чтобы ее поддерживать, и единственный кандидат в каталоге расширений — это "extension for PeachPie - the PHP compiler for .NET and .NET Core". W T F. Удалил все плагины, перезапустил VS Сode, установил заново расширения — не работает. В интернетах советуют... переустановить плагины. Сделал еще раз те же действия — заработало. W T F.

С точки зрения самой разработки — есть какая-то подсветка, которая ощутимо подтормаживает. Автодополнение есть, но не очень впечатлило, подсовывает фигню какую-то (очень субъективное ощущение, но как есть). Иногда раскрываются выведенные типы, но они тормозят вместе с подсветкой.

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

Интересности

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

Синтаксис

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

Часто торчит наследие C и C#: например, "процентные" форматы вывода в sprintf. При этом в сравнениях используется непривычный синтаксис: <> вместо != и not вместо ! — кто-то не любит восклицательный знак. А вот and и or это по-прежнему && и ||.

Объявления функций похожи на Haskell'овские. Однако любую рекурсивную функцию надо явно помечать — это как-то тухло и непонятно зачем. Еще стремно, что для лямбд используется ключевое слово function (хорошо хоть его можно сократить до fun). Функция без аргументов неотличима от обычного выражения (и это хорошо), поэтому для грязных функций без аргументов надо явно передавать unit (скобочками).

Для классов используется странный синтаксис, но что-то в этом есть: меня заинтересовала идея, когда реализация интерфейса явная и локализованная. Вместо override для всех методов нужно написать, мол, реализую интерфейс такой-то: этот метод так, этот сяк. Хотя это стоит пощупать: может, на бумаге хорошо, а в реальности будет как с проверяемыми исключениями в Java.

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

Классная фича — единицы измерения на уровне синтаксиса языка, причем для системы СИ (которая международная система единиц) все единицы уже определены. Был бы я студентом и писал бы решение какой-нибудь типовой задачи по физике — обязательно попробовал бы.

Работа с коллекциями

Списки объявляются через точку запятой: [1; 2]. Альтернатива — по элементу на строку, т.е. разделитель — перенос строки. Судя по всему логика тут в том, что ; и перенос строки "взаимозаменяемы" (на самом деле нет). А запятая — разделитель кортежа, т.е. [1, 2] — это то же самое, что и [(1,2)].

Все полезные функциональные методы — статические. Никаких тебе someList.filter someLambda, вместо этого надо писать List.filter someList someLambda. Когда речь идет о преобразующих функциях — это еще ладно, но вот когда приходится писать Seq.head generator вместо generator.head — это уныло. Местами даже проще было использовать низкоуровневый итератор (Enumerator). Вообще статические методы — это грустно, поменяешь тип коллекции — и всю цепочку переписывать. При этом extension-методы есть, шаблонные типы есть — вроде ничего не мешает реализовать абстрактные map, filter и т.д. Может, это "так исторически сложилось" (extension-методы были в F# не всегда), может, система типов недостаточно мощная, но от этого приятнее не становится.

Есть сахарный метод конвеера, |>: подобно unix'овому pipe |, с его помощью можно передавать результат выполнения предыдущей функции в следующую. Например

[1;2;3] |> List.filter (fun x -> x % 2 <> 0) |> List.map ((*) 5)

выдаст [5; 15]. Можно еще сделать композицию функций через >>, но применимость ее в цепочках, на мой взгляд, сомнительна.

Массивы определяются через интересную комбинацию [| |]. Разочаровало, что они по умолчанию изменяемые, хотя списки — иммутабельные. Неконсистентно, однако.

Странно, что в туре не было словарей и множеств. Нативные для F# можно сделать из списка с последующим преобразованием. Вообще инициализация коллекций не всегда очевидна.

Немного чужеродно выглядит обычный for, когда есть итераторы. Местами читается как SQL-style LINQ, т.е. погано.

Особый интерес вызывают имена некоторых методов. Как вам, например, foldBack2, intersectMany, map3, mapi2, zip3 (когда нет zip2), sortInPlaceBy vs sortInPlaceWith vs sortWith? Еще позабавило, что в ответах на SO иногда рекомендовали использовать LINQ для некоторых преобразований, особенно если начало или конец — C#-коллекция.

Алгебраические типы данных

Реализованы интересно, но странно, что нет из коробки перечисления и строкового представления (они по факту POCO). Забавно, что произведение типов можно записать через умножение.

Single-case Discriminated Union — с одной стороны прикольно, а с другой — это просто альтернативный синтаксис для record, как POCO с одним полем, который особо нового ничего не привносит. Для снятия обертки предлагают использовать паттерн-матчинг, стандартного метода класса вроде как нет.

Взаимодействие с C#

При вызове чего-то шарпового возникает немного вырвиглазная ситуация: часть методов (C#) в UpperCamelCase, а часть (F#) — в обычном camelCase. А стандартная библиотека общая, так что этого не избежать.

С# список и F# список не совместимы: попробовал создать шарповый, чтобы потом по нему map'ом пробежаться и меня постигла неудача. Надо конвертировать через Seq.toList.

Некоторые источники утверждают, что в F# есть null-safety. Это, увы, ЛПП: это гарантируется только в "чистом" F#, вызвал любую С# библиотеку (в т.ч. стандартную системную) — жди беды или обрабатывай явно.

Итого

Какой-то киллер-фичи не нашел (всякая асинхронщина и ФП сейчас почти в каждом утюге). Не могу сказать, что язык удобный, да и с консистентностью у него есть проблемы. Без C# тяжело что-то написать. Вообще напоминает ситуацию со Scala. В ней тоже сильна ФП-парадигма, многие вещи приходится делать через Ж...аву, причем интероп иногда фиговый, свои коллекции, да и изначальное позиционирование было как "ФП на существующей экосистеме". Но у Scala есть какая-никакая ниша, работа с коллекциями там приятнее (особенно в 2.13/3.0), сам язык логичнее, экосистема богаче и нет большого брата с телеметрией. В общем, в F# есть интересные идеи, но использовать его я, конечно, не буду :)

СсылкаКомментировать

Как распознать хорошего лидера

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

Начну с банального: лидер это все-таки тот, кто пашет вместе со всеми для достижения общей цели. Он — часть команды, а не прослойка между ней и более высоким руководством.

Причем это не значит, что он делает ту же работу, что и вы, но он обязан вносить вклад в общее дело. Соответственно, если общение с руководителем сводится к тому, что он раздает приказы и собирает отчеты, то нужен ли он вам? Следствие этого — прозрачность работы. Команда должна понимать, чем занимается лидер и в чем его роль, не хуже, чем то же самое про своих товарищей. Тем или иным образом лидер знает, куда движется его команда и что делает в данный момент (если он не совсем конченный) — и команда должна то же самое знать про него.

Лидер, как никто другой, должен грамотно управлять своим временем. Если он этого не может, то как он это будет делать для команды? На некоторых иногда смотришь и думаешь: неужели напоминалки и таск-трекеры — это какие-то космические технологии, почему они до сих пор для себя их не настроили? Очень стрёмно слышать от своего руководителя "ой, я забыл про %важную задачу%", когда ты ему про нее напоминал до этого раза три. Для выполнения этого пункта, внезапно, нужно научиться делегировать и доверять. Конечно, бывают авралы, но если лидер на постоянной основе пашет круглые сутки и/или почти ни один вопрос нельзя решить без него — то стоит задуматься, а умеет ли он вообще управлять хоть чем-то? А чтобы выстроить обоюдное доверие, нужно не только проводить 1:1 (см. коротыш про общение), но и банально быть честным по отношению к команде. Не кормить всякими завтраками "да-да, надо бы, сделаю", а сразу говорить "не можем, у компании нет на это денег" и т.п.

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

Чтобы иметь возможность спокойно делегировать, команду нужно развивать. Причем не только по процессам, но и по технологиям. И ответственность лидера — обеспечить время и возможность для развития. Но как же это сделать, когда нужно прям вчера сделать миллион задач и некогда все это? Процессы лучше менять сразу, как решили, а не ждать у моря погоды. А для технологий поможет стратегическое планирование и roadmap — чтобы было понятно, что нужно будет делать хотя бы на ближайший год и понимать, идет ли команда с опережением плана (и можно спокойно развиваться) или с отставанием (и надо решать проблемы). Есть мнение, что в agile не нужно долговременное планирование. На мой взгляд, это чушь собачья: без плана развития команда с большой вероятностью будет тонуть в болоте "текучки" и тушить один пожар за другим. Agile — это про возможность быстрой корректировки плана, а не про его отсутствие. Нормальный план также существенно снижает вероятность переработок в команде.

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

Итого:

В мире, где пони кушают радугу и какают бабочками, хороший лидер:

  1. работает как член команды;
  2. делает понятную работу и очевидно полезен команде;
  3. хорошо управляет своим временем и временем команды;
  4. умеет делегировать и доверять команде, не занимается микроменеджментом;
  5. честен и несет ответственность за свои слова, решения и команду;
  6. занимается стратегическим планированием и развитием команды;
  7. открыт к критике и развивается вместе с командой.

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

Дополнительно можно почитать три статейки на эту тему: раз, два, три.

СсылкаКомментировать

Rust и Wasm

Решил я в конце новогодних каникул немного заняться саморазвитием. Выбор пал на язык, на котором надо переписать ElasticSearch, а то и вообще все. Дополнить я это решил Уэб-технологиями, а именно священным граалем желанным отказом от Javascript.

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

Ну что,

Базовые знания

Материалов, чтобы изучить Rust — предостаточно. Я прочел A half hour to learn Rust, паттерны и периодически заглядывал на Rust by Example. Ну и StackOverflow, куда же без него.

Официальный сайт языка предлагает его ставить скачиванием скрипта через curl | sh. Это триггернуло мое образование и я на такое не согласился. Безопасность начинается очень весело.

Страница с альтернативными путями установки содержит еще очень удобный способ: запустить комбо curl | sh в командной строке (sic!). Ну или использовать установщики из tar.gz. Альтернативный путь для Убунты:

snap install rustup --classic

Snap тоже то еще дно, но его я переживу, хотя гореть ему вместе с электроном в одном котле.

Дальше методом тыка я понял, что нужно сделать так:

rustup install default
rustup default stable

Написать об этом для бумеров на сайте в очевидном месте видимо никто не решился, потому что все равно правильный путь — это curl | sh. Все ставится локально для пользователя, по крайней мере по умолчанию.

После этого пишем сложную функцию

fn main() {
    println!("Hello World!");
}

Компилируем через rustc 1.rs и готово, можно получить приветствие через ./1!

Системщики могут посмотреть на LLVM с помощью rustc --emit=llvm-ir 1.rs, который выглядит довольно страшно.

Первые шаги с Wasm

Тут может возникнуть закономерный вопрос

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

(тут мог быть ваш каламбур про wasm → wasp)

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

По теме Wasm + Rust есть туториал, в котором есть примеры и скупые объяснения. Начать предлагают традиционно с curl | sh:

curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

Благо, тут хотя бы пишут нормальную альтернативу:

cargo install wasm-pack

Затем предлагают сразу сгенерировать все что надо через cargo-generate, вместо того, чтобы показать, как что-то подобное делать с нуля. Я и в этот раз не сделал, как предлагали, а просто скопировал git-репозиторий с шаблоном, чтобы его поковырять. Шаблонные данные поправил руками.

Дальше нужно сбилдить проект

~/.cargo/bin/wasm-pack build

Поставить npm, если его еще нет

sudo npm install npm@latest -g

А потом инициализировать шаблон приложения с Wasm:

npm init wasm-app www

Экосистема, аднака! Которая еще и выдаст эмодзи в консоль.

(дед во мне прям кряхтит от кринжа)

После этого у нас будет готова HTML-страничка, которая умеет делать простой alert. Миллиард терабайт node_modules в комплекте.

Но мы с вами живем в мире, где нельзя просто так взять и сделать alert на простой HTML.

Если просто открыть эту html-ку в браузере, то придет мой старый врагCORS.

Поэтому надо еще и запустить сервер

npm run start

А потом еще и поколдовать с webpack, чтобы он запускался не только на localhost. Проверил — даже на моем не самом свежем планшете работает.

Манипуляция DOM

В 2018 году на Хабре писали, что такое делать нельзя, а если и можно, то с извращениями. Отлично, значит этим мы и займемся. Поверхностное изучение вопроса привело меня к библиотеке stdweb. Документация там так себе, тупо ссылки на соответствующие разделы из JS. Да и вообще это не полноценное API, а биндинги к JS.

На основе примера я начал стряпать что-нибудь простенькое, типа передвижения объекта.

Первые грабли

Первый раз все компилировалось весьма медленно. Удалось словить веселые приколы с дефисом против подчерка в имени файла. Потом вляпался в отличия статической строки от динамической — надо руками кастовать между этими типами, да и макрос для форматирования строки тоже не блещет изяществом:

let x = 5;
let y = 6;
alert(format!("Example of format {} {}", x, y).as_str());

Привет плюсовому c_str()!

Трейты, с одной стороны прикольная вещь, а с другой — работают практически как имплиситы в Scala, со всеми вытекающими. К ним еще вернусь.

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

Через некоторое время у меня все успешно скопилировалось, чтобы упасть с паникой при открытии страницы. Понять, что не так — почти невозможно, стектрейс из с кишочками из библиотеки торчит.

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

Озарение

Потом понимаю — что запускаю-то я сервер через npm. Горячая загрузка и все такое — это классно, но имеет ли код при таком способе запуска доступ к DOM? Ссаный фронтенд, где факт того, что у тебя запустилось, ни хрена не значит :(.

Переделываю свой код на основе другого примера. Не работает.

Ладно, наверно я дебил, пробую скомпилировать сам пример, но там ошибка компиляции:

error: missing documentation for a function

Закомментировал линтер, ура, скомилировалось, и.... Uncaught (in promise) Error: undefined. Bellissimo. Потом еще выясняется, что эта библиотека толком и не поддерживается уже.

Возврат к wasm-bindgen и новые грабли

Ок, я тупой, надо было дальше читать туториал по wasm-bindgen и не выпендриваться. Моей ошибкой (кроме использования неактуальной библиотеки) было непонимание, что предыдущие действия были нужны для создания npm-модуля для подключения через package.json. А мне-то нужна была фронтовая библиотека, и для прямой загрузки в браузере нужно было использовать флаг --target web для wasm-pack. Запуск веб-сервера все равно нужен, но делать это можно любым инструментом, даже python3 -m http.server.

Хочу сделать двигающийся объект, смотрю пример про request_animation_frame (тут я его немного сократил):

#[wasm_bindgen(start)]
pub fn run() -> Result<(), JsValue> {
    let f = Rc::new(RefCell::new(None));
    let g = f.clone();

    let mut i = 0;
    *g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
        if i > 300 {
            body().set_text_content(Some("All done!"));
            let _ = f.borrow_mut().take();
            return;
        }

        i += 1;
        let text = format!("requestAnimationFrame has been called {} times.", i);
        body().set_text_content(Some(&text));

        request_animation_frame(f.borrow().as_ref().unwrap());
    }) as Box<dyn FnMut()>));

    request_animation_frame(g.borrow().as_ref().unwrap());
    Ok(())
}

Первое впечатление — жесть, как вообще это читать? Однобуквенные переменные, куча кастов — это точно про безопасность? Вообще по wasm-bindgen дока приемлемая, но в примерах, по ощущениям, люди совсем не стесняются писать "лишь бы работало" и использовать однобуквенные переменные.

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

Следующая ошибка —

expected an `FnMut<()>` closure, found `[closure@src/lib.rs:68:51: 84:6]

Компилятор показывает услужливо место, что не так. Услужливо дает возможность прочитать объяснение ошибки для даунов (rustc --explain E0277): так мол и так, ты совсем дебил, нужно определить трейт для типа. Смотрю в пример, импорты, Cargo.toml — все то же самое, что и в примере. Качаю пример — он билдится. Думаю, что еще может быть не так, методом тыка обнаруживаю, что если использовать свою функцию в замыкании, то возникает такая ошибка. Ссылочная прозрачность, епрст! Будущий я поймет, что проблема была во владении переменными, но даже с учетом этого описание ошибки все равно трешовое.

Еще одна ошибка с типами:

x += dx;
expected `i32`, found `i8`

Вот честно, я даже не хочу разбираться, в каком хитром кейсе я словлю проблем при добавлении 8-битного числа к 32-битному и как правильно сделать тут преобразование типов. Тут уже опускаешься даже ниже плюсов. При этом для ублажения компилятора достаточно сменить i8 на i32 — со сложением двух i32 проблем-то точно не будет, кек.

Дальше было трахание с указателями, статиками, замыканиями — очень интересно конечно... но хотелось бы попроще. Я вроде на современном языке пишу, а не на кроссплатформенном ассемблере.

Порадовал комментарий к замыканиям из примера wasm-bindgen:

Normally we'd store the handle to later get dropped at an appropriate
time but for now we want it to be a global handler so we use the
`forget` method to drop it without invalidating the closure. Note that
this is leaking memory in Rust, so this should be done judiciously!

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

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

Еще всякие забавные мелочи были, например, приколы с неймингом: обнаружил, что есть flat_map, но не для Option. После Scala это выглядит странно. Еще забавное — нет унарного плюса (хотя мне бы он пригодился для читаемости).

Extension-методы — больше похожи на implicit class в Scala. Но еще и интерфейс надо определять обязательно (по крайней мере, как я понял). Не очень удобно для разового расширения, но enterprise-джавистам понравится.

Немного фронтенд-треша

Проблемы у меня возникли и по самой сути задачи двигающегося объекта: пришлось копаться с отличиями offsetWidth, clientWidth, scrollWidth, настоящими и CSS-пикселями, position fixed и absolute, нюансами margin (который зависит от тега и от браузера) и так далее. Без поллитры помощи друга не обошлось. Это, конечно, добавило мне "любви" к фронтенду.

Рефакторинг

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

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

error[E0277]: expected a `FnMut<()>` closure, found `[closure@src/dom_utils.rs:100:51: 110:6]`

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

error[E0310]: the parameter type `TContext` may not live long enough

но это легко решилось.

Захотел выделить бойлерплейт для создания wasm-замыканий, но оказалось, что это сложно. Я поленился, потому что, по видимому, решение было близко к тому, что сокращение кода не сократило бы его, и решил попробовать другую фичу — макросы. Любители аббревиатур тут просто плясать могут: надо помнить, например, чем отличается tt от ty. Особенно весело читать это в первый раз в примерах. Через некоторое количество тупки у меня все-таки получилось сделать то, что я хотел, через макрос.

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

Заключение

Что получилось в итоге — можно посмотреть тут, а исходники — тут.

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

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

У меня сложилось впечатление, что язык на самом деле низкого уровня, просто с сахаром. Надо понимать все чуть ли не на уровне ассемблера, но знать концепции на уровне Scala. Везде, везде сраные детали реализации. С числами два стула — либо использовать адекватные типы данных и постоянно конвертировать, либо забить и использовать везде один (как сделал я).

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

СсылкаКомментировать