
Ну, не ваша, хабравчане, а ваша, создатели языков, библиотек, фреймворков итд. Но давайте не забегать вперёд.
Я как-то привык что если что-то ломается или плохо работает, то это я виноват. Это называется «брать ответственность за свои поступки» или, в случае программиста, за свой код, и это считается хорошим делом.
Разумеется, по эго это бьёт иногда больно, и некоторые моменты вспоминать не очень приятно. Самое страшное, что я когда-либо делал — коммитил приватный ключ в публичной репо. Вот написал и мне опять стыдно. Но я осознаю, что это всё я.
Но внезапно я открыл для себя тот факт, что не во всех ошибках моя вина. То есть да, это моя голова думает код, это мои руки печатают этот код, но ошибка идёт не от меня. Ошибка заложена ещё раньше, вообще задолго до меня, а иногда даже задолго до моего рождения.
Сейчас я вам это покажу. Будет интересно, но впереди много боли. Я предупредил.
Итак, у 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 и adduser (и groupadd с 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++ были спроектированы так, чтобы разрабы делали ошибки с памятью. Точка.
Вот статья про баги С и их жизнь в Linux. Они там хорошо перечислены, знайте врага в лицо
Вот создатель curl запретил strcpy, потому что иногда проще что-то не использовать, чем страдать
Вот человек создаёт типобезопасный компилятор, чтобы хоть как-то избавиться от типичных для C ошибок
Вот опять какие-то use-after-free и неправильный учёт размера строки в Grub
Все эти 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)

olivera507224
12.03.2026 08:30git checkout— выбор ветки,git branch- выбор ветки
git switch- выбор ветки
kaka888
12.03.2026 08:30С этим комментарием стало куда комичнее

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

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

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

kesn Автор
12.03.2026 08:30Автор призывает не только добавлять хорошее, но и удалять плохое. Причём даже не в варианте "сожжём всё старое" (хотя я из этого лагеря), а через deprecation. Кому надо старьё - есть версионирование.
Тот же
git checkout <branch>- пусть пишет deprecation warning в течение года, раз появилсяgit switch. Или мы теперь на каждую плохо названную команду будем добавлять хорошо названный дубликат?

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

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

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

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

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

Belarus
12.03.2026 08:30при создании софта казалось хорошей идеей
Или ожыдалось, што это временное решение - нет времени на обдумывание.

Ogra
12.03.2026 08:30Подкину мое любимое в копилку.
Хотите вызвать какую-то команду в линуксе, чтобы она выполнялась не с одним файлом, а рекурсивно? Добавьте ключ
-r:rm -r;cp -r, например. Но только не дляchownиchmod, они хотят ключ-R! И эти команды, между прочим, входят в единый стандарт POSIX!Хотите указать порт при подключении по
ssh? Нет ничего проще, просто добавьте ключ-pХотите скопировать что-то черезssh? Используйте командуscp, но ей для указания порта нужен ключ-P.
Jogker
12.03.2026 08:30И когда уже запомнил какой ключ к каким штанам и даже осилил мнемонику...
тут и настигло крестьянина послабление ))
telnet dupa.com 80

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

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

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

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

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

