Сайт The Daily WTF уже 14 лет собирает курьёзные, дикие и/или печальные истории из мира ИТ. Я перевёл несколько рассказов, показавшихся мне интересными. Все имена и названия компаний изменены. Первая часть находится здесь.

История первая. Назло бабушке...


[Оригинал]

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

Клайв блестяще прошёл интервью и спустя неделю поступил на работу. В почтовом ящике его уже ждало письмо от кого-то по имени Брэндон. Оно гласило: «Надо встретиться».

Брэндон сидел в своём офисе, приклеившись к эргономическому креслу середины 90-х и как будто став с ним одним целым. Он поднял взгляд от монитора и посмотрел на Клайва. «Ты работаешь на меня».

Краткими фразами, состоявшими из односложных слов, Брэндон объяснил, что никто из принимавших решение о найме не будет встречаться с Клайвом. Клайв несёт ответственность только перед ним.

«Ну ладно… Так, когда меня нанимали, то сказали, что нужно настроить Subversion. Мне начинать с этого?», — спросил Клайв.

«Нет».

«Можно узнать, почему? Вы предпочитаете что-то другое? Хотите обсудить варианты?»

«Нет».

Клайв подождал. Брэндон не торопился разворачивать свою мысль. Он просто смотрел на Клайва. Продолжал смотреть и смотреть.

Клайв потихоньку вернулся в свой кубикл и начал с изучения кодовой базы. Она находилась на общем сетевом ресурсе, а управление версиями имело вид «file.pl.old», «file.pl.old.old». Оказалось, что код написан на Perl, и что он нечитаем даже по стандартам Perl. Он вырос в культуре «если парсится, значит запустится», не содержал комментариев и абсолютно не имел тестов. Единственным союзником Клайва был Ли, ещё один нанятый специалист, который тоже отчитывался перед Брэндоном, но у него была двухнедельная фора в анализе кода. Когда Клайв где-то застревал, он выглядывал из-за стены своего кубикла и спрашивал Ли.

Как ледник, спускающийся с горы, Клайв медленно прокладывал путь сквозь код. Неделю за неделей он постепенно нарабатывал понимание. А потом пришло письмо от Брэндона: «Надо встретиться».

«Вы мешаете команде разработчиков», — сказал он.

«Что?»

«Вы с Ли слишком шумите. Это офис, а не клуб по интересам».

«Это безумие. Я просто задавал ему вопросы по работе! Вы что, хотите, чтобы мы устраивали планёрки в конференц-зале, чтобы просто задать вопросы?»

«Да».

Брэндон замолчал и снова начал пристально смотреть на Клайва. Он смотрел, и смотрел… Клайв понял намёк и сбежал в свой кубикл.

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

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

А затем пришло письмо от Брэндона: «Надо встретиться».

«Кэрол говорит, что ты на неё давишь», — сказал Брэндон.

«Что?»

«Ты шлёшь ей письма, даже после того, как она отвечает на вопросы. Она сказала, что ты созвал совещание, а сам на него не явился. Это нужно прекратить».

«Это безумие. Она ни разу не ответила, я могу показать свои входящие и доказать это».

«Кэрол не пользуется электронной почтой», — объяснил Брэндон. «Интерн распечатывает её электронные письма, а она отвечает по внутренней почте офиса. Она очень занята. У тебя есть техническое задание. Выполни его, и перестань ей докучать».

«Что? Вы хотите чтобы я реализовал решение, даже не поговорив с пользователем, который знает все требования?»

Брэндон посмотрел на него. Он продолжал смотреть и…

С помощью Ли за следующие несколько месяцев Клайву удалось добиться серьёзного прогресса. Он научился справляться с абсурдным форматом дат (даты отсчитывались как количество дней с 3 апреля 1974 года, а ещё как количество месяцев с предыдущего понедельника, а ещё как количество недель со следующего воскресенья). Они справились с тем, что никому нельзя обновлять Firefox до версий выше 3, и с тем, что нельзя было работать сверхурочно, потому что все серверы отключались ровно в 18.00. Кэрол не поддерживала связь, Брэндон просто смотрел на них, а остальные сотрудники обращались с ними, как с прокажёнными.

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

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

От уровня высшего руководства поступили комментарии в духе «Не важно, сколько придётся потратить». Бухгалтеры предупреждали: «Если мы пойдём на это, у нас не останется денег на премии в конце года!»

Брэндон оставил собственное примечание: «Наш бизнес слишком специфичен. Они не справятся. Пустая трата денег. Они не справятся».

Всё встало на свои места. Брэндон не предсказывал, а давал обещание. И он его исполнит — за следующие несколько недель Клайв и Ли никак не смогут сделать то, что было обещано изначально.

