Обмен электронных валют по самому выгодному курсу!
 


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

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




[vkontakte] [facebook] [twitter] [odnoklassniki] [mail.ru] [livejournal]

Каталог сайтов