baldr
12.03.2026 08:30С одной стороны - читал и плакал. kesn, как всегда, жгёт по-живому. Можно ещё добавить Makefile как пример боли "чукча писатель". Про JavaScript - есть классическое WAT видео.
С другой стороны - ну глупо предъявлять претензии bash - он 35 лет назад был придуман чтобы немного улучшить ещё более неудобные проблемы
sh, никто не знал к чему это приведёт. Длинные имена файлов с пробелами в 1989 году, хм? Какие там альтернативы были для скриптинга тогда? И программисты менее избалованные были этими вашими пробелами и отступами, так что сравнивать его с Python - авторнемногопередергивает и сам это знает.css - тоже, я подозреваю, что автор знает как исторически развивался этот
языкстандартсинтаксис и сколько разных групп в него вложило.. то что вложило.. Одни префиксы--my-feature, которые поддерживаются только одним каким-то браузером - чего стоят..Мы все погрязли в легаси, и так просто это не решить - надо всё сжечь и сделать заново. Ну, как бы, всем понятно, что этого не будет. Даже просто новый браузер написать - нужно 100 человек и 100 лет, и никто не будет им пользоваться пока он не начнёт поддерживать старые кривые сайты, которые все показывают
правильнокак хочет пользователь.И да, я проголосовал "Принятие", потому что знаю почему так, сам уже делал так и знаю что без такой ситуации обойтись сложно. Всё меняется.

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. Что нужно сделать, чтобы удержать пользователя?
Качественный UI: Сделать интерфейс на Skia более красивым и плавным, чем у современных браузеров.
Каталог контента: Встроить "магазин" или список Godot-плагинов прямо в стартовую страницу.
Reader Mode: Сделать упор на идеальный режим чтения, который переверстывает "кривые" сайты в удобный текстовый вид.
4. Главный риск
Современный CSS: LiteHTML поддерживает не все свойства Flexbox и Grid. Если верстка популярных текстовых сайтов (например, GitHub или StackOverflow) будет ломаться, пользователи уйдут.
Вердикт:
Это не замена основному браузеру, а "Второй браузер" для безопасного чтения и легких игр. Как инструмент для специфических задач или как платформа для инди-игр проект имеет высокий потенциал.

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. Итоговая логика работы
Пользователь заходит на сайт (JS выключен, работает только LiteHTML).
Нажимает "Разрешить JS".
Broker запускает NsJail + QuickJS.
QuickJS запрашивает скрипты у Broker -> выполняет их -> шлет команды на обновление в Render Process.

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 МБ (современная графика + игры + безопасность).

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 месяцев.

nomhoi
12.03.2026 08:30«Plans are nothing; planning is everything.»

Maxor1k
12.03.2026 08:30А дальше что? Или токены кончились?

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. Технологический стек реализации
Формат данных: FlatBuffers (позволяет обращаться к данным в памяти без распаковки) или Protobuf.
Конвертер (Proxy): Специальный сервис (или модуль в Брокере), который берет "грязный" HTML из сети и "выпрямляет" его в чистый бинарный BDOM.
Рендерер: Облегченная версия вашего приложения, где вместо LiteHTML стоит BDOM-Reader, вызывающий методы Skia.
4. Преимущества для бизнеса
Уникальность: Это не просто очередной браузер, а закрытая экосистема с высочайшим уровнем защиты.
Экономия: Снижение затрат на серверные мощности и трафик.
Контроль: Вы полностью контролируете, какие элементы могут быть отображены, а какие — нет.

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

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

osipov_dv
12.03.2026 08:30Наверно, для своего времени система пользователей/групп и права rwx были неплохим решением.
вообще-то еще есть setfacl

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

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

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

withkittens
12.03.2026 08:30Да тут у многих беда с хабами.
Упомянули интернет - фигачим хаб дотнета! А что, .NET, интернет - да какая разница, похоже же.
Или человек бухтит про что-то своё жизненное, упоминает как 10 лет назад юзал EntityFramework - всё, пихаем в хаб с сишарпом!

kukovik
12.03.2026 08:30Ну смотрите. Из вашей статьи стало понятно, что вы просто не понимаете/принимаете некоторых базовых для остальных людей вещей.
Баш -- развитие старого доброго sh и, например, && в нем появился как ответ на потребности, а & был давно и запускает процесс в фоне потому, что И. Запускает a & b & c одновременно.
Или апострофы. Не секрет, что в строка в апострофах суть гранит, а строка в двойных кавычках может содержать внутри переменные. Это не только в баше так, заметим.
Или имя переменной в кавычках. Тут, скорее всего, поведение по умолчанию вызвано необходимой совместимостью со старыми добрыми скриптами.
Ну а \; для -exec в find вообще не проблема. На самом деле там нет никакого \. Просто надо донести это до файнда так, чтобы баш не воспринял это как разделитель команд в строке. Можно было бы написать ';', например. А почему именно этот символ? Да потому, что он является устоявшимся завершением строки с командой.
Вы, в общем, чаще задавайте себе вопросы "зачем" и "почему" и всякая вот эта как бы магическая чушь легче осядет в голове и не будет вызывать страданий.
PS. Вот, кстати, фейлы (игнорирование) при пайпах меня тоже как-то сильно удивили. Очень уж неинтуитивно.

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

