Стой под стрелой
10.4K subscribers
17 links
Ведет @nikitonsky. Рекламы нет
Download Telegram
После https://t.me/nikitonsky_pub/38 несколько людей мне написали, что нечестно хвастаться Кложей, не объяснив, что же конкретно там сделали. Справедливо, исправляюсь.

Почему в Кложе нет проблем с сериализацией? Потому что весь язык построен вокруг работы с голыми структурами данных (списками, мапами, массивами, множествами). Конечно, такие структуры легко серилизовать в любом языке. Уникальность Кложи в том, что и остальной язык тоже построен так, что больше тебе ничего не нужно. Голые структуры максимально глубоко, естественно и _удобно_ интегрированы везде, настолько, что кроме них почти ничего и нет.

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

Потом оказывается, что «просто» сохранить/прочитать недостаточно, нужно и мигрировать, и поддерживать несколько версий, и разные JSON/YAML/Protobuf/XML поддерживать одновременно, и иногда еще в разном виде отдавать разным клиентам. В мире Кложи ты просто пишешь маленькие трансформирующие функции под каждую такую «проблему», которые тоже лежат и не отсвечивают для остального кода. Скажем, взять поле «зарплата» из мапы «отдел» и переложить в мапу «сотрудник», в случае со словарями тривиально. Это просто еще одна функция, которая вызовется где-то между отправкой на сериализацию и собсвенно сериализацией. Локальность проблемы и ее решения, красота.

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

Другими словами, чтобы решить проблему сериализации, нужно починить весь остальной язык. Что объясняет, почему нельзя это решение «просто перенести» куда-то еще.

Что-то близкое есть в JS с его JSON. Пока ты не используешь JS-классы и не суешь прототипы, не используешь ничего кроме строковых ключей, не разрешаешь в своих мапах ключи constructor и prototype и вообще ограничиваешь ключи [a-z][a-z0-9]*, ну и не складываешь ничего кроме строк, чисел (не слишком больших) и true/false, не пользуешься Map/Set и сериализуешь только в/из JSON. Но в остальном да, похоже.

И в Erlang, кстати, похоже. Вообще, когда начинаешь думать о распределенных системах, понимаешь, что классы жутко негибкая, капризная штука и с сетью не дружат вообще. А дружат чистые данные. И что вместо того, чтобы с этим фактом героически бороться (и проигрывать), проще принять и задизайнить под это язык. Что? Классов не будет? Что ж, невелика потеря.
Для разнообразия простой, короткий, конкретный совет:

Всегда используйте длинные версии ключей в Bash-скриптах.

Да, это весь совет. Потому что читать все эти

grep -wq
ls -hal
ps -efH
nc -4u -w0
curl -fLo4
tar -cvzf
xargs -0vrf


просто нет сил уже. Голова не резиновая, есть более полезные вещи чтобы ее занять. А ключи везде разные, системы нет, прописные буквы и строчные часто означают СИЛЬНО разное, гуглить это невозможно, да и выглядит как оккультная магия.

Сравните:

/bin/bash -eux

и

/bin/bash -o errexit -o nounset -o xtrace


Ладно, у себя в консоли можете делать что хотите. Но скрипты увидят другие люди, и эти люди тоже чьи-то дети! Хотя бы ради них.
Я уже четыре года смотрю на Ютубе видео про Третьих Героев Меча и Магии. Сэра Троглодита всегда, других геройщиков по настроению. Иногда в фоне пока работаю — очень классная музыка.

Сам я до этого играл в универе на любительском уровне, так что это еще и очень образовательные видео. Узнал много нового, научился всяким трюкам (я так думал).

Так вот, сел я на выходных поиграть сам, и... У меня ничего не получилось! То есть не «в два раза хуже», а раз в 100 хуже, полный крах. Я практически ничего не мог сделать, армия таяла на глазах, куда идти я не знал, в итоге сидел дома строил капитолий.

К чему я это? Больше всего меня поразило, насколько долго (четыре года!) можно смотреть на чужую игру в упор и не видеть суть. Оказывается, смотреть/слушать/учиться и делать самому это два принципиально разных навыка. Очевидно, да, но все-таки мне почему-то казалось, что если учиться чему-то, то навык деланья будет как бы автоматом качаться тоже, только в более медненной пропорции. Типа 1/2 или 1/3. Или 1/5 хотя бы. Но нет. Стоит на месте.

