bash.im ithappens.me zadolba.li

Программизмы

13487

Когда запятые кончились

Корректность значения не проверяется, говорите? Их есть у меня!

Год где-то 2005, в районных электросетях (РЭС) работает биллинговая программа. Ну, работает более-менее, вроде всё настроено. Мануалов, как водится, нет, приходится всё делать методом не всегда научного тыка.

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

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

13485

Только так - или никак

Знакомая работает в медицинском учреждении. Получили новый аппарат для экспресс-анализа на наркотики. Сам аппарат — коробочка 5×7 см, по usb подключается к ноутбуку. В коробочку вставляется картридж с анализом, она его сканирует. На ноутбуке программа, сравнивающая цвета полосок на скане с эталоном и считающая концентрации.

Все нормально, но при попытке записи результата выдаётся ошибка: «7:» is not valid integer data.

Пляски с бубном дали результат парадоксальный — все заработало при установке в системе формата времени HH: MM: SS.

Теперь я не могу понять, откуда растут руки у разработчиков? Взять системное время в текстовом виде, порезать по два символа в три целочисленных поля и даже не сделать проверки на корректность значения…

13484

Древние грабли

Древнее золото, говоришь?

А у меня сразу возник вопрос. Если обработка XML требовала затянуть в память все, и там разбирать ("проблема была в сильной связанности") - то почему загрузка в CSV этого же не требовала?

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

Я вот совсем недавно исправлял проблему - нельзя было загружать XML'и больше определенного размера. Памяти не хватало. Оказалось, там парсер грузит всю структуру в память целиком и только потом обрабатывает. Заменил на потоковый. Потребление памяти константное, скорость работы возросла.

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

13481

Древнее золото редко блестит

Однажды я писал утилиту для анализа большого объёма сильно связанных данных на XML + C# — заказчик хотел модные (в 2007 году) технологии. Ничего сложного — XML-документ загружался в память, последовательно обрабатывался, результаты складывались в базу. Проблема была в сильной связанности — при обработке почти каждого узла приходилось подгружать целые секции из разных концов этого же документа.

Когда соотношение объёма входных данных и объёма ОЗУ машины пересекло критическую отметку, программа сошла с ума. Показатель Time in GC достиг значения 98%, а значит, программа не работала, вместо этого среда выполнения занималась очисткой и дефрагментацией памяти. Проведённое расследование показало — при нехватки памяти и попытке подгрузить дополнительные данные для обработки текущего узла ОС скидывала обрабатываемые данные в своп, а затем доставала их обратно. И сборщик мусора работал над свопом, что в тысячи раз медленнее работы с ОЗУ.

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

К чему я всё это. Файловый обмен — замечательная вещь, и мало форматов лучше, чем CSV. Он легко читается машиной и человеком, его поддерживают большинство программ — от офисных до научных пакетов. Используя магию bash, его можно бить на части (подзадачи для вычислительной фермы, например) или объединять (результаты вычислений), записывать в сеть, в устройства, в другие процессы, выложить в облачное хранилище или отправить по email, перезапросить в случае отказа сети, создать резервную копию, принять и обработать тысячей и одним способом.

Если технология существует давно, это не значит, что она плохая. Это значит, что она уже пережила пару поколений желающих использовать только новейшие технологии. Они ушли или перегорели, а технология осталась.

13434

Совпадение? Не думаю!

Вступлюсь за честь коллег из геймдева.

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

Во-вторых, сессия для инициализации рандома используется в одиночных играх с целью борьбы с читерской магией load-save. Другого смысла постоянно дёргать seed просто нет.

В-третьих, игрокам вообще много что кажется, и они с радостью это интерпретируют как факты. Плюс — confirmation bias. Я помню случай, когда игроки были свято убеждены, что по случайному номеру в ссылке для предотвращения кеширования можно было определить успех ремонта предмета. И никого не смущало, что к скрипту шли десятки обращений в секунду и от момента генерации ссылки до последующего запроса рандом дёргался более тысячи раз.

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

Искренне порадовался аргументу про сокращение выборки. Ясное дело, что чем меньше выборка, тем более она неравномерна — это очевидно. Нетрудно получить «решку» в 8−10 случаях из 10, шанс на это чуть более 5%. А вот получить её в 80−100 случаях из 100 уже вряд ли удастся хотя бы раз за миллион попыток.

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

13432

