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

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

Разумеется, по эго это бьёт иногда больно, и некоторые моменты вспоминать не очень приятно. Самое страшное, что я когда-либо делал — коммитил приватный ключ в публичной репо. Вот написал и мне опять стыдно. Но я осознаю, что это всё я.

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

Сейчас я вам это покажу. Будет интересно, но впереди много боли. Я предупредил.


Итак, у core-разработчиков есть туча способов создать проблему там, где её нет. Они и создают. Для простоты я разделил по категориям.

❯ Наказание за забывчивость

Принцип простой: добавьте какую-нибудь ненужную рутину и создайте наказание за то, что пользователь про эту рутину забудет.

Подстановка в bash

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

FOLDER="My Documents"
rm $FOLDER

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

Кстати, двойные и одинарные кавычки ведут себя по-разному, за что отдельное спасибо! Почему не сделать как в питоне, где есть явно r-string и f-string?

Макросы в C

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

#define SQR(x) x * x
int b = SQR(1 + 2);

Поведение Bash

Моё любимое. Представьте, что вы готовите блюдо по инструкции, и там где-то в середине написано «положите всё в духовку». Вы так и делаете, и тут вдруг духовка взрывается и сносит половину вашего дома! Ваши действия? Конечно, продолжить готовить! Достаёте горящие угли вперемежку с ошмётками кухни, раскладываете по тарелкам и подаёте гостям. Ну, по крайней мере, авторы Bash так и делают, ведь даже если какая-то команда отработала с ошибкой, то это не повод останавливаться.

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

#!/bin/bash
(dos-make-addr-conf | tee config.toml) && dosctl set template_vars config.toml

... то можно, к примеру, положить cloudflare.

Назови по-тупому

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

В postgres по умолчанию создаётся 3 базы данных. Не default, frozen_template и template, а postgres, template0, template1. Э?

Let's encrypt сложит challenge-файлы в /.well-known/acme-challenge/. Почему well-known? Потому что хорошим разработчикам этот факт хорошо знаком, что тут непонятного? На самом деле пошло от well known URIs, но как же меня бесит это название! А точка в .well-known как бы означает, что это скрытая папка, хотя в URL такой концепции нет, и зачем вообще скрывать папку, которая, наоборот, должна быть максимально публичной?

Javascript придумал отображать объекты [object Object], и, честно говоря, я бы и сам лучше не придумал!

const user = { name: "Alex" };
console.log("User: " + user);

// User: [object Object]

В python-регулярках мы назовём поиск всех вхождений re.findall(), а поиск одного - нет, не re.find(), а re.search().

Да я уверен, тупых названий вы сами видели миллион и одну штуку.

Иногда я смотрю на какое-то название и представляю себе автора, который его придумывал:

❯ Обнуление предыдущих знаний и отсутствие логики

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

Bash

Прям как синтаксис в Bash:
- if должно закрываться fi. Значит, for должно закрываться rof, а while - elihw? А вот и нет, только if-fi. В остальных done.
- Переменная окружения и значение по умолчанию: ${ENV_VAR:-10}. Нет, значение по умолчанию не -10, а 10! Зачем там дефис? Я хз.
- && - это что-то вроде логического И. & — это запуск процесса в фоне. Можете сами придумать какие-нибудь интересные кейсы при случайной замене одного на другое, но, скорее всего, придумывать ничего не надо, и с вами такое уже случалось.
- Для выполнения математических операций в файле скрипта можно использовать конструкцию вида $((a+b)). Чем мы провинились? Где согрешили? Я не знаю.
- Много всякого можно найти на shellcheck wiki, но иногда лучше что-то и не знать, наверно.

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

Команды

useradd / adduser

Есть ещё useradd и addusergroupadd с addgroup), сколько я их не использовал, всё равно не запомнил, какая что делает.

useradd вообще классная, там есть короткие флаги -g и -G, -M и -m, -p и -P, которые непонятно зачем вообще есть (когда есть нормальные длинные) и, главное, непонятно, по какому принципу выбраны:

-g, --gid GROUP          name or ID of the primary group of the new account
-G, --groups GROUPS      list of supplementary groups of the new account
-m, --create-home        create the user's home directory
-M, --no-create-home     do not create the user's home directory
-p, --password PASSWORD  encrypted password of the new account
-P, --prefix PREFIX_DIR  prefix directory where are located the /etc/* files

Какого вообще чёрта --create-home и --no-create-home существуют одновременно, ведь если есть поведение по умолчанию, то нужен только противоположный флаг?

Unix-way. Даже вдвойне

Смотрите, как одна программа делает одну вещь, и делает её хорошо:

  • find -exec {} \; — найдёт файлы/папки и выполнит над ними указанную команду; за синтаксис с \; и использование -exec вместо --exec ставлю отдельный плюс! А ещё можно делать сразу три действия — искать, проверять на пустоту и удалять: find . -type d -empty -delete. Это уже не unix way, это unix highway!

  • tar -cvz не только создаёт tar, но и делает gzip сжатие

Синтаксис команд

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

Про find -exec я уже говорил. Рядом в котле варятся tar xvf и ps axf, которые позволяют писать вообще без дефисов, потому что так интереснее! Или вот тоже: dd if=input.img of=output.img bs=4M.

Можно ещё сделать какие-то постоянно используемые флаги выключенным по умолчанию, ха-ха. Я на уровне мышечной памяти пишу rsync -avzP, потому что так оно работает нормально, но какой там флаг за что отвечает — я не помню.

Обработка текста

Результат работы команд — не json, а текст. Но при этом мы автоматизируем всё командами и делаем pipe одних команд в другие. Поэтому в скриптах появляются grep, sed и awk, которые пытаются распарсить вывод для человека и подготовить его для следующей команды.

Это же тупость! Программа преобразует чистые данные в текст, чтобы потом извлечь из текста чистые данные и передать дальше, а там опять такая же чехарда. А знаете, кто за это платит? Мы, когда опять пишем какую-то нечитаемую белиберду на awk.

Логи

Я даже не знаю, где хранятся логи: то ли в /var/log/, то ли в journalctl, то ли в dmesg. Даже если я узнал, где нужные логи, то у каждой утилиты свой формат логов — если посмотреть dmesg, там какой-то ад.

Crontab

А ещё я постоянно гуглю crontab формат, потому что мой мозг просто его отвергает. Я когда вижу формат МИНУТЫ ЧАСЫ ДЕНЬ-НЕДЕЛИ, у меня начинается эпилепсия. Или там другой формат? Видите, я опять не могу вспомнить.

Python

В python, например, можно вызывать функцию с позиционными аргументами calculate(1, 2, Operation.SUM), а можно с именованными calculate(arg1=1, arg2=2, operation=Operation.SUM). Именованные аргументы всегда лучше, потому что они явные и не ломаются при рефакторинге. Мой друг как-то час дебажил, потому что перепутал порядок аргументов.

И казалось бы: разрешите только один позиционный аргумент, а если аргументов 2 или больше — заставляйте их указывать по имени. Но знаете, в питоне подумали и сделали, что можно запретить kwargs. Типа одни разработчики могут заставлять других разработчиков писать хрупкий код. Отличный план :D

Шутка для таких же стариков, как я
Шутка для таких же стариков, как я

IP

Ну ладно, я привык к 127.0.0.1, хотя какого хрена? Я привык к 192.168, хотя какого чёрта? Я привык к 172.17, хотя какого лешего?

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

CSS

Лайт-версия: чтобы запутать разраба, вертикальное и горизонтальное центрирование называются совершенно по-разному.

display: flex;
justify-content: center; /* Горизонтальное центрирование */
align-items: center; /* Вертикальное центрирование */

Pro-версия: css сделали настолько тупо, что никто всё ещё не умеет центрировать элементы.

YAML

Пишете вы docker-compose.yml. И делаете переменную KEY пустой.

Это запишет в переменную окружения KEY пустое значение:

environment:
  KEY:
  OTHER_KEY: 123

Это передаст значение KEY с хоста внутрь контейнера:

environment:
  - KEY
  - OTHER_KEY=123

Во-первых, почему можно написать одно и то же разными способами? Во-вторых, почему работает по-разному?!

❯ Сокрытие сложности

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

Go

Вот у нас есть dns резолвер системный в linux, это ж здорово! Можно почитать статью на Хабре, потом её продолжение, потом третью часть, потом умереть от старости пока читал, но всё-таки понять, как оно всё работает. А потом взять Go, а он, оказывается, подумал-подумал и решил, что к чёрту этот Линупс ваш, он будет использовать свой собственный резолвер, с блекджеком и resolv.conf.

Python + Django

В python есть дескрипторы, чтобы доступ к атрибуту был непредсказуемым — может выполниться всё что угодно. В Django этим воспользовались, и там доступ к атрибуту может неявно сделать запрос к БД. Типа написали вы так:

for source in sources[:100]:
    print(source.account)