Так что всем теоретикам и диванным комментаторам привет, я с вами!
История дня: два британских аристократа организовали в Оксфорде в 1946 году общество для людей с высоким интеллектом. Единственным критерием приема был IQ тест, нужно было показать результат в верхних 2%, то есть лучше чем 98% остальных людей. Назвали Менза («стол» на латыни), имея в виду круглый стол и что в сообществе все на равных.

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

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

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

Про создание Ninja есть отличная статья, гуглится по «The Performance of Open Source Software Ninja». Там много хороших решений, но я хочу подчеркнуть две вещи.

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

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

Вторая интересная мысль о том, что мало написать быструю программу, надо еще создать ощущение скорости. В случае Ninja решение настолько же простое, насколько и гениальное: если Ninja делать нечего, она печатает ровно одну строчку («ninja: no work to do.») и завершается.

Казалось бы, подумаешь. Я тоже так могу! Не можешь. Естественная тенденция для программистов: насрать в лог побольше и удивляться, чего это их программу никто не любит. Какой-нибудь Maven высирает 25 строк даже если не сделал НИ-ЧЕ-ГО. Найти упоминание об этом важнейшем факте в 25 строках разноцветного орнамента, скажем так, нетривиально.

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

Во-первых, нужно осознать это как проблему: каждый случайный println и raise в кодебазе это, внезапно, интерфейс вашего приложения, его лицо, одежка, по которой его будут встречать. А значит применимы все принципы и законы UI/UX.

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

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

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

То, что ninja печатает ровно одну строку не значит, что она ничего не делает. Нет, происходит куча работы, о которой потенциально можно было бы рассказать: какое окружение использовалось, какие версии программ, в какие папки смотрели, сколько файлов обработано. Просто Эвану хватило яиц принять решение, что это все внутренняя кухня, которая пользователям не особо интересна, пока программа работает нормально и никакая жопа нигде не отвалилась. Ну и хватило сил пойти и реализовать такой вот corner case.

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

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

Облачные провайдеры. Я знаю, что там сейчас целые стеки и можно накликать себе целую high-load систему, не выходя из AWS Console (No Code, хе-хе), и я даже работал в таких стартапах. Но для меня это всегда было не больше, чем способом создать N линуксовых виртуалок. Но я и не понимаю толком, зачем кому-то что-то большее, так что эта история совершенно прошла мимо.

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

“Современный” фронтенд. Что такое вебпак, гульп, бабель, постцсс, сасс, лесс, автопрефиксер. Что из этого актуально, что устарело, кто за что отвечает, кто в кого входит — ноль идей. Тешу себя мыслью, что «а зачем, вы каждый год разные», но тем не менее.

Git CLI. Это чуть более сознательный выбор. Всегда пользуюсь графическим UI, потому что CLI неудобный и нелогичный, хрен запомнишь. Сам Git, мне кажется, я знаю неплохо, но вот CLI... Восхищаюсь людьми, которые могут на память воспроизвести git log --pretty=oneline --abbrev-commit, git push origin :branch и помнят, чем отличаются git diff, git diff --staged, git diff --cached и git diff HEAD и все пять видов reset (mixed, soft, hard, merge и keep).

Ни разу не писал приложения под телефоны, какие-нибудь AppKit/UIKit для меня пустой звук.

SQL знаю довольно посредственно, на уровне select/join/group by/limit/in (давно не пользовался). Таблицу не создам (ну, с гуглом наверное создам), индекс не оптимизирую, запрос не отлажу, шардинг/репликацию не настрою, изоляцию транзакций не выберу.

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

Ассемблер не знаю совсем, даже не знаю, как его добывать и на что там смотреть.

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

Бенчмаркинг и оптимизация кода. Кажется, есть какие-то более правильные, «научные» способы (не знаю!), но все что я когда-либо делал, это запускал код в цикле на 100 000 раз и потом делил время на 100 000.

Машин лернинг, AI и биткоины прошли точно так же мимо. В теории я понимаю, что нужно комп с мощной ГПУ и гонять на нем сетку пару дней, на практике даже на видео не видел, как это делается и какой софт для этого используется.

Такой вот список. Делитесь своими слепыми пятнами, самозванцы!
Самое тупое, что может быть — это апскейльнутые до 60 fps и 4k видео из менее детального источника. Эта функция иногда встроена прямо в телевизоры, иногда на Ютуб заливают предобработанные локально видосы. И это максимально ужасно.