funca
12.03.2026 08:30Вот, кстати, фейлы (игнорирование) при пайпах меня тоже как-то сильно удивили. Очень уж неинтуитивно.
Потому, что например в `cat file | head`, этот head как только получает достаточно данных, закрывает входной поток, а cat получает SIGPIPE и умирает с ошибкой. Это эффективно, т.к. файл не надо читать до коца. Но если пайп не проигнорит ошибку от cat, то с ошибкой завершится и вся эта конструкция, что будет конфузить.

Dr_Faksov
12.03.2026 08:30Значительнаячасть времени программиста уходит на то, чтобы найти работающий способ получения описанного в документации результата, поскольку описанный в документации способ получения этого результата - не работает.И начинать, наверное, надо с "железа", которое в "в металле" не соответствует описанию. И драйвера, написанные по описанию, не позволяют делать то , что должны. И программы, опирающиеся на драйвера... И не опирающиеся. К примеру, CAD для проектирования чипов, который не создает "железа" идентичного эмулятору. В связи с чем чип, воплощённый "в металле" не соответствует описанию.... :)
Вообще, такое впечатление, что вся IT - индустрия гадит друг дружке в штаны :) Из лучших побуждений, чтобы облегчить жизнь, естественно :)

Fil
12.03.2026 08:30if должно закрываться fi. Значит, for должно закрываться rof, а while - elihw? А вот и нет, только if-fi. В остальных done
Еще есть case-esac

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

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

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

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

withkittens
12.03.2026 08:30А что не так с pwsh?

Habr4687544
12.03.2026 08:30
ahabreader
12.03.2026 08:30Это всё равно движение в верном направлении по сравнению с прошлыми шеллами и критики по делу там немного.
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'Дальше обсасывается поведение
Write-Host(неWrite-Outputakaecho(полезное: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.

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 лет).

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

feelamee
12.03.2026 08:30FILES="file1 file2" rm $FILESсмотрите, а теперь экранировать не нужно)
самый очевидный практический пример - флаги компилятору.А вообще, тут претензия должна быть не к экранированию, а к тому что в баше все есть строки. Будь там тип Path, то проблемы не было бы

ReadOnlySadUser
12.03.2026 08:30самый очевидный практический пример - флаги компилятору.
Не делайте так! Не надо! А bash есть массивы, используйте их!
flags=( "--flag-one" "--flag=value with spaces" ) gcc "${flags[@]}"Эту буквально единственный вменяемый способ передачи флагов. Всё остальное - отлукавого

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

ReadOnlySadUser
12.03.2026 08:30За последние 20 лет я не видел ни одного реального окружения, где бы отсутствовал bash. Буквально ни одного случая.
Да меня всякий шелл - это bash, а там где не bash - всегда можно вызвать `bash - c`

oficsu
12.03.2026 08:30Прямо сейчас у меня в initramfs нет баша, вообще. И очень вряд ли, что у вас иначе. А шелл-скрипты там есть и бывают в немалом количестве. У меня там ash, вроде
А ещё в дебиане вызовется dash, если ваш шебанг вдруг окажется так весьма популярным
#!/bin/shвместо#!/bin/bashА ещё нередко встречается busybox в эмбеде, там тоже ash
А ещё нередко в прод ставят контейнеры, в которых до предела минимизируют окружение. И если там и нужен шелл, то им тоже будет не баш
А иногда даже бывает нужна совместимость с zsh, в котором есть массивы, но доступ в которых работает иначе. Это, например, важно для скриптов автокомплита. Там вариант с
bash -cнесколько затруднителена там где не bash - всегда можно вызвать
bash - cЭто не всегда решает автор скрипта

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, но если мне придётся там много скриптовать - я просто его туда упакую.

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

mayorovp
12.03.2026 08:30В таких контейнерах никогда шелл-скрипты и не запускают. В них упаковывают одну программу на каком-нибудь go и её же и запускают.
Иногда этой программе нужно подготовить окружение, и тогда используется скрипт в качестве entrypoint.

