Читать в телеге. Когда-то там были посты не только от меня.
Выбор коммита для добавления изменений в git
Одна из устоявшихся практик добавления изменений — rebase + fast-forward. Часто со схлопыванием всех коммитов в один жирный. История конечно получается красивая и это просто, но сваливать в одну кучу бизнес-изменения и рефакторинг — так себе затея, атомарные коммиты все-таки не зря придумали.
Но тогда после очередного rebase на основную ветку может возникнуть проблема, когда ваши красивые коммиты перестают компилироваться (например, кто-то тоже порефакторил от души).
И после этого делать коммит fix after rebase
или лепить все исправления в последний коммит через --amend
как-то уныло.
В этом случае поможет --fixup
:
git add someChangedFiles
git commit --fixup=OLD_COMMIT_HASH
git rebase --interactive --autosquash OLD_COMMIT_HASH^
который позволяет добавлять изменения в существующий коммит, даже если он не последний.
Порядок просмотра файлов при код-ревью
Довольно интересное исследование показало:
- Большинство разработчиков ревьюят изменения тупо по порядку следования файлов (и обычно это алфавитный).
- Тесты комментируют реже, их качество обычно меньше волнует разрабов.
- Если тесты ревьюить после основного кода (как это обычно происходит, потому что
t
— в конце алфавита), то в них реже находят баги (для основного кода существенной разницы нет). - Больше всего комментариев оставляют к первым файлам в списке, и баги в них обнаруживают с большей вероятностью.
В общем, сначала ревьюер смотрит внимательно, потом расслабляется, а тесты смотрит уже наискосок. Вроде довольно очевидно, но теперь на это есть пруфы. И это на фоне того, что лишь сравнительно недавно GitHub сделал функцию просмотра дерева измененных файлов (я еще в январе на это жаловался).
Добавлю, что еще в далекие года ведущие эксперты™ писали в своем супер-мануале про порядок просмотра при ревью: тикет, тесты, код, тесты. Потому что на ревью важнее выявлять фундаментальные проблемы: неправильные требования, сделано не то, работает не как надо и т.п. И уже во вторую очередь идут ошибки в реализации. А для всякого форматирования и прочей мелочевки должен быть линтер, а не человек.
Ковариантность и контравариантность
У меня всегда были трудности с запоминанием умных терминов, хотя суть вариантности довольно проста. Если иерархия типов сохраняется в том же порядке для производных типов (которые используют искомый тип как параметр), то это (ко)вариатность. Если идет в обратном порядке — (контра)вариантность. Если нет иерархии — (ин)вариантность.
Если термины из теории категорий — “сложна”, то можно мыслить в терминах потребитель/производитель, в Kotlin и C# параметры так пишут — in
и out
вместо всяких плюс-минусов, и это гораздо читаемее.
Вариации сна для компьютера
- Бездействие (aka S1, idle) — CPU остановлен, система потребляет чуть меньше энергии: почти все устройства переключены в режим низкого энергопотребления или выключаются (ЖД, подсветка экрана).
- Сон (aka S2) — как S1, только CPU отключен от питания (разница с S1 минимальна, на некоторых материнских платах его даже не реализовывают).
- Ждущий режим (aka S3, sleep, suspend) — почти все устройства, кроме RAM отключены, состояние хранится в памяти. RAM обновляется медленнее. Жрет питание/батарею, но чуть-чуть.
- Гибернация (aka S4, hibernate) — состояние сохраняется на диск (например, в swap), питание полностью отключается. Однако не все оборудование корректно может восстановить свое состояние, поэтому этот режим обычно отключен по умолчанию.
- Мягкое выключение (aka S5, soft off) — все выключено, но некоторые порты/контроллеры включены (чтобы включить комп по LAN или по нажатию кнопки на клавиатуре, например).
- Гибридная гибернация (aka hybrid suspend) — состояние сохраняется на диск, как в гибернации, а потом комп переводится в ждущий режим. Позволяет быстрее выходить из сна, но не терять состояние, если питание пропадет. Есть еще вариация, когда сначала выполняется suspend, а потом, по таймеру или по триггеру (например, низкий заряд батареи) кратенько просыпается и уходит в гибернацию.
- Modern Standby (aka S0 low power idle, S0ix) и PowerNap — маркетинговые названия для S0 (полностью работающий комп), просто часть функций системы не работают и часть периферии отключена. Но при этом выполняются фоновые задачи и остается подключение к сети (“как у смартфона”). Питание потребляет много, и ноутбук в таком режиме в сумку класть не стоит — может перегреться.
Псевдонимы для типов и value-классы
Почти бесплатно повысить читаемость кода можно за счет псевдонимов типов (type aliases). Например, можно заменить в бизнес-логике безликий UUID
на UserId
. Есть это почти во всех языках: typedef
или даже #define
в С, using
в C++ и C#, type alias
в Kotlin, type
в Scala/Haskell и даже в питоне, но не в Java. Можно еще использовать локально: импортировать тип с другим именем. Но у псевдонимов есть банальный недостаток: ничто не помешает в UserId
запихать другой UUID
, например ObjectId
.
Чтобы компилятор проверял подобные несоответствия, можно использовать value-типы (они же Single case discriminated union). Обычно это класс-обертка над целевым типом, который имеет единственное поле и при возможности заменяется после компиляции на целевой тип. Например, в Kotlin это будет
@JvmInline
value class UserId(val value: UUID)
В некоторых языках еще можно добавить логику (например, проверку в init
блоке или добавить методы-геттеры). Если value-типов нет, можно достичь примерно того же, используя data-классы с одним полем (а где-то может и вообще разницы не будет).
Очевидная проблема оберток в том, что к содержимому всегда придется обращаться через поле или паттерн-матчинг, а не напрямую (и какой-нибудь делегацией это, увы, не решается). А из-за потенциального оборачивания страдают библиотеки, использующие рефлексию, например, сериализация в JSON. В Scala это решили с помощью opaque-типов: рядом с объявлением класса он работает прозрачно, как type alias, а снаружи — непрозрачно, и доступа к целевому типу нет. При этом там есть и “обычные” value-классы, которые почти такие же, как в Kotlin.
А еще и в Scala и в Kotlin ждут Project Valhalla, чтобы можно было создавать пользовательские типы, доступ к которым осуществляется по значению, а не по ссылке. В комбинации с inline это позволит решить часть проблем с оборачиванием value-типов.
Help me, step-container, I'm stuck!
Иногда контейнер не может подняться, а в логах полезной информации нет. Надо бы провалиться в консоль контейнера, чтобы проверить все изнутри, но из-за постоянных рестартов это не получится сделать. В этом случае можно сохранить текущее состояние контейнера:
docker commit %container_id% %some_name%
и стартовать его с командной строкой:
docker run -it %some_name% /bin/sh
А потом уже дебажить, что там внутри не так.
Из-за чего команда профессионалов может работать фигово
В продолжение заметки про продуктивность — а почему в команде из кучи замотивированных сеньоров все равно может получиться что-то не то?
Причин может может быть масса [1, 2, 3]:
- Они делают не то, что важно (в т.ч. из-за того, что увлекаются чем-то интересным для себя), часто из-за отсутствия четких целей, отсутствует фокус или пытаются сделать все и сразу.
- Они ждут другую команду (из-за плохого разделения, из-за мутных процессов или из-за чрезмерной специализации, когда не можешь помочь другой команде).
- Нет четкого понимания, что нужно сделать, тратится время на выяснение этого (хорошо еще, если до разработки, плохо, если на миллионе встреч).
- Нет четких зон ответственности и ролей — непонятно, кто должен что сделать, чаще всего превращается в “не делает никто”. В том числе когда что-то можно было предотвратить малой кровью вместо героического расхлебывания последствий.
- Они не умеют говорить “нет”.
- Нет долговременного планирования, слишком большая сосредоточенность на скорости, а не качестве.
- Они плохо понимают предметную область и конечного потребителя.
- Нет признания результатов, чувства достижения, мотивация делать нормально падает, появляется куча новых проблем.
Визуализация explain для SQL-запроса и рекомендации по индексам
Хороший пост, в котором как для даунов объяснены основные типы индексов, даны советы, когда какие использовать, и приведены примеры запросов, которые можно оптимизировать. А еще там есть ссылка на сервис, в котором можно визуализировать выполнение запроса и получить рекомендации, какие индексы добавить.
Docker на Маке
brew install docker
поставит только клиенты докера, без демона. docker-machine
, который запускает докер в виртуалке (sic!), уже устарел и его хрен запустишь. Docker Desktop платный (ну, на честном слове платный, но все же) и требует GUI…
В итоге рабочий вариантом оказалась… установка кубера, а именно minikube. Можно еще туннелирование в локальную сеть настроить или поиграться с kubectl. Но после таких приколов хотя бы понимаешь, почему некоторые считают, что надо минимум 32 гига оперативки для разработки на маке :/
Даты изобретения сортировок и кроссбаузерность
После изучения всяких фронтенд штук захотелось сделать что-нибудь очень простое. Прям наговнякать. Решил сделать временную шкалу с датами изобретения алгоритмов сортировки на CSS, и собирать HTML на голом питоне.
Во-первых, я все еще офигеваю, как сложно сделать какие-то элементарные вещи. Во-вторых, что до сих пор в трех разных браузерах одно и то же форматируется по-разному, хотя я использую все из древних стандартов. Я честно пытался написать кросс-браузерно, но в итоге мне это надоело и я сделал браузеро-зависимый CSS на основе всяких трюков. Отправляйте пулл-реквест, если умеете верстать или хотите попытать силы :)
Наконец, самой тяжелой частью оказались собственно данные. Нашел статью, где вроде есть все, что нужно, но качество данных там просто впечатляющее. Например, в качестве авторов Stooge Sort указаны “Prof. Howard Fine and Howard”. А на самом деле она фигурирует в упражнении из Кормена: “Professors Howard, Fine, and Howard have proposed the following “elegant” sorting algorithm…” — и является отсылкой на комедийный коллектив. Изначально в планах было найти оригинальные источники для каждой сортировки, но это оказалось нетривиальной задачей, поэтому я отложил это до лучших времен.
Демка тут.