Вскоре он получил письмо от своего рекрутёра. «Этой компании по-прежнему нужен дополнительный персонал. Хотите продлить договор ещё на шесть месяцев?»

Наученный Брэндоном, Клайв ответил коротко: «Нет».

История вторая. Отбросьте всё невозможное…


[Оригинал]

Отбросьте всё невозможное…
…и то, что останется, будет XML, каким бы невероятным он ни оказался.


Уильям Хогарт, «Абсурдная перспектива»

У разработчиков есть множество слабостей, и одна из них такова: они не любят говорить, что чего-то нельзя сделать. Именно поэтому когда клиент Гленна М, компания TelCo, спросила, будет ли её задача действительно невозможной, он вместо того, чтобы извиниться и решительно покачать головой, ответил: «Ну, теоретически…»

В результате Гленн написал это:

<value>
    <mult>
        <op>
            <mult>
                <op>
                    <add>
                        <op>
                            <div>
                                <op>
                                    <bitwise_and>
                                        <op>
                                            <baseNToInt base="16">
                                                <regex>
                                                    <op>(?:0x)?([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])</op>
                                                    <op><getRowOid>.1.3.6.1.4.1.2011.2.217.1.4.1.1.6</getRowOid></op>
                                                    <op>%4$s%3$s%2$s%1$s</op>
                                                </regex>
                                            </baseNToInt>
                                        </op>
                                        <op>8388607</op>
                                    </bitwise_and>
                                </op>
                                <op>8388608</op>
                            </div>
                        </op>
                        <op>1</op>
                    </add>
                </op>
                <op>
                   <left_shift>
                       <op>1</op>
                       <op>
                           <sub>
                               <op>
                                   <bitwise_and>
                                       <op>
                                           <right_shift>
                                               <op>
                                                   <baseNToInt base="16">
                                                       <regex>
                                                           <op>(?:0x)?([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])</op>
                                                           <op><getRowOid>.1.3.6.1.4.1.2011.2.217.1.4.1.1.6</getRowOid></op>
                                                           <op>%4$s%3$s%2$s%1$s</op>
                                                       </regex>
                                                   </baseNToInt>
                                               </op>
                                               <op>23</op>
                                           </right_shift>
                                       </op>
                                       <op>255</op>
                                   </bitwise_and>
                               </op>
                               <op>127</op>
                           </sub>
                       </op>
                   </left_shift>
               </op>
           </mult>
       </op>
       <op>
           <if>
               <op>
                   <eq>
                       <op>
                           <bitwise_and>
                               <op>
                                   <right_shift>
                                       <op>
                                           <baseNToInt base="16">
                                               <regex>
                                                   <op>(?:0x)?([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])\s*([0-9a-fA-F][0-9a-fA-F])</op>
                                                   <op><getRowOid>.1.3.6.1.4.1.2011.2.217.1.4.1.1.6</getRowOid></op>
                                                   <op>%4$s%3$s%2$s%1$s</op>
                                               </regex>
                                           </baseNToInt>
                                       </op>
                                       <op>31</op>
                                   </right_shift>
                               </op>
                               <op>1</op>
                           </bitwise_and>
                       </op>
                       <op>1</op>
                   </eq>
               </op>
               <op>-1</op>
               <op>1</op>
           </if>
       </op>
   </mult>
</value>

Это XML DDF, Data-Definition File для приложения мониторинга систем, которое поддерживал Гленн. Видите ли, система может считывать данные из любого устройства с поддержкой протоколов Modbus/TCP или SNMP, если у него есть файл DDF, задающий точки данных и способ их отображения. Схема DDF содержит показанные выше арифметические, булевы, регулярные и условные операторы, необходимые для того, чтобы система знала, как нужно очищать данные.

Логика на XML? Да, это само по себе WTF. Но настоящий WTF в том, что показанный выше код делает. Компания мониторила устройство, выводившее значения температуры (это решить довольно просто), но оно представляло их в виде строки из восьми символов, обозначающей шестнадцатеричное значение 32-битного числа с плавающей запятой в формате IEEE–754. Задача, которую Гленн должен был сразу признать невозможной, заключалась в преобразовании этого безумного значения в численный вид. В DDF, несмотря на всю его выразительную силу, не было оператора приведения типов.

Поэтому Гленн написал показанный выше DDF, который делал следующее:

  • Так как порядок байтов был перевёрнут, используем <regex>, чтобы вернуть их обратно в порядок, с которым можно работать
  • Используем <baseNtoInt>, чтобы выполнить преобразование из octetString в uint_32
  • Используем <bitwise_and> и <right_shift>, чтобы отделить знаковый бит, экспоненту и мантиссу
  • Преобразуем знаковый бит в +/–1
  • Устраняем смещение экспоненты, а затем выполняем <left_shift> 1 на эту величину
  • Делим мантиссу на 223
  • Перемножаем три последних элемента, чтобы получить готовый результат. Так как <mult> и другие операторы принимают только 2 операнда, действия должны быть вложенным