Дело вот в чем. Движение на экране — это иллюзия. Глазу показывают статичные картинки, а мозг внутри аппроксимирует их в непрерывный процесс. Если ему показывают 24 кадра в секунду, снятые с интервалом в 1/24 секунды, все честно: он видит положения объектов ровно тогда, когда они на самом деле происходили, и может воссоздать любые промежуточные состояния.

А теперь подстава: движение бывает нелинейным. Объект может ускоряться, замедляться, поворачиваться, удаляться и приближаться. Это значит, что если бы то же самое видео снимали каждые 1/48 секунды, средние кадры не были бы арифметическии средними между двумя соседними.

Но это ровно то, что делает аппроксимация! Она просто ставит между двумя известными кадрами их усреднение с коэффициентом 0.5. Это не просто бессмысленно, это вредно! Мозг не может нормально считать движение, потому что этот среднее состояние противоречит соседним, и ему нужно его как-то отфильтровать. Поэтому такое движение выглядит максимально неестественно и смысла в нем немного: картинка выглядит хуже с аппроксимацией, чем без нее. Наверное, единственный смысл такой фичи маркетинговый (сможете смотреть своего Чарли Чаплина в 120 fps, деды!).

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

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

Если вы купили новый телевизор, первое, что нужно с ним сделать — отключить любые «улучшатели» изображения. Это интерполяция кадров, да, но также задранный контраст, шарпенинг (выкручивайте в 0), шумоподавление, и мало ли что там еще бывает. Точно все со словами «улучшение», «умный» или «демо режим».

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

Доказательство, что эти «улучшения» не делают лучше, очень простое. Вы правда думаете, что голливудская студия потратила 200 миллионов долларов на фильм, но не догадалась задрать цвета и пошарпить изображение? Если бы изображению от этого становилось лучше, это сделали бы еще при мастеринге. Но почему-то не делают. То же самое касается растягивание 24-кадрового (мульт)фильма до 60 кадров. Думаете, в голливуде не умеют делать то, что встроено в каждый телевизор?
Возвращаемся к постам про программирование. Сегодня hot take: логирование переусложнено.

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

Давайте посмотрим на уровни логирования. Что такое, например, WARNING? ERROR понятно, что-то сломалось. На INFO мы еще посмотрим, но пока предположим, что INFO это что-то, что очень нужно сообщить пользователю, но ничего не сломалось. А что в такой системе координат WARNING? Ничего не сломалось, сообщить надо, но чуть нужнее, чем INFO?

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

Теперь INFO. Типа, это что-то, что надо сообщить пользователю. Это тоже очень сомнительная мысль, что программа общается с пользователем через лог-файлы. Нормальная работа это нормальная работа, если все идет хорошо, то в лог никто смотреть и не будет. А если не идет, то это уже ERROR!

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

В целом у DEBUG такая же тонкая грань с INFO, как и у WARNING с ERROR. Идея о том, что есть некоторый промежуточный человек между разработчиком и пользователем, которому интересны какие-то дополнительные сообщения, кроме ошибок, но не интересен весь остальной вывод для разработчика, ну, зыбкая.

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

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

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

Поэтому:

- уровня логирования нужно всего два: verbose и important.
- В important печатаем только ошибки и business-critical вещи, в verbose печатаем все, что может быть интересно разработчику.
- Всякие «успешно подсоединился к хосту АБВ», «забиндил порт УФХЦЩ» — в verbose и не нужны (см. мой пост про ninja).
- Библиотеки всегда фильтруются только по important, потому что отлаживать их обычно все-таки надо только самим разработчикам библиотеки.

Такой дизайн. Аж руки зачесались свою библиотеку пойти написать.
К статической типизации я отношусь взвешенно. Понимаю, что у нее есть как плюсы, так и минусы. Как и с любой технологией, главное, чтобы вы понимали, что конкретно вы получаете. Осознанность (©) ().

Про самое осмысленное использование я читал у Gary Bernhardt в Execute Program. Они взяли TypeScript, заморочились тем, чтобы протянуть одни и те же типы от базы до конечных API-endpoints (а может и до фронтенда? Наверное, до фронтенда тоже). Ну и типа пожинали плоды потом, наслаждались тем, как любое изменение показывает тебе _действительно_ _все_ места, которые ломаются в системе. Отличный пример использования статической типизации как инструмента для достижения конкретной цели, которая сформулирована явно.

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

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

