Раздувающийся код: от телефонии к биткойну

sirerПрофессор Корнелльского университета и исследователь криптовалют Эмин Гюн Сирер (Emin Gün Sirer) рассуждает о причинах разрастания программных проектов и неочевидных долгосрочных последствиях реализации segregated witness для биткойна.

Каждый программист знаком с раздуванием программ. Оно везде: корпоративное ПО, вынуждающее предприятие изменять свои процессы («почему у курсов в Корнелле четырёхзначные номера?»), финансовое ПО (кроме высокочастотного трейдинга), Javascript-фреймворки (несмотря на попытки переиспользования left-pad), веб-бэкенды (привет, Django), реляционные базы данных, операционные системы, USB-драйверы, браузеры, плагины к браузерам, программы для просмотра PDF, оказывающиеся издательскими системами, мобильные приложения, можете продолжить список.

Однако маловероятно, что команды разработчиков прикрепляют к своим Scrum-доскам стикеры типа «добавить бесполезный код». И вряд ли внедрённые вражеские секретные агенты отправляют злонамеренные пулл-реквесты в open-source-проекты (когда спецслужбам надо добавить бэкдор, они делают это, модицифируя предыдущий бэкдор — код не раздувается!). Так как же программы разрастаются? Кто за этим стоит? Чья это вина?

Кто не виноват

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

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

Кто же тогда виноват?

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

История о расширении struct

Возможно, лучший пример раздувания ПО встретился мне во времена работы в крупном телекоме, где работала гигантская автоматическая телефонная станция. Она была действительно громадной и легко могла бы обслуживать миллионный город. В её основе лежала ОС UNIX, но сама ОС показалась бы вам детскими игрушками в сравнении с реализацией протокола передачи сигналов, включавшей, по слухам, около 15 миллионов строк кода.

Представьте, что вы обслуживаете массив кода такого размера, и вам нужно добавить поле в структуру. Например, требуется добавить поле в структуру call-data-record (CDR), чтобы показывать, принадлежит ли вызываемый номер к списку друзей и семьи. Логично было бы дополнить определение структуры и вписать туда: «uint is_friend_fam:1;». Это бы добавило в структуру дополнительный бит, с которым можно было бы делать всё что угодно.

Но когда у вас столько кода, вы не можете просто увеличить размер структуры. Тем более, если время простоя системы огранчено двумя часами за 40 лет, а неуклюжий техник, в 1987 году менявший систему питания и нажавший не на ту кнопку, уже потратил половину этого бюджета. Изменить размер структуры нельзя: изменятся способ размещения структур в памяти и размер выделяемой дополнительной памяти. Что будет, если код ошибочно запишет данные вне границ структуры? Последствия непредсказуемы, но ничего хорошего вас точно не ждет.

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

Подумали? Теперь проверьте, похоже ли ваше решение на следующую идею.

bloat1

Итак, вы открываете определение структурного типа. Ищете поле, которое кажется наименее значимым и наименее используемым. Убеждаетесь, что ниже вашего уровня в стеке вызовов оно не используется. Допустим, структура содержит нечто под именем «uint inap_ain23», и оно используется только выше вашего уровня. Вы понятия не имеете, что значит inap_ain23 и что оно делает. Вы сохраняете значение inap_ain23, когда управление проходит через ваш уровень. Теперь выше вашего уровня inap_ain23 не существует: вы «переиспользовали» это поле. Теперь это is_friend_fam. Можете даже для пущего удобства объявить псевдоним: «#define is_friend_fam inap_ain23». Плюс к тому, вы получили несколько дополнительных бит! Бонус!

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

Было плохо, стало еще хуже

Решение не особенно элегантно, но оно хуже, чем вы думаете. Невозможно проконтролировать все пути передачи управления на верхние уровни стека. Кто-то обязательно промахнется и оставит бит «друзья и семья» там, где должна была быть контрольная информация для базы данных, что вызовет серьезный сбой. Поэтому инженеры создали процесс, который проходил по структурам данных (на работающей системе!) и проверял инварианты вроде «поле inap_ain23 должно содержать номер порта, если старший бит равен 1». Если обнаруживалось несоответствие, процесс пытался починить структуру данных, чтобы избежать сбоя. Повторю: они пытались угадать, что бы это поле могло содержать, и бездумно подставляли угаданное значение.

И еще хуже

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

Но это была еще не самая запутанная часть истории. Слушайте дальше.

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

bloat2

В масштабе системы, ничто не сохраняло свой первоначальный смысл.

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

Как вы думаете, какие поля гениальные инженеры использовали в качестве «наперстков»? Конечно, те, которые кажутся не настолько важными, например, то же поле inap_ani23. Поскольку умные коллеги независимо переиспользовали одни и те же «редко используемые» поля, они в итоге и становились самыми используемыми.

Такого же сейчас обычно не происходит?

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

