LINUX.ORG.RU

COMEFROM считается вредным?

 


0

1

Когда Дейкстра написал своё письмо «Оператор GOTO считается вредным», в качестве шутки придумали ещё более вредный оператор COMEFROM (и ассемблерную инструкцию CMFRM). Он позволял указать метку и выполнять дополнительный код всегда, когда выполнение проходило через эту метку.

Но вот смотрю я на нынешние методологии программирования и вижу огромный пласт COMEFROM в SQL в виде CREATE TRIGGER, подписку на события в других языках (паттерн Наблюдатель). Многомерный COMEFROM в HTML через CSS.

Неужели это считается удобным для понимания и отладки?

★★★★★
Ответ на: комментарий от monk

С чего это? Там где был COMEFROM метки были глобальны.

Ты писал что-то похожее на си, ну да ладно, не суть.

выполняется

COMEFROM не выполняется, это не команда, а директива компилятору «поставить goto сюда после указанной метки». То есть это по сути всего лишь метка, на которую надо перейти.

f(x)
{
10: y = x + 1;
20: z = y * 2;
    goto 50;
30: return z;
}

g(y)
{
40: if(y == 42) {
50:    ;
60: }
70: return 0;
}
firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

COMEFROM не выполняется, это не команда, а директива компилятору

С чего это? Напомню, что в оригинальной шутке это ещё и инструкция ассемблера была. Или вот реализация на Ruby:

$come_from_labels = {}

def label(l)
  if $come_from_labels[l]
    $come_from_labels[l].call
  end
end

def come_from(l)
  callcc do |block|
    $come_from_labels[l] = block
  end
end

Обычная функция.

monk ★★★★★
() автор топика
Ответ на: комментарий от firkax

Это всё же не штатное использование CSS.

В том-то и дело, что почти штатное. Современные сайты, какой ни возьми, скопировать блок в другой сайт — адская работа: половина картинок в CSS, половина текста в CSS. Причём передвижение блока в другое место сносит все эти настройки. Выдрать из пачки стилевых файлов настройки для конкретного блока тоже почти нереально.

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

Этим по-хорошему должна IDE заниматься.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

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

Первоисточник этой идеи тут: https://web.archive.org/web/20180716171336/http://www.fortran.com/fortran/com...

Во-первых, там речь про фортран. Метки в нём не глобальные, как ты пытался заявить.

Во-вторых, работает он именно так, как писал я, за одним исключением. Там действительно может быть сопутствующий if, но этот if, так же как и comefrom, НЕ выполняется в той строчке где он написан. Выполняется он в строчке той метки, которая аргумент comefrom, и при успехе делается goto.

Опустив неудобный фортрановский синтаксис, ситуация такая:

50:    qwe
..............
       if(i==1) comefrom 50
       asd
эквивалентно такому
       qwe
       if(i==1) goto 50a
..............
50a:   asd

Comefrom это не «установить хук когда код дойдёт до comefrom», это именно «указание компилятору вставить goto, возможно условное, в нужную строчку». Условное тут - goto, а не его вставка. Вставка делается всегда.

А реализация на ruby не соответствует спецификации. Соответствующую спецификации сделать, наверно, невозможно.

В-третьих, там в конце («box 2») прямым текстом говорится что это аналог цикла с меткой на конце, про который я выше писал, а этот самый цикл с меткой широко использовался фортран-программистами десятилетиями и ничего странного они в нём не видели.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 4)
Ответ на: комментарий от monk

Этим по-хорошему должна IDE заниматься.

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

firkax ★★★★★
()
Ответ на: комментарий от firkax

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

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

monk ★★★★★
() автор топика
Ответ на: комментарий от firkax

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

Текст первоисточника вот: https://web.archive.org/web/20160313141741/http://homepage.ntlworld.com/brook.street/funny/pdp11.htm

https://forum.osdev.org/viewtopic.php?f=13&t=26083

Фортран был чуть позже.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Да, конкретно это - про любое. Но в С++ там двусмысленность, тут двусмысленность, где-то поможет IDE (которая не всем удобна), где-то не поможет, в итоге часто получается лапша где сложно разобраться в потоке выполнения кода.

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

firkax ★★★★★
()
Ответ на: комментарий от monk

Там не COMEFROM а CMFRM и нет описания что она делает. И оно не было ответом на письмо, про которое ты писал.

Когда Дейкстра написал своё письмо «Оператор GOTO считается вредным», в качестве шутки придумали ещё более вредный оператор COMEFROM

Ответ на письмо - именно то что я привёл.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Во-первых, там речь про фортран. Метки в нём не глобальные, как ты пытался заявить.

