...

суббота, 26 января 2019 г.

[лонгрид] 20 лет программистской карьеры в большом маленьком городе

We do what we must because we can — GLaDOS

Ижевск, наши дни


Первого февраля исполняется ровно два десятилетия, как я начал официально подвизаться в должности инженера-программиста:
Фото первого разворота трудовой книжки с датой 1 февраля 1999 года

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

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

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

Но вы ведь любите автобиографии с аутистическими шутками, не правда ли?

Ижевск, середина восьмидесятых


Началось всё давным-давно. Однажды матушка, работавшая программистом в обычном советском НИИ, занимавшемся реверсным инжинирингом образцов западной вычислительной техники, взяла меня на работу — показать, чего как, и дать поиграть. Недавно к ним пришли контрабандные «персоналки» — PC/AT, на которых запускались всякие диггеры и тетрисы.

До появления «персоналок» НИИ работал на советских ЕС (клон IBM S/360, занимает комнату) и СМ (клоны VAX, PDP-11 и др., обычно шкаф размером с пианино), так что дома у нас валялось некоторое количество бобин использованной перфоленты и распечаток с ассемблерными листингами. Ещё матушка периодически моталась в командировки с пакетами «блинов» — сменных накопителей на магнитных дисках весом этак килограммов по 15, боявшихся поездок в троллейбусах, — так что к экскурсии я был морально готов. Ожидал увидеть нечто, похожее на декорации тогдашних фантастических фильмов.

И увидел. Но мигающие лампочками и гудящие вентиляторами шкафы, в которых что-то крутится, впечатляли не так сильно, как «Диггер», пусть даже на монохромном экране. Как и софт для разводки печатных плат, рисовавший их на плоттере (смотреть, как чернильный маркер летает по листу формата А1 — это казалось просто каким-то волшебством), и бесконечные полотна знакомых уже ассемблерных листингов программ для станков с ЧПУ, над которыми тогда работала матушка.

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

А чтобы стать хорошим программистом, надо хорошо учиться. Первые три класса общеобразовательной школы я закончил с отличными оценками, и смог попасть в лицей с математическим уклоном. Вообще, на тот момент в городе было несколько школ с хорошим качеством обучения — 29, 30, 41 (лицеи), 56 (гимназия). Каждая — локальный брэнд и династический рассадник технической, гуманитарной, или управленческой, в зависимости от профиля, элиты. Особенно «тридцатка» — практически все местные заводские руководители (а Ижевск традиционно был городом-заводом) закончили именно её, причём, как правило, маткласс. Как, впрочем, и моя матушка (и она в итоге тоже стала руководителем, но об этом позже).

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

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

Я болтался где-то в районе шестого места, периодически забираясь выше, поэтому в периоды подготовки к олимпиадам частенько оказывался с остальными продвинутыми ребятами на задних партах, где мы вместо самостоятельных работ решали задачи+, задачи° и даже задачи**. По правде говоря, олимпиадным мышлением я не обладаю — хорошо вкапываюсь в суть проблем, но мыслю слишком медленно и всегда делаю несколько пробных подходов, так что выше городского этапа ни разу не продвигался. Ну, хотя бы поучаствовал, а результатом можно считать тренировку мозгов на нестандартные (но воспроизводимые, в отличие от решений типичных олимпиадников, которые обычно применимы строго ad hoc) решения.

Да и вообще, даже последний троечник по меркам элитного рассадника был бы отличником в обычной школе, и в универе мне это аукнулось… впрочем, по порядку.

По прошествии многих лет, оценивая свои возможности, я иногда начинаю сожалеть, что не выбрал вместо местного технического университета какой-нибудь столичный, — мои одноклассники успешно закончили и МФТИ, и МГУ (и некоторые в итоге работают в Гугле), — но это сожаление быстро проходит, когда я вспоминаю, как я учился в общеобразовательные семестры до «экватора». Никак не учился — школьного багажа, включая дополнительные курсы, которые вели те же вузовские преподаватели, было достаточно, чтобы получать «автоматы» почти по всем предметам. Впрочем, даже вступительные я не сдавал — студентом стал ещё в апреле, пройдя подготовительные курсы, и набрав высший балл на пробных экзаменах. Это было, как вы понимаете, несложно.

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

Только самообразование боем могло меня спасти, и на втором курсе я нашёл себе работу. Прямо тут же, на кафедре, сисадмином. Работа так себе, но зато она гарантировала неограниченный доступ к факультетским серверам и интернету, а также позволяла торчать в лабе хоть сутками напролёт — и тратить время не только на трёп в IRC с девочками из соседнего универа, но и кодить что-нибудь для всяческих случайных заказчиков. Минимальная зарплата в 1999 году была 83 рубля 49 копеек (зачем платить студенту больше?), а бутылка «Букета Чувашии» в «деканатском» магазине напротив учебного корпуса стоила 4.60, так что мы с другими такими же студентами-админами иногда скидывались на пару ящиков дешёвого пива для физрука, чтобы не тратить время на бессмысленные круги по стадиону.

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

В ижевском «механе» тоже нашлась своя олимпиадная команда, тоже на тот момент занимавшая далеко не последние места в международных соревнованиях, но мне показалось куда интереснее обеспечивать инфраструктуру для турниров, чем ломать на них голову. Тем более что зачёт полагался и так, и этак. Кроме совсем уж низкоуровневых и скриптовых языков, я не чурался и утилит для бухгалтерии на связке 1С и Visual Basic, и для деканата на FoxPro и Delphi, а для развлечения пописывал аплеты на Java для своего «хомяка» на факультетском сервере.

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

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

Лето 2001 года, Ижевск


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

К счастью, подвернулась конторка с говорящим названием «Виртуальный Ижевск». Микроскопическая веб-студия, но как тогда полагалось любой уважающей себя веб-студии — с каталогом региональных ресурсов, вебчат-сервером, и уймой проектов разного уровня маргинальности. Как вам, к примеру, онлайновый конкурс красоты «Мисс БикиНю-2002»? А если я скажу, что у него был закрытый раздел для спонсоров, с более интересными фоточками «моделей»?

Тот ещё гадюшник, короче. Зато, у директора конторки была идея фикс перевести все разработки студии с тормозного Perl на свежевышедший PHP версии 4, потому что где-то он прочитал, что за ним будущее (а ещё он банально быстрее). В общем-то, директор не ошибся. Продержался я там всего полгода, но хотя бы выучился сносно писать на PHP — причём, из-за специфики проектов приходилось заботиться о безопасности. Ломать их с целью накрутки голосов пытались серьёзно — спонсорские призы в конкурсах были достаточно неплохие, типа поездок на компанию друзей в Турцию, и девочки из того самого соседнего вуза собирали целые команды доброжелателей, готовых вывести их в топы.

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

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

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

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

Отдел мультимедиа выпускал презентационные диски для местных заводов (например, Ижмаша и СЭГЗ), а отдел интернет-технологий и был той самой веб-студией, регулярно бравшей призы на отраслевых выставках. Честно говоря, стена в директорском кабинете, увешанная дипломами от пола до потолка, внушала. В основном, клиентам, приходившим на предмет договориться о разработке очередного представительского сайта.

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

Срочно потребовался «ну хоть кто-нибудь» на замену, а я очень удачно оказался свободен. Матушка просто притащила меня за шкирку, бросила за консоль конторского сервера, и велела брать всё интернетовское в свои руки. Мультимедийный отдел точно так же оказался в руках сына маминой подруги, и мы как начали фигачить!