Fair dice roll

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

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

  • if (rand(10000)==1) и прочие подобные глупости при использовании генератора с нормальным распределением.

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

  • Генерация энтропии на основе данных игрока/сессии/сервера. Обычно легко прослеживается и явно заметна.

  • Выборка случайного элемента из некоторой части списка для придания «большей случайности». За пределами добра и зла. В одной известной игрушке про убийство монстров это привело к тому, что можно было сутками пытаться выбить шмотку, которой просто не может выпасть в текущей сессии.

  • Скрытая манипуляция выборкой под видом случайного выбора.

  • ...и даже сочетание всего вышеперечисленного.

Самое смешное, что когда пользователи жалуются на симптомы подобных решений или вычисляют примерный алгоритм работы рандомизатора, начинается тотальное отрицание всего и вся. В крайнем случае, проверки алгоритма за пределами возможного матожидания, например: «Вот, смотрите, мы протестировали алгоритм на выборке из 300 выстрелов, всё чудесно». После этой фразы уже объяснять, что если сократить количество итераций до 30, картина меняется в корне, абсолютно бесполезно.

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

13430

Великий корейский рандом

Пишу на C# тест для детей. Первое задание — перевести маску подсети в сокращённый вид, второе — перевести маску подсети из сокращённого вида в полный, далее — вычисление адреса сети и так далее.

Прога практически дописана. И тут я замечаю, что у меня выдается одна и та же маска в первом и во втором задании, просто в разной форме записи. Ну, совпало, думаю.

Перезапускаю прогу ещё раз… маска совпадает. Ещё четыре раза — маска совпадает. Проверяю переменные — всё в порядке. Проверяю вывод данных — всё в порядке. Лезу в подпрограмму генератора маски — опять не вижу ошибок! Прямо перед отображением работы запускаю вхолостую генератор маски шесть раз — каждый раз выдаёт разные.

И тут я замечаю, что маски в первом и во втором задании больше не одинаковы. Перезапускаю программу — маски разные. Удаляю холостые запуски генератора — маски опять разные. Это всего лишь шесть раз подряд маски в первом и во втором случае совпали. Рандом — такой рандом…

13427

Неуловимый Джо

Индусы, индусы, индусы… Пару часов назад столкнулся с тем, что коренные американцы способны накосячить не хуже индусов.

Вставляем в программу проверку входных бинарных данных (_fpclass) — вдруг нам NaN пришёл (или ещё какой мусор). И неожиданно для себя получаем три жирных бага в совсем других местах: зависание, некорректное отображение данных, движение времени «большими рывками».

Пара часов отладки — и становится понятно, что дело в том, что _fpclass портит режим сопроцессора. И вместо long double у нас сопроцессор считает всё как float.

Открываем сорцы. Ну странно же, когда стандартная подпрограмма из системной библиотеки не восстанавливает регистры оборудования. Гм, она восстанавливает. Угу, восстанавливает. Но с перепутанным порядком параметров в вызове _control87.

Дата написания кода — © 1998, 2000. Версия библиотеки 10.0. Как этот баг прожил 15 лет? 15 лет, Карл!

13417

Не путайте тёплое с мягким

Прихожу на сайт. Смотрю — висит объявление: «Требуется java-разработчик». А внутри чёрным по белому написано: «Требуется писатель руками на java для разработки проекта на Unity3D». Народ! Когда вы научитесь отличать Java от JavaScript? Unity3D — это популярный игровой движок с возможностью писать скрипты на C#, JavaScript и Boo (никому не нужном).

Почти что никого, никого из новичков не волнует, что написано JavaScript. И они начинают в описании проектов писать: «Язык программирования: Java». Начинают набор именно Java-программистов в команду. И самое страшное — начинают спорить, что Java и JavaScript отличаются как С++ от С, в ответ на справедливое замечание, что, мол, а название ЯП неправильное висит. Ну спорщиков-то не очень много, и они быстро сливаются под напором аргументов.

Менеджеры — это отдельный разговор. На эту тему много было сказано и ещё многое скажут, только намекну, что студии, где манагеры ищут Java-разработчиков на проект в Unity3D, можно посочувствовать.