В следующий раз Гленн решил придерживаться той же тактики, потому что недавно услышал, что TelCo модифицирует устройство, чтобы оно выводило температуру в integer.

История третья. Команда А(нти)


[Оригинал]

В 1980-х снимался телесериал под названием «Команда А». В нём был проныра, способный обмануть кого угодно. Чтобы получить нужное, он давал как бы правдивые обещания (похоже на маркетинг?) Ещё в команде был крутой парень, который мог запугать любого, чтобы добиться своего. Он знал, как достичь цели, но в глубине души был добрым парнем. У команды был лидер, способный всегда придумать план и спасти ситуацию. И ещё был один немного безумный (но в хорошем смысле) парень, на которого всегда можно положиться в бою. Время от времени в команде появлялся помощник, занимавшийся вмешательством и разведкой. Эта группа ребят работала как хорошо смазанный механизм. Они не могли потерпеть неудачу! Они были командой!


Команда «А» ни разу не писала документации по методологии управления проектом. Неудивительно, что все они — находящиеся в розыске уголовники.

Alex получил работу в новом проекте по замене устаревшей и неподдерживаемой системы. Естественно, заявлялось, что компания «стремится всё делать правильно!» Проект полностью финансируется. У команды будет нужное оборудование и персонал, необходимый для выполнения работы. Есть полная поддержка шести уровней руководства плюс всех пользователей. Алекс был настроен оптимистично.

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

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

Начало проекта было хорошим.

Первой задачей был найм разработчиков для команды. Для этого компания искала (очень далеко) офшорных специалистов, чтобы найти максимально дешёвых талантов. В конце концов, каждого ведь можно обучить, правда? Команда из 11 разработчиков в сумме имела 13-летний опыт работы, а для управления ими был нанят лид с пятилетним опытом.

Следующим важным решением был выбор используемой базы данных. В компании активно использовались три. Так как все базы данных хранились на централизованных серверах, одну из них исключили сразу: оборудование серверов БД было чрезмерно мощным для обработки ожидаемой в пределах разумного периода времени нагрузки. Из оставшихся двух одну активно использовали все члены команды. Они знали её синтаксис, странности и ограничения. Третья была настроена неверно, а потому имела репутацию ненадёжной. Тем не менее, она тоже была корпоративным стандартом. Несмотря на возражения команды, была выбрана третья.

Руководство проекта решило, что отдел QA можно привлечь позже.

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

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

Настало время для проверки первого спринта, и во время обзора кода обнаружилось множество всевозможных «красных флагов». Лид джуниоров заявил, что архитектурная документация была всего лишь рекомендацией, которую он проигнорировал в угоду желаниям разработчиков. Естественно, это вызвало реакцию типа «да вы что, совсем там ...?» и кучу писем вверх по цепочке руководства. Но менеджера проектов и руководство выше это, похоже, не интересовало: они сказали, что джуниоры не должны были этого делать, но они им доверяют.

Работа продолжалась. Архитекторы продолжали указывать на несовместимые с требованиями изъяны и дефекты реализации. Все предложения игнорировались, потому что лид офшорной команды заявил: «Google взращивает атмосферу инноваций и творчества; нам тоже нужно так делать!» Ему напомнили, что Google — это (по большей частиi) «мозговой центр», а лид работает с со строго нормированным проектом в строго регулируемой отрасли. Что архитектура, которую одобрили сорок с лишним менеджеров, — это не вариант или рекомендация, а требование. Что это не детский сад, где поощряется творчество — нужно придерживаться утверждённого плана! Мы же говорим не о том, как правильно писать подпроцедуру или инкапсулировать объект, а о том, что поточная обработка использована неверно и не в тех местах, а доступ к базам данных и межпроцессные коммуникации не будут масштабируемыми. Что нужно не создавать множественные процессы, а просто использовать потоки. Что не стоит использовать файлы в качестве семафоров просто потому, что так делали в школе. И список будет ещё будет долгим.

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

В реальном мире, если студент думает, что учитель ошибается, то ему не изменяют оценку. Стажёр режет там, где показывает ему хирург, и никак не наоборот. Генерал не обсуждает стратегию с рядовыми. Если ты вступаешь в профсоюз, и на правах новичка требуешь тех же полномочий, что и руководители профсоюза, то упокоишься рядом с Джимми Хоффа [американский профсоюзный лидер, неожиданно исчезнувший при загадочных обстоятельствах]. Опыт говорит с восклицательными знаками. Неопытность говорит с вопросительными.