По сути, разработчики биткойна предложили умный трюк: переиспользовать транзакции типа «каждый может потратить» для реализации механизма отделенного свидетеля (segregated witness, segwit). Для старых версий биткойна это выглядит так, будто кто-то в буквальном смысле сорит деньгами: отправляет из так, что каждый может их забрать. Новые же версии ПО гарантируют, что забрать их может только тот, кто предоставит цифровую подпись необходимого типа, которая хранится и проверятся независимо. Для старого ПО ситуация выглядит так, как будто кто-то выбросил деньги, а кто-то другой их подобрал, тогда как новые версии с самого начала знали, что подобрать их мог только тот, кому они изначально предназначались. Как ни крути, это очень умная идея, и я испытываю глубокое уважение к ее авторам. Я почти готов признать это отличным решением, но мне не дает покоя чувство дежа-вю: это же и есть переназначение полей в структурах телефонной станции! Только речь идет о разных версиях ПО, а не о разных уровнях стека протоколов.

Цена сложности

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

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

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

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

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

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

Автор: Emin Gün Sirer
Источник: Hacking, Distributed



Categories: Разработчикам, Сообщество, Технологии, Футурология

14 replies

  1. Автор периодически публикует подобные заумно-академические статьи, наводящие тень на плетень по высосанному из пальца поводу. Картель “алчных майнеров”, который якобы погубит биткойн – это тоже его идея, кстати сказать.

  2. Пример статьи, как писать не нужно, чтобы тебя дочитали до конца или читали хотя бы кусками. На какую мысль наталкивает автор? Типа он где-то уже это видел, дежавю, когда речь идет о сегвит.
    1. В коде не миллионы строк, как в случае с АТС.
    2. К любому коду, связаному с битком имеют доступ множество кодеров и каждый видит этот код со своей колокольни.
    3. Существует возможность тестовой версии. В его АТС это нереально.
    Можно продолжать еще.

  3. вот она, боль и исповедь IT-шника 🙂
    таких костылей и подпорок полно в любой мало-мальски развивающейся системе, и не только софтовой, почитать хотя бы историю развития x86, как там только не извращались, чтобы обеспечить совместимость с более ранними версиями

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

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

      Разработчики – далеко не всегда тупые прагматики, бегущие за рублём. Их, как и настящих художников, могут привлекать красивые, элегантные решения. Например – кольцевые подписи, реализованные в Cryptonote или системы с нулевым доказательством ( в битновостях об этом было – потерял ссылку)

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

      Я не враг и не противник битка, но чтобы дать ему возможность выжить – нужно очень реалистично и объективно оценить его возможности. Все эти бесталанные “улучшатели” могут его попросту убить.

      У битка есть естественное преимущество – безусловный авторитет. И надо задумываться о том, как это сохранить, а не только тупо воевать со всеми подряд.

      • тут есть очень тонкий момент

        красивая и элегантная система не всегда оказывается коммерчески успешной – парадокс с точки зрения программиста, который смотрит на нее “изнутри”

        мое непрофессиональное имхо – в условиях сжатых сроков, SegWit – неплохое решение для масштабируемости

        • Отлично! Но тут и собака зарыта. Выбор – коммерция или элегантность. Кто-то выберет одно, а кто-то другое. В любом случае – отток разработчиков. Не все прагматики, есть и художники.

          Многие предпочтут элегантность

        • IMHO Художники чаще более талантливы

  4. Гениально

    В топку segregated witness!

    • критикуешь – предлагай

      • Bip101, только начать с 2 мбайт

        Вместо блоков передавать хеши, сами транзакции брать из мемпула. Задекларировать 5-10 способов сортировки транзакций.

        Добавить опцию чтобы можно было хранить локально не весь блокчейн, а 1/64 срез. Для 6000 узлов будет 100 кратное дублирование каждого блока.

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

          чем эти костыли лучше (с точки зрения простоты сопровождения системы) костылей SegWit ? 🙂

          • Это не костыли.

            Всё очень просто.

            Хочешь хранить весь блокчейн – храни.

            Хочешь – оставь 1/64

            Это масштабирование до 7000 транзакций в секунду в течении 20 лет.

      • Предлагаю – создание децентрализованных систем обмена криптовалют, аки возможное безболезненное отступление с позиций битка без потери всех преимуществ децентрализации

  5. “К счастью, сложность и избыточность кода саморегулируются: после достижения предельных значений люди просто оставляют раздутый проект и переходят на более лаконичные и элегантные платформы.”
    !!!
    По другому можно сказать, что все эти бесконечные “улучшения”, какими бы замечательными они ни были, и в малейшей степени не были заложены в дизайн изначальной структуры.
    Поэтому, с точки зрения нынешнего состояния, из-за бесконечных “наслоений” получается что-то не слишком эстетичное и весьма распухшее. Это отпугивает и разработчиков и пользователей. Сроки новых разработок имеют тенденцию к росту.

Поделитесь своими мыслями