На самом деле, трудно представить более стрессовую ситуацию: прийти на место признанных профессионалов, подхватить на лету повисшие в воздухе чужие и совершенно незнакомые разработки, и делать вид, что ты способен вот это всё потянуть. Да ещё и как минимум, не хуже. Я плохо помню последующие несколько месяцев — с утра надо было посещать пары (слава богу, не все, часть предметов мне таки перезачли) в своём универе, потом тащиться через весь город в соседний, и там торчать до глубокой ночи. И выходные тоже торчать на работе. Ещё и тусоваться хотелось, а то, как же быть студентом, да обходиться без пьянок…

Потребовалось около полугода, чтобы прочитать все RFC по технологиям, все руководства к используемым языкам и платформам, разобрать все нагромождения унаследованного от старой команды кода (особенно, университетские сайты, написанные на жуткой смеси ранних версий Java и PHP с самописными расширениями), и параллельно вести разработку собственного программного базиса — на PHP only. Не только потому что новая метла должна мести по-новому, но хотя бы из гигиенических соображений: за десять лет работы старой команды в проектах, которые велись без какого-либо контроля версий, накопилось много мусора, и на свежий взгляд это было очень заметно.

Также стоит учесть, что на рынке в тот момент ещё не существовало сколько-нибудь развитых CMS, да и вообще, «рынком» тогдашнее положение дел в веб-разработке назвать было довольно сложно. Каждая контора писала что-то самобытное, с собственным зоопарком. Поэтому я обложился всеми доступными примерами опенсорсных «-нюков», и за последующее лето, в аккурат перед пятым курсом, собрал из запчастей своих форумных и конкурсных «движков» простенькую CMS, позволявшую разрулить сиюминутные задачи. Вдобавок, меня тогда пропёрло по теме UX, и, проштудировав все имевшиеся на тот момент HIG, я почему-то проникся майкрософтовским Wizards 97, идеологию которого и постарался максимально воплотить в интерфейсе, реализовав пошаговые мастера на все случаи жизни.

Поздняя версия CMS, мастер создания страницы сайта

Как ни странно, но шалость удалась. Я не только сумел успешно завершить аварийно повиснувшие проекты, — уже на собственном движке, — но и ускорил разработку в несколько раз. Более того, из-за необходимости тщательно задокументировать имевшиеся у нас проекты, у меня само собой возникло подробнейшее техническое задание на типовой представительский сайт для какого-нибудь типового завода — а также методичка для авторского сопровождения сайтов муниципальных образований, так как один из проектов был именно таким, а ФЗ с шаблоном тогда ещё не было.

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

Более того!

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

Но не будем отвлекаться на философию. В мультимедиа-центре, пардон, «Центре мультимедиа и Интернет-технологий Удмуртского госуниверситета» я проработал почти пять лет. Свой диплом для Ижевского гостехуниверситета я сделал целиком и полностью на примере внедрения экспериментальной версии своей CMS для муниципального сайта города Глазова (в будущем он тоже возьмёт награду премии Рунета в своей категории; проект жив до сих пор — в четвёртой версии). Это был первый в истории кафедры диплом на тему чего-то околоинтернетовского, поэтому на вкладыше тема значится как «Система управления континентом [sic!] сайта». Ну не знал ещё в 2003 году никто слова «контент», так что я не стал исправлять тот артефакт эпохи.

Мы быстро заполнили недостающее штатное расписание такими же студентами (ну может, не настолько целеустремлёнными и упёртыми, как я) — и превратились в этакую кузницу кадров. Многие ребята, которые начинали карьеру в моём отделе практикантами или дипломниками, теперь руководят собственными конторами и проектами, и являются вполне уважаемыми в городе специалистами. Я же, поставив разработку на поток, постепенно совершенствовал и сам процесс, и свою CMS (в частности, придумал собственный компилятор языка разметки для шаблонов, позволявший менять дизайн сайта на лету, не только стили, но и HTML, и скриптовую обвязку).

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

Мне пришлось не один раз поучаствовать в переговорах (особенно тяжело было разговаривать с муниципалами — и, господи, как сильно я удивился, когда в появившемся через время ФЗ нашёл прямые цитаты из своей методички; попала в руки законодателей она скорее всего через одного из наших муниципальных клиентов), разруливать конфликты интересов между членами команды, обучать пользователей, и даже пару раз браться за дизайн. Сам не заметил, как стал сеньором.

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


Однажды я забыл сбросить серверный кеш CSS после восстановления сайта из бэкапа, и кто-то из коллег по цеху мгновенно закинул скриншот с напрочь разъехавшейся вёрсткой на линч к Лебедеву. For lulz. Итог: один известный мем.

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

Для развлечения я однажды провёл совершенно бесчеловечный эксперимент. Была такая программа повышения квалификации сельских учителей информатики — когда со всех 25 районов республики их согнали по разным ижевским вузам, и прочитали им курс про основы сайтостроения. Естественно, я преподавал на примере своего движка, и не смог не воспользоваться такой прекрасной возможностью, чтобы провести A/B тестирование нескольких версий интерфейса на сотнях несчастных. Было весело, узнал несколько совершенно неожиданных вещей о паттернах поведения и восприятии пользователей (майкрософтовцы были ой как правы, когда переделали UI Офиса на контекстно-зависимый — он наиболее эффективен; также из интерфейсов совершенно точно НЕЛЬЗЯ убирать кнопку “Save” с дискетой).

Но, в конце концов, мне всё же стало скучно.

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

Было очевидно, что настало время поступить, например, как предыдущая команда, и выйти из здания. К сожалению, я не обладаю достаточными качествами, чтобы быть директором-коммерсом. Техническим — могу, но вот зачем? На вебе свет клином не сошёлся, да и прогресс на месте не стоит. Что было эксклюзивом в 2002 году, в 2006 уже мэйнстрим, и вообще, Битрикс свёл сайтостроение в ремесло.

А не двинуть ли в смежную область. Почему бы и да?

Только в какую?

Я ходил побеседовать в несколько разных контор. Не везде меня, страшного тролля с Ижайти, встречали с распростёртыми (троллей вообще мало кто любит), но выбор был достаточно богат. Можно было и в десктопный .NET пойти, и железяки программировать (правда, это на заводе, так что скорее нет, чем да). Но особенно мне понравилась идея разработки больших систем для кровавого энтерпрайза — только вот в этом мире рулила Java, а знание Java у меня было только на уровне тех самых аплетов с универского «хомяка», да лапшеобразного легаси, похороненного на прошлой работе. Ещё и версии 1.1.

Поэтому я принял предложение из местного телекоммуникационного провайдера Ижсвязьинвест (он был «мультисервисный», то есть, предоставлял интернет, телефонию, и телевидение; после кризиса 2008 года умер в агонии и был куплен Билайном, но на тот момент процветал).

Тут необходима небольшая ремарка про историю местных интернетов. Она довольно-таки отличается от типичной для России благодаря компании «Марк» (как бы она ни называлась за свою более чем двадцатилетнюю историю) — ещё с середины девяностых Ижевск очень сильно опережал и скоростями, и покрытием, и меньшей ценой не только все соседние регионы, но и Москву. Причём эпоха DSL-модемов сменилась эпохой меди, а потом и оптики до дома очень быстро, и быстрый интернет в Ижевске стал массовым, а сам этот провайдер явился отличнейшей кузницей технических и управленческих кадров для местной отрасли. Даже местная фидошная туса и то была в основном over марковский Ethernet.

