...

пятница, 5 ноября 2021 г.

[Перевод] Три ужасные фичи программирования из прошлого


Я верю в программистское клише о том, что большинство плохих фич имеет причины для существования. Ненавидимый многими оператор goto позволяет быстро и удобно выбраться из глубоко вложенной структуры, если пользоваться им с умом. Определённая степень нестрогости типов позволяет им быть более изящными. Указатели памяти могут заставить вас возненавидеть свою жизнь, но они были критически важны в те годы, когда компьютерное «железо» было слабее современного умного термостата. Список можно продолжать.

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


On Error Resume Next реализует подход, который в большинстве ситуаций возникновения ошибок хуже всего: «просто продолжай выполнение». При этом он делает его правилом по умолчанию для любой проблемы. Представьте, если бы мы реагировали так же на сбои в реальной жизни. Попал в аварию на машине? Просто продолжай ехать!
' Напишем этот код на случай, если не сможем
' подключиться к базе данных.
On Error Resume Next 
Dim TotalPayment As Decimal
Dim TotalHours As Decimal
Dim HourlyRate As Decimal
TotalPayment = 5000
' Ой, мы забыли задать TotalHours!
' Код не должен работать, но у нас есть иммунитет к ошибкам.
HourlyRate = TotalPayment / TotalHours 
' Всё вроде правильно. Давайте обновим базу данных!
Call UpdateDatabase(HourlyRate)

Но подождите, это ещё не всё. On Error Resume Next не имеет никаких границ. Он будет продолжать свой путь через длинные цепочки ошибок. Он даже не обязан находиться в начале модуля вашего кода (можно вставить его в любое место кода и переключиться на обычный режим при помощи On Error Resume 0).

On Error Resume Next гарантирует, что вы достигнете конца процедуры кода, и это соблазняет нас своей определённостью. Вам не нужно беспокоиться, что будет пропущена какая-то важная часть кода очистки. Но в пункт назначения вы доберётесь не совсем в том виде, в котором начинали путь. Это агрессивная стратегия «дойти до конца любой ценой», только «доходить до конца», если тебе не удалось выполнить обязательную задачу, обычно и не стоит.


Единственная возможная причина, по которой можно использовать On Error Resume Next — это полное отсутствие кода обработки ошибок, когда вам нужно пропустить все не особо важные проблемы, которые вы игнорируете. Например, вы можете осознанно создавать файл, который уже может существовать, настраивать принтер, который может не поддерживаться, или выполнять вычисление, которое может привести к делению на ноль. Объективно, в такой момент вы поступаете не очень хорошо, но мы вас прощаем. Однако если вы используете On Error Resume Next, то усугубляете своё невежество, беззаботно двигаясь в сторону ещё большей катастрофы.

Не буду врать. Я пользовался этой конструкцией (и она мне нравилась!) задолго до того, как начал учиться программированию по-настоящему. А теперь она практически мертва. VB.NET, текущая версия VB, которая влачит жалкое существование в мире .NET, заменила On Error Resume Next логичной структурированной обработкой ошибок. Хотя так ли это на самом деле? Оказывается, в современном .NET можно по-прежнему использовать обработку ошибок из созданного десятки лет назад классического VB, в том числе и On Error Resume Next. Это полное безумие, но, к счастью, кажется, сегодня этим никто не занимается.

Тем не менее, On Error Resume Next всё ещё существует в реальном коде. Возможно, вы обнаружите, что эта конструкция ломает макрос электронной таблицы Excel, которую использует ваш отдел продаж. Спасибо тебе за это, VBA.


Даже по расплывчатому имени встроенной функции DoEvents() уже можно понять, что стоит готовиться к чему-то уродливому. И метод Application.DoEvents() нас не разочаровал.

DoEvents() — это когда-то популярный, но очень опасный костыль для Windows-приложений, не желающих работать с многопоточностью. Например, классическим сценарием его использования является обновление UI в коротком цикле. Представим, что мы выполняем очень затратную по времени математическую операцию и помещаем значения в list box. Пользователь не увидит ни одно из этих чисел, пока вы не вернёте управление и Windows не сможет обновить окно. Обычно этого не случается, пока код не завершит работу, если вы не выполняете эту работу в фоне в отдельном потоке.

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

for (int i = 0; i < 1000000; i++) 
{
    listBox.Items.Add(someCalculation(i));
    // Этот цикл блокирует UI. Мы можем отрефакторить код
    // или просто вставить этот маленький костыль...
    Application.DoEvents();
}

Но после этого ситуация быстро начинает развиваться по наклонной. Проблема в том, что ты точно не знаешь, что будет делать DoEvents(). Другие части приложения могут получать сообщения Windows (допустим, если пользователь куда-то нажал), и могут начать выполнять свой код в месте, которое ты создал при вызове DoEvents(). Звучит печально, но на самом деле это очень весело (если вам нравится отладка по ночам), потому что результат на каждом компьютере будет чуть-чуть различаться!

Как знает любой программист, самая худшая проблема — это не когда что-то не работает, а когда программа работает на одном компьютере, но обладает такой вариативностью, что в другом месте способна загадочным образом вылетать. И если у вас нет пока этой проблемы, DoEvents() вносит как раз нужное количество недетерминированности, чтобы она появилась.

Разумеется, работать с потоками сложно. Но в .NET уже есть простые в использовании инструменты, например, компонент BackgroundWorker, позволяющие выполнять всю нужную работу в другом потоке при помощи простой модели на основе событий. Нет необходимости придумывать какие-то костыли с помощью DoEvents(), и их создают очень немногие. Тем не менее, этот метод по-прежнему таится в библиотеке классов, импортированный из олдскульного VB и доступный для каждого языка .NET, в том числе и для современного C#.

Отвратительно.


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

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

Фича, которую я называю динамическим goto, имеет в языке COBOL имя оператора ALTER. Он позволяет изменять существующую команду GO TO так, чтобы она передавала контроль не той части программы, которая прописана в коде. Оператор GO TO даже можно изменять многократно, перемещать в разные части кода, как по лабиринту. Проще говоря, ALTER берёт всё то, что вы ненавидите в GO TO — запутывающий поток выполнения, спагетти-код — и значительно это усиливает.

Вот о какой структуре я говорю

IF WS-X = 2 THEN         
     ALTER SELECT-PATH TO PROCEED TO PATH-2.
ELSE IF WS-X = 3 THEN
     ALTER SELECT-PATH TO PROCEED TO PATH-3.
END-IF.
GO TO SELECT-PATH.
SELECT-PATH.
     GO TO PATH-1.

В этом примере команда GO TO SELECT-PATH может перенести нас к метке SELECT-PATH (которая затем направляет нас к PATH-1). Но в зависимости от предыдущей проверки переменной её можно изменить так, чтобы она перенаправляла нас к PATH-2 или к PATH-3. И кто знает, что могло бы произойти в других частях кода. Другими словами, мы не можем точно сказать, куда приведёт нас GO TO, если только не знаем полную историю выполнения приложения. Но не волнуйтесь, наверняка где-то есть лог, который можно изучить.

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


Да, на маломощных компьютерах того времени с небольшим размером стека подобная чёрная магия имела смысл (возможно?). И может быть, были времена, когда On Error Resume Next и DoEvents() были наилучшими компромиссами в случае сложных проблем. Сегодня, спустя шестьдесят с лишним лет после создания языка, оператор ALTER считается устаревшим, и запланировал к удалению. Что ж, по крайней мере, они решили исправить ошибку.

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

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

Adblock test (Why?)

Комментариев нет:

Отправить комментарий