Особое умиление вызывают «правильные» парни, которые в резюме, указывая свои знания, помимо разных языков, на которых писали хелловорлд в пятом классе, пишут также JavaScript (подразумевая, что использовали его в Unity3D). Чувак! Ты можешь сколь много времени писать на «JavaScript» в юнити, но в реальном вебе подели свои знания на ноль, поскольку, честно говоря, яваскрипт в юнити обладает рядом специфичных свойств, которых в тру JavaScript никогда не было и нет.

Даже на Bitbucket’е при указании языка программирования можно выбрать UnityScript, что означает JavaScript в Unity3D.

Возможно, это самая важная причина, почему народ, пишущий на UnityScript, потом бросает его в пользу куда более мощного С#.

Суть этой истории в том, что яваскрипт в юнити суть кака или, «выражаясь», неудачное решение.

13390

Тревожные звоночки

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

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

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

И чего, собственно, были вызовы?

Но, определённо, были же.

И зачем вообще их было отслеживать?

Да и глупо бы вышло. Странноватое ещё понятие — «пропущенные вызовы функции».

Спустя секунды приходит осознание…

13379

Один на всех и все на одного

Серверная — склад — база эникейщиков. Входит сотрудник.

— Здрасте, у нас там проблема. Я там пишу всем макросы на визуальном басике. У некоторых не работают. Гляньте.

Никто в «визуальном» не разбирается, но храбро идём к проблемным машинам. Беглый осмотр ничего подозрительного не выявил. Сравнение настроек также не показало отклонений. Рапортуем: настройки стандартные, запретов нет, проблема под капотом макросов. «Клиент» приходит снова:

— Проблема не в макросах: у других-то работает!

— Согласны, мистика, но из-за макроса переустанавливать систему мы не будем. Но если админ одобрит, переустановим хоть всем.

Все идём к админу: вот, так и так.

— Ошибку гуглили?

— Нет, не моё это дело, я только пишу.

— Ясно. Наши сотрудники провели осмотр и ничего не обнаружили. Проблема в макросе.

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

— Отладка VBS вне их рабочих обязанностей.

— И что мы будем делать?

— Предлагаю так: один из них, который захочет, найдёт документацию, выучит VBS, загуглит ошибку и исправит её. А вы тем временем пишете «по собственному желанию», потому что одного специалиста по макросам нам будет достаточно.

Никто так и не уволился. Зато обогатился знаниями об отличиях 32 и 64-битных систем и стандартных путей разных версий Офиса.

13338

Икебана из костылей

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

А вот вторая мысль была реалистичнее: строили всегда как умели и как хотели, а до наших дней дошло то, что дошло. И тут осенило. А как надо строить, чтоб на века? Вот пирамида. Устойчивей некуда. Её можно было и целиком из песочка насыпать. Или навалить тёплой и мягкой субстанции в тех же габаритах. И ничего — стояло бы совсем как живое. Вот Парфенон — крыша на подпорках, политкорректно именуемых колоннами. Нотр-Дам тоже весь в подпорках и костылях, именуемых контрфорсами. Мда. А вот модульные универсальные решения типа хрущоб разваливаются на глазах.

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

13333

Самопроизвольное индоизвержение

А я теперь официально признаюсь быдлокодером.

Дело в том, что мне приспичило написать обёртку на Go для сишного API Eggdrop. Всё было хорошо до тех пор, пока я не попытался реализовать подобие Tcl-функции bind для C-функций. Полдня я потратил на исследование исходников, так как всемогущий поисковик мне не помог, но не нашёл ничего, кроме функций добавления и бинда Tcl-функций. В результате у меня получился такой механизм: генерируется имя вида eapi:bind_xxx, затем под этим именем добавляется привязываемая функция, затем получившаяся Tcl-функция привязывается как обычно. Возвращаемым значением этого монстра является номер бинда (то самое xxx в названии функции).

Простите меня, оно само.

13305

Руки на высоте плеч

Найдено в коде одного проекта. В stdafx.h:

#ifndef handsOutOfShoulders 
  #define handsOutOfAss 
#endif

Далее, в about:

#ifdef handsOutOfShoulders 
  /* нормальные реквизиты фирмы */
#else
  #ifdef handsOutOfAss 
    message = std::wstring(L"У нас появился долборукий
    дятел, разваливший сборку, и я не отвечаю
    за работоспособность программы");
  #else
    message = std::wstring(L"БЛ&$Ь!!! ПОКАЖИТЕ МНЕ,
    КАК ОНО РАБОТАЕТ!!!");
  #endif
#endif