Выходцы из «Марка» основали не одну конкурирующую контору, включая и Ижсвязьинвест. Притащили они с собой и биллинг — монструозную систему, работавшую внутри кластерного Oracle, причём, классического, поверх фирменного сановского железа и под солярой. Почти вся бизнес-логика этого хтонического чудища была зашита в чудо-хранимки, даже письма об оплате клиентам рассылались из асинхронного джоба. На Java (версии 1.4) был написан только небольшой кусочек админки, при том, что почти целиком он генерировался через System.out.println(), и выглядел… хреново он выглядел. На пользовательский же веб-интерфейс смотреть без слёз было нельзя (и не только потому что он был весь шрифтом 7.5pt).

Особенно, по сравнению с тем, во что к тому времени превратилась админка моей CMS. А была она HTA-приложением, неотличимым внешне от нативного, и даже не требующем наличия мыши, даром что писана на чистом JS/DHTML. Начальник отдела так впечатлился, что немедленно взял меня как специалиста по UX. Интеншен был прост — натянуть на хтоническое чудище биллинга красивый, быстрый, удобный, и нативный интерфейс, и продавать его другим провайдерам. По набору возможностей и в самом деле он, будучи разрабатываемым с середины девяностых, рвал все имеющиеся аналоги как тузик, но у конкурентов был вменяемый UI, а у TELSI его не было.

To put lipstick on a pig… Однако, нельзя так просто прикрутить интерфейс к логике, если у неё нет эндпоинтов для чего-либо, хотя бы отдалённо похожего на AJAX.

Слой эндпоинтов надо было написать, интегрировав в существующий код. Но в 2006 году Java 1.4 была уже совсем реликтом, так что перво-наперво я переписал имевшийся зародыш админки на актуальную версию с попутным причёсыванием и приведением кода в порядок (ага, опять пришлось копаться в унаследованной субстанции). По ходу изучил технологический стек J2EE, но ввиду специфики ограничился пока что сервлетами и всей сопутствующей обвязкой, а также JDBC (надо же хранимки дёргать), Velocity (как наиболее похожий на PHP шаблонизатор), и, собственно, core Java.

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

— Окей, — сказал начальник отдела.

Чтобы вытащить всё это дело на свет божий, мне потребовался год. Год! Объём кода был просто чудовищен. Я за раз могу запомнить около мегабайта исходников, а тут код только хранимок тянул на пять. Половина неиспользуемых, другая половина рекурсивно вызывают друг друга, и в целом сплетаются в такой клубок лапши, что распутать его без помощи начальника было решительно невозможно.

Работать приходилось шесть дней в неделю, по одиннадцать часов в день — остальные программисты сидели над поддержкой существовавшего монстра и не могли мне помочь. Итогом стал микроинсульт (это в 26-то лет, my ass!) и полгода на таблетках. Урок усвоил — больше с тех пор не перенапрягаюсь. Работа не сбежит, а здоровье вот может.

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

TELSI, редактор справочника АТСTELSI, конструктор представления справочникаTELSI, редактор записи справочника
Картинки кликабельные

Блин, но что-то мне это напоминает… Ах ну да, документ-порталы же. Что же получается, все информационные системы примерно на одно лицо, если смотреть в профиль? Ну и ладно.
Окей, сказал я. И за несколько месяцев реализовал документно-ориентированное хранилище поверх оракловых таблиц при помощи триггеров, хранимок, создающих вьюхи, и какой-то там матери. Получилось не очень быстро, но вроде заработало с полпинка. Пора было шалтая-болтая вводить в опытную эксплуатацию.

И тут в Ижевск пришёл кризис 2008 года


Хотя, он ко всем пришёл.

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

— Гуд бай, — сказал я. — Теперь я старик, — сказал я, — и я знаю кунг фу, — сказал я, — и я благодарен вам, что вы мне его показали, но я хочу практиковать своё кунг фу, а не ерундой страдать.

К сожалению, сделать было сложнее, чем сказать. В бушующий кризис с кровавым энтерпрайзом дела обстоят примерно так же, как и с TELSI (то есть, «отложено» на «потом»), но зато в новой экономической реальности западный бизнес в качестве одного из средств снижения расходов обратил внимание на российских аутсорсеров, и у них попёрло. Пришлось отложить на несколько лет энтерпрайз и заняться тем, что приносило деньги.

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

Основным направлением аутсорса была разработка геоинформационных систем, — то есть систем, которые что-нибудь делают с картографическими данными.

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

Ну как, «на замену». Нужен был ведущий разработчик на новый проект — тоже ГИС, только про спутниковое телевидение. SatBeams.com — жив до сих пор, можете посмотреть, только, пожалуйста, не обрушьте.

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

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

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

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

Вдвоём со вторым программистом справились где-то за год. Работает, — если вы её хабраэффектом сейчас не уронили, — до сих пор. Люблю я этот проект. И не только за бэкэнд, но и за satbar.js — последний интерфейсный виджет, который мне довелось написать (с тех пор я больше к пользовательским интерфейсам не прикасался).

satbar — переключалка спутников — сверху карты

Неожиданным препятствием стало рисование футпринтов транспондеров на Гуглокартах — Гугл вообще класть хотел своё толстое mojo на сторонние интеграции, и планомерно выпиливал из каждого нового релиза API карт 2.х фичу за фичей, в результате чего оное обросло невероятным набором костылей, и потребовало постоянной поддержки со стороны второго программиста после сдачи проекта в эксплуатацию, — а на третью версию API он его мигрировал, периодически уходя в депрессию. Зато заказчик хвалился фоткой рабочего стола ПМа Гуглокарт с ярлыком нашего сайта с подписью “nice polygons” — он тайком сделал её, когда был в гостях в тамошней штаб-квартире.

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

Я снова пошёл по накатанной, и собрал несколько сайтов, практически не приходя в сознание. От нечего делать обновил CMS интерфейсными наработками, притащенными с предыдущей работы — включая некое подобие Ribbon UI из свежего Офиса (по ходу, сделал это раньше, чем сам Майкрософт, но, конечно, попроще, хотя, волшебный одиночный ALT там тоже работал. Как-нибудь покажу, это достойно отдельного рассказа). Взялся даже за разработку десктопной базы на .NET+SQLite для одного государственного заказчика взамен легаси-решения на Firebird (или даже FoxPro? А, не помню, но на чём-то совсем древнем), но этот проект принёс с собой новый ПМ, претендовавший на роль техдира, и нам не удалось договориться по срокам. Так что закопаться в десктопный стек .NET мне не удалось.

И тут мне вдруг написал бывший студент, когда-то делавший у меня в отделе диплом (те самые «документ-порталы»). Он за прошедшие пять лет вырос в руководителя собственной конторы, по удачному стечению обстоятельств занимавшейся кровавейшим энтерпрайзом — SAP, и ещё немножко разработкой на Java EE.

Вот так удача. Ему как раз требовался эксперт для помощи над новым проектом.