Но не в этой «команде».

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

Примерно в это время руководство уступило просьбам и попросило нескольких коммерческих пользователей написать тесты коммерческого уровня (например электронную таблицу, которую можно передать в JBehave для JUnit-тестирования). Разработчики предоставили код и несколько простых примеров в электронных таблицах. Архитекторы сказали, что нужно нанять сотрудников отдела QA, потому что пользователи редко знают, как работать с пограничными случаями, проблемами точности и т.д. Но деньги нельзя было тратить. Спустя шесть месяцев работы коммерческие пользователи заявили, что все тесты для всего приложения (т.е. всё техническое задание) готово. Только взглянув на него, можно было понять, что они не учли пограничные случаи, пустые примеры, проблемы точности и большинство других аспектов, которые обычно требуют тестов. На самом деле, они поместили все записи, которые могли теоретически (с их точки зрения) существовать, в один огромный тест прохождения-непрохождения (pass-fail test). Разумеется, когда что-то изменится и неизбежно произойдёт сбой, нет никаких возможностей узнать, где он случился.

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

Министерство сэкономило кучу денег благодаря использованию дешёвого труда, отсутствию отдела QA и применению «политически целесообразной» базы данных. Разумеется, весь код в части настройки, написанный офшорной командой, был ужасен. БОльшая его часть была сложна в изучении, поддержке, отладке и улучшении.

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

История четвёртая. Передать через нулевой указатель


[Оригинал]

У Максин возникли проблемы с отображением веб-сайта в браузере с дополнением NoScript. Это неудивительно — некоторые браузеры не могут правильно работать с NoScript, но удивляло то, что браузер выдавал исключения Java. При включении JavaScript страница с ошибкой пропадала, но как же так? Отсутствие JavaScript приводит к исключениям Java!?

Она открыла код страницы и обнаружила, что сервер ожидает параметр «innerCHK»; вероятно, какой-то токен сессии или безопасности, который должен передаваться в строке запроса URL. Если его нет, то сервер возвращает страницу с ошибкой, отображающей java.lang.NullPointerException. К счастью, разработчики фронт-энда придумали следующий великолепный фрагмент JavaScript для решения проблемы:

// Проверка ошибки
if (document.body.innerHTML.indexOf('java.lang'+'.NullPointerException') != -1){   
   if (document.location.href.indexOf('innerCHK=') == -1){    
        document.location.href = document.location.href + "&innerCHK=" + Math.random()*10000 ;
   }
}
// Конец проверки

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

<script type="text/javascript">
    if (typeof dojo== "undefined") {        
        document.writeln('<scr'+'ipt src="' + '/wps/themes/./dojo/portal_dojo/dojo/dojo.js' + '"></scr'+'ipt>');
    }
    if (typeof dijit == "undefined") {      
        document.writeln('<scr'+'ipt src="' + '/wps/themes/./dojo/portal_dojo/dijit/dijit.js' + '"></scr'+'ipt>');
    }      
 </script>     
 <script type="text/javascript">     
 dojo.require("dijit.form.Button");
 dojo.require("dojo.cookie");
 //dojo.require("dijit.form.DropDownButton");
 dojo.require("dijit.Dialog");
 dojo.require("dijit.form.TextBox");
 dojo.require("dijit.form.CheckBox");
 dojo.require("dijit.form.ComboBox");
 </script>

Некоторые, если не все ссылки на CSS обрабатываются таким образом. Я нашёл в исходниках как минимум три (идентичных) вхождения. Повторюсь Dojo.cookie импортируется.

<script name="DojoEnable_script" language="JavaScript">if (typeof dojo == "undefined") {
    dojo.require("dojo.cookie");
    dojo.require("dojo.parser");
    djConfig = { parseOnLoad: false, isDebug: false};
    document.write("<script src='http://www.****************.com:80/ps/PA_WPF/factory/dojo/dojo/dojo.js'> </" + "script>");
    document.write("<link rel='stylesheet' type='text/css' href='http://www.****************.com:80/ps/PA_WPF/factory/dojo/dojo/resources/dojo.css' />");
    document.write('<link rel="stylesheet" type="text/css" href="http://www.****************.com:80/ps/PA_WPF/factory/dojo/dijit/themes/tundra/tundra.css"/>');
    document.write('<link rel="stylesheet" type="text/css" href="http://www.****************.com:80/ps/PA_WPF/factory/dojo/dijit/themes/tundra/tundra_rtl.css"/>');
    dojo.addOnLoad(function() { if (!document.body.className) document.body.className = 'tundra'});
}


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

var locale = 'en'.replace(/_/, '-').replace(/iw/, 'he').toLowerCase();

Дети, помните, всегда используйте продуманные названия для констант! Волшебные числа — это плохо. Кроме 2008, с ним всё в порядке.