А это 100 скрытых запросов к БД! Джанго не скажет, что вы забыли сделать JOIN, он просто будет долбать БД. То есть фреймворк по умолчанию спроектирован так, чтобы появлялась проблема N+1, а язык спроектирован так, чтобы это можно было сделать. Комбо!

❯ Просто заложи ловушку

Bash

NAME=value
NAME= value
NAME = value

Первое присвоит переменной NAME значение value.
Второе присвоит NAME пустое значение и вызывает value.
Третье вызовет команду NAME с аргументами = и value.

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

Ещё мне КРАЙНЕ НРАВИТСЯ, что bash кладёт огромный болт на неопределённые переменные и считает их пустой строкой. Это гениальное решение позволило успешно удалить данные с сетевого хранилища целого универсисета в Киото (открывайте ссылку с осторожностью, там реально жёсткое технопорно).

Динамическая и слабая типизация

Если вы выбираете динамическую типизацию для языка, то вы сразу выбираете ошибки в рантайме — ведь обязательно где-то проскочит не тот тип и положит, например, Cloudflare. Мне не нравится, что пишут «python — динамически типизированный язык». Ведь должно звучать так: «python — язык с ошибками в рантайме и сложными тулзами для интроспекции и проверки типов». Звучит уже не так круто, да?

Та же ситуация со слабой типизацией. Выбрали слабую типизацию — выбрали, что у тысяч программистов в рантайме одни данные совершенно неожиданно превратятся в другие, что приведёт к непредвиденным последствиям. Так бы и писали: «javascript и php — языки с непредвиденными последствиями».

> parseInt(0.5)
0
> parseInt(0.05)
0
> parseInt(0.005)
0
> parseInt(0.0005)
0
> parseInt(0.00005)
0
> parseInt(0.000005)
0
> parseInt(0.0000005)
5

SQL

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

DELETE ... WHERE a == 1 OR 2

Вы видите? Сам синтаксис подталкивает сделать эту ошибку.

А ещё SQL позволяет писать текст прямо в запросе, без всякой интерполяции! То есть SELECT one, two, three FROM table. И есть комментарии (--)! И это хорошо, потому что без этих замечательных вещей не было бы SQL инъекций, и жить было бы скучно. А ведь если бы SQL разделял запрос и данные (SELECT %s FROM %s, (name, age), users) и делал бы подстановку именно значений, а не текста, то не было бы инъекций. Но SQL меняться не собирается, и мы изобретаем ORM, которые хоть как-то нас защищают.

rm

Если сделать так, что rm может удалять несколько файлов, раздалённых пробелом, то наверняка в комбинации с подстановкой переменных в Bash получится что-то интересное! :) rm $SOMETHING — никто не знает, что случится.

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

Javascript

Если вы выбрали язык, который вместо match_from_beginning() называет метод test()...

> const regexp = /1234/
> regexp.test("12345")
true

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

C / C++

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

Все эти malloc(sizeof), memcpy / strcpy, сами указатели и арифметика с ними и подобное — это пистолет, в котором одно дуло направлено вперёд, а другое назад, прям на вас, и при стрельбе нужно не забывать уворачиваться. А ведь можно не уворачиваться, просто возьмите нормальный пистолет.

Дополнительно в C++ добавили... Нет, не так. В C++ СПЕЦИАЛЬНО добавили случаи, при которых происходит хрен знает что. Не программа падает, нет. ХРЕН ЗНАЕТ ЧТО происходит. Undefined behavior. Собственно, на что был расчёт?

Вы пишете...

int x = 2147483647;  
x = x + 1;

... и C++ это хавает.

Вы пишете...

int arr[3] = {1,2,3};  
int x = arr[5];

... и C++ это хавает.

Он вообще вам не помогает, ему норм и так. Вот вам целый манул по тому, насколько C++ пофиг.

Отступы и форматирование

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

if (condition) do1(); do2()
// или
if (condition)
  do1();
  do2();