Суть (и вообще, смысл) этого проекта объяснить сложновато. Я до сих плохо представляю себе мотивы венчурного капиталиста из Татарстана, который пожелал сделать этакий SaaS/PaaS/коробочный аналог MS SharePoint + Exchange + Lync + Dynamics, но только с серверной частью на J2EE, и с «толстым» клиентом на Java Swing. Сначала он называл его NewOutlook, а потом и вовсе OfficeSuite. Плюс ко всему, он был ещё и маководом, из-за чего пришлось покупать в контору мак — свинговые компоненты для толстого клиента вели себя под MacOS временами непредсказуемо. Очень, очень странный проект. Но жутко интересный.

Всего им занималось не меньше трёх команд — кроме нас, ребята сидели в Казани и где-то в Минске, что ли? или в Киеве?, причём основной считалась наша команда: именно нам надо было разрабатывать общую архитектуру и серверной части, и клиента, механизм синхронизации данных между ними, а также для примера реализовать модуль управления личными финансами. Остальные должны были заниматься Unified Communications и всем прочим, что нафантазировал (без конкретного ТЗ) инвестор.

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

Я тоже сначала не знал «как», но глаза боятся, а руки делают. За умными словами «клиентская и серверная архитектура» на самом деле кроется всего лишь несколько хорошо оформленных в виде структурированного документа набросков по дизайну. Конечно, «какие угодно объекты» — так себе требование, но, к счастью, проектировать хост-плагинные системы, которые умеют описывать свои типы объектов, я умею ещё со времён мультимедиа-центра, хитрые ORMы после ижсвязьинвестовского монстра меня не пугают, а недостающим J-технологиям из состава EE я могу научиться достаточно быстро.

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

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

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

Блин. Такой вот он, венчурный бизнес. Слегка ненавижу.

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

Не всегда к лучшему.

На дворе был уже 2011 год. По-прежнему в Ижевске


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

Вот уж где истинно кровавый энтерпрайз.

ЕПАМы (а также Люксофты и прочие подобные монстры, известные в народе как «галеры») — это фабрики. Гигантские заводы по выпуску софта на заказ, и процедура производства там абсолютно конвейерная. Всем рулят «процессы» — писанный и неписанный свод формальных и неформальных правил на все случаи жизни, от самых незначительных ежедневных рутинных моментов — до форс-мажора и полного пц. Неважно, в какой уровень системы ты встроен, тебе остаётся только подчиняться им, хочешь ты того, или нет. Нужно быть винтиком в механизме, и играть свою роль.

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

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

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

Их там к счастью, не большинство, нормальных больше, но общую картину такие «отличники» (от других) Очень! Сильно! Портят!

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

Как я вообще туда попал?

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

И первый же проект, куда я попал, оказался, реально, монстрическим. Заказчик — банк с круглым зелёным логотипом, команда на полсотни человек (это только в ЕПАМе, в других субподрядчиках ещё такая же толпа), сама система mission critical. Писаться должна на Java EE, но при этом есть сюжетный твист: вендор — IBM. Платформа — WebSphere AS и MQ, база данных — DB2. Даже сама JVM в этой связке экзотическая — J9, довольно сильно на тот момент отличавшаяся от сановского референса, и кошмарно глюкавая (но известно об этом мне стало потом, когда я наткнулся на пачку багов).

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

Я сказал: ага («архитекторов» набралось человек 15, ребята оказались… разные, а кирпич переписывался по ходу пьесы раза три до неузнаваемости).

И понеслась… За два года пришлось участвовать в разработке нескольких подсистем. Шлюзы во внешний мир. Внутреннее файловое хранилище. Синхронизация статусов обработки платежей для операторов «большой тройки». Система развёртывания модулей — всего их было порядка 25 — на вычислительную ферму (в итоге не выжила, но applications.xml остался в составе продукта).

Однажды совершенно случайно влез в зону ответственности другой команды, и выяснил, что существует 19 уникальных способов сделать поиск ресурса в JNDI. Пришлось поставить всех на уши, забрать модуль конфигурации себе, и привести к единому знаменателю. Пролилось немало крови. Это энтерпрайз!

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

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

А ещё — куча конфликтов в команде из-за того самого фактора личной несовместимости, включая меня и ПМа…

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

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

На какое-то время отлегло. Но всё равно было некомфортно, так как сам климат в офисе с его подковёрными играми и интригами — он сколько ни дистанцируйся, а давит.

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

Единственно доступная на тот момент команда разработки оказалась составлена из «отличников» со скамьи запасных, вечно там сидевших. Всех тех, кого не взяли на остальные проекты из-за токсичности, но проходящих ассесменты по формальным признакам, и потому не могущих быть уволенными. Я уже потом понял, что это мне такая награда за конфликт с ПМом первого проекта, но было поздно.

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

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

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

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

Урок я выучил: мне действительно нечего делать на фабрике. Урок номер два: никогда больше не соваться в менеджмент.

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

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

Ижевск, лето 2014 года


Одна из, гммм… пьянок, — что уж греха таить, именно что айтишных пьянок, — проходила в конторе, буквально только что возникшей из ниоткуда. Американский стартап со смешным названием tinypass (сколько шуток шутили гусары, прикрывая пальцем p на логотипе) решил открыть центр разработки в Ижевске. Основатель и совладелец оказался местным — и только что вернулся на родину. Сразу же кинул клич — чуваки, го в американский стартап, я сделал!

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

Пришёл. Посмотрел на проект. Взгрустнул было.

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

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

Проект же мы постепенно, общими усилиями, привели ко вменяемому состоянию. Узким местом оставалось воспитание техлидов, но они сидели в НЙ. Здесь-то их не было. Здесь-то и пиво можно пить прямо в офисе каждый (никаких исключений!) четверг, и отлично проводить время в компании лучших из лучших разработчиков города. Хэштег #ижевскийгугл.

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

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

Поначалу много писал всякого на Java, затем сделал несколько крайне неудачных экспериментальных фич подряд (отрицательные результаты очень не понравились упрямым нью-йоркцам, но местные коллеги верили в мой опыт — в итоге я оказался прав, и меня не уволили). Поставил процесс Continuous Integration — и сделал возможным появление в команде QA инженеров. В здоровом коллективе должны быть девушки-тестировщицы! А если серьёзно, то постановка процессов DevOps, вероятно, это самое полезное из того, что я сделал там за все чуть более чем два с половиной года.

Затем снова много писал на Java, уже в составе команды с третьим нью-йоркским лидом. Вот он был суперский чувак и классный профессионал — жалко, что быстро сбежал (от остальных двух, потому что они его там чуть не съели), и в результате гигантскую работу по локализации системы (никогда не пытайтесь локализовать систему, в которой изначально не предполагалось локализации! Лучше перепишите её с нуля) мы, потратив год, так до окончательной победы и не довели — с первого раза. Потом уже медленно и печально доделывали в фоновом режиме.

Вы когда-нибудь пробовали мержить ветку, которая в проекте на миллионы строк кода затрагивает несколько сотен тысяч? Прикольное ощущение, не каждая американская горка даст такое почувствовать. По результатам мержа… ничего не упало и не испортилось. Нам выдали локализованный тортик.

Автор (в оранжевом худи) и его коллеги с тортиком. На тортике написано «всё отлично» на 3 языках

После этого опять был какой-то совсем уж безумный DevOps и плохо формализованные, но классные исследовательские задачи. Можно было вдоволь пописать кода и на питоне под гугловый BigQuery, и всякую там муру на Node.js (старому джависту очень полезно менять иногда коня, а иногда лыжи). Ну а вишенкой на торте стала задача, за которую я трижды отказывался браться ввиду её сомнительности в этическом плане.