if(typeof (MONTHS_IN_YEAR) == 'undefined')
{
    MONTHS_IN_YEAR = 12;
}

if (typeof (isDisableDate) == 'undefined') {
    var isDisableDate = function (date, year, month, iday)
    {
        if(date.getFullYear() == 2008)
        {
            return true;
        }
        return false;
    }
};

Никакое плохо реализованное приложение не будет завершённым без своей плохо реализованной библиотеки DateTime. Должен быть какой-то научный закон по этому поводу.

var month=new Array(12);
month[0]="1";
month[1]="2";
month[2]="3";
month[3]="4";
month[4]="5";
month[5]="6";
month[6]="7";
month[7]="8";
month[8]="9";
month[9]="10";
month[10]="11";
month[11]="12"; 

if(typeof (MONVALUE) == 'undefined')
{
    MONVALUE = new Array
    ("Jan",
     "Feb",
     "Mar",
     "Apr",
     "May",
     "Jun",
     "Jul",
     "Aug",
     "Sep",
     "Oct",
     "Nov",
     "Dec");
}

//функция преобразует дату из str в Num
function Month2Num(month)
{
    if(month=="JAN")return "01";if(month=="FEB")return "02";if(month=="MAR")return "03";if(month=="APR")return "04";if(month=="MAY")return "05";
    if(month=="JUN")return "06";if(month=="JUL")return "07";if(month=="AUG")return "08";if(month=="SEP")return "09";if(month=="OCT")return "10";
    if(month=="NOV")return "11";if(month=="DEC")return "12";
}

В коде задано множество (часто дублирующихся) CSS-стилей, например это не очень синий стиль.

div.wpfThemeBlueBackgroundPanelTable  
{ 
    background: #F6F9FC; 
    padding: 10px; 
} 
/*---- Синяя панель ----*/ 
table.wpfThemeBlueBackgroundPanelTable  
{ 
    background: #F6F9FC; 
    padding: 10px; 
} 

Далее идут два идентичных массива, из которых используется только один.

var arr_location_001 = new Array();
var arr_location_002 = new Array();

arr_location_001['AU'] = {value:'AU', title:'australia', text:'Australia'};
arr_location_002['0'] = {value:'AU', title:'australia', text:'Australia'};

arr_location_001['CA'] = {value:'CA', title:'canada', text:'Canada'};
arr_location_002['1'] = {value:'CA', title:'canada', text:'Canada'};

arr_location_001['CN'] = {value:'CN', title:'china', text:'China'};
arr_location_002['2'] = {value:'CN', title:'china', text:'China'};

arr_location_001['FR'] = {value:'FR', title:'france', text:'France'};
arr_location_002['3'] = {value:'FR', title:'france', text:'France'};

arr_location_001['HK'] = {value:'HK', title:'hong_kong', text:'Hong Kong'};
arr_location_002['4'] = {value:'HK', title:'hong_kong', text:'Hong Kong'};

/*  фрагмент множества строк аналогичного кода */

Вот ещё один красивый маленький скрипт, которому удалось избежать многого в том числе собственных тэгов сприпта! Разработчик получает бонусные очки, если писал его в смирительно рубашке, заколоченный в заполненный скорпионами гроб, свисающий с вертолёта над Гудзоном. (На самом деле это может быть самым логичным объяснением бОльшей части кода.)
<SPAN name="onloadScript"><input type="hidden">function onSelectInfo(calendar, date, elem_date) { 
    elem_date = document.getElementById(&quot;Day_NArr&quot;);       
    elem_mon_year = document.getElementById(&quot;Month_NArr&quot;); 
    hidden_elem = document.getElementById(&quot;temp_date_NArr&quot;); 
    doOnSelect(calendar, date, elem_date, elem_mon_year, hidden_elem); 
} 

Calendar.setup( 
    { 
        inputField : &quot;temp_date_NArr&quot;,// ID of the input field 
        ifFormat : &quot;%b, %e, %Y&quot;, 
        onSelect: onSelectInfo, 
        range : [currentYear, nextYear], 
        dateStatusFunc : dateStatusHandler, 
        button : &quot;cal_dep&quot; // ID of the button 

    } 
); 

var _InfoVerAccurateFunc = clone(accurateDate); 
initializeDate(new Array('Month_NArr', 'Day_NArr'), _InfoVerAccurateFunc);">
<script type="text/javascript">

Помните все импортированные выше dojo.cookie? Кто-то добавил библиотечную функцию для считывания куки. Тем не менее, кто-то другой посчитал необходимым написать скопипастить из Интернета функцию Get_Cookie, но не один раз, а четырежды!