Сделайте фигурную скобку { обязательной после if — никто почти и не заметит, но эта ошибка умрёт навсегда, и не будет никаких отключений проверок SSL сертификатов в Apple.

Elasticsearch

Elastic по умолчанию возвращает 10 результатов. Вы не делаете никакие слайсы, он просто тупо возвращает 10 результатов. Это же именно то, что вы хотели при поиске?

Предлагаю такую замечательную идею перенять и сделать, чтобы postgres на каждый запрос возвращала только 10 строк, а ls показывала только 10 файлов. Вот заживём!

Python

Накину на python, раз уж я в нём немного шарю.

create_task

А давайте создадим функцию asyncio.create_task(), чтобы пользователь мог запускать фоновые задачи? Но только если эта задача нигде не хранится, то давайте сборщик мусора её убьёт, ведь это именно то, что хочет пользователь, когда запускает фоновую задачу! И это очевидно, да. Никаких warning о том, что пользователь не присвоил результат никакой переменной, нет. Зачем?

strip

Ещё можно, например, назвать метод strip(...), но вырезать он будет не то, что указано, а каждый элемент того, что указано.

> "surf".strip("suffix")
"r"

При этом в самом языке можно в принципе указать, чтобы функция принимала *args, и тогда это выглядело бы так и работало бы предсказуемо:

> "surf".strip("suffix", "prefix")
"surf"

Да блин, в современном питоне даже есть removeprefix() / removesuffix(), которые делают ровно то, о чём говорят, но strip() всё равно оставили как будто для лулзов, чтобы разрабы писали "Bearer abcdef12345".strip("Bearer ") и удивлялись.

Django

Чтобы секретный ключ гарантированно попал в исходники, в джанго команда создания проекта startproject генерирует секретный ключ и суёт его сразу в код:

SECRET_KEY = "kwdudjeh:uwndy6&jdjdp..."

Больные ублюдки хотят, чтобы ваш ключ утёк.

requests

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

А как делать нормально — скрыто в advanced секции.

❯ Сделай сразу плохо и оставь навсегда

Git

Git сделали настолько неинтуитивным, что в интернете миллион статей, видео и курсов для его изучения. Сама концепция веток и их слияния — замечательная, и понять её никому не составит труда. Но сам интерфейс git... Я даже не буду сильно раскрывать тему, это потянет на отдельную статью и, кажется, на Хабре такая уже была. Поэтому просто накидаю тезисов.

  • git add -p позволяет выбирать, что добавить в staging, но игнорирует новые файлы. Я так миллион раз забывал добавить файлы миграций в коммит

  • git add -p предлагает выбрать / не выбирать в staging целые чанки изменений; если изменения большие, то можно их разбить при помощи s — но до отдельных строчек git разбивать не станет и заставит нажать e, чтобы провалиться в редактор изменений и не суметь сделать ручками правильный diff — по крайней мере, у меня почти никогда не получается

  • git checkout - выбор ветки, git checkout -- . — та же команда, но это откат всех unstaged изменений в папке (но новые файлы останутся)

  • git checkout -- . — откат unstaged изменений, git restore --staged — откат staged изменений (да, совершенно разные команды для похожего функционала)

  • git checkout — выбор ветки, git switch - выбор ветки

  • git push запушит коммит, но не запушит тэг у этого коммита

  • ...

Я могу так про git до бесконечности. Своим синтаксисом он тормозит всех, особенно начинающих разрабов. Мы встряли с этим динозавром, ребята. Возможно, нам стоить смотреть на альтернативы вроде Jujutsu, чтобы хоть как-то сдвинуться с этой мёртвой точки.

Asyncio

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

Asyncio в том виде, как реализовано в питоне, закладывает, что ВСЕ библиотеки будут разбиты на красные/синие. Или разноцветные, но тогда asyncio заставляет разноцветные библиотеки создавать корявые sync_to_async(), syncify() и им подобные. Или же create() и acreate() (django), invoke() + ainvoke() (langchain). В каждой библиотеке свои костыли, порождённые одним решением в дизайне языка.

Javascript

Здесь не про язык, а про сам факт того, что, заходя на сайт, запускается какая-то неизвестная программа. Хм, что может пойти не так? Криптомайнеры в браузере? XSS, CSRF, Clickjacking и ещё хренова туча уязвимостей (в том числе в самих js движках), от которых только и успевают клепать фичи-заплатки? А виноваты мы, обычные разрабы, потому что не включили CORS, CSP, CSRF, secure cookies, не начертили круг мелом вокруг сайта и не станцевали джигу-дрыгу перед продакш сервером. Ага, спасибо.

Система прав в linux

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

Сейчас другое время. Android нам показал, что можно раздавать права на функционал (доступ к камере, доступ к файлам итд). Но в линуксе всё так же любая программа имеет доступ ко всему home, может ходить в сеть и делать вообще что угодно, а права на файлы я всё так же выставляю циферками 0+1+2+4. Любой код из сети может оказаться криптовымогателем и зашифровать мне все данные или отправить собранные пароли злоумышленнику. И это возможно, потому что система прав это позволяет.

SeLinux, скажете вы. Да, но нужно настраивать и вкуривать — даже если что-то настроено по умолчанию в дистрибутиве, придётся потом настраивать ручками для новых программ. SeLinux хорош тем, что когда я читаю какой-нибудь мануал и вижу секцию «how to make it work with SeLinux», то радуюсь, что мне не нужно её читать и плакать. За это спасибо.

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

Ещё есть пакеты Flatpak, которые изолируют приложение, но вообще-то могут быть созданы кем угодно (где-то читал историю, что в flatpak залили малварь под видом криптокошелька). Или Docker, который изолирует приложение, но поди запусти его с GUI.

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

❯ Вывод

Да, мы делаем ошибки. Да, мы забываем. Но это не наша вина. Так и задумано.

Во всех примерах выше неправильно спрашивать «может ли это привести к ошибке». Правильный вопрос: «когда, где и сколько раз это приведёт к ошибке?»

Как этого избежать? Обычно никак, мы просто набиваем шишки, собираем опыт и запоминаем, какие вещи следует обходить стороной, или используем утилиты вроде shellcheck, которые проверяют на распространённые ошибки. Но в тех случаях, когда выбор есть — наверно, надо стараться не использовать те технологии, что ставят палки в колёса и подкладывают свинью. Если операционная система начинает обновляться посреди работы, то наверняка есть такие, которые подобного себе не позволяют. Если баш-скрипт — это постоянная борьба с синтаксисом и подводными камнями, то почему бы не выбрать скриптовый язык из нормальных?

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


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

Ну а спонсирует мои статьи Timeweb Cloud, за что ему отдельное спасибо. Пользуюсь и рекомендую.


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале 

Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
Читайте также:

Комментарии (144)


  1. bfDeveloper
    12.03.2026 08:30

    Тон статьи такой, как будто все эти примеры сделаны специально, чтобы лично вы страдали. Куча примера на баше, хотя он страше доброй половины читателей, как и C с C++, и с тех пор многое поменялось. Выглядит как предъява паправозу, что он дымит и не использует электричество. Делай хорошо, а плохо не делай. Будь Вангой. Спасибо, кэп.


    1. malkovsky
      12.03.2026 08:30

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


      1. saege5b
        12.03.2026 08:30

        ОЛДы писали на баше без экранирования :) и всё замечательно работало, потому, что не было в именах файлов пробелов, и имена были предсказуемого короткие с короткими расширениями.

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

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


    1. Yuuri
      12.03.2026 08:30

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


  1. olivera507224
    12.03.2026 08:30

    • git checkout — выбор ветки, git branch - выбор ветки

    git switch - выбор ветки


    1. kesn Автор
      12.03.2026 08:30

      Точно! Спасибо, поправил


    1. kaka888
      12.03.2026 08:30

      С этим комментарием стало куда комичнее


      1. olivera507224
        12.03.2026 08:30

        Это же гит. Можно десять лет с ним работать и горя не знать, а в один чудесный день вдруг выяснить, что он умеет что-то очень полезное, что тебе всегда было очень нужно, но ты даже и не знал, что оно тебе нужно.


        1. Cerberuser
          12.03.2026 08:30

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


          1. blind_oracle
            12.03.2026 08:30

            Только большинство гайдов в инете которые нуворишь найдёт - всё равно будут учить старому способу...


            1. Wesha
              12.03.2026 08:30

              которые нуворишь

              Что-что делаешь?


          1. kesn Автор
            12.03.2026 08:30

            Автор призывает не только добавлять хорошее, но и удалять плохое. Причём даже не в варианте "сожжём всё старое" (хотя я из этого лагеря), а через deprecation. Кому надо старьё - есть версионирование.

            Тот же git checkout <branch> - пусть пишет deprecation warning в течение года, раз появился git switch. Или мы теперь на каждую плохо названную команду будем добавлять хорошо названный дубликат?


    1. DmitryOlkhovoi
      12.03.2026 08:30

      zsh
      gco [name]


  1. Akon32
    12.03.2026 08:30

    Каждое из этих решений, вероятно, при создании софта казалось хорошей идеей. Но спустя 20-30 лет (а в случае sh ~50 лет) успешного использования мода поменялась, появились новые знания, новые принципы... Критика напоминает критику IPv4, когда есть IPv6: мы все знаем, как лучше, но мгновенно ничего улучшить не можем.

    А может, если софт с этими "особенностями" ("багами") выжил за десятилетия, то они не вредны и даже чем-то полезны?..


    1. Ogra
      12.03.2026 08:30

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


    1. dimaaannn
      12.03.2026 08:30

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

      Что, вам надо перейти на локальный адрес в сети? Ахаха! Поднимай днс лошара! Не хочешь? А придётся.

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


      1. SCode
        12.03.2026 08:30

        И почему все гайды по ipv6, которые мне попадаются такие сложные.


      1. ahabreader
        12.03.2026 08:30

        но по факту нафиг не сдалось.

        Южноамериканский регистратор сейчас продвигает IPv6 как "причина №1 для перехода на IPv6 - следы этих мерзавцев больше не смогут запутаться в провайдерском NAT'е".

        Картинка


        1. ProMix
          12.03.2026 08:30

          Надо адаптировать опыт западных коллег


      1. omaxx
        12.03.2026 08:30

        link-local адреса в ipv6 это не замена dhcp в локальных сетях, но они очень выручают на point-to-point линках, чтобы не прописывать все эти /30 и /31 как в ipv4


      1. VADemon
        12.03.2026 08:30

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


    1. Belarus
      12.03.2026 08:30

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

      Или ожыдалось, што это временное решение - нет времени на обдумывание.


  1. Ogra
    12.03.2026 08:30

    Подкину мое любимое в копилку.

    Хотите вызвать какую-то команду в линуксе, чтобы она выполнялась не с одним файлом, а рекурсивно? Добавьте ключ -r : rm -r ; cp -r , например. Но только не для chown и chmod, они хотят ключ -R ! И эти команды, между прочим, входят в единый стандарт POSIX!

    Хотите указать порт при подключении по ssh ? Нет ничего проще, просто добавьте ключ -p Хотите скопировать что-то через ssh? Используйте команду scp, но ей для указания порта нужен ключ -P.


    1. Jogker
      12.03.2026 08:30

      И когда уже запомнил какой ключ к каким штанам и даже осилил мнемонику...

      тут и настигло крестьянина послабление )) telnet dupa.com 80


    1. Ghrec
      12.03.2026 08:30

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


      1. tenzink
        12.03.2026 08:30

        It depends. Для спонтанных задач в консоли, для регулярных в редакторе (vim/vscode/cursor/...)


        1. Ghrec
          12.03.2026 08:30

          Имею в виду, разработчики командной строки её в иде или в командной строке разрабатывали?


          1. tenzink
            12.03.2026 08:30

            Интересно, почему это кажется важным. Ну и всей этой истории уже лет 40+ наверное


          1. Ogra
            12.03.2026 08:30

            chmod на который я тут жалуюсь, появился впервые в 1971 году. Не то, что IDE тогда не было, редакторы тогда были совершенно другие. Еще даже vi не появился.


  1. baldr
    12.03.2026 08:30

    С одной стороны - читал и плакал. kesn, как всегда, жгёт по-живому. Можно ещё добавить Makefile как пример боли "чукча писатель". Про JavaScript - есть классическое WAT видео.

    С другой стороны - ну глупо предъявлять претензии bash - он 35 лет назад был придуман чтобы немного улучшить ещё более неудобные проблемы sh , никто не знал к чему это приведёт. Длинные имена файлов с пробелами в 1989 году, хм? Какие там альтернативы были для скриптинга тогда? И программисты менее избалованные были этими вашими пробелами и отступами, так что сравнивать его с Python - автор немного передергивает и сам это знает.

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

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

    И да, я проголосовал "Принятие", потому что знаю почему так, сам уже делал так и знаю что без такой ситуации обойтись сложно. Всё меняется.


    1. nomhoi
      12.03.2026 08:30

      Как думаете, получится реализовать?

      План разработки защищенного браузера (Skia + LiteHTML + Godot + NsJail)

      1. Архитектура системы (Multi-process Isolation)

      • Broker Process (Host): Управляющий процесс. Владеет окном (GLFW/Wayland), управляет IPC, координирует обмен DMA-BUF.

      • UI/Render Process (NsJail 1): Skia + LiteHTML. Рендеринг интерфейса и страниц. Без доступа к сети и FS.

      • Plugin Process (NsJail 2): Godot Engine. Запуск игр/приложений. Доступ к GPU (/dev/dri), изоляция логики.

      2. Этапы разработки и сроки

      Этап 1: Core Broker & IPC Foundation (4–5 недель)

      • [ ] Создание Broker-процесса на C++ (Main Loop).

      • [ ] Настройка IPC: Unix Domain Sockets для команд + memfd_create/dmabuf для графики.

      • [ ] Реализация механизма передачи File Descriptors (FD) между процессами.

      • [ ] Базовая конфигурация NsJail (Namespaces: mount, user, pid, net, uts).

      Этап 2: Skia & LiteHTML Renderer (6 недель)

      • [ ] Интеграция Skia в изолированный процесс (NsJail 1).

      • [ ] Реализация litehtml::container для отрисовки через Skia API.

      • [ ] Настройка Font Manager (проброс шрифтов через Read-only bind-mount).

      • [ ] Система "тайлинга": рендеринг страницы в shared buffer, который забирает Broker.

      Этап 3: Godot Plugin Bridge (4–5 недель)

      • [ ] Подготовка Godot (GDExtension) для работы в режиме "External Texture".

      • [ ] Настройка NsJail 2 с пробросом GPU драйверов (/dev/dri) и .pck файлов.

      • [ ] Реализация синхронизации кадров: Godot рендерит в DMA-BUF -> Broker передает дескриптор в Skia UI.

      • [ ] Проброс Input-событий (мышь/клава) из Broker в Godot через сокет.

      Этап 4: Безопасность и Seccomp (2 недели)

      • [ ] Написание жестких Seccomp-профилей для каждого процесса (запрет лишних syscalls).

      • [ ] Настройка Cgroups (ограничение RAM/CPU для игровых плагинов).

      • [ ] Аудит IPC: валидация всех входящих данных в Broker.

      Этап 5: UI & Финализация (3 недели)

      • [ ] Разработка браузерной обвязки на Skia (вкладки, адресная строка).

      • [ ] Реализация логики загрузки ресурсов (Broker качает данные -> передает в Render Process).

      • [ ] Оптимизация Zero-copy переключения контекстов.

      3. Общий эстимейт

      • MVP (Минимально рабочий прототип): ~4 месяца.

      • Stable Version (Полная изоляция + Плагины): ~6 месяцев.

      4. Стек технологий

      • Язык: C++20 / GDScript (для игр).

      • Графика: Skia (UI), Godot (Plugins), Vulkan/OpenGL.

      • Layout: LiteHTML (CSS2.1 + частичный CSS3).

      • Изоляция: NsJail (Linux kernel features).

      • Связь: Unix Sockets (SCM_RIGHTS), Shared Memory.

      Будут ли пользователи пользоваться таким браузером?

      Ответ зависит от позиционирования. Без поддержки JavaScript 90% современного интернета (соцсети, банки, видеохостинги) не будут работать. Однако этот проект занимает уникальную нишу.

      1. Целевая аудитория (Кто придет?)

      • Любители "Minimal Web": Те, кто читает Wikipedia, блоги, документацию и новостные ленты. Для них отсутствие JS — это плюс (нет рекламы, нет слежки, мгновенная загрузка).

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

      • Геймеры и инди-разработчики: Если вы создадите каталог Godot-игр, пользователи будут использовать браузер как легкий игровой клиент (аналог Flash-плеера нового поколения).

      • Владельцы слабого железа: Пользователи старых ноутбуков и Raspberry Pi, на которых Chrome потребляет всю память.

      2. Ключевые преимущества для пользователя

      • Мгновенный отклик: Благодаря Skia и отсутствию тяжелого JS-движка, интерфейс и страницы будут летать.

      • Безопасность: Архитектура с NsJail гарантирует, что даже если страница попытается атаковать систему, она останется заперта в "песочнице".

      • Идеальная типографика: Использование Skia позволяет сделать чтение длинных текстов максимально комфортным (субпиксельное сглаживание, кастомные шрифты).

      3. Что нужно сделать, чтобы удержать пользователя?

      1. Качественный UI: Сделать интерфейс на Skia более красивым и плавным, чем у современных браузеров.

      2. Каталог контента: Встроить "магазин" или список Godot-плагинов прямо в стартовую страницу.

      3. Reader Mode: Сделать упор на идеальный режим чтения, который переверстывает "кривые" сайты в удобный текстовый вид.

      4. Главный риск

      • Современный CSS: LiteHTML поддерживает не все свойства Flexbox и Grid. Если верстка популярных текстовых сайтов (например, GitHub или StackOverflow) будет ломаться, пользователи уйдут.

      Вердикт:

      Это не замена основному браузеру, а "Второй браузер" для безопасного чтения и легких игр. Как инструмент для специфических задач или как платформа для инди-игр проект имеет высокий потенциал.


      1. nomhoi
        12.03.2026 08:30

        План реализации опционального модуля JavaScript (QuickJS + IPC Isolation)

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

        1. Архитектурная схема взаимодействия

        [ UI/Render Process (LiteHTML) ] <--- IPC (Unix Socket) ---> [ JS Runtime Process (QuickJS) ]
                  ^                                                         ^
        
                  |                                                         |
        [ Broker Process ] <-------------------------------------------------
        

        2. Этапы реализации

        Этап 1: Интеграция QuickJS (1-2 недели)

        • [ ] Сборка QuickJS как статической библиотеки внутри изолированного бинарника-воркера.

        • [ ] Создание основного цикла (Event Loop) в JS-процессе для обработки входящих сообщений.

        • [ ] Реализация базовых функций console.log и setTimeout через проброс сигналов в Broker.

        Этап 2: Протокол DOM Bridge (2 недели)

        • [ ] Зеркалирование DOM: LiteHTML при парсинге создает упрощенную карту ID элементов и передает её в QuickJS.

        • [ ] Proxy Objects: Создание в QuickJS объектов-оберток (например, document.getElementById), которые при вызове методов генерируют IPC-сообщение (например, SET_STYLE, CHANGE_TEXT).

        • [ ] Событийная модель: Рендерер ловит клик -> отправляет ID элемента в JS-процесс -> QuickJS запускает привязанный onclick обработчик.

        Этап 3: Интерфейс управления (1 неделя)

        • [ ] On-Demand Activation: Реализация кнопки в Skia UI для запуска/убийства процесса js_worker для конкретной вкладки.

        • [ ] Permissions: Настройка прав (разрешить ли JS доступ к буферу обмена или геопозиции через запрос к Broker).

        Этап 4: Изоляция NsJail (1 неделя)

        • [ ] Sandboxing: Конфигурация NsJail для JS-процесса с null сетевым интерфейсом и пустой файловой системой.

        • [ ] Seccomp: Максимальное ограничение системных вызовов (только read/write в сокет, brk/mmap для памяти).

        3. Технические детали (Технологии)

        • Движок: QuickJS (поддержка ES2023, малый вес ~1MB).

        • Транспорт: Unix Domain Sockets (тип SOCK_SEQPACKET для сохранения границ сообщений).

        • Сериализация: Simple Binary Protocol или FlatBuffers (для минимальных задержек при передаче изменений DOM).

        4. Итоговая логика работы

        1. Пользователь заходит на сайт (JS выключен, работает только LiteHTML).

        2. Нажимает "Разрешить JS".

        3. Broker запускает NsJail + QuickJS.

        4. QuickJS запрашивает скрипты у Broker -> выполняет их -> шлет команды на обновление в Render Process.


      1. nomhoi
        12.03.2026 08:30

        Оценка размера дистрибутива (Binary Size)

        Ваш браузер будет в 10–20 раз компактнее Chromium-решений (Electron/QtWebEngine), так как в нем отсутствуют тяжелые зависимости и избыточные компоненты.

        1. Вес компонентов (Static Linkage)

        При статической сборке всех модулей в один или несколько бинарников:

        | Компонент            | Технология       | Ожидаемый вес |
        | :------------------- | :--------------- | :------------ |
        | **Графическое ядро** | Skia             | 15–22 МБ      |
        | **Layout Engine**    | LiteHTML         | 1.5–2 МБ      |
        | **JS Runtime**       | QuickJS          | ~1 МБ         |
        | **Game Runner**      | Godot (Export)   | 12–18 МБ      |
        | **Изоляция**         | NsJail           | < 1 МБ        |
        | **Broker/IPC**       | Custom C++       | < 1 МБ        |
        | **ИТОГО (Full)**     |                  | **30–45 МБ**  |
        

        2. Варианты дистрибуции

        • Minimal (Core + UI): Только Skia и LiteHTML. Вес ~20–25 МБ.

        • Standard (Core + JS): Добавляется модуль QuickJS. Вес ~26 МБ.

        • Gaming Edition (Full): Включает рантайм Godot для плагинов. Вес ~45 МБ.

        3. Методы оптимизации размера

        • LTO (Link Time Optimization): Удаление неиспользуемых частей Skia (например, PDF-рендерера или редких кодеков) экономит до 30% веса.

        • Strip Symbols: Удаление отладочной информации (strip --strip-all) уменьшает бинарник в 2-3 раза.

        • System Libraries: Если линковать fontconfig, freetype и openssl динамически (использовать системные в Linux), размер упадет до 10–15 МБ.

        • UPX Compression: Сжатие финального исполняемого файла может довести размер до невероятных 8–12 МБ (с небольшой задержкой при распаковке в RAM при старте).

        4. Сравнение с конкурентами

        • Google Chrome: ~350 МБ

        • Pale Moon: ~40 МБ

        • Dillo (старый легкий браузер): ~2 МБ (но без Skia и Godot)

        • Ваш проект: ~35 МБ (современная графика + игры + безопасность).


      1. nomhoi
        12.03.2026 08:30

        План разработки защищенного браузера (Slint + Skia + Godot + NsJail)

        1. Архитектура "Slint-Native"

        • Host Process (Broker): Управляет окном и ресурсами.

        • UI Process (Slint + Skia): Основной интерфейс (вкладки, меню, настройки) на Slint.

        • HTML Plugin (LiteHTML): Опциональный модуль для отрисовки Web-контента внутри Slint-виджета.

        • Game Plugin (Godot): Изолированный игровой модуль.

        2. Этапы разработки

        Этап 1: Фундамент на Slint + Skia (4 недели)

        • [ ] Настройка CMake для интеграции Slint с использованием Skia Renderer.

        • [ ] Дизайн основного интерфейса браузера на языке .slint (адресная строка, боковая панель).

        • [ ] Реализация Broker Process для запуска UI в первом контейнере NsJail.

        • [ ] Настройка IPC (Unix Sockets) для передачи URL и команд между Slint и Брокером.

        Этап 2: Опциональный HTML-модуль (3 недели)

        • [ ] Создание кастомного виджета в Slint (NativeWindowHandle или Image), куда будет транслироваться буфер от LiteHTML.

        • [ ] Компиляция LiteHTML как динамического плагина, который подгружается только при необходимости.

        • [ ] Реализация "песочницы в песочнице": HTML-рендерер изолирован от основного интерфейса Slint.

        Этап 3: Интеграция Godot (4 недели)

        • [ ] Использование DMA-BUF для проброса текстур из Godot напрямую в Skia-контекст Slint.

        • [ ] Настройка фокуса ввода: Slint передает управление клавиатурой/мышью в процесс Godot при клике на игровую область.

        Этап 4: JS-модуль (QuickJS) (3 недели)

        • [ ] Подключение QuickJS как опционального скриптового движка для Slint-интерфейса или HTML-модуля.

        • [ ] Разграничение прав: JS в интерфейсе (Slint) и JS на страницах (HTML) работают в разных NsJail.

        Этап 5: Безопасность и Hardening (2 недели)

        • [ ] Seccomp-профили для Slint (запрет доступа к сети, только IPC).

        • [ ] Cgroups для ограничения потребления RAM игровыми плагинами.

        3. Технологический стек

        • UI Framework: Slint (основной).

        • Graphics: Skia (через Slint Renderer).

        • HTML Support: LiteHTML (опциональный C++ плагин).

        • Games: Godot 4.x (через NsJail + DMA-BUF).

        • Security: NsJail, Seccomp, Linux Namespaces.

        4. Сроки и команда

        • MVP (Slint UI + NsJail): 2.5 месяца.

        • Полная версия (HTML + Godot + JS): 5-6 месяцев.


      1. nomhoi
        12.03.2026 08:30

        «Plans are nothing; planning is everything.»


        1. fugal-fringe-0p
          12.03.2026 08:30

          Порошок, уходи.


          1. nomhoi
            12.03.2026 08:30

            как у вас все просто :)


        1. SWATOPLUS
          12.03.2026 08:30

          Горшочек не вари


          1. Ghrec
            12.03.2026 08:30

            Горшочек, не вари порошочек


        1. Maxor1k
          12.03.2026 08:30

          А дальше что? Или токены кончились?


          1. omaxx
            12.03.2026 08:30

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


          1. nomhoi
            12.03.2026 08:30

            Не кончились. Во-первых, спать пошёл. Во-вторых, хорошего помаленьку.
            А что, выглядит захватывающе? Требуете продолжения?
            У меня такого полно.

            Бизнес-требование: Бинарный протокол отображения (Binary-First Browser)

            1. Концепция (The Core Idea)

            Заменить классический текстовый стек (HTML/CSS) на компактный бинарный формат данных (назовем его BDOM — Binary Document Object Model). Сервер или Брокер конвертирует входящие данные в типизированные структуры, которые Skia отрисовывает напрямую, минуя тяжелые текстовые парсеры.

            2. Ключевые бизнес-требования (Business Requirements)

            BR-B1: Нулевой синтаксический оверхед (Efficiency)

            • Требование: Использование сериализованных структур (например, FlatBuffers или кастомный двоичный протокол) для передачи дерева элементов от Брокера к Рендереру.

            • Критерий успеха: Скорость десериализации страницы в Рендерере должна быть в 10–50 раз выше, чем парсинг аналогичного HTML/CSS в LiteHTML.

            BR-B2: Безопасность через типизацию (Binary Security)

            • Требование: Исключить возможность текстовых инъекций. Бинарный формат должен строго определять типы данных (размер шрифта — int, цвет — uint32, координаты — float).

            • Критерий успеха: Полная невозможность выполнения классических XSS-атак, так как данные в принципе не интерпретируются как исполняемый код или скрипт.

            BR-B3: Экономия трафика (Bandwidth Optimization)

            • Требование: Бинарный документ должен занимать на 40–70% меньше объема, чем эквивалентный HTML-код, за счет отсутствия тегов, кавычек и дублирующихся строк.

            • Критерий успеха: Возможность комфортной работы браузера на каналах связи от 64 кбит/с.

            BR-B4: Нативный мапинг на Skia (Rendering Alignment)

            • Требование: Структура бинарного документа должна максимально соответствовать командам рисования Skia (Draw Calls).

            • Критерий успеха: Рендерер должен начинать отрисовку первого кадра (First Meaningful Paint) сразу после получения первых байтов бинарного потока, не дожидаясь закрывающего тега.

            3. Технологический стек реализации

            1. Формат данных: FlatBuffers (позволяет обращаться к данным в памяти без распаковки) или Protobuf.

            2. Конвертер (Proxy): Специальный сервис (или модуль в Брокере), который берет "грязный" HTML из сети и "выпрямляет" его в чистый бинарный BDOM.

            3. Рендерер: Облегченная версия вашего приложения, где вместо LiteHTML стоит BDOM-Reader, вызывающий методы Skia.

            4. Преимущества для бизнеса

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

            • Экономия: Снижение затрат на серверные мощности и трафик.

            • Контроль: Вы полностью контролируете, какие элементы могут быть отображены, а какие — нет.


      1. vladkorotnev
        12.03.2026 08:30

        Реализовать — вряд ли, набурлестать иишницей — так вы уже


  1. ivandukin
    12.03.2026 08:30

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


    1. kukovik
      12.03.2026 08:30

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


  1. osipov_dv
    12.03.2026 08:30

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

    вообще-то еще есть setfacl


    1. bear11
      12.03.2026 08:30

      Который дичь еще та! Часть ключей имеет краткий вариант, часть нет. Как вообще эти ACL работают, особенно вместе с обычными UGO, понимать сложно.
      -k, --remove-default remove the default ACL
      --set=acl set the ACL of file(s), replacing the current ACL
      --set-file=file read ACL entries to set from file
      --mask do recalculate the effective rights mask
      -n, --no-mask don't recalculate the effective rights mask


    1. ReadOnlySadUser
      12.03.2026 08:30

      Вообще ещё есть capabilities :)


  1. vadimr
    12.03.2026 08:30

    Определённая правда жизни в изложенном есть, но несколько смущает, что половина описанных ситуаций заканчивается одним и тем же результатом "положит Cloudflare". Получается, что не в самих этих ситуациях дело?


  1. axeax
    12.03.2026 08:30

    Пишите на php там хорошие доки (с)


  1. eao197
    12.03.2026 08:30

    Каким боком это все к хабу C++? Автор даже не смог привести ни одной специфичной именно для C++ проблемы. Те два жалких примера с UB -- это чистый Си, а C++у это досталось по наследству.


    1. withkittens
      12.03.2026 08:30

      Да тут у многих беда с хабами.

      Упомянули интернет - фигачим хаб дотнета! А что, .NET, интернет - да какая разница, похоже же.

      Или человек бухтит про что-то своё жизненное, упоминает как 10 лет назад юзал EntityFramework - всё, пихаем в хаб с сишарпом!


  1. kukovik
    12.03.2026 08:30

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

    Баш -- развитие старого доброго sh и, например, && в нем появился как ответ на потребности, а & был давно и запускает процесс в фоне потому, что И. Запускает a & b & c одновременно.

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

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

    Ну а \; для -exec в find вообще не проблема. На самом деле там нет никакого \. Просто надо донести это до файнда так, чтобы баш не воспринял это как разделитель команд в строке. Можно было бы написать ';', например. А почему именно этот символ? Да потому, что он является устоявшимся завершением строки с командой.

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

    PS. Вот, кстати, фейлы (игнорирование) при пайпах меня тоже как-то сильно удивили. Очень уж неинтуитивно.


    1. vvbob
      12.03.2026 08:30

      Я вот эту всю фигню с find постоянно забываю. Когда мне эта команда требуется, читаю, запоминаю, через несколько дней, можно мне пистолет к голове приставить, сказать что пристрелите если не отвечу, что там надо написать для выполнения команды над найденными файлами, и я скажу - "стреляйте, все равно не вспомню".

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


    1. funca
      12.03.2026 08:30

      Вот, кстати, фейлы (игнорирование) при пайпах меня тоже как-то сильно удивили. Очень уж неинтуитивно.

      Потому, что например в `cat file | head`, этот head как только получает достаточно данных, закрывает входной поток, а cat получает SIGPIPE и умирает с ошибкой. Это эффективно, т.к. файл не надо читать до коца. Но если пайп не проигнорит ошибку от cat, то с ошибкой завершится и вся эта конструкция, что будет конфузить.


  1. Dr_Faksov
    12.03.2026 08:30

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

    И начинать, наверное, надо с "железа", которое в "в металле" не соответствует описанию. И драйвера, написанные по описанию, не позволяют делать то , что должны. И программы, опирающиеся на драйвера... И не опирающиеся. К примеру, CAD для проектирования чипов, который не создает "железа" идентичного эмулятору. В связи с чем чип, воплощённый "в металле" не соответствует описанию.... :)

    Вообще, такое впечатление, что вся IT - индустрия гадит друг дружке в штаны :) Из лучших побуждений, чтобы облегчить жизнь, естественно :)


  1. Fil
    12.03.2026 08:30

    if должно закрываться fi. Значит, for должно закрываться rof, а while - elihw? А вот и нет, только if-fi. В остальных done

    Еще есть case-esac


    1. Dr_Faksov
      12.03.2026 08:30

      "И пусть в моих поступках не было логики, я не умею жить по-другому..." (с) В. Меладзе


      1. Wesha
        12.03.2026 08:30

        Скорее © моя жена


        1. Cerberuser
          12.03.2026 08:30

          Сочувствую.


          1. Wesha
            12.03.2026 08:30

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


            1. kesn Автор
              12.03.2026 08:30


    1. randomsimplenumber
      12.03.2026 08:30

      #if

      fi

      #endif


  1. astenix
    12.03.2026 08:30

    Оставьте bash как есть, старый фурычит нормально.

    useradd и adduser (и groupadd с addgroup) — это разные утилиты. Выберите себе одну и пользуйтесь только ею.


    1. mayorovp
      12.03.2026 08:30

      Да я давно уже выбрал одну из них, только всё время забываю какую именно


  1. vilgeforce
    12.03.2026 08:30

    Чудесная пятничная статья, но вышла почему-то в четверг (не иначе автор опять перепутал синтаксис крона?) :-D


  1. danvulkan
    12.03.2026 08:30

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


  1. Daddy_Cool
    12.03.2026 08:30

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


    1. Habr4687544
      12.03.2026 08:30

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


      1. withkittens
        12.03.2026 08:30

        А что не так с pwsh?


        1. Habr4687544
          12.03.2026 08:30

          1. withkittens
            12.03.2026 08:30

            А. Ну я на стадии принятия :)


          1. ahabreader
            12.03.2026 08:30

            Это всё равно движение в верном направлении по сравнению с прошлыми шеллами и критики по делу там немного.

            Set-StrictMode -Version 3.0
            $ErrorActionPreference = 'Stop'

            Дальше обсасывается поведение Write-Host (не Write-Output aka echo (полезное: gal -def write-output)) и не видят, что авторазворачивание массива из 1 элемента - общий принцип языка. По-моему, ошибочный, но всё равно к нему и к общей гибкости надо подстраиваться, а не переть напролом с бойлерплейтом: if ($f.GetType().Name -eq "Object[]") {....

            Похоже, для единообразной обработки достаточно завернуть Get-Content в массив через @(...): Write-Host @(cat .\b.txt)[0]. На имеющийся массив это @(...) не повлияет из-за авторазворачивания, принудительно массив-из-одного-массива создавался бы через запятую перед элементом, отлаживается фигня через вывод ConvertTo-Json ... , например.

            Есть единообразное "всегда получать строку, без разбиения на массив по переносам": cat -Raw.

            Write-Host переваривает $null.


            1. ahabreader
              12.03.2026 08:30

              По-моему, ошибочный

              Ладно, не "по-моему, ошибочный", а эпичная стрельба в ногу, которая себя проявит при работе с вложенными массивами и вообще с JSON. Из-за этого в языке имеется трюк с @(...), с запятой, -NoEnumerate и -AsArray.

              Write-Host @(cat .\b.txt)[0]

              Нет, надо cat -lit из-за похожей грабли (без -LiteralPath нельзя использовать имена файлов, содержащие квадратные скобки). Сам писал о проблемах PowerShell тут: https://habr.com/ru/companies/ruvds/articles/832744/comments/#comment_27131528

              Или вот разделить stdout и stderr без неофициальной документации от mklement0, размазанной по интернету, вряд ли получится:
              $stdout, [string[]] $stderr = $stdoutAndErr.Where({ $_ -is [string] }, 'Split')

              Это завязано на багофичу про то, что stderr имеет тип ErrorRecord и на недокументированный метод Where ("Unfortunately, these methods remain undocumented even today, almost a year since they were publicly released" - прошло ещё 12 лет).


  1. E2a
    12.03.2026 08:30

    Люди для решения конкретно своей нужды изготавливают свой велосипед, применяют его. Пишут код для СЕБЯ, не для других. (Линус Торвальдс из злости написал Git за две недели.) Но велосипед так им и остаётся, разве что в дальнейшем иногда дорабатывается. Согласно философии открытого исходного кода если тебе что-то мешает - возьми и исправь как тебе надо. Именно для этого и открывается исходный код.

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

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


  1. feelamee
    12.03.2026 08:30

    FILES="file1 file2"
    rm $FILES
    

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

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


    1. ReadOnlySadUser
      12.03.2026 08:30

      самый очевидный практический пример - флаги компилятору.

      Не делайте так! Не надо! А bash есть массивы, используйте их!

      flags=(
          "--flag-one"
          "--flag=value with spaces"
      )
      
      gcc "${flags[@]}"

      Эту буквально единственный вменяемый способ передачи флагов. Всё остальное - отлукавого


      1. oficsu
        12.03.2026 08:30

        Не каждый шелл является башем башем, поддерживающим массивы


        1. ReadOnlySadUser
          12.03.2026 08:30

          За последние 20 лет я не видел ни одного реального окружения, где бы отсутствовал bash. Буквально ни одного случая.

          Да меня всякий шелл - это bash, а там где не bash - всегда можно вызвать `bash - c`


          1. oficsu
            12.03.2026 08:30

            Прямо сейчас у меня в initramfs нет баша, вообще. И очень вряд ли, что у вас иначе. А шелл-скрипты там есть и бывают в немалом количестве. У меня там ash, вроде

            А ещё в дебиане вызовется dash, если ваш шебанг вдруг окажется так весьма популярным #!/bin/sh вместо #!/bin/bash

            А ещё нередко встречается busybox в эмбеде, там тоже ash

            А ещё нередко в прод ставят контейнеры, в которых до предела минимизируют окружение. И если там и нужен шелл, то им тоже будет не баш

            А иногда даже бывает нужна совместимость с zsh, в котором есть массивы, но доступ в которых работает иначе. Это, например, важно для скриптов автокомплита. Там вариант с bash -c несколько затруднителен

            а там где не bash - всегда можно вызвать bash - c

            Это не всегда решает автор скрипта


            1. ReadOnlySadUser
              12.03.2026 08:30

              если ваш шебанг вдруг окажется ... #!/bin/sh вместо #!/bin/bash

              Ну... если вы пишете bash-скрипт, то и shebang надо указывать правильный)

              А ещё нередко встречается busybox в эмбеде, там тоже ash

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

              А ещё нередко в прод ставят контейнеры, в которых до предела минимизируют окружение.

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

              А иногда даже бывает нужна совместимость с zsh

              Проще написать две версии скриптов автокомплита, т.к. у zsh вообще своя магия для комплита.

              Это не всегда решает автор скрипта

              Автору ничего решать и не надо) Автор пишет в шебанге условный #!/usr/bin/env bash, а остальное - проблемы пользователя)

              Я не буду отрицать - сценарии где bash недоступен - существуют. Я лишь хочу сказать, что я с такими не сталкивался за 20 лет ни разу. Да, наверное в initramfs нет bash, но если мне придётся там много скриптовать - я просто его туда упакую.


              1. Akon32
                12.03.2026 08:30

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

                Это не так. При использовании python часто запускается стартовый скрипт, который выполняет миграции, запускает cron, supervisor и т.п., а потом само приложение (если этого не делает supervisor). Бывает в 1 образе несколько стартовых скриптов, для воркера и диспетчера например, и тогда в разных контейнерах на одном образе запускаются разные скрипты. Bash ещё как используется, и иногда в образе оказывается sh, а не bash.


              1. mayorovp
                12.03.2026 08:30

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

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


      1. oficsu
        12.03.2026 08:30

        Вообще, по секрету, в баше есть ещё один вариант (или, скорее, их семейство):

        x="--flag-one"
        y="--flag=value with spaces"
        script -c "gcc ${x@Q} ${y@Q}"
        

        И иногда это единственный доступный вариант, если утилита хочет шелл-команду одной цельной строчкой. Как script, например


        1. ReadOnlySadUser
          12.03.2026 08:30

          Q-синтаксис работает и с массивами. Примерно так

          flags=(
              "--flag-one"
              "--flag=value with spaces"
          )
          script -c "gcc $(echo "${flags[@]@Q}")"

          Сработает как надо, но синтаксис получается немножко инопланетный, да


  1. EgorSharin
    12.03.2026 08:30

    Автор, ты немножечко неправ. Автор тупого кода, почти всегда, - тупой индус.


  1. Alexandroppolus
    12.03.2026 08:30

    тупых названий вы сами видели миллион и одну штуку

    Мои топ 1 - браузерные функции atob и btoa. Угадать, что они делают, нереально. Как вообще такой нейминг пролез в стандарты? Плюс ещё и представление данных впечатляет (но это исторически сложилось). Хорошо что теперь появилась нормальная альтернатива.


    1. Metotron0
      12.03.2026 08:30

      В go есть atoi (видимо, в си тоже), в учебнике объяснили, что это ASCII to Integer, соответственно, atob должен делать ASCII to Base64, но оно работает наоборот.


      1. mayorovp
        12.03.2026 08:30

        Потому что это ascii to binary, причёт под ascii понимается base64, а под binary - произвольная строка...


  1. NooControl
    12.03.2026 08:30

    Программирование на С++ всё таки больше похоже на ходьбу по канату: шаг влево, шаг вправо - упал!


    1. Belarus
      12.03.2026 08:30

      Или минное поле.


  1. ppnn
    12.03.2026 08:30

    "универсисета в Киото" это специально?)


  1. belch84
    12.03.2026 08:30

    if должно закрываться fi. Значит, for должно закрываться rof, а while - elihw? А вот и нет, только if-fi. В остальных done

    Ну, лучше бы for ... do и while ... do закрывалось бы od, а case - esac

    Algol-68


  1. nicelight_nsk
    12.03.2026 08:30

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

    Мы постепенно переходим в эру программирования спецификаций и документации


    1. Akon32
      12.03.2026 08:30

      Создать непротиворечивую спецификацию - та ещё задача.


  1. DmitryOlkhovoi
    12.03.2026 08:30

    Javascript придумал отображать объекты [object Object], и, честно говоря, я бы и сам лучше не придумал!

    ну это .toString() :)


  1. ImagineTables
    12.03.2026 08:30

    В C макросы — это тоже тупая подстановка

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


    1. Format-X22
      12.03.2026 08:30

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


      1. ImagineTables
        12.03.2026 08:30

        Зато пока есть #define, который тупо подставляет текст, есть и #include, который тупо подставляет текст. А пока есть #include, будут и файлы. Сравните с тем ужасом, который натворили в ECMAScript с модулями .mjs, которые не могут быть локальными.

        Как бы, C и Unix — близнецы-братья, а в Unix файлы — это всё (или, как они говорят, «всё есть файлы»). Дух общий. Жаль, что с ними он и умрёт. А мы будем пожизненно платить за подписку на dev-эккаунты, чтобы разместить файлы в облаке, иначе эти паразиты откажутся их подключать. К этому всё, боюсь, идёт.


  1. ahdenchik
    12.03.2026 08:30

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


    1. randomsimplenumber
      12.03.2026 08:30

      Ну да, 2то3.py. Иногда приходится запиливать обратно 2 питон (выпиленный из всех линуксов), потомушто старые скрипты никто переписывать не хочет.


  1. LeoLev
    12.03.2026 08:30

    Читаю и думаю, как хорошо что я с этим пока не столкнулся. Разве что с bash но к нему особо у меня претензий нет.


  1. LeoLev
    12.03.2026 08:30

    А ну и еще, пока помню, внесу свои 5 копеек

    В js все удобно

    [1,2,3].join("")

    А на питоне решили что так слишком легко:

    "".join([1, 2, 3])

    Почему??? За что???


    1. omaxx
      12.03.2026 08:30

      Потому что проще сделать один метод для str, который работает с любым Iterable, чем наоборот


      1. randomsimplenumber
        12.03.2026 08:30

        TypeError: sequence item 0: expected str instance, int found

        В смысле с любым?


        1. omaxx
          12.03.2026 08:30

          С любым Iterable[str]


      1. DmitryOlkhovoi
        12.03.2026 08:30

        Ну довольно трудно представить использование join с чем-то другим. Но есть мета программирование через symbol.iterable. А так же можно делать типо такого:

        let obj = { 0: 'a', 1: 'b', length: 2 };
        let result = Array.prototype.join.call(obj, '-');


    1. Akon32
      12.03.2026 08:30

      В python не всё так логично, как декларируется, взять ту же конструкцию "a if cond else b". На scala это "if (cond) a else b" - это легче читается имхо.

      А если нырять в многопоточность в python, можно и не вынырнуть - масса edge case, которые не описаны в документации, а когда они проявляются, и ищешь решение, в стандартной библиотеке находится "ещё один способ сделать это", немного устаревший, но рабочий, в отличие от нового. Декларируется при этом, что "есть только 1 способ сделать".


  1. oficsu
    12.03.2026 08:30

    Переменная окружения и значение по умолчанию: ${ENV_VAR:-10}. Нет, значение по умолчанию не -10, а 10! Зачем там дефис? Я хз

    Загадка может внезапно проясниться, если заглянуть в документацию и увидеть, что там бывает не только -, но и +, и даже = с ?


  1. oficsu
    12.03.2026 08:30

    есть короткие флаги -g и -G, -M и -m, -p и -P, которые непонятно зачем вообще есть

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


    1. mayorovp
      12.03.2026 08:30

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


  1. oficsu
    12.03.2026 08:30

    Какого вообще чёрта --create-home и --no-create-home существуют одновременно, ведь если есть поведение по умолчанию, то нужен только противоположный флаг?

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

    Во-вторых, такие флаги могут явно, чётко и однозначно выражать намерение – "да, именно это я и хочу сделать"

    Тем более, что запоминать флаги иногда проще, чем дефолты


    1. Deosis
      12.03.2026 08:30

      А ещё дефолт может поменяться или зависеть от переменных окружения


  1. oficsu
    12.03.2026 08:30

    на уровне мышечной памяти пишу rsync -avzP

    Я чаще всего rsync использую локально. Так вот, -z не нужен в этом случае, а -v не имеет большого смысла с -P, одна фича которого в свою очередь тоже не нужна при локальном копировании, а вторая удачнее заменяется опцией --info=progress2. Вот из полезных флагов и остался только -a


  1. oficsu
    12.03.2026 08:30

    Именованные аргументы всегда лучше, потому что они явные и не ломаются при рефакторинге

    Это не так. Иногда у аргументов бывает натуральный порядок. Ну, например, вот: divide(numerator = 42, denominator = 123). Или join(first = root, second = subdirectory, ??? = filename). Кстати, да, first, second,... А до скольки нам так имена резервировать?

    Кроме того, иногда аргументы выводимы из контекста, а перепутать их нельзя

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

    А у стен текста есть объективная проблема. Их труднее парсить глазами, труднее даётся визуальная навигация по коду. Это всё приводит к более быстрому истощению внимания разработчика. Быстрее истощается внимание – больше ошибок за 8 рабочих часов. Либо просто ежедневно работаем меньше – пишем меньше фич


    1. mayorovp
      12.03.2026 08:30

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

      Вот с join и правда хорошее возражение.
      Ещё там в PEP была аргументация про прямую совместимость и про LSP.


      1. oficsu
        12.03.2026 08:30

        Стена текста - это проблема того, кто код пишет

        В первую очередь – того, кто читает код. Потому что чем больше стена, тем труднее верно оценить её (во всех смыслах). Страдает не только процесс разработки, но и ревью, и аудита. Да даже банально будут страдать коллеги, которые работают с тобой в общей кодовой базе


  1. oficsu
    12.03.2026 08:30

    Это запишет в переменную окружения KEY пустое значение: environment: { KEY: }
    Это передаст значение KEY с хоста внутрь контейнера: environment: [ KEY ]
    Во-первых, почему можно написать одно и то же разными способами? Во-вторых, почему работает по-разному?!

    Во-первых, обосную значимость второго. Иметь возможность форвардить переменную, не задавая и не вычисляя её значения – это очень полезно и наглядно. Намного нагляднее, чем environment: [ "KEY=${KEY}" ]

    Во-вторых, первое работает по той причине, что yaml – это отдельная от docker compose сущность. Его парсер, вероятно, не позволяет отличить environment: { KEY: "" } от environment: { KEY: }. И потому эти две записи не могут давать разный результат. Либо обе дают пустую строку, либо обе форвардят значение

    А вы бы хотели, чтобы environment: { KEY: "" } передавал не KEY, равный пустой строке, а форвардил непустое значение? Я бы этого не хотел, это было бы контринтуитивно

    Я уж молчу о том, что даже вне контекста достаточно здравого смысла, чтобы понять, что KEY: задаёт <ничего>. И пустая строка является одним из ожидаемых результатов для <ничего>. И точно так же интуитивно очевидно, что в KEY нет задания значения, а потому это особый случай, который тоже интуитивно понятен большинству


    1. VADemon
      12.03.2026 08:30

      Во-вторых, первое работает по той причине, что yaml ... Его парсер, вероятно, не позволяет отличить

      YAML настолько Yet Another, что на его хитрую задницу понадобился простой советский TOML. Прям в названии: "Tom's Obvious, Minimal Language (originally Tom's Own Markup Language)"


    1. omaxx
      12.03.2026 08:30

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

      Нет, у этих двух строк разное значение

      { KEY: } эквивалентно { KEY: null }, и это не тоже самое, что ""


      1. mayorovp
        12.03.2026 08:30

        Здесь уже особенность go, у строки не может быть nil-значения, потому оно превращается в пустую строку.


  1. oficsu
    12.03.2026 08:30

    А ведь если бы SQL разделял запрос и данные (SELECT %s FROM %s, (name, age), users) и делал бы подстановку именно значений, а не текста, то не было бы инъекций

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


  1. oficsu
    12.03.2026 08:30

    Дополнительно в C++ добавили... Нет, не так. В C++ СПЕЦИАЛЬНО добавили случаи, при которых происходит хрен знает что. Не программа падает, нет. ХРЕН ЗНАЕТ ЧТО происходит. Undefined behavior. Собственно, на что был расчёт?

    А как автор объяснит наличие таких специальных случаев в так называемом "нормальном пистолете"? Там аж целое специальное ключевое слово придумали, чтобы разрешить компилятору собирать такие специальные случаи


  1. oficsu
    12.03.2026 08:30

    Сделайте фигурную скобку { обязательной после if — никто почти и не заметит, но эта ошибка умрёт навсегда

    А ничего, что статические анализаторы в современном мире и так подчёркивают жёлтым ошибочные конструкции? Нет, действительно, давайте уж вместо этого писать такие полотна, убивающие early return:

    if (not valid(foo))
    {
        return error("don't do this");
    }
    

    Ну а кому не будет приятно увидеть такой шедевр искусства в своей кодовой базе?


    1. CitizenOfDreams
      12.03.2026 08:30

      А ничего, что статические анализаторы в современном мире и так подчёркивают жёлтым ошибочные конструкции?

      Так откуда анализатору знать, ошибочна ли конструкция if (условие) действие1; действие2; ? Может, программист забыл нарисовать скобочки, а может, и правда хотел, чтобы действие2 выполнялось всегда. Мы это узнаем только когда программа выдаст не то, что от нее ожидалось.


      1. randomsimplenumber
        12.03.2026 08:30

        Анализатор умеет подозревать. А программисту, конечно, виднее.


      1. ahabreader
        12.03.2026 08:30

        Так откуда анализатору знать

        https://godbolt.org/z/dYd5xnbK5

        Но вахтёрствовать и запрещать всё, конечно, легче/приятнее.


      1. oficsu
        12.03.2026 08:30

        Речь про этот пример из статьи:

        if (condition)
          do1();
          do2();
        

        Анализатор прекрасно видит несоответствие отступов отсутствию фигурных скобок. Он это безошибочно подчеркнёт как misleading indentation

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

        Я вот лично попадал в другую нелепую ситуацию:

        if (foo) {
        
        } else if (bar) {
        
        } else if (baz) {
        
        } { // потерял else
        
        }
        

        Что предложите ещё запретить, чтобы решить ещё и эту проблему? Или, может, всё же, запреты это не панацея?


  1. oficsu
    12.03.2026 08:30

    а права на файлы я всё так же выставляю циферками 0+1+2+4

    Вас никто не заставляет так делать. Современный chmod вполне принимает опции в формате chmod u=rwx,g-w,o+x


    1. nidalee
      12.03.2026 08:30

      Только 777, только хардкор.


  1. oficsu
    12.03.2026 08:30

    Или Docker, который изолирует приложение, но поди запусти его с GUI

    Да, прокинуть переменную для дисплея и сокет от xorg/wayland – это неподъемная задача, конечно. Никто не справится:

    docker run -it                                         \
      -e XDG_RUNTIME_DIR=/tmp                                \
      -e WAYLAND_DISPLAY                                       \
      -v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
       container app
    

    Ах да, не забудьте в контейнере поставить само приложение и шрифты к нему, конечно. Это тоже нетривиальное знание, знаете ли


  1. oficsu
    12.03.2026 08:30

    Ещё есть пакеты Flatpak, которые изолируют приложение, но вообще-то могут быть созданы кем угодно

    Так и deb-пакеты может кто угодно. Суть не в сборке, а в репозитории и мейнтейнерах. Не доверяете Flathub? У RedHat есть свой репозиторий flatpak-пакетов от их мейнтейнеров


  1. AppCrafter
    12.03.2026 08:30

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


  1. john_crew
    12.03.2026 08:30

    Я программист-любитель, и сравнительно недавно переехал на линукс... Дошел до сборки ядра) Захотелось всё это дело автоматизировать: скачивание сырцов, сборка, установка. В общем, в статье про bash - это прям в яблочко))


    1. Assortie
      12.03.2026 08:30

      Всегда казалось, что в gentoo это автоматизировано...


  1. akakoychenko
    12.03.2026 08:30

    Джанго не скажет, что вы забыли сделать JOIN, он просто будет долбать БД. То есть фреймворк по умолчанию спроектирован так, чтобы появлялась проблема N+1, а язык спроектирован так, чтобы это можно было сделать. Комбо!

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

    ведь если бы SQL разделял запрос и данные

    А вот это в точку! И, ладно инъекции, вставка большого количества данных, как insert values, или in (...). Вот настоящая боль. И СУБД даже имеют интерфейсы работы с таким (bulk insert в mssql, например, или copy from stdin в pg). Но, боль ещё и в том, что эти интерфейсы поддерживаются очень через жопу в сторонних тулах/библиотеках. К примеру, 5 лет назад, чтобы грузить с питона в mssql достаточно большие датасеты, причём, построчно, что-бы не раздуть RAM, ушёл где-то день, чтобы просто побороть проблемы зависимостей и заставить работать единственную библиотеку, которая так умеет (кажется, ctds). Хотя, оно стоило того. Данные начали заходить в сервер со скоростью 1.3 гигабайта в секунду, и такая скорость могла держаться часами без проседаний. Обидно, что столь мощные инструменты надо выковыривать с каких-то пыльных углов, когда уродство insert values подаётся, как само собой разумеющееся


  1. Assortie
    12.03.2026 08:30

    А давайте расширим контекст этой захватывающей (честно! получил массу удовольствия!) статьи за рамки компьютера в реальный мир!
    Постебемся над велосипедами, ножами, топорами, штопорами!


    1. Wesha
      12.03.2026 08:30

      С радостью готов постебаться над моим, @#%, велосипедом. И какой китайский дыбил его проектировал?


  1. oforum
    12.03.2026 08:30

    С учётом объёма таких примеров, не думали оформить это дальше в живую «антипаттерн-базу» для разработчиков (что-то вроде открытого каталога design traps/footguns с кратким разбором, как это починено в других языках и чем заменить на практике) — как минимум это было бы отличным материалом и для митапов, и для обучения джунов, чтобы часть этих мин они могли обойти по чужим шрамам, а не по своим?


    1. kesn Автор
      12.03.2026 08:30

      Вы явно переоцениваете мой энтузиазм :)


  1. Battary
    12.03.2026 08:30

    Спасибо за статью! На самом деле сам недавно тоже задумывался над кривыми решениями by design, вот некоторые примеры:

    SQL

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

    Java Collections

    В Java, как и в большинстве языков есть такие коллекции как Map (ключ-значение) и List(список/массив на стероидах). Если в Map обратиться к несуществующему ключу через метод .get(key), то в результате будет возвращено значение null. Однако если в том же самом List обратиться к несуществующему индексу по методу .get(index) то будет IndexOutOfBoundsException.

    Хорошо, в той же Map есть метод .getOrDefault(key, default) , может быть он есть в List чтобы вернуть null и не обрабатывать исключение? А такого метода List не реализует!

    Java Strings

    Думаю у многих возникала необходимость разделить строку на массив строк по какому-то разделителю. В Java за это отвечает метод split(regex):

    ",".split(",") = String[0]{}

    и

    "а,".split(",") = String[1]{"а"}

    ",а".split(",") = String[2]{"", "а"}

    но почему нельзя всегда делать:

    "а,".split(",") = String[2]{"а", ""}

    ",".split(",") = String[2]{"",""}

    загадка.

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