Мне отчего-то кажется, что нехорошо детектировать, и тем более пытаться заставить пользователя выключать блокировщик рекламы в браузере. На самом деле, конечным пользователем той middleware, которую предлагает Piano, является издатель, а тот, кто сидит за экраном — это лишь потребитель контента. Но я, вообще-то, стараюсь людей держать за людей, и уважаю их выбор. Не ваше дело, включен у меня адблок или нет. И уж тем более некорректно пытаться выяснить это, ведь способов заблокировать рекламу уйма, и технически сложно проверить их все, а от false positive пострадает куча людей, сидящих на плохом канале где-нибудь в метро.

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

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

Ижевск, сорок лет уже близко


Как же классно, когда можно ничего не делать несколько месяцев между работами. Но потом всё равно становится скучно, и возникает извечный вопрос — а чем бы ещё заняться? Чтобы на него ответить, я составил чеклист.

[x] Веб-разработка была.
[x] ГИС был.
[x] Кровавый энтерпрайз был.
[x] Крутой хипстерский стартап был.
[x] На заводе, где железо, мне явно делать нечего.
[x] Продуктовая разработка тоже не подходит, потому что это надолго, а я и так 11 (одиннадцать!!!11) лет свою CMS пилил, и мне сильно надоело. Что остаётся?
[x] Разве что игры с прочей трёхмеркой, но там дофига матана, этого ещё в школе полно было.
[ ] А, погодите-ка, Big Data выходит в тренды.

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

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

Пришёл я побеседовать в место, где работаю сейчас, а ПМ прямо с порога заявил, что проект — адский. Ничоси. Опять же, ГИС, только расчёты все на MapReduce (а хочется, чтобы на Spark), карты на ArcGIS, и всё это крутится в облаках, которые никто не умеет девопсить. По-моему, прекрасный вариант!

Прошло уже полтора года, и я всё ещё думаю, что вариант прекрасный. Пригодилось вообще всё, чем занимался раньше. Тут мне и спарк на Java (на Scala не перешёл, потому что не кандидат наук), и AWS с девопсом, и веб-ГИС, и отличный офис с нормально оборудованной кухней, где каждый четверг (ну ладно, пускай с исключениями) можно устраивать заседание кулинарного клуба.

Я же, в конце концов, неплохой шеф-повар, с таким-то опытом. Готовить люблю и умею. Пиццу, пасту, печеньки, другие вкусности на букву П, — как и на любые другие буквы. И девушек в офисе много. А в соседнем отделе пилят HUDWAY. Хэштег #сделановудмуртии.

Итого, продолжаем работать дальше.

Потому что we do what we must because we can, и я пока всё ещё могу.

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

И не надо думать, что для того, чтобы быть ценным кадром, надо было начинать 20 лет назад. Можно просто прийти к таким, как я (а я здесь такой отнюдь не один, много нас), и мы научим всему, что знаем сами, и вы нас ещё сделаете — потому что вы моложе, и мозги у вас быстрее работают.

Ребята, ну вот серьёзно, что вы забыли в Москве (или в заграницах)? Зарплата там всего вдвое выше, зато здесь можно купить трёхкомнатную квартиру на верхнем этаже новой высотки в центре за те же деньги, что в столице двушку б/у на замкадной окраине (тем более, без mortgage на 25 лет). Тут нет необходимости тратить три часа в день на выматывающей душу комьют (и необходимости водить машину тоже, общественный транспорт хороший по сравнению с).

Правда, тут нет и аймакса с икеей, и подсветки, как где-нибудь на бульварном кольце, тоже не найти. Но зато и спокойнее (и, в основном, всё по-русски, хотя и американские стартапы, где всё на буржуйском, тоже есть). А если душа движухи требует, то Казань неподалёку, там двихужа бывает. В конце концов, у нас есть РокИжайти — единственный труёвый айтишный рок-фестиваль ever.

Здесь вполне можно жить, заниматься интересными задачами, и не устать даже через 20 лет.

Let's block ads! (Why?)

OpenSceneGraph: Основы работы с текстурами

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

OSG поддерживает несколько текстурных атрибутов и режимов текстурирования. Но, перед тем как говорить о текстурах, поговорим о том, как в OSG оперируют с растровыми изображениями. Для работы с растровыми изображениями предусмотрен специальный класс — osg::Image, хранящий внутри себя данные изображения, предназначенных, в конечном итоге, для текстурирования объекта.


Лучшим способом загрузки изображения с диска служит применение вызова osgDB::readImageFile(). Оно очень похож на уже набивший нам оскомину вызов osg::readNodeFile(). Если у нас есть битмэп с именем picture.bmp, то его загрузка будет выглядеть так
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");


Если изображение загружено корректно, то указатель будет валидным, в противном случае функция возвратит NULL. После загрузки мы можем получить информацию об изображении, используя следующие публичные методы
  1. t(), s() и r() — возвращают ширину, высоту и глубину изображения.
  2. data() — возвращает указатель типа unsigned char* на "сырые" данные изображения. Через данный указатель разработчик может непосредственно воздействовать на данные изображения. Получить представление о формате данных изображения можно, используя методы getPixalFormat() и getDataType(). Возвращаемые ими значения эквивалентны параметрам формата и типа функций OpenGL glTexImage*(). Например, если картинка имеет формат пикселя GL_RGB и тип данный GL_UNSIGNED_BYTE то используются три независимых элемента (беззнаковых байта) для представления RGB-компонент цвета

Можно создать новый объект изображения и выделить под него память

osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(s, t, r, GL_RGB, GL_UNSIGNED_BYTE);
unsigned char *ptr = image->data();
// Далее выполняем с буфером данных изображения любые операции 


Здесь s, t, r — размеры изображения; GL_RGB задает формат пикселя, а GL_UNSIGNED_BYTE — тип данных для описания одной цветовой компоненты. Внутренний буфер данных нужного размера выделяется в памяти и автоматически уничтожается, если на данное изображение нет ни одной ссылки.

Система плагинов OSG поддерживает загрузку чуть ли не всех популярных форматов изображений: *.jpg, *.bmp, *.png, *.tif и так далее. Этот список нетрудно расширить, написав собственный плагин, но это тема для отдельной беседы.


Для наложения текстуры на трехмерную модель необходимо выполнить ряд шагов:
  1. Задать геометрическому объекту текстурные координаты вершин (в среде трехмерных дизайнеров это называется UV-разверткой).
  2. Создать объект атрибута текстуры для 1D, 2D, 3D или кубической текстуры.
  3. Задать одно или несколько изображения для атрибута текстуры.
  4. Прикрепить текстурный атрибут и режим к набору состояний, применяемому к отрисовываемому объекту.

OSG определяет класс osg::Texture, инкапсулирующий все виды текстур. От него наследуются подклассы osg::Texture1D, osg::Texture2D, osg::Texture3D и osg::TextureCubeMap, которые представляю различные техники текстурирования, принятые в OpenGL.

Наиболее употребимый метод класса osg::Texture это setImage(), задающий изображение, используемое в текстуре, например

osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(image.get());


или, можно передать объект изображения непосредственно в конструктор класса текстуры
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D(image.get());


Изображение можно получить обратно из объекта текстуры вызвав метод getImage().

Другим важным моментом является задание текстурных координат для каждой вершины в объекта osg::Geometry. Передача этих координат происходит через массив osg::Vec2Array и osg::Vec3Array вызовом метода setTexCoordArray().

