Комментарии 86
— assert
— система логгирования
— try..except..finally
ну и сам собой — никакого деления на release/debug. Тяжелые проверки включаются динамически из конфига на том же коде.
Ну и технологии ловли на каждый тип бага. Например, перепахивание памяти ловится через аппаратные breakpoint на запись в данную память. В идеале — их можно настроить на логгирование, потом убрать дубликаты из списка адресов и посмотреть, откуда писали.
Тогда у меня был Пентиум 200, если кто помнит, в АТ-блоках питания был не только вход, но и выход на монитор.
Естественно, для пущего удобства туда был включён сетевой фильтр, куда воткнут помимо монитора усилитель Вега с советскими 35-ваттными колонками (ну потому что это удобно — включил комп и сразу все что нужно включилось).
Баг заключался в том, что при прослушивании музыки комп иногда перезагружался.
Естественно, когда через полгода усилок был вместо АТ-выхода воткнут в обычную розетку, все прекратилось :)
А у знакомых физиков такой «сетевой фильтр» был сделан из ферритового кольца диаметром сантиметров 30. Спасал компьютер от перезагрузки при включении рядом мощного генератора электронных пучков.
"Декларативный" стиль и отлаживать-то невозможно — так что да, гейзенбагов (т.е. багов, которые исчезают или проявляются в отладчике) в нем допустить сложно.
Что нить типа такого:
smallestNum = (x<y && x<z)? x: (y<x && y<z)? y: (z<y && z<x)? z;
switch case — это вполне себе императивный стиль
Основы декларативного программирования на Lua
«Декларативный» стиль и отлаживать-то невозможно
Да щито вы говорите?
Стыдно должно быть за попытку выглядеть умным, но не знать что такое декларативный стиль не в языках разметки.
Императивный стиль
int F(int x) { return x*x; }
int sum = 0;
for (int i=0; i<arg.Length; i++)
{
if (arg[i] > 10) sum += F(arg[i]);
}
Декларативный стиль
int F(int x) { return x*x; }
int sum = arg.Where(x => x > 10).Sum(F);
Source: Императивный vs Декларативный стиль
То, что вы привели — это не декларативный, а императивный стиль с элементами функционального.
Wikipedia:
К подвидам декларативного программирования также зачастую относят функциональное и логическое программирование — несмотря на то, что программы на таких языках нередко содержат алгоритмические составляющие, архитектура в императивном понимании (как нечто отдельное от кодирования) в них также отсутствует: схема программы является непосредственно частью исполнимого кода[1].
А есть «чисто декларативные» языки типа SQL/HTML.
Вы спорите с академическими фактами, а не со мной.
На повышение уровня декларативности нацелено языково-ориентированное программирование.
Декларативность это более широкое понятие, которое описывает процесс c проекции «каково нечто», а не «как его создать». Порядок выполнения и способ достижения не важен.
А есть «чисто декларативные» языки типа SQL/HTML.
Подскажите, пожалуйста, SQL-отладчик.
К подвидам декларативного программирования также зачастую относят функциональное и логическое программирование
Чистое функциональное программирование — да, декларативно. Но императивное программирование с элементами фугкционального остается императивным.
Подскажите, пожалуйста, SQL-отладчик.
Если потрудитесь отматать в начало треда, то речь шла о коде в «декларативном стиле», а не о «декларативных языках», которые мне пришлось упомянуть, чтобы вернуть вас с Луны на Землю.
Чистое функциональное программирование — да, декларативно. Но императивное программирование с элементами фугкционального остается императивным.
Попытка до упора отстоят свое, пусть и ошибочное изначально замечание — умиляет.
Ясно же, что сначала для вас было открытием «декларативный стиль» != «декларативный язык».
А теперь вы пытаетсь съехать, когда вас ткнули в академические формулировки.
Пора тредик закрывать — иначе он скатывается во флейм.
Декларативный стиль — это описание "что желаем получить", в противоположность "что делаем" императивного стиля.
На C# можно писать в декларативном стиле.
К примеру, известная ORM-библиотека Entity Framework предполагает именно такое описание для настройки отображения (да и сама концепция ORM по своей сути декларативна).
При этом процесс работы с БД через EF — императивен, в отличии от настройки отображения.
Также к декларативным библиотекам можно отнести:
- AutoMapper;
- IoC-контейнеры (как и саму идею IoC);
- XML-сериализацию/десериализацию (кроме той части, что идет через интерфейс IXmlSerializable);
- кучу мест в ASP.NET MVC, которые настраиваются через атрибуты.
Вот это все — и есть декларативный стиль. А приведенный вами кусок кода arg.Where(x => x > 10).Sum(F);
— типичный императивный вызов.
Так вы отказываетесь от своих слов что декларативный стиль нельзая отлаживать?
Я вам привёл ссылки на академические/мета-академические (вики) источники — и примеры скопипастил из них, дабы не порождать флейма. Можете поспорить с авторами. Или как это часто бывает — создать свою альтернативную вселенную, в которой все ваши аргументы будут логичны и правильны, а оппонента — нет.
Вроде бы человек вроде вас должен трепетно относиться к железной логике конструктивизма.
Я вам привёл дословную цитату:
К подвидам декларативного программирования также зачастую относят функциональное и логическое программирование.
После этого тред можно закрывать.
Но вы с упорством, достойным лучшего применения, пытаетесь за что-нибудь зацепиться и сделать хорошую мину при неудачном изначальном остроумии про «невозможность отлаживать декларативный стиль».
Глупо, потому что вы думали до этого иначе. А теперь вот подчитали на скорую руку и с пеной оправдываетесь.
Надо уметь проигрывать и признавать ошибки. Вот и всё.
P.S.:
Ваша попытка сделать удачную мину при неудачном остроумии — засчитана, но не суть вашего изначального выпада.
Ловкие и хитрые конструкции в коде — это удел специалистов среднего уровня, которые изучили трюки, но еще не накопили опыта и интуиции в том, когда следует их применять, а когда — нет.
Я нашел функцию, где происходит ошибка, функция вызывается из стандартного шаблона представления (layout), в котором по бокам разная информация для пользователя, в ней происходит вызов процедуры БД, куда передается user_id. Но он не может быть null, null он только на странице логина, а у нее свой шаблон.
В Firebug кроме GET-запроса страницы логина никаких других запросов к сайту нет.
Опытным путем выяснилось, что ошибка также появляется, если нажать Ctrl+F5 или Ctrl+Shift+R.
Firebug запрос фавиконки вообще не показывает.
При открытии сайта браузер запрашивает файл favicon.ico.
На тестовом сервере его почему-то нет, и на рабочем тоже.
Управление передается на index.php, приложение не находит подходящий роут и показывает ошибку 404.
Ошибка выводится со стандартным layout.
Текущего пользователя еще нет, база выдает ошибку, ошибка попадает в flash messages.
Открывается страница логина, показывает все flash messages из сессии.
Перезапустил прогу и девайс — все ок. За день это повторилось раза два или три, на первый взгляд, совершенно бессистемно.
В конце концов заметил, что это происходило, только если угол наклона девайса был 19 градусов…
N=31 это спутник G32 (GPS), в Питере он бывает виден редко, а вот на демонстрация в Москве — частенько. Так что дома все работало, а на демонстрациях — иногда был генеральский эффект.
Нашли чтением кода через 3 года после первого сбоя. И дня через 2 после начала охоты на этот баг. Воистину неуловимый Джо — это Джо, которого никто не хочет ловить.
Интереснее, что человек может быть отличным алгоритмистом и отвратительным кодировщиком одновременно.
Что касается понимания чисел — меня всегда удивляло, как она может, глядя на значение чисел в матрице сказать, какой баг был допущен в обработке. Причем в процентов 95 — подтверждается.
«не думаю» (ц) :)
Сознательно написать число с точкой в надежде, что оптимизатор превратит его в целое… Как-то неправдоподобно. Но, даже, если ход мысли был именно такой, это всё-равно принципиальное непонимание как числа в компьютере устроены. Даже если оптимизатор превратил бы 2.0 в 2, ошибка бы была и на IA32. Там ведь у сибилдера целые тоже 32-х битовые. Поэтому, видимо, точку добавили именно потому, что без точки не работало, а понять почему не работало, не осилили.
Впрочем, не важно, история сама по себе интересная.
Да, для программиста оптимизация pow(2,N) в целое — маловероятна. И у программеров есть понимание, что pow(2,N) и pow(2,0,N) — одно и тоже, ибо аргумент у pow вещественный. И есть понимание отличий внешних от внутренних функций. Внутренние — это функции вроде sqrt, которые могут быть оптимизированы до машинной команды. А pow — внешняя, даже pow(x,2) не оптимизируется до x*x.
> Даже если оптимизатор превратил бы 2.0 в 2, ошибка бы была и на IA32
АРГУМЕНТИРУЙТЕ. Желательно тестовым примером. Или доказательством, что 56 бит на IA32 мантиссы мало.
> точку добавили именно потому, что без точки не работало, а понять почему не работало, не осилили.
Значит тоже не понимаете, что pow(2,N) и pow(2,0,N) дает одинаковый код.
>> Даже если оптимизатор превратил бы 2.0 в 2, ошибка бы была и на IA32
>АРГУМЕНТИРУЙТЕ. Желательно тестовым примером. Или доказательством, что 56 бит на IA32 мантиссы мало.
Это вы меня смутили операцией **, я си давно забыл, думал, что там есть такая. С pow-то понятно, фактические аргументы будут приведены к типу формальных.
>Отличие в том, что вы ДУМАЕТЕ, а я ЗНАЮ. Под 100 тысяч строк этого автора поддерживаю.
Вы продолжаете утверждать, что автор знал устройство чисел в компьютере? Вроде, довольно очевидно, что не знал. Тем, кто знает и в голову не приходит использовать вещественную арифметику в виде целочисленной, надеясь на оптимизатор. Тем более, в таком простом случае. Про оптимизатор я теперь даже затрудняюсь понять что вы про него имели в виду. Довольно очевидно, что оптимизатор вряд ли станет оптимизировать вещественную функцию в целочисленную. За токое канделябром бьют.
я тоже забыл. :-)
> Довольно очевидно, что оптимизатор вряд ли станет оптимизировать вещественную функцию в целочисленную.
ДА НУ?
const int four = 2.*2.;
Получаем mov [ebp-0x04], 0x00000004
Если компилятор сумеет просчитать функцию с константными аргументами при компиляции — он вполне оптимизирует вещественную функцию в целый результат.
> За токое канделябром бьют.
Ну бейте. Компилятор Borland C++ Builder
> Тем, кто знает и в голову не приходит использовать вещественную арифметику в виде целочисленной, надеясь на оптимизатор.
Как видите — оно даже работает.
Но чтобы отличить, где оптимизация сработает, а где нет — нужно понимать хотя бы отличие внутренних функций от внешних. А вот это математик не знала.
> Вы продолжаете утверждать, что автор знал устройство чисел в компьютере?
ДА, разумеется. Думаю, что о накоплении ошибок в вычислительных алгоритмах она знает побольше вас. Просто возможности компилятора не понимает.
>const int four = 2.*2.;
>Получаем mov [ebp-0x04], 0x00000004
А где здесь функция? Вы уровня сложности pow напишите. Она же степени через экспоненты считает?
>Ну бейте. Компилятор Borland C++ Builder
Этого даже бить не надо. Само помрэ.
>Как видите — оно даже работает
Вы убедительно доказали, что оно НЕ работает.
>Но чтобы отличить, где оптимизация сработает, а где нет — нужно понимать хотя бы отличие внутренних функций от внешних. А вот это математик не знала.
Нет. Для решения этой проблемы надо знать лишь устройство чисел в компьютере и всё. Основы. А ваше деление на внутренние и внешние — это какое-то местечковое сибилдерное шаманство? Вот его точно знать не надо. Его даже гуголь не знает. А код, зависящий от знания способа оптимизации компилятора == говнокод. Тем более, в случае такой банальщины.
>Думаю, что о накоплении ошибок в вычислительных алгоритмах она знает побольше вас. Просто возможности компилятора не понимает.
Ну, продолжайте пребывать в этой ложной уверенности. А лучше дайте «ей» что-нибудь уже почитать. Да и сами не пренебрегайте. В конце концов, это вам, не мне, разгребать 100к+ говнокода, написанного без какого бы то ни было понимания. «я могу писать со скоростью 400 знаков в минуту, правда такая фигня получается...».
Разрабатывали комп для военных. Разработали. Всё работает. Весит, правда, как новорожденный буйволёнок, но это не наша вина. (Зачем-то наши вояки для всех устройств требуют работоспособность при -60С. И действительно её проверяют. За счёт одного этого устройство весит в 3 раза больше, чем могло бы при -20С, и главное никому нафиг не нужно это).
В качестве сдачи заказчику — участие взвода с нашей системой в больших учениях в полях. Взвод с нашей системой и 10 со старой. Средство связи у всех взводов, включая наш, одинаковое — такая рация с функцией передачи цифровых данных. Давным давно у этой рации мы обнаружили забавный глюк — она не передаёт сообщения длинной 31 байт. Больше или меньше — да. Ну да ладно, учли это в коде и вроде работает. Прямо за полчаса до начала учений всё проверили, всё работает.
Раздаётся приказ к началу учений, и тут у нас пропадает связь. Вся, разом, как накрыли медным тазом. И в течении 5 часов связи не было. Раздался приказ об окончании учений. И сразу хренак! Всё снова работает. Потом были долгие разборки-штрафы-санкции, но по сути это событие закрыло компанию, где я работал.
И только через месяц мы как-то по наитию догадались, в чём дело. В API на передачу пакета данных в рацию есть поле «приоритет». По документации значение от 1 до 10, 1 — самый главный. Ну мы и поставили 1 для сообщений писец-класса, 2 для класса срочных и так далее. Наивные. В старой системе же стоял приоритет 1 для всего вообще, включая телеметрию. Поэтому когда вокруг было 10 раций со старой системой, всё работало. А вот когда их стало 500 — на нас просто не хватило эфирного времени.
То есть косяк был даже не наш, но в армии, сами знаете, это ничего не значит. Отака фигня.
Итог: я просидел 5 часов на ящике с гранатами при -15, и с тех пор сыплю фразами типа «если этот сервер поддерживает одновременно 400 абонентов, это ещё не значит, что он поддержит и 350»
Вообщем после исследования обнаружилось, что слово «продолжить» несколько длиннее слова «continue» и своим хвостом залезает в кусок памяти с цветом одежды.
Скорее, не надо было допускать в коде UB. Отключение оптимизации — это костыль, приемлемый для старого кода, но говорить "надо отключить оптимизацию" нельзя. "Надо" только исправить код.
UB — это undefined behavior. Ситуация, в которой, согласно стандарту языка, компилятору разрешено делать что угодно. Современные оптимизаторы считают такую ситуацию невозможной и оптимизируют код исходя из этого
Обычно это означает выкидывание условий, которые оптимизатор счет невозможными.
К UB относятся ситуации целочисленного знакового переполнения, разыменования нулевого указателя, бесконечный цикл.
Вот одна из статей на эту тему: https://habrahabr.ru/company/pvs-studio/blog/276657/
Значит, было что-то другое. В статье рассказано только про один вид UB.
Вообще-то этот пример раньше на начальных курсах института проходили. Компилятор FORTRAN OE, IBM 360/370. Псевдокод основной идеи на Си.
if (div != 0)
Res = sum /div;
else {
div = 1;
Res = sum /div;
};
Оптимизируя общие подвыражения, оптимизатор сделал так
Res = sum /div;
if (div == 0)
div = 1;
Угу, общие подвыражения он вынес. Но с водой потерял и ребенка. Пример классический, многократно описан в книгах. Стыдно такое не знать.
Нарушение strict aliasing — это UB.
Ну или признайтесь, что несколько поторопились.
Для недоверчивых — https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=optimization 2231 баг, связанный с оптимизацией, некоторым багам — уже больше 10 лет.
Так что UB иногда бывает, но баги оптимизации встречаются чаще. Вот ещё статья с примерами https://habrahabr.ru/post/114117/ к чему приводит strict aliasing.
Что касается пользы от оптимизатора — она процентов 5-10. Если больше — значит программист надеется на оптимизатор и пишет неэффективно. С другой стороны, переделка алгоритмов дает выигрыш в разы, в моей практике — и в тысячи раз.
UB править стоит — но не фанатично. Ибо в виндосовских хеадерах UB более, чем хватает. Как пример — преобразование беззнакового в знаковое того же размера. Иногда даже компилятор ругается (warning или hint) на особо наглый код. Суть в том, что если вы попадете на машину, где используется прямой или обратный код — вашей программе будет плохо. Но с вызовами виндовскими хеадерами — будет так же плохо. Иными словами — на такой машине windows все равно работать не будет и ваш код там бесполезен.
По моим оценкам отказы оптимизации
30% ошибка программиста
5% тонкие эффекты изменения точности
65% баги оптимизатора
А если не верить компилятору — программирование не имеет смысла.
Как пример 2.0 было равно 16.0 в одной из ранних ЕС-1036. В микрокоде вещественные числа на равенство сравнивали по мантиссе. И забыли сделать сравнение ординаты.
Ещё пример. комп (М6000) иногда портил файловую структуру диска. Длительное расследование показало, что во время длиной (на пару страниц) процедуры поиска дорожки и сектора в бите С (бит целочисленного переполнения) держится тип операции: чтение или запись. Но что-то в процессоре иногда сбоило и вместо бит С менялся той командой, которая его менять не должна была.
Та же машина как-то раз после старта попросила включить перфоратор и вывела на перфоленту «Можете ли вы длительное время выполнять нужную однообразную работу, например считать или переводить с иностранных языков?» После этого она демонстративно сломалась.
Чтобы не думали, что баги были только на древних процах -Pentium-III, производства Малазии имел обыкновение зависать при уменьшении рабочего набора. Починили заменой процессора. скорее всего причиной — трещина в кристалле. Но все тесты он проходил на ура.
Можете для повышения кругозора посмотреть errata на современные SOC-чипы. Там тоже немало.
А уж ошибок в компиляторах — ВАГОНЫ. Их в сотни раз больше, чем ошибок в процессорах.
Это как со слоном — то, что вы никогда не видели слона — не означает, что слон не существует. Это означат, что вы ходите по одним местам, а слоны — по другим.
Так что пока вы копируете стили из учебника — все будет более-менее хорошо. Как только начнете думать сами, а не повторять чужие образцы — сразу вероятность нахождения бага в компиляторе увеличится.
Но верить в программировании — нельзя НИКОМУ — ни процессору, ни компилятору, ни ядру linux, ни господу богу. Нужно просто писать НАДЕЖНО. Так, чтобы ваши ошибки в коде не мешали работать остальным частям вашей программы.
https://gcc.gnu.org/bugzilla/buglist.cgi?bug_status=RESOLVED&resolution=FIXED&target_milestone=6.2 — список ошибок, исправленных в gcc 6.2. как писали в readme к одной популярной библиотеке «Исправлены обнаруженные ошибки, оставлены необнаруженные, добавлены новые.»
P.S. Могу ещё и про ошибки в ядрах linux и FreeBSD рассказать.
Первые 10 багов по этой ссылке:
1. нет предупреждения в случае, когда функция, объявленная как pure или const, не является таковой;
2. вылет с OOM;
3. FP EMULATION медленнее чем в clang;
4. std::pow(std::complex(0),1) возвращает (Nan, Nan) в режиме -funsafe-math-optimizations;
5. генерируется неверная отладочная информация для конструктора и деструктора;
6. что-то с маркером для оператора return, использующимся при отладке;
7. неоптимальность преобразования fold_binary_op_with_conditional_arg;
8. отсутствие векторизации в определенном случае;
9. отсутствие оптимизации в некотором случае;
10. какое-то лишнее предупреждение.
Из этих багов только 1 может привести к генерации неверного кода — но только в режиме оптимизации, который еще надо специально включить.
И остальные незакрытые баги — такие же.
>только в режиме оптимизации, который еще надо специально включить.
Любуйтесь. https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/Optimize-Options.html#Optimize-Options
-Ofast включает -ffast-math, -ffast-math включает -funsafe-math-optimizations
gcc — хороший компилятор, но как в каждой большой программе — там хватает ошибок.
С одним интересным эффектом, если первый логический монитор в левом нижнем углу то проблем нет, а если в любом другом положении, то на вопрос о мониторе по умолчанию, приходит ответ, что нет монитора по умолчанию.
Баг чинился у клиентов очень просто перестановкой мониторов и почти всегда вызывал полнейшее недоумение :)
Это было моё первое задание, когда году в 1998м я пришел устраиваться на подработку эникейщиком. Было там пять совершенно одинаковых компьютеров отвратительной конфигурации – процессор AMD K5 100 непонятно на какой материнской плате, 16 мегабайт памяти, видеокарта AliMagic и гигабайтный винчестер Samsung.
Так вот, на один из этих компьютеров программа 1С: Предприятие 7.5 прекрасно устанавливалась, а на второй нет. Инсталлятор вылетал с ошибкой на скольких-то там процентах и всё. Не помогала даже переустановка Windows на чистый диск. Я просто поменял жесткие диски местами. Вот и попробуйте угадать, как куда программа после этого устанавливалась, а куда нет.
Почему? А кто её знает… Я ж говорю – проще решить проблему, чем найти объяснение феномену.
Если оборудование брало время оттуда, то происходил сбой.
Существую ли способы найти баг, который воспроизводится раз в N лет?
ДА. Внимательным чтением кода находится и не такое.
> Если оборудование брало время оттуда, то происходил сбой.
Нет, причина не в этом. TOW (время он начала недели) сбрасывается в 0 при смене недели. А эфемериды в момент смены недели не меняются, действуют те, что приняты на старой неделе. Если специально не учесть этот момент и не добавить 604800 к текущему времени, расчет эфемерид происходит на начало предыдущей недели.
> Насчет номера недели, он переполняется примерно раз в 19 лет.
Для расчета координат это не важно. Для теплого старта — хватит 3-4 бит номера недели (альманахи старше месяца лучше не использовать). Реально в альманахах — 8 бит.
Важно это для показа даты, но там или опираемся на ГЛОНАСС-М (там есть номер четырехлетия) или на ГЛОНАСС (день внутри четырехлетия и его совпадение с датой по GPS) или используем L2 (там 13 бит номера недели, то есть переполнение раз в 157 лет) или на SRAM (часто вместе с RTC).
Иными словами в приемниках GPS/ГЛОНАСС ошибка раз в 19 лет крайне маловероятна, а вот в приемниках чистого GPS — она может быть (особенно при сдохшей батарейке).
Это я ещё не глянул стандарт на SBAS…
— Какое?
— Пойди в землю, которую я тебе укажу, собери там всех рыжих и вели им никогда не есть крокодилов.
— Чего!?
— Не важно. Просто передай это, и всё.
Пророк задумчиво почесал в бороде.
— У этой заповеди есть какой-то высший смысл? — спросил он. — Доступный человеческому пониманию?
— Конечно, есть! — воскликнул Шамбамбукли. — Сейчас объясню. Посмотри на небо. Видишь звёзды?
— Вижу. А что с ними не так?
— Хотел бы я это знать, — вздохнул Шамбамбукли. — Понимаешь, звезды управляют всевозможными процессами на земле, у каждой из них своя важная функция в этом мире. Ну так вот, обрати внимание на те три звезды; я заметил, что когда кто-нибудь рыжий ест крокодила, пусть даже маленький кусочек, их функции выдают неверные значения. А это приводит к ошибочной индексации памяти, в результате которой… впрочем, тебе незачем знать технические подробности. А мне, сам понимаешь, проще дать лишнюю заповедь, чем переделывать всё мироздание. Эти космические взаимодействия так перепутаны…
— Но почему запрет касается только рыжих?! Почему только им нельзя питаться крокодилами?
— А вот это, — сказал Шамбамбукли, — меня самого очень интересует.
Мотороллер история не моя, все претензии к автору.
Гейзенбаг, или как Луна портит код