function Get_Cookie(check_name) {
    // first we'll split this cookie up into name/value pairs
    // note: document.cookie only returns name=value, not the other components
    var a_all_cookies = document.cookie.split( ';' );
    var a_temp_cookie = '';
    var cookie_name = '';
    var cookie_value = '';
    var b_cookie_found = false;

    for (i = 0; i < a_all_cookies.length; i++) {
        // now we'll split apart each name=value pair
        a_temp_cookie = a_all_cookies[i].split( '=' );


        // and trim left/right whitespace while we're at it
        cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');

        // if the extracted name matches passed check_name
        if (cookie_name == check_name) {
            b_cookie_found = true;
            // we need to handle case where cookie has no value but exists (no = sign, that is):
            if ( a_temp_cookie.length > 1 ) {
                cookie_value = unescape(a_temp_cookie[1].replace(/^\s+|\s+$/g, ''));
            }
            // note that in cases where cookie is initialized but no value, null is returned
            return cookie_value;
            break;
        }
        a_temp_cookie = null;
        cookie_name = '';
    }
    if (!b_cookie_found) {
        return null;
    }
}

Думаю, что метод разработки ПО, использованный этой командой — это новоиспечённая система под названием «Долби по клавишам CTRL-C и CTRL-V keys!!!!!!11!!!!11!11». Уверен, что в ближайшее время она заменит Agile.

История пятая. Люди-роботы


[Оригинал]

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

Гендиректор SuperbServices, Inc дал Роланду серьёзную задачу, которая могла спасти компанию, или, по крайней мере, умственное здоровье сотрудников. «Нам нужно автоматизировать всю обработку, чтобы мы могли сосредоточиться на предоставлении услуг», — сказал CEO. «Самое ценное у нас — это наши услуги, а всё остальное — ненужная возня. Нужно её автоматизировать, этим ты и займёшься. Ты будешь сотрудничать с людьми-роботами, чтобы автоматизировать всё: подтверждение операций, покупки, транзакции средств, электронную почту клиентов — всё!»

«С… людьми-роботами?»

У Роланда появилась новая фобия. Серверный отдел компании был необычно эксцентричным, даже по стандартам отрасли. Лишь немногие видели его сотрудников, а до Роланда доносились только странные слухи.

Когда Роланд постучал в дверь серверной, камера наблюдения над его головой ожила и бесстрастный голос робота спросил его: «ЧТО ТЫ ИЩЕШЬ?»

«Э-э-э, привет… Я Роланд, менеджер проекта. Вроде мы с вами, ребята, или кто вы там, должны автоматизировать задачи?»

«ЧТО ТЫ ПРЕДЛОЖИШЬ НАМ?», — ответил зловещий металлический голос.

«Ну-у-у… Роботы любят пончики? У меня с собой. Это всё, что есть».

«ПРОХОДИ». Электронный замок на двери с щелчком открылся. Роланд потянул дверь на себя, и из-за неё вырвался ледяной воздух. Внутри мерцали тысячи огоньков и стоек, рядом был небольшой столик. Над столиком мигала лампочка. «ПОЛОЖИ ПОДНОШЕНИЕ СЮДА», — сказал из тени автомат.

Роланд положил коробку с пончиками на стол и медленно отошёл. Из-за его спины раздался крик «СПАСИБО!». Роланд подпрыгнул. Тщедушный человек с модулятором голоса захихикал и взял пончик.

«Привет, Роланд, я Рой», — сказал он, отложив модулятор. «У нас бывает не так много посетителей, поэтому мы любим над ними поиздеваться. Ну чё, тебе нужно что-то автоматизировать?»

Роланд был удивлён тем, насколько обычным оказался Рой, а не тем, что он на самом деле человек. «А, да, гендиректор сказал, что вы много работаете с роботами, и что мы можем использовать их для автоматизации всего, что сможем», — Роланд изложил требования и спецификации.

Рой вздохнул. «Жаль тебя разочаровывать, но мы на самом деле не имеем ничего общего с роботами, что бы там гендир ни думал. Он настаивает, что Ruby — это имя робота, хотя это просто язык программирования. Но большинство этих процессов мы сможем автоматизировать. Дай нам две недели, столько пончиков и энергетических напитков, сколько мы можем потребить, и мы с этим справимся».

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

«Роланд, проделана мастерская работа, теперь эти машины дёргают за рычаги, приводящие в движение наш бизнес», — похлопал его по спине гендиректор. «Твоей наградой будет новый проект — ещё один проект с максимальным приоритетом. Наш финансовый отдел не справляется с объёмом транзакций. Я поговорил с финансовым директором и она хочет, чтобы вы с людьми-роботами автоматизировали новый купленный нами продукт — MoneyWorx. Не подведи меня!»