После задания текстурных координат мы должны установить номер текстурного слота (юнит), так как OSG поддерживает наложение нескольких текстур на одну и ту же геометрию. При использовании одной текстуры номер юнита всегда равен 0. Например, следующий код иллюстрирует задание текстурных координат для юнита 0 геометрии

osf::ref_ptr<osg::Vec2Array> texcoord = new osg::Vec2Array;
texcoord->push_back( osg::Vec2(...) );
...
geom->setTexCoordArray(0, texcoord.get());


После этого мы можем добавить атрибут текстуры в набор состояний, автоматически включая соответствующий режим текстурирование (в нашем примере GL_TEXTURE_2D) и применить атрибут к геометрии или узлу, содержащему данную геометрию
geom->getOrCreateStateSet()->setTextureAttributeAndModes(texture.get());


Обращаем внимание на то, что OpenGL управляет данными изображения в графической памяти видеокарты, но объект osg::Image вместе с теми же данными располагается в системной памяти. В результате мы столкнемся с тем, что у нас хранятся два экземпляра одних и тех же данных, занимая память процесса. Если данное изображение не используется совместно несколькими атрибутами текстуры, его можно удалить из системной памяти сразу после того как OpenGL перенесет из в память видеоадаптера. Для включения этой функции класс osg::Texture предоставляет соответствующий метод
texture->setUnRefImageDataAfterApply( true );

Чаще всего используется техника 2D-текстурирования — накладывание двухмерного изображения (или изображений) на грани трехмерной поверхности. Рассмотрим простейший пример наложения одной текстуры на четырехугольный полигон
Пример texture
main.h
#ifndef         MAIN_H
#define         MAIN_H

#include    <osg/Texture2D>
#include    <osg/Geometry>
#include    <osgDB/ReadFile>
#include    <osgViewer/Viewer>

#endif


main.cpp
#include       "main.h"

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( osg::Vec3(-0.5f, 0.0f, -0.5f) );
    vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) );
    vertices->push_back( osg::Vec3( 0.5f, 0.0f,  0.5f) );
    vertices->push_back( osg::Vec3(-0.5f, 0.0f,  0.5f) );

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) );

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.0f) );

    osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
    quad->setVertexArray(vertices.get());
    quad->setNormalArray(normals.get());
    quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
    quad->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4) );

    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb");
    texture->setImage(image.get());

    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(quad.get());
    root->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}



Создаем массив вершин и нормалей к грани
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back( osg::Vec3(-0.5f, 0.0f, -0.5f) );
vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) );
vertices->push_back( osg::Vec3( 0.5f, 0.0f,  0.5f) );
vertices->push_back( osg::Vec3(-0.5f, 0.0f,  0.5f) );

osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) );


Создаем массив текстурных координат
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
texcoords->push_back( osg::Vec2(0.0f, 1.0f) );
texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
texcoords->push_back( osg::Vec2(1.0f, 0.0f) );


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

Далее создаем квадрат, присваивая геометрии массив вершин и массив нормалей

osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
quad->setVertexArray(vertices.get());
quad->setNormalArray(normals.get());
quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
quad->setTexCoordArray(0, texcoords.get());
quad->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4) );


Создаем объект текстуры и загружаем изображение, используемое для нее
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb");
texture->setImage(image.get());


Создаем корневой узел сцены и помещаем туда созданную нами геометрию
osg::ref_ptr<osg::Geode> root = new osg::Geode;
root->addDrawable(quad.get());


и, наконец, применяем атрибут текстуры к узлу, в который помещена геометрия
root->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());


Класс osg::Texture2D определяет, являются ли размеры изображения текстуры кратными степеням двойки (например 64х64 или 256х512) автоматически масштабируя неподходящие по размеру изображения, фактически применяя функцию gluScaleImage() OpenGL. Существует метод setResizeNonPowerOfTwoHint(), определяющий, нужно или нет изменять размер изображения. Некоторые видеокарты требуют кратность размера изображения степени двойки, в то время как класс osg::Texture2D поддерживает работу с произвольным размером текстуры.


Как мы уже говорили, текстурные координаты нормированы от 0 до 1. Точке (0, 0) соответствует левый верхний угол изображения, а точке (1, 1) — правый нижний. Что будет, если задать текстурные координаты больше единицы?

По-умолчанию, в OpenGL, как и в OSG текстура будет повторятся в направлении оси, значение текстурной координаты превысит единицу. Этот прием часто используют, например чтобы создать модель длинной кирпичной стены, использую небольшую текстуру, повторяя её наложение многократно как по ширине, так и по высоте.

Этим поведением можно управлять через метод setWrap() класса osg::Texture. В качестве первого параметра метод принимает идентификатор оси, к которой следует применить режим наложения, передаваемый в качестве второго параметра, например

// Повторять текстуру по оси s
texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT ); 
// Повторять текстуру по оси r
texture->setWrap( osg::Texture::WRAP_R, osg::Texture::REPEAT ); 


Данный код явно указывает движку повторять текстуру по осям s и r, если значения текстурных координат превышают 1. Полный список режимом наложения текстур:
  1. REPEAT — повторять текстуру.
  2. MIRROR — повторять текстуру, отразив зеркально.
  3. CLAMP_TO_EDGE — координаты, выходящие за пределы от 0 до 1 привязываются к соответствующему краю текстуры.
  4. CLAMP_TO_BORDER — координаты, выходящие за пределы от 0 до 1 будут давать установленный пользователем цвет границы.

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

Для динамического запекания текстуры необходимо выполнить три шага:

  1. Создать объект текстуры для рендеринга в неё.
  2. Отрендерить сцену в текстуру.
  3. Использовать полученную текстуру по назначению.

Мы должны создать пустой текстурный объект. OSG позволяет создать пустую текстуру заданного размера. Метод setTextureSize() позволяет задавать ширину и высоту текстуры, а так же ещё глубину в качестве дополнительного параметра (для 3D-текстур).

Для выполнения рендеринга в текстуру её следует присоединить к объекту камеры путем вызова метода attach(), принимающего в качестве аргумента объект текстуры. Кроме того данный метод принимает аргумент, указывающий, какую часть буфера кадра следует рендерить в данную текстуру. Например, для передачи буфера цвета в текстуру следует выполнить следующий код

camera->attach( osg::Camera::COLOR_BUFFER, texture.get() );


К другим, доступным для рендеринга частям кадрового буфера, относятся буфер глубины DEPTH_BUFFER, буфер трафарета STENCIL_BUFFER дополнительные буферы цвета от COLOR_BUFFER0 до COLOR_BUFFER15. Наличие дополнительных буферов цвета и их количество определяется моделью видеокарты.

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


Для демонстрации техники рендеринга в текстуру реализуем такую задачку: создадим квадрат, натянем на него квадратную же текстуру, а в текстуру выполним рендеринг анимированной сцены, конечно же с полюбившейся нам цессной. Программа, реализующая пример вышла достаточно объемной. Однако всё равно приведу её полный исходный текст.
Пример texrender
main.h
#ifndef                MAIN_H
#define         MAIN_H

#include    <osg/Camera>
#include    <osg/Texture2D>
#include    <osg/MatrixTransform>
#include    <osgDB/ReadFile>
#include    <osgGA/TrackballManipulator>
#include    <osgViewer/Viewer>