oficsu
12.03.2026 08:30Вообще, по секрету, в баше есть ещё один вариант (или, скорее, их семейство):
x="--flag-one" y="--flag=value with spaces" script -c "gcc ${x@Q} ${y@Q}"И иногда это единственный доступный вариант, если утилита хочет шелл-команду одной цельной строчкой. Как
script, например
ReadOnlySadUser
12.03.2026 08:30Q-синтаксис работает и с массивами. Примерно так
flags=( "--flag-one" "--flag=value with spaces" ) script -c "gcc $(echo "${flags[@]@Q}")"Сработает как надо, но синтаксис получается немножко инопланетный, да

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


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

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

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

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

nicelight_nsk
12.03.2026 08:30К сожалению или к счастью, большинство этой боли/проблем не будет касаться разработчиков, так как отвечать за генерацию кода уже должен не человек.
Мы постепенно переходим в эру программирования спецификаций и документации

DmitryOlkhovoi
12.03.2026 08:30Javascript придумал отображать объекты
[object Object], и, честно говоря, я бы и сам лучше не придумал!ну это .toString() :)

ImagineTables
12.03.2026 08:30В C макросы — это тоже тупая подстановка
В этом их сила. Меньше всего я бы хотел, чтобы препроцессор удивлял меня могучим интеллектом.

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

ImagineTables
12.03.2026 08:30Зато пока есть
#define, который тупо подставляет текст, есть и#include, который тупо подставляет текст. А пока есть#include, будут и файлы. Сравните с тем ужасом, который натворили в ECMAScript с модулями .mjs, которые не могут быть локальными.Как бы, C и Unix — близнецы-братья, а в Unix файлы — это всё (или, как они говорят, «всё есть файлы»). Дух общий. Жаль, что с ними он и умрёт. А мы будем пожизненно платить за подписку на dev-эккаунты, чтобы разместить файлы в облаке, иначе эти паразиты откажутся их подключать. К этому всё, боюсь, идёт.

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

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

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

LeoLev
12.03.2026 08:30А ну и еще, пока помню, внесу свои 5 копеек
В js все удобно
[1,2,3].join("")
А на питоне решили что так слишком легко:
"".join([1, 2, 3])
Почему??? За что???

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

randomsimplenumber
12.03.2026 08:30TypeError: sequence item 0: expected str instance, int found
В смысле с любым?

DmitryOlkhovoi
12.03.2026 08:30Ну довольно трудно представить использование join с чем-то другим. Но есть мета программирование через symbol.iterable. А так же можно делать типо такого:
let obj = { 0: 'a', 1: 'b', length: 2 }; let result = Array.prototype.join.call(obj, '-');

Akon32
12.03.2026 08:30В python не всё так логично, как декларируется, взять ту же конструкцию "a if cond else b". На scala это "if (cond) a else b" - это легче читается имхо.
А если нырять в многопоточность в python, можно и не вынырнуть - масса edge case, которые не описаны в документации, а когда они проявляются, и ищешь решение, в стандартной библиотеке находится "ещё один способ сделать это", немного устаревший, но рабочий, в отличие от нового. Декларируется при этом, что "есть только 1 способ сделать".

oficsu
12.03.2026 08:30Переменная окружения и значение по умолчанию: ${ENV_VAR:-10}. Нет, значение по умолчанию не -10, а 10! Зачем там дефис? Я хз
Загадка может внезапно проясниться, если заглянуть в документацию и увидеть, что там бывает не только
-, но и+, и даже=с?

oficsu
12.03.2026 08:30есть короткие флаги -g и -G, -M и -m, -p и -P, которые непонятно зачем вообще есть
Затем, что любой шелл – это в первую очередь шелл, а не язык программирования. Приоритеты шелла склоняются в сторону удобства интерактивного набора и возможности выразить собственную мысль командой так быстро, как только можно. Это позволяет дольше сохранять концентрацию внимания, не забивая мозг вторичной рутиной вроде правильного написания или перечитывания длинных имён, которые вполне возможно уже не влезли в строку

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