Я очень рад за Сергея (каждый программирует как хочет), но я все-таки предпочитаю, чтобы инструмент помогал мне решать мои проблемы, а не создавал новые на пустом месте. На возражения «типы вскрывают настоящую сложность задачи, которую ты не осозновал» предлагаю признаться: почти все мы пишем довольно несложные вещи. Вся привнесенная сложность — она, ну, привнесенная, от скучающего ума, ищущего, куда себя приложить. В конце концов нам нужно взять байты из одного места и отправить их в другое. Все, что происходит между, происходит только в нашей голове, и нам решать, будет ли эта башня из мысленных конструкций готическим собором или кратчайшей тропинкой из точки А в точку Б.

Я не хочу сказать, что я против статической типизации по какой-то причине (хотя наверняка кто-нибудь это именно так и прочитает). Я пытаюсь сказать, что типы сами по себе они ничего не дают и не делают программу лучше. Они не заменяют логику, простоту, хороший дизайн. Но если знать, чего ты от типов хочешь, их можно правильно использовать. А если не знать, то получится как всегда.
Одна из самых полезных привычек, которые я недавно приобрел — заводить папку script в каждом проекте и складывать туда скрипты с предсказуемыми именами. run.sh, build.sh, test.sh, bootstrap.sh — что-то типа такого. Прочитал я это, кажется, в Гитхаб блоге, но найти уже не могу. Но я даже лучше вам расскажу!

Раньше было как? Проект на Кложе, значит есть стандартный project.clj, ну и ты вроде как помнишь что надо писать lein run или lein repl. Потом появляются алиасы, они конечно записаны в project.clj, но туда надо постоянно заглядывать, чтобы вспомнить. Потом появился deps.edn, какие-то проекты на него уже переехали, а какие-то пока не трогал, и приходится помнить уже про два способа запуска: clj и lein. Потом где-то решил поэкспериментировать с тестовым фреймворком, и вот там уже своя особая команда для запуска тестов. Потом где-то автоматизировал весь релиз, а где-то достаточно простого lein deploy. Потом появилась Fira Code, которая вообще на Питоне, и блог на Руби.

Короче, вы поняли. Зоопарк. С каждой конкретной системой в отдельности (maven, pip, lein, clj, gradle, cmake) можно было бы обойтись мышечной памятью и встроенными алиасам, но самих систем слишком много, они все разные и помнить все это нет никаких сил.

Нужна объединяющая идея. И это папка script! Настолько же удобно и гениально, насколько просто и тупо. Но это работает! Это просто набор стандартных ручек, которые всегда торчат одинаково в одних и тех же местах у _всех_ проектов. Внутри может быть one-liner типа lein run — пофиг! Главное, что про это не нужно помнить.

Насколько это удобно понимаешь, когда встречаешь проект без этого. Где-то положил скрипты в корень — и все никак не можешь их вызвать. Где-то сумничал и назвал start.sh вместо run.sh и вот пальцы уже тупят, не понимая, почему не получается запустить проект. А вот когда это работает, то это настоящее волшебство и совсем другой уровень жизни. Рекомендую.

UPD: В комментах деды топят за Make для этих же целей. Про Make у меня есть такое мнение:

- Нет автодополнения по Tab (или его надо устанавливать)
- Скрипты могут быть на разных языках (у меня как минимум есть sh, python, clojure и babashka)
- Неудобно запускать из другой директории
- Ну и Make надо учить. Если уже его знаешь, наверное, он может выглядеть привлекательно. Я не знаю, и каких-то других аргументов именно _за Make_, кроме «я его уже знаю», не видно.
Моя любимая фигня — когда программисты садятся в терминал и начинают пытаться сделать его красивым. Ну там сначала цвета, конечно, потом крутилки из /-\|, потом прогрессбары разной степени изобретательности, потом рамочки, потом powerline и панельки всякие в vim-е, ну и git log --graph конечно, в котором люди вообще вензеля выписывают.

Вопрос, собственно: если вам так сильно нужны графические штуки, в чем смысл сидеть в терминале и его убогими, никак для графики не предназначенными костылями воспроизводить парадигмы графического UI? Чтобы доказать — кому, что? Vim с панелями в терминале это не CLI, это такой же GUI, только плохо нарисованный.

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

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

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

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