Именно фортрановский вариант вполне адекватен. Хотя там тоже бывает COMEFROM в широком смысле в виде команды AT (если её использовать не исключительно для отладки).

monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от firkax

Согласен. Моё изначальное сообщение очень нечёткое получилось. Претензии у меня по сути к перечисленному во втором абзаце, но как правильно назвать, не придумал.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

всегда что-то зависит

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

Один человек

Другой человек

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

пока из общее количество больше 42. У него всё работает

Эталонный говнокод из палаты мер и весов.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

Так внутри СУБД структура таблиц и есть интерфейс. Я понимаю, когда внешним приложениям можно пачку хранимок и представлений вместо таблиц выдать, но при написании хранимок интерфейсом будут как раз таблицы.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

внутри СУБД структура таблиц и есть интерфейс

Это интерфейс СУБД. Приложение должно реализовать собственную абстракцию данных поверх СУБД (или чего-то ещё) так что бизнес логика не должна знать что там снизу происходит и есть ли там вообще СУБД, или оно магически само сохраняется.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

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

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

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

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

В лиспе, кстати, аналогичная проблема с методами before/after/around. Либо разработчик класса-наследника обязан знать и учитывать их все, либо есть очень большая вероятность, что дополнительный метод перестанет корректно взаимодействовать с основным.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Если удаление записи становится не атомарной операцией

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

если кому-то надо будет написать отмену

Не кому-то, а тебе. Мы ж договорились что это твоя часть кода.

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

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

no-such-file ★★★★★
()
Ответ на: комментарий от monk

Либо разработчик класса-наследника обязан знать и учитывать их все, либо есть очень большая вероятность, что дополнительный метод перестанет корректно взаимодействовать с основным

Ну опять же, если нужно что-то знать про другой код, то это говнокод. По определению. Собственно никакой инструмент не позволяет этого избежать, наговнокодить можно всегда. Это не вопрос инструмента, это вопрос политики. И во времена goto можно было писать так, чтобы не получались макароны.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Ну опять же, если нужно что-то знать про другой код, то это говнокод.

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

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Потому что разработанные им методы должны соответствовать принципу подстановки Лисков

Для этого достаточно знать только интерфейс, а не конкретный код.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Для этого достаточно знать только интерфейс, а не конкретный код.

Интерфейс. Который состоит из ограничений на входящие данные, ограничений на результат и инвариантов на последовательности методов.

before/after/around этот интерфейс часто меняют.

monk ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

А не должны.

Сложно написать что-то, что должно выполняться после (или перед, или вместо) произвольного кода и не мешать этому коду.

Вот, например https://github.com/gwkkwg/cl-graph/blob/3cb786797b24883d784b7350e7372e8b1e743508/dev/graph.lisp#L569

При том что сама реализация add-vertex вообще ничего не делает. В результате если сделать потомка basic-graph с более оптимальной структурой хранения, то перед переопределённым add-vertex всё равно будет вызываться запись в устаревший (graph-vertexes graph).

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Вредность goto (была) в том, что читая программу очень сложно понять, что именно она делает

Да, goto делает чтение однопоточной программы на сях сложнее. Впрочем, сейчас даже CPU делает миф про «понять что именно она делает» ещё более мифом (см. meltdown). Но это не отменяет того факта, что goto — зло: использование goto как правило менее читаемо, чем альтернативы, отсюда и нелюбовь к нему.

Сейчас современные версии COME FROM делают примерно то же самое. «INSERT VALUES 1 INTO T1»

Нет. Триггер мало отличается от ещё одного клиента этой же БД (ну, внутри транзакций есть детали, но это мелочи). БД ни разу не однопоточна и твой код ни разу не единственный юзер.

SELECT COUNT(*) FROM Products; // 42
DELETE FROM Products;
SELECT COUNT(*) FROM Products; // всё равно 42

Ну так два select’а не обязаны возвращать один и тот же результат (и вообще ничего не должны) за вычетом ситуаций когда ты держишь лок на всю эту таблицу.

x3al ★★★★★
()
Ответ на: комментарий от x3al

Ну так два select’а не обязаны возвращать один и тот же результат

После DELETE ожидается нулевое количество записей.

Триггер мало отличается от ещё одного клиента этой же БД

Если делать DELETE по первичному ключу с GENERATED ALWAYS, то новый клиент не может создать новую запись по такому же первичному ключу. То есть либо DELETE должен вернуть ошибку, либо есть гарантия, что записи с таким ключом нет. Если не учитывать триггеры.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

После DELETE ожидается нулевое количество записей.

Между DELETE и SELECT другой клиент может сколько угодно записей добавить. Ну или дропнуть таблицу.