oficsu
12.03.2026 08:30Какого вообще чёрта --create-home и --no-create-home существуют одновременно, ведь если есть поведение по умолчанию, то нужен только противоположный флаг?
Во-первых, бывают нетривиальные алиасы и функции с сильно недефолтным поведением. Иногда переопределить недефолт дефолтом может быть проще, чем отказаться от алиаса
Во-вторых, такие флаги могут явно, чётко и однозначно выражать намерение – "да, именно это я и хочу сделать"
Тем более, что запоминать флаги иногда проще, чем дефолты

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

oficsu
12.03.2026 08:30Именованные аргументы всегда лучше, потому что они явные и не ломаются при рефакторинге
Это не так. Иногда у аргументов бывает натуральный порядок. Ну, например, вот:
divide(numerator = 42, denominator = 123). Илиjoin(first = root, second = subdirectory, ??? = filename). Кстати, да,first,second,... А до скольки нам так имена резервировать?Кроме того, иногда аргументы выводимы из контекста, а перепутать их нельзя
Но что важнее, вы назвали преимущества именованных аргументов. Но умолчали их проблему. А проблема есть и она видна в первом примере. Они генерируют стену текста там, где и без них всё ясно
А у стен текста есть объективная проблема. Их труднее парсить глазами, труднее даётся визуальная навигация по коду. Это всё приводит к более быстрому истощению внимания разработчика. Быстрее истощается внимание – больше ошибок за 8 рабочих часов. Либо просто ежедневно работаем меньше – пишем меньше фич

mayorovp
12.03.2026 08:30Стена текста - это проблема того, кто код пишет, и если он готов с этой проблемой мириться - с какого вообще перепугу мы ему что-то запрещаем?
Вот с join и правда хорошее возражение.
Ещё там в PEP была аргументация про прямую совместимость и про LSP.
oficsu
12.03.2026 08:30Стена текста - это проблема того, кто код пишет
В первую очередь – того, кто читает код. Потому что чем больше стена, тем труднее верно оценить её (во всех смыслах). Страдает не только процесс разработки, но и ревью, и аудита. Да даже банально будут страдать коллеги, которые работают с тобой в общей кодовой базе

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нет задания значения, а потому это особый случай, который тоже интуитивно понятен большинству
VADemon
12.03.2026 08:30Во-вторых, первое работает по той причине, что yaml ... Его парсер, вероятно, не позволяет отличить
YAML настолько Yet Another, что на его хитрую задницу понадобился простой
советскийTOML. Прям в названии: "Tom's Obvious, Minimal Language (originally Tom's Own Markup Language)"

omaxx
12.03.2026 08:30И потому эти две записи не могут давать разный результат. Либо обе дают пустую строку, либо обе форвардят значение
Нет, у этих двух строк разное значение
{ KEY: }эквивалентно{ KEY: null }, и это не тоже самое, что""
mayorovp
12.03.2026 08:30Здесь уже особенность go, у строки не может быть nil-значения, потому оно превращается в пустую строку.

oficsu
12.03.2026 08:30А ведь если бы SQL разделял запрос и данные
(SELECT %s FROM %s, (name, age), users)и делал бы подстановку именно значений, а не текста, то не было бы инъекцийВы не поверите, но в современном мире это так и работает. Движки баз данных знают, что такое биндинг параметров и поддерживают его. И современные ORM под капотом это используют

oficsu
12.03.2026 08:30Дополнительно в C++ добавили... Нет, не так. В C++ СПЕЦИАЛЬНО добавили случаи, при которых происходит хрен знает что. Не программа падает, нет. ХРЕН ЗНАЕТ ЧТО происходит. Undefined behavior. Собственно, на что был расчёт?
А как автор объяснит наличие таких специальных случаев в так называемом "нормальном пистолете"? Там аж целое специальное ключевое слово придумали, чтобы разрешить компилятору собирать такие специальные случаи