Роланд вернулся в серверную. Если предыдущий проект по автоматизации был таким простым, то и тут всё будет так же, правда? Он показал Рою требования.

Загоревшая в свете мониторов кожа Роя стала ещё бледнее. «Это плохо, очень плохо! Опасно!», — закричал Рой. Он в ужасе размахивал руками. «Это не MoneyWorx, а MoneyDoesntWorx. Даже когда сервисы работают, они требуют токенов RSA-SecurID. Кто-то должен вручную вводить код».

«Вручную? То есть чтобы система работала, кто-то должен быть на линии?»

«Да. У нас используется три токена SecurID, поэтому обычно, когда MoneyWorx просит новый код, „на линии“ должны быть три человека».

«Так что это не сработает. Если для проведения транзакций людям придётся принимать ночные звонки, то гендиректор будет в ярости. А он уже подписал договор — мы должны сделать так, чтобы система работала без звонков людям».

Рой открыл энергетик и содрогнулся. «Дай мне выходные, я над этим подумаю. И мне понадобится больше этого», — он потряс банку. «А ещё этого», — он показал на коробку пончиков.

Роланд появился к выходным и положил свои подношения. Рой принял их, но ничего не сказал. Люди-роботы были слишком заняты, чтобы болтать. Когда настал понедельник, Роланд вернулся с новыми пончиками и энергетическими напитками. «Эврика!», — воскликнул Рой, когда Роланд зашёл. «Позволь продемонстрировать тебе мой шедевр!»

«Шедевром» была пустая коробка из под пончиков, поставленная набок. Токены RSA-SecurID были приклеены к коробке. Вся система была установлена перед дешёвой веб-камерой. «Когда MoneyJerks будет требовать новый двухфакторный токен, кто-нибудь может удалённо зайти на эту машину, проверить токены и ввести правильный код».

«Не думаешь, что эту коробку нужно защитить паролем?», — предложил Роланд.

«Думаешь, что пользователи запомнят, как логиниться?», — возразил Рой.

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

«Это потрясающе! Они навели свои роботические глаза на эти фиговины, и теперь никому в компании не нужно говорить MoneyWorx, что нужно делать! Великолепная работа, Роланд. Поздравь от меня людей-роботов. Их робот — просто чудо».

Роланд был счастлив, что работа завершена, но не мог избавиться от чувства, что совершил ужасную ошибку. Финансовая безопасность компании оказалась в руках дешёвенькой веб-камеры без всякой защиты, за исключением отсутствия публичного IP-адреса.

image

Когда катастрофа наконец разразилась, она не приняла форму мошеннической транзакции. Поздно ночью в среду коробка от пончиков упала. Роланду пришлось звонить Рою, который поехал в офис и поставил коробку на место. Проведя анализ причин сбоя, Рой прилепил коробку к серверной стойке изолентой, обеспечив таким образом постоянный аптайм.