#endif


main.cpp
#include       "main.h"

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
osg::Geometry *createQuad(const osg::Vec3 &pos, float w, float h)
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f, -h / 2) );
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f, -h / 2) );

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 1.0f) );

    osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
    quad->setVertexArray(vertices.get());
    quad->setNormalArray(normals.get());
    quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
    quad->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    return quad.release();
}

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    osg::ref_ptr<osg::Node> sub_model = osgDB::readNodeFile("../data/cessna.osg");

    osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;
    transform1->setMatrix(osg::Matrix::rotate(0.0, osg::Vec3(0.0f, 0.0f, 1.0f)));
    transform1->addChild(sub_model.get());

    osg::ref_ptr<osg::Geode> model = new osg::Geode;
    model->addChild(createQuad(osg::Vec3(0.0f, 0.0f, 0.0f), 2.0f, 2.0f));

    int tex_widht = 1024;
    int tex_height = 1024;

    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    texture->setTextureSize(tex_widht, tex_height);
    texture->setInternalFormat(GL_RGBA);
    texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
    texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);

    model->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());    

    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setViewport(0, 0, tex_widht, tex_height);
    camera->setClearColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
    camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    camera->setRenderOrder(osg::Camera::PRE_RENDER);
    camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
    camera->attach(osg::Camera::COLOR_BUFFER, texture.get());

    camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
    camera->addChild(transform1.get());

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild(model.get());
    root->addChild(camera.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    viewer.setCameraManipulator(new osgGA::TrackballManipulator);
    viewer.setUpViewOnSingleScreen(0);

    camera->setProjectionMatrixAsPerspective(30.0, static_cast<double>(tex_widht) / static_cast<double>(tex_height), 0.1, 1000.0);

    float dist = 100.0f;
    float alpha = 10.0f * 3.14f / 180.0f;
    
    osg::Vec3 eye(0.0f, -dist * cosf(alpha), dist * sinf(alpha));
    osg::Vec3 center(0.0f, 0.0f, 0.0f);
    osg::Vec3 up(0.0f, 0.0f, -1.0f);
    camera->setViewMatrixAsLookAt(eye, center, up);

    float phi = 0.0f;
    float delta = -0.01f;

    while (!viewer.done())
    {
        transform1->setMatrix(osg::Matrix::rotate(static_cast<double>(phi), osg::Vec3(0.0f, 0.0f, 1.0f)));
        viewer.frame();
        phi += delta;
    }

    return 0;
}



Для создания квадрата напишем отдельную свободную функцию
osg::Geometry *createQuad(const osg::Vec3 &pos, float w, float h)
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f, -h / 2) );
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f, -h / 2) );

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 1.0f) );

    osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
    quad->setVertexArray(vertices.get());
    quad->setNormalArray(normals.get());
    quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
    quad->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    return quad.release();
}


Функция принимает на вход позицию центра квадрата и его геометрические размеры. Далее создается массив вершин, массив нормалей и текстурных координат, после чего созданная геометрия возвращается из функции.

В теле основной программы загрузим модельку цессны

osg::ref_ptr<osg::Node> sub_model = osgDB::readNodeFile("../data/cessna.osg");


Для того чтобы анимировать эту модель, создадим и инициализируем трансформацию поворота вокруг оси Z
osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;
transform1->setMatrix(osg::Matrix::rotate(0.0, osg::Vec3(0.0f, 0.0f, 1.0f)));
transform1->addChild(sub_model.get());


Теперь создадим модель для основной сцены — квадрат на который будем выполнять рендеринг
osg::ref_ptr<osg::Geode> model = new osg::Geode;
model->addChild(createQuad(osg::Vec3(0.0f, 0.0f, 0.0f), 2.0f, 2.0f));


Создаем пустую текстуру для квадрата размером 1024х1024 пикселя с форматом пикселя RGBA (32-битный трехкомпонентный цвет с альфа-каналом)
int tex_widht = 1024;
int tex_height = 1024;

osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setTextureSize(tex_widht, tex_height);
texture->setInternalFormat(GL_RGBA);
texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);


Применяем эту текстуру к модели квадрата
model->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get()); 


Затем создаем камеру, которая будет запекать текстуру
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setViewport(0, 0, tex_widht, tex_height);
camera->setClearColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


Вьюпорт камеры по размеру совпадает с размером текстуры. Кроме того не забываем задать цвет фона при очистке экрана и маску очистки, указывая очищать как буфер цвета, так и буфер глубины. Далее настраиваем камеру на рендеринг в текстуру
camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
camera->attach(osg::Camera::COLOR_BUFFER, texture.get());


Порядок рендеринга PRE_RENDER указывает на то, что рендеринг этой камерой выполняется до рендеринга в основную сцену. В качестве цели рендеренга указываем FBO и прикрепляем к камере нашу текстуру. Теперь настраиваем камеру на работу в абсолютной системе координат, а в качестве сцены задаем наше поддерево, которое мы желаем рендерить в текстуру: трансформация поворота с прикрепленной к ней моделькой цессны
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->addChild(transform1.get());


Создаем корневой групповой узел, добавляя в него основную модель (квадрат) и камеру обрабатывающую текстуру
osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(model.get());
root->addChild(camera.get());


Создаем и настраиваем вьювер
osgViewer::Viewer viewer;
viewer.setSceneData(root.get());
viewer.setCameraManipulator(new osgGA::TrackballManipulator);
viewer.setUpViewOnSingleScreen(0);


Настраиваем матрицу проекции для камеры — перспективная проекция через параметры пирамиды отсечения
camera->setProjectionMatrixAsPerspective(30.0, static_cast<double>(tex_widht) / static_cast<double>(tex_height), 0.1, 1000.0);


Настраиваем матрицу вида, задающую положение камеры в пространстве по отношению к началу координат подсцены с цессной
float dist = 100.0f;
float alpha = 10.0f * 3.14f / 180.0f;

osg::Vec3 eye(0.0f, -dist * cosf(alpha), dist * sinf(alpha));
osg::Vec3 center(0.0f, 0.0f, 0.0f);
osg::Vec3 up(0.0f, 0.0f, -1.0f);
camera->setViewMatrixAsLookAt(eye, center, up);


Наконец, анимируем и отображаем сцену, меняя угол поворота самолета вокруг оси Z на каждом кадре
float phi = 0.0f;
float delta = -0.01f;

while (!viewer.done())
{
    transform1->setMatrix(osg::Matrix::rotate(static_cast<double>(phi), osg::Vec3(0.0f, 0.0f, 1.0f)));
    viewer.frame();
    phi += delta;
}


В итоге мы получаем довольно интересную картинку

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


OSG поддерживает возможность прикрепить к камере объект osg::Image и сохранить содержимое буфера кадра в буфер данных изображения. После этого возможно сохранить эти данные на диск используя функцию osg::writeImageFile()
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage( width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE );
camera->attach( osg::Camera::COLOR_BUFFER, image.get() );
...
osgDB::writeImageFile( *image, "saved_image.bmp" );


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

Let's block ads! (Why?)

[Из песочницы] Производительность торговой платформы на простом примере

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

Обычно мы рассматриваем производительность в единицах пропускной способности. Например в Гигафлопах. Задача оптимизации в таких случаях сводится к выполнению максимального количества вычислений за единицу времени или решение задачи за минимальное время. Дизайн процессора рассчитан в первую очередь на достижение максимального количества вычислений за единицу времени и стандартные техники оптимизации на то же самое.

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

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

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

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