Мораль такая, что при разработке UI-фреймворка надо стремиться к лаконичности и простоте разработки терминала, только без минусов.
Одна из вещей, про которые я железно уверен, что они работают (а таких ОЧЕНЬ немного) — код-ревью.

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

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

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

Завалилось наше начинание очень скоро и очень живописно. Коммитили мы конечно быстро, но такая фигня получалась. По итогам все согласились (абсолютно добровольно и единогласно!) что не хватает именно код ревью. Нагляднее и доходчивее я мало что ощущал в жизни.

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

Казалось бы, раз это такие важные вещи, почему бы их не обсуждать проактивно? А потому что. Не получается. Не работает. На каждый чих собирать собрание не будешь, многие вещи не кажутся важными тебе, но покажутся другим. Про что-то ты просто заблуждаешься и думаешь «чего тут обсуждать». Ревью — это регулярный повод для разговора, удобный для всех участников.

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

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

Так что вот, рекомендую попробовать.
Продолжая про код ревью, я научился тогда одной важной вещи, плоды которой пожинаю до сих пор. Называется «не доебываться по мелочам».

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

Правило очень простое: комментировать только тогда, когда можешь внятно сказать, почему так делать нельзя. Не «будет лучше», а именно нельзя. Альтернативные решения, которые ты придумал — в топку. Советы по улучшению перформанса в некритичных местах кода — помалкиваем. Форматирование или собственные идеи по наименованию тем более. Заворачивать что-то из-за соображений на будущее, «когда-то нам понадобится Х, поэтому давай сейчас переделаем», тоже не окей — когда понадобится, тогда и сделаем.

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

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

Как результат, все инкрементальные тулзы без исключения, которые я видел, работали ненадежно и требовали постоянного перезапуска. Clojure, ClojureScript Compiler, Gradle, Kotlin Compiler, вся IDEA целиком. Причем не в единичных случаях, а на постоянной основе — либо изменений оказывалось так много, что какая-то ошибка обязательно да вылезала, либо просто юзкейс не предусмотрели.

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

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

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

- Куча артефактов древних, про которые ничего не понятно, кроме того факта, что рано или поздно они выстрелят в ногу. В чем разница между $(...) и бэктиками? Между [] и [[]]? И еще там какой-то пробел где-то рядом с этими скобками очень важно поставить, кажется. Каждый раз, когда читаю про то, как правильно дважды эскейпить символы в scp, иду в итоге и напиваюсь.

- Многое bash не делает сам, а делегирует «утилитам». Самое подлое, что эти утилиты разные, но только _слегка_, то есть вроде как бы делают одно и тоже, но вроде как и чуть-чуть по-разному. Такая бомба замедленного действия, ждущая своего исключения. Я написал, может, двести строк на Bash, но даже на таком маленьком объеме успел напороться на два расхождения в xargs и в sed между macos и linux.

- Запустить bash на Windows в принципе можно, но означает притащить с собой половину Линукса, и все равно наесться говна на кросс-платформенности. Нет в mingw какого-нибудь zip и привет. Лично я очень долго уговаривал Windows выбрать правильный виндовый компилятор, но вызывать его из линуксового bash-скрипта.

- В Bash опасные дефолты. Очень легко написать программу, которая работает сейчас у меня, но не сработает в слегка другой ситуации. Пробелы в именах файлов до сих пор не побеждены, да! Я лично жрал это говно послдений раз вот прям в 2020. Не проверили exit-код? Зажуем ошибку. Опечатались в переменной? Подставим пустую строку! Запустили скрипт из другой директории? Посчитаем все пути относительно нее!

- Иногда же наоборот, бесит, что там, где как раз «разумные дефолты» диктуют ошибку проглотить (например, потому что работа уже сделана и ничего делать не надо), оно ломается и ставит раком весь процесс. mkdir поверх уже созданной папки? Ужас какой, остановим всю программу! cp с нулем файлов (ну потому что предыдущий шаг ничего не произвел)? Невозможно, на всякий случай тормози поезд! `rm`-у нечего удалять? Непорядок, начальника!

Да, любой из аргументов выше можно опровергнуть: да ты не разобрался. Ну вот мой hot take: а я и не хочу разбираться! Мне не нужна докторская по скриптингу, скриптинг это по определению то, на что я хочу тратить минимум усилий. Чем безболезненне, тем лучше.