Комментарии (26)


  1. kaljan
    26.01.2019 08:49
    +10

    коробка с ключами это, конечно, сильно)


    1. vassabi
      26.01.2019 09:16
      +2

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

      Возможно, я слишком долго вращался в «кровавам интерпрайзе», но истории №2 и №3 — это обычные жизненные истории (отгородиться от идиотизма и сделать работающее решение), из разряда «а что тут такого, все логично ?»
      №1 — это конечно патология.
      А №4 — это один-в-один проект с моего прошлого места работы. Только у них лапша в коде одного языка — javascript, а не CGI через SSI, с включением скриптов на awk, shell, tcl::tk и внезапно С…
      (UPD: а увидел я это добро потому что «ну да, ты у нас по контракту на С# пишешь, но ты же справляешься классно, помоги там ребятам лигаси код посапортить». Прихожу, и что я вижу: ребятам пришло уведомление, что у них XSS+CSRF+SQL\HTML\* injection-ы и т.д. и т.д. А у ребят все ходят под рутом везде, и в БД, и файлы ап\даун-лоадить можно любые. И пароли — все хранятся в открытом виде. В общем — придумайте любое секьюирити нарушение, там оно будет.… еле оттуда сбежал ....)


      1. mk2
        26.01.2019 09:40

        3 — это скорее «отгородиться от идиотизма бюрократии и сделать свой идиотизм»


        1. vassabi
          26.01.2019 09:58

          по крайней мере — у них идиотизм рабочий. У бюрократии — когда как.
          Я уже столько раз сталкивался, что проще посмотреть в код (который годами уже работает и используется в продакшене людьми), чем найти документацию по той же функциональности (которая была написана 10 лет тому назад двумя-пятью людьми, согласована и внесены правки на совещании аналитиков, все уже уволились\на пенсии, архив частично оцифрован и т.д.)


          1. mk2
            26.01.2019 10:02

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


            1. vassabi
              26.01.2019 10:46

              ну, посмотреть на утвержденную документацию я бы хотел только для бюрократических игр (на ее основе гораздо легче продавливать решения — она же утвержденная! или топить существующую систему — потому что любые разночтения можно толковать в пользу «срочно переписать» и т.д.)
              А так — спрашиваешь у людей: «как вы работаете с Х?», они описывают «сюда файлик, тут вчерашняя дата, а там — нажимаю кнопочку и вуаля — отчетик в пдф, я из него табличку копирую на 10й странице». Потом идешь и выписываешь формулы — что хочет та табличка, когда ее код печатает, куда-откуда берет данные и т.д. При таком подходе можно быть хоть как-то уверенным, что будет рабочая часть документации.


    1. snp
      26.01.2019 10:30

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


      1. vassabi
        26.01.2019 10:39
        +1

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


        1. SADKO
          26.01.2019 11:24
          +1

          Так точно, эти и предыдущие SecureID код постоянно показывают, я некогда их сам сотрудникам раздавал, вещь хорошая, хотя и не дешевая…
          … а ещё я её пилил, сначала интереса ради, но потом этот интерес оказался не праздным, из сигналов управления сегментным жк индикатором, микроконтроллер восстанавливал цифры и отправлял их в КОМпот ;-) И я знаю ещё как минимум двух людей, которые «распознавали» цифры с индикатора через вэб камеру…



      1. Zenitchik
        26.01.2019 18:22

        Нажимать сервомашинкой?


    1. dzsysop
      26.01.2019 20:11

      А если вот так? ;-)


  1. Sirion
    26.01.2019 09:29
    +3

    А что во второй истории странного-то? Ручное преобразование float в int средствами имеющегося DSL? А если автору истории показать Fast Inverse Square Root из Quake, он в обморок упадёт?


    1. JC_IIB
      26.01.2019 12:23
      +1

      Я вот тоже не понял. Конфиг того же FreeSWITCH пишется (в одном из вариантов) на чистом XML, и да, там есть логика. Условия, переходы, присваивания и вот это всё. Сколько лет работаю — никакого WTF от этого не ощущаю.


  1. aleksandros
    26.01.2019 09:34
    +1

    Я не очень понял первую историю. Какой смысл платить рекрутинговой компании огромные деньги, а потом не давать работать сотрудникам в полную силу?


    1. kaljan
      26.01.2019 09:38
      +4

      подковерные игры?


      1. aleksandros
        26.01.2019 10:08

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


    1. vassabi
      26.01.2019 09:53
      +8

      самое простое: потому что платит — один, а не дает работать — другой (который уверен, что вы его «подсидите»).


    1. immaculate
      26.01.2019 10:45
      +2

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


    1. s-kozlov
      26.01.2019 11:09
      +1

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


      1. SurfCalavera
        26.01.2019 13:51

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

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


    1. Polaris99
      26.01.2019 15:12

      Кто-то близкий руководству в доле с рекрутинговой компанией? Берут, грубо говоря, с фирмы 100 тыщ за услуги, наемному сотруднику платят 30, остальные 70 делят пополам. Ну или какие-то друзья-родственники, чего ж хорошему человеку не помочь заработать?


  1. mayorovp
    26.01.2019 09:53

    Думаю, что метод разработки ПО, использованный этой командой — это новоиспечённая система под названием «Долби по клавишам CTRL-C и CTRL-V keys!!!!!!11!!!!11!11». Уверен, что в ближайшее время она заменит Agile.

    Новая? Её еще в 2013м году на русский перевели: https://habr.com/ru/post/203646/


  1. BalinTomsk
    26.01.2019 21:17

    В те далекие времена когда MSSQL server (< 2008) позволял писать extended Stored Procedure на C++ и они исполняйлись в основном потоке у меня возникла необходимость читать SRVDECIMAL (эдакий псевдотип для передачи чисел) как __int64.

    Никакое гугление не помогала, потому что кто в силен на SQL не пишет на C++ а кто силен в С++ не лезет к базам.

    Методом научного тыка пришел к следующему решению:

    __int64 DBDECtoi64( DBDECIMAL &val )
    {
        __int64 result = 0;
        for(int i = MAXNUMERICLEN-1; i >= 0; i-- )
        {
            if( val.val[i] > 0 )
            {
                result += val.val[i] * __int64(pow(256, (double)i ));
            }
        }
        return result * ( val.sign == 0 ? -1 : 1 );
    }


    1. vassabi
      26.01.2019 22:20

      кто силен в С++ не лезет к базам.
      да ладно вам — лезем, еще как лезем! (но редко и потихоньку)


    1. qw1
      27.01.2019 11:42

      __int64(pow(256, (double)i )
      1LL << (i*8), не?