Если делать DELETE по первичному ключу с GENERATED ALWAYS, то новый клиент не может создать новую запись по такому же первичному ключу.

Так в запросе ключа нет. Ну и триггер тоже не сможет.

x3al ★★★★★
()
Ответ на: комментарий от x3al
DELETE FROM Products WHERE ID = 42;
SELECT FROM Products WHERE ID = 42; -- из-за триггера вернулась одна запись. Без триггера гарантированно пустое множество
monk ★★★★★
() автор топика
Ответ на: комментарий от vbr

Это называется АОП. Иногда удобно.

Удобно писать. Но не очень удобно (или даже очень неудобно) отлаживать и расширять.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Без триггера гарантированно пустое множество

Только в SERIALIZABLE. Даже в REPEATABLE READ гарантия только на то, что прочитанные строки не меняются, но снаружи транзакции другой клиент может добавить что угодно между DELETE и SELECT. Хотя тот же постгрес в REPEATABLE READ будет гарантировать это, в стандарте такого нет.

Ну и большинство запросов идут в READ COMMITED, где phantom read — обыденность.

Чтобы между DELETE и SELECT в READ COMMITED новых строк не появилось нужно держать лок на всю таблицу.

x3al ★★★★★
()
Ответ на: комментарий от x3al

но снаружи транзакции другой клиент может добавить что угодно между DELETE и SELECT

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

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Он не может добавить строку с первичным кодом другой строки, если код генерируется через GENERATED ALWAYS

Как минимум в постгресе и оракле можно сбросить последовательность, из которой он GENERATED ALWAYS, на произвольное значение.

x3al ★★★★★
()
Ответ на: комментарий от x3al

Что же мне так с примерами не везёт. Ладно, без триггеров внутри одной транзакции DELETE + INSERT с одинаковым ключом эквивалентны UPDATE. С триггером на каскадное удаление DELETE наудаляет много лишнего, а с приведённым триггером на INSTEAD OF получим таинственную ошибку про задвоение ключа.

monk ★★★★★
() автор топика
Ответ на: комментарий от hobbit

Правильно написали, вреден не сам GOTO, а его неумеренное использование.

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

vodz ★★★★★
()

Goto ни фига не вреден, если умело пользоваться. Любой нормальный парсер его использует.

Чем городить машину состояний и в цикле разбирать переменную $state, гораздо эффективнее использовать goto и вызов функции – адрес выполняемой инструкции + стек – кодируют состояние лучше всего.

unDEFER ★★★★★
()
Ответ на: комментарий от vodz

то для интерпретируемых всё сложно

GOTO в интерпретируемом бейсике отлично себя чувствовал. Просто надо понимать, что синтаксис является всего лишь сахаром над теми же GOTO.

как сделать следующую итерацию цикла, если мы обошли его начальную инициализацию

Если понимать, что любой цикл развёртывается в

  инициализация
начало: 
  если не условие1 goto конец;
  действия
  ...
  если условие2 goto начало;
конец:

то проблем никаких. Для интерпретируемого языка всё вообще просто: мы же всё равно каждую команду разбираем отдельно и можем выдавать ошибки типа «неизвестная переменная».

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

GOTO в интерпретируемом бейсике отлично себя чувствовал.

Пока shell-ы были чуть умнее бейсика, то и в них как-то жило goto.

Если понимать, что любой цикл развёртывается в

Ну если всё воспринимать с позиций бейсика...

Для интерпретируемого языка всё вообще просто:

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

vodz ★★★★★
()
Ответ на: комментарий от vodz

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

Только если метку синтаксически можно перепутать с чем-то ещё.

monk ★★★★★
() автор топика
Ответ на: комментарий от vodz

Ну если всё воспринимать с позиций бейсика…

Хоть бейсика, хоть блок-схем, хоть ассемблера, …

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Только если метку синтаксически можно перепутать с чем-то ещё.

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

Хоть бейсика, хоть блок-схем, хоть ассемблера, …

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

vodz ★★★★★
()
Ответ на: комментарий от vodz

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

Метки читаются первым проходом, который просто строит соответствие «метка» - «позиция в файле». Поэтому достаточно, чтобы метки ни с чем не путались. Как в том же бейсике.

а на малокилобайтных полукалькуляторах goto смело вызывали куда угодно, хоть внутрь совсем другой subroutine для экономии памяти.

Зависит от семантики возврата из подпрограммы. Если GOSUB/RETURN, то можно, а если возврат ещё и стек чистит, то должна полностью сигнатура совпадать (впрочем, в таких реализациях и переход между подпрограммами запрещён).

monk ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.