Главная уловка-22 в том, что на баше легко _начинать_. Одно-двухстрочники действительно, ни на чем удобнее не сделать. Но дальше bash не масштабируется, а люди все равно продолжают писать. Чтобы выкинуть bash надо _принять решение_, а чтобы продолжать, не нужно делать ничего, даже просыпаться не нужно.

Какая альтернатива? Питон. Отличный язык, очень лаконичный, минимум церемоний, батарейки все в комплекте: argparse, subprocess, os.path, glob, shutil, urllib.request, zipfile. Везде, ну, почти одинаковый. Кросс-платформенный. На маке/линуксе даже как будто из коробки?

Так что я все понимаю. Сложно. Но надо меняться. Даже если кажется, что все окей, даже если все так делают. Потому что bash — это все-таки затянувшийся несмешной пранк, который почему-то начали воспринимать серьезно. Пора ему туда же, где сейчас Перл — на свалку, ой, простите, полку истории.
Каждый раз, проходя мимо фронтенд-стека, удивляюсь: ребята, у вас уже был идеальный сетап для разработки. Редактируешь файл на диске, хоть HTML, хоть CSS, хоть JS, обновляешь страницу — бам! Все работает. Никаких компиляций, никакой инфраструктуры, никаких приложений, достаточно реально блокнота. TextMate в свое время взлетел как раз на волне того, что для разработки ничего более сложного не было нужно. Компилятор встроен в браузер, оптимизирован под time to interactive, REPL там же офигенный, дебаггер, визуальный отладчик один из лучших.

Как это можно было добровольно испортить? Как можно было отказаться от этих даров богов в пользу медленного и сложного пайплайна? Нажал кнопочку, вебпак запустился, минуту пошуршал, стрелочки в терминале красиво погонял туда-сюда. Минуту!!! ... профит? Потом еще пойди разбери, что он там нагенерил и откуда это пришло (наверняка для этого тоже есть ТеХнОлОгИи, но это тушение пожара огнем какое-то, ей богу).

Получается, деградация веб-разработки произошла ровно по одной причине: она выглядела недостаточно серьезно. Как это – файл в блокноте написал и все? Ты точно разработчик? А деньги за что получаешь? Конференции, опять же, вокруг TextMate-based workflow не соберешь, а молодым людям хочется общаться.

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

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

Что мешает? Количество церемоний. NPM и node_modules подошли близко, но оверхед на публикацию пакета все еще очень высокий. Вот количество файлов в репозитории is-array, js-функции из одной (!) строчки:


.gitignore
.jepso-ci.json
History.md
Makefile
Readme.md
component.json
index.js
package.json
test/test.html


Можно еще меньше, но все равно это как минимум несколько файлов. Кроме этого, нужно завести репу на github, пакет на npm, опубликовать из одного в другое, потом выбирать версию, импортить это в package.json, нажимать npm install, и наконец можно использовать. Сложна!

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

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

И третья проблема это избыточность. is-array это вырожденный случай с одной функцией, обычно, конечно, функций в библиотеке минимум десятки, а дальше сколько угодно. Тебе могут быть не нужны все их них, но подключать приходится все и сразу. JS-ники изобрели tree-shaking, потому что в браузер отдавать балласт не очень здорово, а на бэкенде так и живут — заимпортил сотню классов, использовал два.

И вот к какому решению я пришел:

- Репозиторий функций, а не пакетов.
- У каждой функции уникальное fully qualified имя, как URL.
- Функции иммутабельны. Хочешь что-то поменять — создавай новую. Неудобно, зато надежно.
- Функции могут зависеть только от других уже опубликованных функций.
- Тривиальный импорт из юзер кода. Прям в любом месте import-remote me.tonsky.collections.group-by as group-by; и все, вперед использовать, как будто она у тебя всегда была в соседнем файле написана. Никаких package.json, лок-файлов и никаких прогресс-баров, как она скачивается. Минимум церемоний.

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

Вторая — tree-shaking. В такой схеме он не нужен, потому что импортится только и точно то, что используется.

Нужен ли для этого какой-то специальный язык или рантайм? Кажется, что нет! На Кложе такое можно сделать макросом, у меня даже был прототип, но до продукта я его не довел, потому что там надо тот самый репозиторий организовывать. Кажется, и на JS можно.

Может кто-то сделает.
Сегодня четвертый день, как я борюсь с макОС-ом в попытке поставить системный светофор по нужным мне координатам. Дорогой дневничок,

День первый.