Работа офиса

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

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

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

Итак, наша задача – максимально сократить время обработки сообщений. При этом желательно, чтобы максимальное время обработки не превышало среднее более чем, скажем, вдвое. То есть всплески активности должны быть эффективно обработаны.

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

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

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

Итак, после нововведений, наше время обработки сообщений упало до, допустим, одной минуты. Можно еще потренировать работников для улучшения процесса коммуникации и исполнения. Возможно это даст процентов 15 при правильной мотивации. Знасит мы достигли 51 секунды. Это похоже на оптимизацию программного обеспечения.

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

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

Теперь пора рассмотреть еще внимательнее условия в офисе. Открываются ли двери легко? Не скользит ли пол? Это примерно тоже самое что анализ взаимодействия с операционной системой. Если улучшить уже нечего, можно попытаться избежать использования некоторых частей. Например, вместо того чтобы разносить письма через офис, почему бы не попробовать кидать их из окна в окно? Скажете неудобно? Может и неудобно, зато быстро. Это аналог использования подхода kernel bypass в сетевом стеке.

Вместо использования сетевого стека операционной системы, kernel bypass исполняет сетевой стек в user space. Это помогает избавиться от ненужных копирований данных между стеком системы и пользователя и задержки исполнения потока получения сообщения. В kernel bypass поток получения обычно ждет активно. Он не сидит на локе операционной системы, а непрерывно проверяет переменную лока до тех пор пока она не даст ему разрешение на исполнение.

На самом деле если уж мы начали бросать сообщения через окна, давайте сделаем это эффективно. Наиболее надежный вариант это передать через окно из рук в руки. Этот принцип используется в TCP протоколе. Это не самый быстрый вариант. UDP разрешает просто бросить сообщение без подтверждений. Это быстрее. Никого ждать не требуется. Думаете это предел? Нет, можно еще научиться бросать через окно так, чтобы письмо падал прямо на нужный стол и в нужную папку. Такой подход называется remote direct memory access (RDMA). Думаю, мы понизили время обработки секунд до 35-ти.

А может быть построить офис с нуля вместо того чтобы приспосабливать существующий к нашим нуждам? Такой, чтобы обеспечивал идеальные условия работы. Наверное это позволит улучшить время отклика секунд до 20-ти, а то и меньше. Собственный дизайн офиса – это использование field programmable gate array (FPGA). FPGA – это нечто вроде процессора, железо которого программируется под решение конкретной задачи. Обычный процессор закодирован на выполнение определенного набора инструкций на определенных типах данных и поток исполнения (не путать с потоком приложения) тоже фиксирован. В отличии от процессора, FPGA не запрограммированы заранее под набор инструкций, типов данных и поток исполнения. Они программируются под конкретную задачу и в таком состоянии способны исполнять только ее (до последующего перепрограммирования). Эффективное программирование FPGA – не самая простая задача. Внесение изменений в программу также может потребовать больших усилий. И хотя FPGA не подразумевает найм Усейна Болта (частоты намного ниже, чем процессорные), но неограниченный параллелизм исполнения инструкций позволяет добиваться более низких времен обработки сообщений, чем на процессоре.

Ну и в заключение порекомендую инструменты анализа производительности для программного обеспечения. Intel VTune TM Amplifier и Intel Processor Trace technology помогут увидеть в деталях где и почему тратится процессорное время.

Если есть интерес к теме, можно почитать мои статьи на Intel Developer Zone (на английском), где также приводятся практические технические советы по оптимизации времени отклика.


  • https://intel.ly/2w0R5Tt
  • https://intel.ly/2v8j8Ec

Let's block ads! (Why?)

Сказ о том, как наукам компьютерным обучаться


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

Статей написанных на эту тему на Хабре довольно много, вот только некоторые из них: Почему трава зеленая, а программисты крутые, Как стать программистом Java, Программировать с нуля. «Сейчас» — самое подходящее время, чтобы начать, Научиться программировать становится сложнее.

На дворе 2019 год. Теперь каждый дядя Вася гикбрейнс, нетология, хекслет, html-академия из деревни Пупкино считает своим долгом научить тебя программировать на Python за умеренную плату или сделать сайт на вордпрессе за 5 минут. Большинство современных онлайн-платформ для обучения программированию, как правило, имеют бесплатные вводные курсы (которые подскажут вам, каким тегом вставить картинку на веб-сайт, или научат выводить «Hello World» на Java, но не более того).

За то, чтобы узнать что-то кроме важности закрытия тега параграфа в HTML или узнать о таких умных словах как «Angular» или «SVG» придется уже выложить денежку. Я конечно понимаю, что им (как и всем, в нашем бренном мире) необходимо монетизировать свой ресурс таким образом, но ведь заплатив за месячную подписку или пройдя несколько курсов, на той же html-академии, вы не будете знать и уметь больше, чем если бы вы прочитали пару увесистых книжек по теме, или пролистали официальную документацию и поупражнялись самостоятельно, а у любого более или менее серьезного работодателя эти курсы котируются чуть хуже чем диплом какого-нибудь Тюменского Индустриального Университета (т.е. никак).


Дополнительные 4 часа с наставником стоят 12 000 рублей. Наставнику будет гораздо комфортнее если вы запишитесь именно на этот вариант курса, будет ли комфортнее вам — другой вопрос.

Если вам все же хочется потратить деньги на что-нибудь, непосредственно связанное с программированием, то лучше потратьте их на покупку лицензии Sublime Text 3 (чувствую, как евангелисты Atom или Brackets уже занесли курсор над минусом моей кармы). Вы перестанете видеть окошко, с просьбой о покупке, после каждых десяти нажатий Ctrl+S, а компания, выпускающая самый шустрый, удобный и функциональный редактор кода, станет на 80$ богаче.

Есть множество отличных курсов на coursera, edx или codeacademy. По моему мнению это те немногие ресурсы, заслуживающие внимания, если вы хотите действительно получить знания. На edx можно и вовсе проходить все курсы аудитом, т.е. совершенно бесплатно (также есть возможность получить сертификат по завершению обучения за 100$, но, думаю, что показатель продаж у этих сертификатов такой же как и у лицензионных версий Windows в России, т.е. стремится к нулю).

И насчет знаний английского для изучения программирования на зарубежных ресурсах: если ваш уровень английского настолько низок, что не позволяет даже почитать w3schools.com или посмотреть новый видос на канале The Coding Train, то я вообще не знаю как вы работаете электриком в селе Погорелка Тюменской области зачем вы в принципе интересуетесь изучением программирования. Ну серьезно, стыдно, интересующемуся сферой IT человеку, не иметь уровня Intermediate в наши дни, товарищи, стыдно! На крайний случай всегда остается возможность нажимать на синюю стрелочку, на странице гугл-переводчика. Да что уж там, в Хроме есть возможность мгновенного перевода страницы на русский язык.


Я очень положительно отношусь к обучению программированию по книгам. Одна только O'Reilly выпускает их в год больше, чем количество людей, начинающих изучать программирование, и через неделю бросающих изучение. Мой совет: не ленитесь выполнять упражнения после прочтения каждой главы (они могут показаться слишком тривиальными, но practice make perfect, человек запоминает информацию посредством повторения намного эффективнее).

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

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

Let's block ads! (Why?)