oficsu
12.03.2026 08:30Сделайте фигурную скобку { обязательной после if — никто почти и не заметит, но эта ошибка умрёт навсегда
А ничего, что статические анализаторы в современном мире и так подчёркивают жёлтым ошибочные конструкции? Нет, действительно, давайте уж вместо этого писать такие полотна, убивающие early return:
if (not valid(foo)) { return error("don't do this"); }Ну а кому не будет приятно увидеть такой шедевр искусства в своей кодовой базе?

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

ahabreader
12.03.2026 08:30Так откуда анализатору знать
https://godbolt.org/z/dYd5xnbK5
Но вахтёрствовать и запрещать всё, конечно, легче/приятнее.

oficsu
12.03.2026 08:30Речь про этот пример из статьи:
if (condition) do1(); do2();Анализатор прекрасно видит несоответствие отступов отсутствию фигурных скобок. Он это безошибочно подчеркнёт как misleading indentation
А первый пример банально нереалистичен и рассматривать его нет никакого смысла. Потому что стейтменты обычно длинные и в одну строчку, да ещё и с ифом обычно не влезают – не пройдут ревью
Я вот лично попадал в другую нелепую ситуацию:
if (foo) { } else if (bar) { } else if (baz) { } { // потерял else }Что предложите ещё запретить, чтобы решить ещё и эту проблему? Или, может, всё же, запреты это не панацея?

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Ах да, не забудьте в контейнере поставить само приложение и шрифты к нему, конечно. Это тоже нетривиальное знание, знаете ли

oficsu
12.03.2026 08:30Ещё есть пакеты Flatpak, которые изолируют приложение, но вообще-то могут быть созданы кем угодно
Так и deb-пакеты может кто угодно. Суть не в сборке, а в репозитории и мейнтейнерах. Не доверяете Flathub? У RedHat есть свой репозиторий flatpak-пакетов от их мейнтейнеров

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

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

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 подаётся, как само собой разумеющееся

Assortie
12.03.2026 08:30А давайте расширим контекст этой захватывающей (честно! получил массу удовольствия!) статьи за рамки компьютера в реальный мир!
Постебемся над велосипедами, ножами, топорами, штопорами!
Wesha
12.03.2026 08:30С радостью готов постебаться над моим, @#%, велосипедом. И какой китайский дыбил его проектировал?

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

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]{"",""}загадка.
Зачем было по-умолчанию реализовывать удаление именно завершающих пустых строк из результирующего массива? Как будто нужно либо удалять все пустые строки, либо не трогать вообще.


bfDeveloper
Тон статьи такой, как будто все эти примеры сделаны специально, чтобы лично вы страдали. Куча примера на баше, хотя он страше доброй половины читателей, как и C с C++, и с тех пор многое поменялось. Выглядит как предъява паправозу, что он дымит и не использует электричество. Делай хорошо, а плохо не делай. Будь Вангой. Спасибо, кэп.
malkovsky
Мне кажется, что посыл как раз в том, что сейчас мы можем посмотреть на все это безобразие и осознать, что многого из этого можно было избежать. Мы видим "прекрасные" наглядные примеры того, к чему приводят кривоватости в фундаменте и как сложно его потом менять. Понятно, что ничего из перечисленного в баше не изменится, но смотря на это можно хотя бы не допускать подобного для новых технологий
saege5b
ОЛДы писали на баше без экранирования :) и всё замечательно работало, потому, что не было в именах файлов пробелов, и имена были предсказуемого короткие с короткими расширениями.
Это не фундамент кривой, это смена архитектурных планов в процессе строительства (типа как на квадратный первый этаж, продолжили строить овальный второй, плавно переходящий в круг к этажу к четвёртому). Потом рядом прикопали раз парковку, потом двухуровневую парковку. Выпихнули между ними сауну; потом объединили парковки... Оказалось что ходить в баню через парковку не очень удобно, поэтому сделали прямой выход. Но пока делали, потребовалась детская игровая, поэтому проход в сауну начинается в детской. Потом приложили проход из спален, но они к тому моменту переехали и там - гостиная.
А новые технологии основаны на либерализме, и даже у железячников куча всего "определяется производителем". Ну или авторы поклонники какой-либо школы.
Yuuri
Ну вот почти полвека прошло, а мы до сих пор вынуждены гонять паровозы.