- Мы хотим вписать светофор в наш собственный заголовок. По красоте, как у всех.
- Если скрыть тайтлбар, светофор пропадает. Логично.
- Его можно добавить обратно, но теперь кнопки не реагируют на перемещение.
- Их родительский view тоже не реагирует на перемещение.
- Нужно создать новый view и перенести кнопки туда. Сами кнопки двигать все еще нельзя. Зато теперь можно двигать родительский view!
- У кнопок остается неубираемый оффсет, типа (7, 6). Собственно, чтобы поставить кнопку на X,Y, надо поставить наш view на (X - 7, Y - 6).
- Этот оффсет разный на разных версиях ОС. На Каталине, например, (7, 3).
- Путем нехитрой тригонометрии подгадываем координаты, чтобы кнопки в конечном счете встали куда нужно.
- Тестируем ховер. Плюсы: ховер работает. Минусы: когда наводишь на пустое место, где кнопки стояли раньше, ховер тоже срабатывает :(

День второй.

- Кажется, дело в старом родителе, у которого забрали кнопки, но который до них дозванивается.
- Удаляем его, ховер начинает работать как нужно.
- Тестируем переход в фуллскрин. Чудом фуллскрин работает как надо сразу. Пронесло.
- Выходим из фуллскрина. Кнопки встают на свои дефолтные места. Ну еб твою мать!
- Ладно. Мыж программисты. Выливаем воду из чайника, зовем нашу функцию еще раз, кнопки встают куда нужно.
- Снова тестируем ховер. Работает. Ходим в фуллскрин и обратно. Позицию починили. А ховер, сука, опять не работает!!!

День третий.

- Перепробовано миллион вариантов, кого куда добавлять, за какие методы подергать и какие маски поставить.
- Изучены исходники Electron, включая историю изменений фичи про светофор, Chromium и Firefox.
- Прошерстен StackOverflow.
- Успех пришел из ответа с одним (!) апвотом. Окно после выхода из фуллскрина надо поресайзить — увеличить на пиксель и вернуть обратно.
- Объяснение — кнопки ходят в своего родителя (!) и дергают у него приватный АПИ (!!).
- Причесываю, коммичу наконец (нормально ли, три дня без эякуляции^W^W коммитов?), ложусь спать.

День четвертый.

- Добрые коллеги не поленились каждый завести по тикету, что на старте появился новый warning.
- Оказывается, рутовый view не ожидал, что в него будут что-то добавлять. Он проверяет по списку знакомых ему вьюх, и если что-то незнакомое, то ругается. Но работает.
- В чем смысл такой логики? Ты либо не работай, либо не ругайся. А так пассивная агрессия какая-то.
- Переставил кнопки в content view. Ворнинг ушел. Дело объявляется закрытым.

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

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

Отдельно «порадовало» знакомство с нативной разработкой под Мак. С одной стороны, я понимал, что в проприетарной системе не может быть счастья, но с другой, у Эппла же все такое красивое снаружи, может и внутри как-то неплохо? Конечно же НЕТ. Всю дорогу преследовали флешбеки из веб-разработки, которая ввела меня в депрессию в прошлый раз. Только тут еще хуже — вместо условного MDN и Caniuse, где квирки хоть как-то задокументированы, а буйство фантазии Хрома сдерживается конкуренцией, в Эппл экосистеме о хитрых поведениях и ручках не написано нигде и нужно тупо догадываться.

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

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

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

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

Это пост про программирование, кто не понял.
В контексте UI фреймворков один занимает особенное место в моем сердце. Это Swing. Мне всегда казалось, что в любой сложной вещи можно разобраться. Может быть долго, неудобно, энтерпрайзно, с багами, со странными дефолтами, но это все познаваемые вещи, неприятно, но познаваемые.

Swing это следующий уровень просветления. Познаваемость для него – non-goal. В нем все работает не так, как ожидаешь. Вещи выравниваются по фрактальным направляющим, вместо рядов и строк загадочные лайауты со сторонами света (!), дети встают процентов на 60-75 от доступного места, контролы слегка кривоваты, ресайзятся через раз, а программисты ходят ушибленные, шепчут «достаточно один раз понять gridbaglayout», затягиваются сигаретой и замолкают на двадцать минут. И это при том, что я пятнадцать лет вебом занимался, то есть по определению видел некоторое дерьмо.

Вот как можно было так напрограммировать, а?