Русский
Русский
English
Статистика
Реклама

Разработка по

Перевод System360 проект, едва не погубивший IBM

16.11.2020 20:18:08 | Автор: admin

Если когда-то будет составлен шорт-лист самых выдающихся изобретений XIX-XX веков, в него, без сомнения, войдут первая электролампочка, Ford Model T и IBM System/360. Эта серия мэйнфреймов навсегда изменила компьютерную индустрию и произвела революцию в работе заводов, компаний и государственных учреждений.

Тем интереснее узнать, что перед выходом мейнфрейма на рынок (7 апреля 1964 года) S/360 казался создателям едва ли не самой драматичной ошибкой за всю историю предпринимательства. Чтобы создать программную и аппаратную начинку компьютера, сотрудникам IBM пришлось выложиться по полной. Колоссальные финансовые издержки, переработки и пивоты грозили разорить компанию. Позднее Кейт Павитт, эксперт по научной политике, сравнит эту ситуацию с межплеменной войной: молодая, быстрорастущая компания была вынуждена работать с новыми, практически не изученными технологиями, вызывавшими недопонимание и раздор внутри отделов.

Тогда IBM повезло. Она уже была достаточно велика, обеспечена финансово и полна талантами. То, что грозило крахом, обернулось победой. Но давайте не будем забегать вперед.

К концу 1950-х годов пользователи компьютеров столкнулись с одной очень сложной проблемой. Практически неразрешимой. Организации приобретали огромное количество мейнфреймов. Автоматизировали классические процессы с перфокартами и обрабатывали большие потоки данных. Популярность одного только IBM 1401 живое свидетельство того, как быстро вычисления на компьютере пришли на смену крестьянскому арифмометру. С 1959 по 1971 IBM удалось продать порядка 12 000 таких систем.

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

  • перейти на более навороченный IBM, например, IBM 7000;

  • купить подходящую машину у другой компании;

  • выделить площади и расширить парк 1401-х.

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

Увеличение парка слабых машин тоже плохой вариант. Новая ЭВМ требует увеличения штата как операторов, так и обслуживающего персонала. Рынку требовались системы, которые по мере роста потребностей можно было бы расширить или улучшить, сохранив при этом совместимость со старыми ПО и периферией. В 1950-х и начале 1960-х такой машины не было.

Клиентские проблемы тогда казались IBM цветочками. Противоречия раздирали компанию изнутри. С одной стороны, требовалось создавать более мощные и продвинутые компьютеры. С другой, инженеры, разработавшие 1401, всячески противились инновациям. Популярность машины вскружила им головы. Как вспоминал один из тогдашних сотрудников IBM: Страсти росли. Иногда казалось, что мы соперничаем не с собственными коллегами, а с внешним конкурентом, злым и бескомпромиссным.

Вражда привела к тому, что 7000-е компьютеры из Покипси (Poughkeepsie) были на 100% несовместимы с кодом, написанным для 1400-х. Заказчики, напротив, писали гневные письма в компанию и требовали совместимости. Высшему руководству IBM приходилось мириться со все растущими тратами на поддержку сразу нескольких несовместимых линеек мейнфреймов.

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

Красота и мощь совместимости раскрылись осенью 1960 года, когда IBM представила рынку машину 1410, пришедшую на смену 1401. И программное обеспечение, и периферийные устройства от 1401 подходили к 1410.

И покупатели, и промоутеры IBM по достоинству оценили новинку. А тем временем в Покипси были близки к завершению работы над комплектом из четырех компьютеров, известных как 8000-е. Они должны были заменить прошлое поколение 7000-х.

За разработку новых продуктов в компании отвечал вице-президент по производству и развитию Томас Винсент Лирсон. Он прекрасно понимал, что внутренние распри в IBM требуется прекратить как можно скорее. Вот, что позднее писал об этом генеральный директор IBM Томас Дж. Уотсон-младший: Лирсон решил проблему, применив методику абразивного взаимодействия. Суть в том, чтобы поменять людей из противоборствующих лагерей местами. Так, главный инженер из маленького подразделения был переброшен на руководство флагманским проектом.

Томас Винсент ЛирсонТомас Винсент Лирсон

Лирсон заменил руководителя из Покипси, ответственного за проект 8000, на Боба О. Эванса, который работал менеджером по проектированию 1401 и 1410. Эванс выступал за совместимость всех будущих продуктов. Спустя три месяца на новой должности Эванс заявил о том, что разработка 8000-х машин это тупик, вместо них компании следует сосредоточиться на создании единой линейки совместимых компьютеров. В качестве базы для всех новых систем он предложил новую экспериментальную технологию Solid Logic Technology (SLT). Это могло бы дать IBM массу конкурентных преимуществ.

Фредерик П. Брукс-младший, ранее возглавлявший команду разработчиков 8000, противился переменам. Эванс и Брукс стояли по разные стороны баррикад. Оба инженера имели многолетний опыт работы в сфере проектирования и разработки продуктов, пользовались большим уважением со стороны коллег и высшего руководства. Спустя какое-то время для оценки их перспективных предложений был приглашен другой специалист IBM, Джерриер А. Хаддад. Идею Эванса поддержали, и в мае 1961 года разработки 8000-х систем полностью прекратились.

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

Возглавить новую рабочую группу по системному программированию, проектированию и развитию Лирсон поручил Джону В. Хаанстре, президенту подразделения General Products, производившего модели 1400. Подразделение получило название SPREAD, а Боб Эванс стал её вице-председателем. Позднее к работе был привлечен и Брукс. Уже к декабрю 1961 года группа представила первые технические рекомендации.

Боб Эванс, Фредерик Брукс и Джерриер ХаддадБоб Эванс, Фредерик Брукс и Джерриер Хаддад

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

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

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

Из мемуаров Томаса Уотсона-младшего, генерального директора IBM:

Изначально мы столкнулись сразу с двумя рискованными моментами. Даже одного из них было достаточно, чтобы не спать ночами... Во-первых, стояла задача координировать работы по проектированию аппаратного и программного обеспечения для новой линейки. У нас были команды инженеров по всей Америке и Европе, они одновременно работали над шестью новыми процессорами и десятками новых периферийных устройств ... но в конце концов все это оборудование пришлось бы собрать воедино. Во-вторых, программное обеспечение. Чтобы поддержать System/360, сотни программистов написали миллионы строк программного кода. Беспрецедентно сложный проект. Еще никогда наши инженеры не испытывали столь сильного давления.

Еще один пласт проблем был связан с производством электронных компонентов для новых систем. Электронная промышленность только-только освоила изготовление интегральных схем, а новые компьютеры должны были быть наполнены ими под завязку. Чтобы ни от кого не зависеть, IBM пришлось организовать собственное производство ИС. Очень дорогой и очень рискованный шаг.

В конце концов, комитет корпоративного управления, куда входили Уотсон и совет директоров, скрепя сердце, одобрил предложения группы SPREAD. IBM предстояла самая безумная технологическая гонка в истории.

Скрывать происходящее уже не получалось. Понемногу слухи просачивались в прессу. Корпорации уже предвкушали новые, мощные и совместимые машины, а конкуренты GE, Honeywell, Sperry Univac и прочие гадали, что же получится у IBM.

Между тем, в самой компании зрело недовольство прогрессом разработки. Инженеры, сотрудники производств и сотрудники отдела продаж работали по 100 и более часов в неделю. Им приходилось фактически жить в офисе. Однажды Уотсон решил лично проверить, как идут дела, но получил нагоняй от какого-то инженера. Мол, иди отсюда и не мешай работать. Председатель правления IBM был вынужден ретироваться.

7 апреля 1964 года, ровно в полдень, IBM презентовала новую линейку компьютеров. Более 100 000 клиентов, репортеров и технических экспертов собрались на конференциях в 165 городах США. Это была самая важная презентация в истории компании.

В тот день IBM представила целых 150 наименований новых продуктов: 6 компьютеров, 44 периферийных устройства, включая ленточные накопители, дисководы, принтеры и блоки управления, а также огромное количество программного обеспечения для совместной работы. Один только каталог толщиной был в целый дюйм. Всевозможные руководства, содержащие описания машин, компонентов и программного обеспечения, если их сложить друг на друга, заняли бы более 15 метров.

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

Среди 44 периферийных устройств S/360 был дисковый накопитель 2311Среди 44 периферийных устройств S/360 был дисковый накопитель 2311

За первый месяц IBM удалось продать по предзаказу 100 000 новых машин. Для сравнения, в том же году в Великобритании, Западной Европе, США и Японии было установлено чуть более 20 000 любых компьютеров. Первые поставки младших машин обещались в третьем квартале 1965 года, а старших в начале 1966-го. Временной лаг позволил клиентам точно определиться с приобретаемой моделью, спланировать бюджет, обучить персонал и скорректировать программное обеспечение.

С 7 апреля в IBM начались по-настоящему сложные времена. На разработку System/360 компания потратила 5 миллиардов долларов (порядка 40 миллиардов долларов по современному курсу), что в то время было больше её годового бюджета. Было нанято примерно 70 000 новых сотрудников. И каждый из них понимал: если компьютер провалится, IBM придет конец.

Позже Уотсон писал: Не все оборудование, представленное [7 апреля], было настоящим. Что-то и вовсе было выстрогано из дерева. Обмана не было, мы честно рассказали, что это всего лишь макеты. Но это все равно было слишком опасно. Думаю, так нельзя вести бизнес Слишком многое мы поставили на кон, еще до конца не зная, будет ли затея успешной.

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

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

После вынужденного простоя и решения проблем на линии производство возобновилось с новой силой. За 1966 год было выпущено 90 млн модулей SLT почти втрое больше, чем в прошлом году. IBM открыла новый завод в Ист-Фишкилле, к югу от Покипси. Там за один год произвели больше полупроводниковых устройств, чем все другие заводы во всем мире вместе взятые. Позднее были открыты производственные линии в Берлингтоне, штат Вирджиния, и в Корбей-Эсон, Франция.

Из-за спешки в производстве дефекты обнаруживались приблизительно в четверти всех модулей SLTИз-за спешки в производстве дефекты обнаруживались приблизительно в четверти всех модулей SLT

Расширение производственных мощностей за пределами США создало проблемы в координации деятельности и изготовлении компьютеров. Артур Уотсон ранее уже управлял небольшими заводами IBM за пределами США, но не имел опыта в решении инженерных проблем, не говоря уже о глобальных проблемах в разработке и производстве. Это была задача не его масштаба. А Лирсон и команда продажников просили о дополнительных улучшениях в линейке продуктов вдобавок ко всем остальным проблемам. В октябре 1964 года IBM пришлось объявить о значительных задержках поставок.

Том отстранил Артура от работы и передал его обязанности Лирсону, который, в свою очередь, пригласил четырех инженеров-менеджеров для решения проблем с заводами. Четыре всадника Лирсона всего за пять месяцев усердной работы смогли вернуть прежние сроки поставок. В январе 1966 года Лирсон стал президентом IBM.

Генри Кули, Клеренс Фриззелл, Джон Гибсон и Джон ХаанстраГенри Кули, Клеренс Фриззелл, Джон Гибсон и Джон Хаанстра

Артура перевели на роль заместителя председателя, но уже в 1970 году он предпочел выйти на пенсию. В своих мемуарах Томас Уотсон-младший признался, что с 1964 по 1966 год пребывал в состоянии паники и глубоко сожалеет о том, как обращался с Артуром. Я не чувствовал ничего, кроме стыда и разочарования из-за моего отношения к нему ... Да, мы устроили революцию в компьютерной индустрии, и объективно S/360 это величайший триумф в моей деловой карьере. Но всякий раз, когда я вспоминаю об этом, я думаю о брате, который так из-за меня пострадал.

Проблемы с программным обеспечением также замедлили поставки S/360. Еще в 1963 году среди разработчиков царил хаос. Они, поначалу безуспешно, пытались заставить операционную систему OS/360 выполнять более одного задания одновременно. Проблемы возникали и с прикладным ПО.

Фред Брукс вызвался помочь компании, и IBM подключила к проекту операционной системы еще порядка 1000 человек, что нанесло огромный удар по бюджету. Но увеличение числа разработчиков почти не помогло. Этот опыт лег в основу книги Брукса Мифический человекомесяц (Addison-Wesley, 1975). До сих пор это одна из самых читаемых книг по вычислениям. Годы ушли на то, чтобы разработать, протестировать и отладить новую ОС, а задержка по поставкам сократилась всего до одного месяца.

Поставка S/360 в ЯпониюПоставка S/360 в Японию

В 1965 году IBM каким-то чудом удалось отгрузить клиентам первые несколько сотен компьютеров S/360. Да, их качество в некоторых аспектах уступало заявленному. Запчастей не хватало, периферия глючила и выходила из строя. В ПО обнаруживались критические ошибки, и почти каждый клиент так или иначе сталкивался с проблемами. Но начало было положено. Отдел продаж всячески умасливал заказчиков, а в недрах IBM тысячи человек трудились над тем, чтобы исправить ошибки в уже поставленных компьютерах и не допустить их в новых партиях.

Филиалы IBM нанимали сотни новых системных инженеров. В основном это были выпускники технических колледжей, умевшие отлаживать программное обеспечение. Разработчики героически решали программные проблемы S/360, в то время как полевые инженеры, устанавливающие оборудование, исправляли аппаратные баги.

И, несмотря на множество проблем, клиенты продолжали заказывать S/360 быстрее, чем в IBM успевали их собирать. К концу 1966 года заказчики получили девять различных моделей S/360, всего 7700 штук.

Конкуренты IBM тоже не дремали. Burroughs, GE, Honeywell, NCR и Sperry Rand, представили собственные системы, совместимые друг с другом, но не с IBM. Еще одна, меньшая когорта производителей, выпускала IBM-совместимые компьютеры по лицензии RCA.

Пять лет спустя стоимость всех установленных компьютеров IBM в мире выросла до 24 миллиардов долларов, а у конкурентов до 9 миллиардов. Другими словами, IBM S/360 моментально увеличил популярность компьютеров до немыслимых высот. Годовой рост отрасли во второй половине 1960-х выражался двузначными числами по сравнению с прошлыми годами, поскольку многие тысячи организаций перешли на использование компьютеров. Спрос на вычисления вырос из-за технологических инноваций, разработанных IBM, а также потому что пользователи накопили достаточно опыта, чтобы осознать ценность компьютера.

НАСА закупило несколько машин S/360, в том числя для Центра космических полетов ГоддардаНАСА закупило несколько машин S/360, в том числя для Центра космических полетов Годдарда

IBM также выросла более чем вдвое с 127 000 сотрудников по всему миру в 1962-ом до 265 000 к концу 1971 года. Выручка увеличилась с 3,2 млрд в 1964 году до 8,2 млрд долларов в 1971-ом.

S/360 без преувеличения создал новую компьютерную субкультуру. Тысячи программистов умели пользоваться только ПО от IBM. Еще несколько тысяч человек, занимавшихся обработкой данных, работали только с оборудованием S/360: это были машины для перфорации, принтеры, ленточные накопители, дисководы и отраслевое программное обеспечение. К началу 1970-х компьютерное пространство в значительной степени принадлежало IBM по обе стороны Атлантики, а также на развивающихся рынках Латинской Америки и Японии.

Спустя годы, когда у представителей IBM спросили, решится ли компания на что-то подобное снова, кто-то из руководства ответил: Черт возьми, да больше никогда!. Уотсон придерживался такого же мнения. Комментируя ситуацию в 1966 году, он заявил: При всех наших размерах мы больше никогда не решимся на проект такого же масштаба. Помня об уроке S/360, Уотсон ввел новое корпоративное правило: никогда не объявлять о новой технологии, которая потребует от компании выделения более 25% производственных мощностей.

История разработки S/360 это история целого поколения сотрудников IBM. Карьеры взлетали и рушились в одночасье, личная жизнь и семьи приносились в жертву великой идее. И никто, ни один человек в компании не мог знать, чем обернется вся эта затея. Насколько сильно S/360 перевернет компьютерный мир.

Зато теперь об этом знаем все мы.

Подробнее..

Обновление МойОфис в 3 раза ускоряет почту, добавляет новые функции и еще 4 иностранных языка

07.07.2020 20:09:59 | Автор: admin


В начале июля 2020 года МойОфис выпустил второе крупное обновление. В новой версии 2020.01.R2 наиболее заметные функциональные изменения произошли в средствах для работы с электронной почтой и календарем. Была произведена оптимизация серверных компонентов МойОфис Почта, которая привела к 3-кратному увеличению скорости рассылки писем на 500 и более адресатов.


Почтовая система


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



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



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

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



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



Изменился и редактор событий в МойОфис появились расширенные настройки повторения событий,





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



просмотр занятости других участников и рекомендации по планированию встреч.

МойОфис SDK


Комплект средств для разработчиков МойОфис SDK пополнился новым компонентом Автономным модулем редактирования (АМР). Это специальная веб-версия редакторов МойОфис, которая предназначена для интеграции в продукты сторонних разработчиков. Такие приложения нетребуют отдельного сервера и содержат полный набор функций редактирования и форматирования, нопри этом не имеют функций совместной работы. Редактор в АМР обрабатывает только те файлы, которые передаем ему информационная система приложение или сервис, куда интегрирован саммодуль АМР.

Автоматический модуль редактирования позволяет добавить функции редактирования документов вSaaS-сервисы без необходимости передачи пользовательских данных за пределы защищенного периметра и без необходимости развёртывания дополнительных серверов. Новый модуль доступен технологическим партнерам по специальной ISV-лицензии, которая приобретается отдельно.

МойОфис Document API, другой компонент из состава МойОфис SDK, также был обновлен. Теперьпользователи могут задействовать функции для работы с отдельными разделами документа ивыбирать книжный или альбомный формат страницы.

МойОфис Текст и МойОфис Таблица


В текстовом редакторе появилась функция принудительной очистки форматирования текста, которую можно вызвать через кнопку на панели инструментов или с помощью сочетания клавиш [CTRL]+[ПРОБЕЛ].



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

Средства форматирования


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



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



Базовый шаблон документа


В релизе 2020.02.R2 появилась возможность замены базового шаблона нового документа. Поумолчанию, изменить базовый шаблон может только системный администратор. Это сделано длятого, чтобы обычный пользователь не мог случайно заменить настройки документов, принятые ворганизации. Основные пользовательские шаблоны можно хранить в любом месте доступ книмосуществляется через пункт меню [Файл] [Создать по шаблону].

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

Замена базового шаблона нового документа на отдельном компьютере происходит в несколько действий:
Запустите настольное приложение МойОфис от имени администратора.
Создайте требующийся образец шаблона, который содержал бы всю необходимую информацию, разметку страницы и колонтитулы.
Выберите пункт меню [Файл] и затем [Сохранить шаблон...]. Сохраните шаблон в специальной папке [Default Template].

Программа ищет базовые шаблоны в стандартных папках установки, доступ к которым есть только уадминистраторов. Например, в операционной системе Windows эта папка располагается по адресу "C:\Program Files\MyOffice\Default Template", а в Linux "/usr/local/bin/my_office".

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

Почтовый клиент




Дизайн почтового клиента МойОфис Почта для ПК был обновлён, он получил оформление скристальным паттерном и возможность отображения изображений пользователей (аватаров). Ранеетакая функция была доступна только в облачной версии почтового клиента.

Средства локализации


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



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

Мобильные приложения


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



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

Делаем новую версию API. Быстро и легко

09.04.2021 14:13:02 | Автор: admin

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

Первый вопрос, который нужно задать: А точно ли стоит реализовать новую версию API? Возможно, уже работающая версия отвечает всем необходимым вам критериям. Но если вы уже для себя ответили да на поставленный вопрос, то читайте дальше и найдете наш ответ. Если вы ответили нет, то тоже читайте дальше и применяйте наш опыт проектирования и реализации в следующих ваших проектах.

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

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

Рассмотрим на примере API для работы с котиками то, как мы совершенствовали один из наших проектов.

Как понять, что стоит реализовать новую версию API

Для того чтобы понять, что API пора спасать, необходимо пройтись по следующим пунктам:

  • определить уровень зрелости API;

  • проверить, есть ли у API версионность;

  • проверить наличие документации API.

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

Определим уровень зрелости API

Для этого идеально подходит модель Леонарда Ричардсона, в которой он выделяет четыре уровня зрелости API:

  • Уровень 0: Один URI и один HTTP метод (в основном метод POST);

  • Уровень 1: Несколько URI и один HTTP метод;

  • Уровень 2: Несколько URI, каждыи из которых поддерживает разные HTTP методы;

  • Уровень 3: HATEOAS. Ресурсы сами описывают свои возможности и взаимосвязи.

Если API соответствует 0 или 1 уровню зрелости, то определенно есть куда расти, потому что:

  • Если используется один URI, то не понятно с каким ресурсом работаем;

  • Не понятно, что делаем с ресурсом, так как используется один HTTP метод;

  • Использование одного URI создает трудности с документацией API;

  • Использование одного URI создает трудности с логированием входящих запросов;

  • Из-за использования одного URI, информация о типе ресурса передается в теле запроса.

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

Проверим, есть ли у API версионность

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

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

Рассмотрим 3 основных способа версионирования API и разберем подробнее каждый из них. Современные разработчики выделяют следующие способы:

- Использование разных URI (Uniform Resource Identifier);

- Использование параметра запроса;

- Использование заголовка, Accept Header/Media Type.

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

  1. Использование разных URI простой в проектировании, реализации и документировании способ версионирования. Однако он имеет целый ряд недостатков:

  • приводит к загрязнению URI, так как префиксы и суффиксы добавляются к основным строкам URI;

  • разбивает существующие URI, то есть все клиенты должны обновиться до нового;

  • приводит к увеличению размер HTTP кэша для хранения нескольких версии;

  • создает большое количество дубликатов URI, может снизить производительность приложения из-за увеличения количества обращении к кэшу;

  • является крайне негибким и не позволяет просто изменить ресурс или небольшой их набор.

Пример:

GET v1/cats/{name}

2.Использование параметра запроса позволяет легко документировать версионность и рекомендуется к использованию в случае, если важно HTTP кэширование. Однако и этот способ приводит к загрязнению пространства URI.

Пример:

GET cats/{name}?version=v1

3. Использование заголовка и Accept Header/Media Type также легко документировать и, в отличие от предыдущих способов не приводит к загрязнению пространства URI. Но и у этого способа выделяют несколько минусов:

-приводит к неправильному использованию заголовков, так как они изначально предусматривались не для версионирования;

-требует для управления версиями на основе заголовка и типа медиа использования таких инструментов, как Postman, или создания автоматизированных средств, добавляющих необходимый заголовок в HTTP запрос.

Пример:

GET cats/{name}

Headers: version=v1

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

Проверим наличие документации API

Даже к фену удобно иметь описание, не говоря уже о серьезной проекте разработки ПО. Поэтому наглядное описание API всегда удобно для использования как backend, так и frontend разработчиками. Документация может быть реализована, например, с помощью Swagger (фреймворк для спецификации RESTful API), он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы с помощью Swagger UI:

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

  • соответствует 0 уровню зрелости (Один URI и один HTTP метод);

  • невозможно достоверно установить, с каким ресурсом API работает и какие функции выполняет;

  • отсутствуют автоматизированные средства документации API, что приводит к неполной документации API или ее полному отсутствию для некоторых запросов;

  • появляются сложности с поддержкой обратной совместимости, так как нет версионности API.

Для большего понимания того, что не нравилось, приведем пример того, как выглядел наш API.

Пример:

POST /cats - должен вернуть котика по имени Пушок (гарантируется, что у

requestBody: { котиков уникальные имена);

"name": "Pushok"

}

POST /cats - должен вернуть вернуть список белых котиков;

requestBody: {

"color": "white"

}

Цели реализации нового API

Если в предыдущем пункте мы уверенно выявили хотя бы один пункт в существующем API, то самое время переходить к реализации нового. Основными целями, которые преследует разработчики при создании API являются:

  • Ускорение процесса клиентской и серверной разработки;

  • Снижение временных затрат на поддержку и развитие API;

  • Добавление автогенерации документации API;

  • Поддержка версионности API для упрощения поддержки обратной совместимости с помощью версионности API.

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

Повышаем уровень зрелости API

Для решения вышеперечисленных проблем и достижения целей было принято решение о переходе на 2 уровень зрелости API. Это позволило понять, с каким ресурсом идет текущая работа и что с ним необходимо сделать, плюс появилась возможность документировать API.

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

1.Разделение текущего API на смысловые части для выделения соответствующего ресурса для каждой части;

2.Использование методов, соответствующих действиям над ресурсами: GET, POST, PUT, DELETE;

3.Обозначение ресурса во множественном числе;

Пример:

GET /cats - должен вернуть список котиков

GET /cats/Pushok - должен вернуть котика по имени Пушок

(гарантируется, что у котиков уникальные имена)

4. Указание фильтрации в параметрах.

Пример:

GET /cats?color=white - должен вернуть список белых котиков

Добавляем версионность

После повышения зрелости API выходим на новый этап и выбираем способ версионирования. Для текущего проекта был выбран способ версионирования с использованием собственного заголовка. Это решение не попадает в пункт неправильное использование заголовков, так как будет использоваться собственный заголовок. Для удобства было решено указывать версии вида 2.n.

Для начала реализуем контроллер:

После этого для реализации версионности, создадим enum:

Далее создадим конвертер, используя внутренний Spring Framework интерфейс Converter<S,T>. Он преобразует исходный объект типа S в целевой типа T, в нашем случае преобразует текстовое значение версии в тип Enum ApiVersion до попадания в контроллер:

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

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

Указание версии было решено сделать обязательным.

Документируем

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

Рассмотрим пример реализации документации для созданного выше контроллера. Для этого реализуем интерфейс:

С его помощью можно добавить название и описание API текущего контроллера, описание каждого запроса, плюс есть возможность указать схему тела запроса. Остальную информацию Swagger получает из аннотаций контроллера.

Перейдя в Swagger UI увидим задокументированное API:

Получим более подробную информацию:

Преимущества новой версии API

На примерах выше был продемонстрирован переход с 0 уровня зрелости API на 2 уровень зрелости согласно модели Ричардсона, благодаря этому мы получили:

  • повышение уровня зрелости API, что дало понимание, с каким ресурсом мы работаем и что с ним делаем;

  • версионирование, которое позволит вносить изменения в текущий API;

  • удобное документирование и появление внятной документации, да и документации вообще.

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

Скорость разработки при переходе на новую версию API значительно возросла, ведь разработчикам стало значительно удобнее и приятнее работать. Наш путь разработки оказался вполне рабочим и его смело можно рекомендовать для применения в реальных проектах. А может в вашем арсенале есть другие варианты? Готовы обсудить!

Подробнее..

Заметки по книге Философия разработки ПО

02.09.2020 20:13:34 | Автор: admin


Возможно, вы понимаете как писать хороший код, как придерживаться хорошего дизайна. Но структурировать эти знания не получается. Книга Джона Оустерхаута A philosophy of software design может помочь исправить это.


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


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


О чем книга


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


Он выделяет 2 пути борьбы со сложностью:


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

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


Что такое сложность


Чтобы бороться со сложностью, нужно хорошо прояснить для себя, а что же это такое.
Симптомы сложности:


  1. Небольшие правки в функциональности требуют изменений кода во многих местах.
  2. Большая когнитивная сложность. Разработчику приходится изучить большое количество информации и держать многое в памяти, чтобы понять, как работает код.
  3. Не очевидно, что необходимо менять в коде, чтобы изменить функционал.

Главные причины сложности:


  1. Большое количество зависимостей
  2. Неочевидные вещи в коде:
    • Общие названия переменных
    • несколько целей у переменных
    • плохая документация
    • неочевидные зависимости (или утечка зависимостей)

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


По этой причине автор выделяет 2 подхода программирования, он называет их:


  • тактическое
  • стратегическое

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


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


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


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

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


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



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


Модули должны быть глубокими


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


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


Интерфейс может быть:


  1. Формальный это сигнатура, публичные методы, свойства класса и т.д.
  2. Неформальный комментарии к модулю, нюансы работы.

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


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

Если ваш модуль очень маленький, то скорее всего интерфейс этого модуля больше, чем его функционал.


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


Утечка информации


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


  • Интерфейс
  • Через back-door. Знания не описанные в интерфейсе, например, когда о формате файла знают несколько классов, хотя для она важна только для одного. Такая утечка гораздо хуже утечки через интерфейс.
    При обнаружении утечки, следует ответить на вопрос Как изменить модули, чтобы знание влияло только на 1 класс?. Возможно модули стоит объединить в один или вынести информацию наружу и обернуть её в более высокоуровневый модуль.

Временная декомпозиция


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


Общецелевые модули


Это модули с заделом на будущее, с возможностью использовать где-то ещё.


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


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


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


  1. Какой самый простой интерфейс покроет все мои нужды?
  2. В скольких ситуациях этот метод будет использован? Если только в одной, то скорее всего вы делаете интерфейс неправильно.
  3. Насколько легко использовать интерфейс в данный момент?

Разные слои, разные абстракции


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


Какие проблемы на разных слоях абстракции могут возникать:


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

Старайтесь не перекладывать ответственность на верхний уровень


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


Разделить или объединить


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


  1. Модули обращаются к общей информации.
  2. Используются совместно. Один нельзя использовать без другого.
  3. Решают общую задачу.
  4. Тяжело понять одну часть кода без другой.
  5. Если после объединения интерфейс упростится.

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


Специализированный код это та часть кода, которая очевидно нужна только в конкретной задаче.


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


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


Работа с исключениями


Здесь под исключением понимаются не только exception которые выкидываются в коде. Это любые ситуации, которые вызывают необычное поведение системы. Когда что-то происходит не так, как задумывалось.


Исключения добавляют сложность в интерфейсе потому что:


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

Исключение это тоже часть интерфейса. Чем больше исключений у интерфейса, тем он сложнее.


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


Способы скрыть исключение:


  1. Игнорировать исключение. Принять его за нормальное поведение.
  2. Обработать внутри модуля, не выбрасывая его внаружу.
  3. Обработать множество исключений в одном обработчике, прокидывая через несколько уровней вверх и обработав в одном месте.
  4. Просто прервать программу с ошибкой, когда обрабатывать её бесполезно.

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


Проектируй дважды


Не стоит реализовывать первую пришедшую идею. Стоит рассмотреть несколько вариантов. Это позволит сэкономить время на переписывании кода.


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


Стоит подумать о том:


  • какой из них проще
  • является ли вариант более переиспользуемым
  • будет ли реализация более производительной

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


Рассмотрение вариантов не должно отнимать много времени, в среднем 1-2 часа. Чем более важный и крупным модуль мы хотим разрабатывать, тем больше времени на обдумывание мы можем потратить.


Зачем писать комментарии


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


  1. Хороший код самодокументируемый. Этот подход ошибочный, потому что:
    • В коде нельзя дать высокоуровневое описание того, что делает метод или причину того, или иного решения в реализации.
    • Если пытаться упростить реализацию для легкого понимания, то придется разбивать модуль так, что это может усложнить его интерфейс.
    • Если пользователь читает всю реализацию, то ему приходится читать не только важную информацию, но и не важную, из-за чего теряется смысл в абстракции.
    • Некоторые нюансы передаваемых аргументов и свойств нельзя описать в коде.
  2. Нет времени писать комментарии. Отсутствие комментариев вынудит потратить дополнительное время на понимание кода в будущем, из-за чего оно будет потрачено в ещё большем объеме.
  3. Комментарии устаревают и вводят в заблуждение. На самом деле поддержка правильно написанных комментариев не занимает много времени. Это потребуется только если происходят большие изменения в коде.

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


Как писать комментарии


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


Я просто перечислю ряд важных советов которые дает автор.


Хороший прием использовать другие слова в комментарии чем в коде.


Описывая переменные, думайте существительными, а не глаголами.


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


Цель комментариев в реализации дать понимание, что делает код (не как он это делает). Они нужны только для больших и сложных реализаций. Для простой реализации комментарий не нужен. Если разработчик поймет, что делает код, ему будет гораздо легче понять сам код.
Также бывает так, что общая логика размазана по нескольким модулям, например отправка и получение http запроса. Комментировать это трудно, поскольку дублировать комментарии нежелательно.


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


Комментарии лучше писать вначале


Какие выгоды это дает:


  1. В процессе написания кода вы можете ориентироваться на написанные комментарии.
  2. Комментарии становятся инструментом проектирования дизайна кода. Если вы замечаете, что для интерфейса слишком длинный комментарий, скорее всего ваш интерфейс слишком сложный и лучше придумать что-то попроще.
  3. Раннее комментирование позволяет сформировать хороший дизайн и абстракцию ещё до написания кода.

Именование переменных


Именование это одна из форм документирования.
Правильное именование позволяет:


  • легче находить ошибки
  • уменьшает сложность
  • уменьшает необходимость в комментариях

Имя должно быть:


  • Не слишком общим, например count. Если трудно подобрать полноценное имя, то это признак того, что вы делаете что-то не так. Возможно переменная имеет слишком много назначений. Лучше разбить её на несколько.
  • Консистентным, т.е. такое имя должно использоваться в других местах с таким же назначением, и не использоваться другое имя для такого же назначения.

Консистентность кода


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


  • наименованиях
  • стиле кода
  • интерфейсе
  • в паттернах (например MVC улучшает консистентность)

Консистентность дает:


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

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


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

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


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

Если ответ на любой вопрос да, то тогда можно нарушить консистентность.


Тренды в разработке ПО


Наследование в ООП


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


Agile


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


Unit тесты


В отличие от интеграционных тестов, за unit тесты отвечают разработчики. Интеграционные лучше отдавать отдельной QA команде.
Unit тесты хороши тем, что позволяют не бояться рефакторить код, если они имеют хорошее покрытие.


TDD


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


Паттерны


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


Геттеры и сеттеры


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


Заключение от меня


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


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

Подробнее..

Перевод Что такое SDLC? Этапы, методология и процессы жизненного цикла программного обеспечения

02.10.2020 12:06:25 | Автор: admin
Цитируя автора книги Managing Information Technology Projects Джеймса Тейлора, жизненный цикл проекта охватывает всю деятельность проекта. Задачей же разработки ПО является выполнение требований продукта. Если вы хотите научиться создавать и выпускать высококачественное ПО, вам придется следовать плану. Со слов Тейлора, вашей целью должен стать всесторонний анализ деятельности проекта и контроля каждого этапа его разработки. Вот только с чего именно начать?

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

Принципы работы SDLC и почему им пользуются


На диаграмме ниже можно ознакомиться с шестью основными этапами SDLC.



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

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

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

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

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

Этапы SDLC и лучшие практики и методологии


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

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

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

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

Этап #1: Анализ требований


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

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

Этап #2: Планирование


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

Хороший пример:

Я много читал об истории Инстаграма, чей этап планирования занял невероятно много времени. Это совпало с бурным ростом социальных сетей, поэтому взаимодействие пользователей с продуктом во многом все еще было неизвестно. Разработчики знали, что сильный первичный опыт (съемка, редактирование и обмен фотографиями) обеспечит рост, успех и высокую конверсию, а корректное планирование упростит проектирование, поэтому планировали соответствующе и тратили на дизайн много времени. Они всегда смотрели на шаг вперед и думали о будущем социальных сетей и электронной коммерции.

Планируйте то, что вы можете контролировать, и помните о вещах, планировать которые вы не сможете. Это поможет вам получить прочную основу для перехода к третьему этапу.

Этап #3: Проектирование и дизайн


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

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

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

Этап #4: Разработка ПО


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

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

Этап #5: Тестирование


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

Этап #6: Развертывание


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

Объединяя все вместе: подход SDLC


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

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

Разработка ПО может быть трудным, и в то же время полезным занятием.

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

Мой друг хотел основать лучшее рекламное агентство для Facebook и обратился ко мне и другим специалистам за помощью. Несмотря на его большие амбиции, я посоветовал ему воспользоваться фреймворком SDLC чтобы сначала провести анализ требований. Я спросил его: Какие проблемы ты хочешь решать? Чего хотят твои пользователи? И самое главное, как эта платформа поможет тебе достичь твоих целей?

Сформулировав эти вопросы вокруг SDLC, он смог лучше отточить свой финальный продукт и предоставить нужные инструменты правильным пользователям. Он сузил свой кругозор до более строгого определения его проблемной области и смог выделить ресурсы на планирование еще до того как он начал делать что-либо еще.

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

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

Следовательно, цикл продолжается.

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

Фраза Создавать круто должна стать вашей путеводной звездой, а SDLC инструментом и помощником.
Подробнее..

Перевод Понятность ПО самый важный показатель, который вы не отслеживаете

24.11.2020 14:17:12 | Автор: admin

Привет, Хабр! В OTUS открыт набор на новый поток курса "Software Architect", в связи с этим приглашаем всех желающих на онлайн день открытых дверей, в рамках которого наши эксперты подробно расскажут о программе обучения, а также ответят на ваши вопросы.


Основные идеи

  • Все течет, все меняется. Программирование как нельзя лучше подтверждает правильность этого изречения: разработчики ежедневно модифицируют, адаптируют, дорабатывают и даже перерабатывают свои системы.

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

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

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

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

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

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

Пример

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

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

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

Устроившись на работу, любой начинающий разработчик осознает, что написать фрагмент кода для решения простой задачи по информатике (или найти ответ на StackOverflow) это совсем не то же самое, что встроить такой фрагмент в большую сложную систему.

Что такое понятность?

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

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

  • Завершенность. Система должна поставляться с определенным набором исходной информации (исходный код, документация и т.д.). У инженера должна быть вся важная информация о системе.

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

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

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

Понятность среды выполнения

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

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

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

Наблюдаемость и понятность не одно и то же

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

  1. Предупреждение о неправильном функционировании системы и помощь в выявлении причины ошибки, чтобы восстановить нормальную работу системы.

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

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

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

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

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

Борьба со сложностями

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

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

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

Понятность нового ПО

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

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

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

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

Понятность существующего ПО

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

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

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

Заключение

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

С другой стороны, мы убедились, что повысить понятность системы (упростить ее, тщательнее поработать над проектом, написать более простой код) не всегда просто. Чаще всего мы запрыгиваем в вагон на ходу, когда взять управление в свои руки практически невозможно. Отсюда вывод, что понятность это ключевой показатель, который необходимо отслеживать и которым надо управлять. Обеспечив понятность системы, можно добиться максимальной скорости ее доработки с максимально качественными результатами, даже если условия для этого не самые благоприятные.


Подробнее о курсе "Software Architect". Посмотреть открытый урок по теме "Мониторинг и Алертинг" можно здесь.


Читать ещё:

Подробнее..

Перевод Java Optional не такой уж очевидный

03.02.2021 22:16:57 | Автор: admin

NullPointerException - одна из самых раздражающих вещей в Java мире, которую был призван решить Optional. Нельзя сказать, что проблема полностью ушла, но мы сделали большие шаги. Множество популярных библиотек и фреймворков внедрили Optional в свою экосистему. Например, JPA Specification возвращает Optional вместо null.

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

Optional не должен равняться null

Мне кажется, никаких дополнительных объяснений здесь не требуется. Присваивание null в Optional разрушает саму идею его использования. Никто из пользователей вашего API не будет проверять Optional на эквивалентность с null. Вместо этого следует использовать Optional.empty().

Знайте API

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

public String getPersonName() {    Optional<String> name = getName();    if (name.isPresent()) {        return name.get();    }    return "DefaultName";}

Идея проста: если имя отсутствует, вернуть значение по умолчанию. Можно сделать это лучше.

public String getPersonName() {    Optional<String> name = getName();    return name.orElse("DefautName");}

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

public Optional<String> getPersonName() {    Person person = getPerson();    if (ALLOWED_NAMES.contains(person.getName())) {        return Optional.ofNullable(person.getName());    }    return Optional.empty();}

Optional.filter упрощает код.

public Optional<String> getPersonName() {    Person person = getPerson();    return Optional.ofNullable(person.getName())                   .filter(ALLOWED_NAMES::contains);}

Этот подход стоит применять не только в контексте Optional, но ко всему процессу разработки.

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

Отдавайте предпочтение контейнерам "на примитивах"

В Java присутствуют специальные не дженерик Optional классы: OptionalInt, OptionalLong и OptionalDouble. Если вам требуется оперировать примитивами, лучше использовать вышеописанные альтернативы. В этом случае не будет лишних боксингов и анбоксингов, которые могут повлиять на производительность.

Не пренебрегайте ленивыми вычислениями

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

public Optional<Table> retrieveTable() {    return Optional.ofNullable(constructTableFromCache())                   .orElse(fetchTableFromRemote());}

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

public Optional<Table> retrieveTable() {    return Optional.ofNullable(constructTableFromCache())                   .orElseGet(this::fetchTableFromRemote);}

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

Не оборачивайте коллекции в Optional

Хотя я и видел такое не часто, иногда это происходит.

public Optional<List<String>> getNames() {    if (isDevMode()) {        return Optional.of(getPredefinedNames());    }    try {        List<String> names = getNamesFromRemote();        return Optional.of(names);    }    catch (Exception e) {        log.error("Cannot retrieve names from the remote server", e);        return Optional.empty();    }}

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

public List<String> getNames() {    if (isDevMode()) {        return getPredefinedNames();    }    try {        return getNamesFromRemote();    }    catch (Exception e) {        log.error("Cannot retrieve names from the remote server", e);        return emptyList();    }}

Чрезмерное использование Optional усложняет работу с API.

Не передавайте Optional в качестве параметра

А сейчас мы начинаем обсуждать наиболее спорные моменты.

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

public void doAction() {    OptionalInt age = getAge();    Optional<Role> role = getRole();    applySettings(name, age, role);}

Во-первых, API имеет ненужные границы. При каждом вызове applySettings пользователь вынужден оборачивать значения в Optional. Даже предустановленные константы.

Во-вторых, applySettings обладает четырьмя потенциальными поведениями в зависимости от того, являются ли переданные Optional пустыми, или нет.

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

Если взглянуть на javadoc к Optional, можно найти там интересную заметку.

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

Optional буквально символизирует нечто, что может содержать значение, а может нет. Передавать возможное отсутствие результата в качестве параметра звучит как плохая идея. Это значит, что API знает слишком много о контексте выполнения и принимает те решения, о которых не должен быть в курсе.

Что же, как мы можем улучшить этот код? Если age и role должны всегда присутствовать, мы можем легко избавиться от Optional и решать проблему отсутствующих значений на верхнем уровне.

public void doAction() {    OptionalInt age = getAge();    Optional<Role> role = getRole();    applySettings(name, age.orElse(defaultAge), role.orElse(defaultRole));}

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

С другой стороны, если значения age и role могут быть опущены, вышеописанный способ не заработает. В этом случае лучшим решением будет разделение API на отдельные методы, удовлетворяющим разным пользовательским потребностям.

void applySettings(String name) { ... }void applySettings(String name, int age) { ... }void applySettings(String name, Role role) { ... }void applySettings(String name, int age, Role role) { ... }

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

Не используйте Optional в качестве полей класса

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

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

Отсутствие сериализуемости

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

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

Хранение лишних ссылок

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

Плохая интеграция со Spring Data/Hibernate

Предположим, что мы хотим построить простое Spring Boot приложение. Нам нужно получить данные из таблицы в БД. Сделать это очень просто, объявив Hibernate сущность и соответствующий репозиторий.

@Entity@Table(name = "person")public class Person {    @Id    private long id;    @Column(name = "firstname")    private String firstName;    @Column(name = "lastname")    private String lastName;        // constructors, getters, toString, and etc.}public interface PersonRepository extends JpaRepository<Person, Long> {}

Вот возможный результат для personRepository.findAll().

Person(id=1, firstName=John, lastName=Brown)Person(id=2, firstName=Helen, lastName=Green)Person(id=3, firstName=Michael, lastName=Blue)

Пусть поля firstName и lastName могут быть null. Мы не хотим иметь дело с NullPointerException, так что просто заменим обычный тип поля на Optional.

@Entity@Table(name = "person")public class Person {    @Id    private long id;    @Column(name = "firstname")    private Optional<String> firstName;    @Column(name = "lastname")    private Optional<String> lastName;        // constructors, getters, toString, and etc.}

Теперь все сломано.

org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: person,       for columns: [org.hibernate.mapping.Column(firstname)]

Hibernate не может замапить значения из БД на Optional напрямую (по крайней мере, без кастомных конвертеров).

Но некоторые вещи работают правильно

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

Jackson

Давайте объявим простой эндпойнт и DTO.

public class PersonDTO {    private long id;    private String firstName;    private String lastName;    // getters, constructors, and etc.}
@GetMapping("/person/{id}")public PersonDTO getPersonDTO(@PathVariable long id) {    return personRepository.findById(id)            .map(person -> new PersonDTO(                    person.getId(),                    person.getFirstName(),                    person.getLastName())            )            .orElseThrow();}

Результат для GET /person/1.

{  "id": 1,  "firstName": "John",  "lastName": "Brown"}

Как вы можете заметить, нет никакой дополнительной конфигурации. Все работает из коробки. Давайте попробует заменить String на Optional<String>.

public class PersonDTO {    private long id;    private Optional<String> firstName;    private Optional<String> lastName;    // getters, constructors, and etc.}

Для того чтобы проверить разные варианты работы, я заменил один параметр на Optional.empty().

@GetMapping("/person/{id}")public PersonDTO getPersonDTO(@PathVariable long id) {    return personRepository.findById(id)            .map(person -> new PersonDTO(                    person.getId(),                    Optional.ofNullable(person.getFirstName()),                    Optional.empty()            ))            .orElseThrow();}

Как ни странно, все по-прежнему работает так, как и ожидается.

{  "id": 1,  "firstName": "John",  "lastName": null}

Это значит, что мы можем использовать Optional в качестве полей DTO и безопасно интегрироваться со Spring Web? Ну, вроде того. Однако есть потенциальные проблемы.

SpringDoc

SpringDoc это библиотека для Spring Boot приложений, которая позволяет автоматически сгенерировать Open Api спецификацию.

Вот пример того, что мы получим для эндпойнта GET /person/{id}.

"PersonDTO": {  "type": "object",  "properties": {    "id": {      "type": "integer",      "format": "int64"    },    "firstName": {      "type": "string"    },    "lastName": {      "type": "string"    }  }}

Выглядит довольно убедительно. Но нам нужно сделать поле id обязательным. Это можно осуществить с помощью аннотации @NotNull или @Schema(required = true). Давайте добавим кое-какие детали. Что если мы поставим аннотацию @NotNull над полем типа Optional?

public class PersonDTO {    @NotNull    private long id;    @NotNull    private Optional<String> firstName;    private Optional<String> lastName;    // getters, constructors, and etc.}

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

"PersonDTO": {  "required": [    "firstName",    "id"  ],  "type": "object",  "properties": {    "id": {      "type": "integer",      "format": "int64"    },    "firstName": {      "type": "string"    },    "lastName": {      "type": "string"    }  }}

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

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

Решение

Что же нам делать со всем этим? Ответ прост. Используйте Optional только для геттеров.

public class PersonDTO {    private long id;    private String firstName;    private String lastName;        public PersonDTO(long id, String firstName, String lastName) {        this.id = id;        this.firstName = firstName;        this.lastName = lastName;    }        public long getId() {        return id;    }        public Optional<String> getFirstName() {        return Optional.ofNullable(firstName);    }        public Optional<String> getLastName() {        return Optional.ofNullable(lastName);    }}

Теперь этот класс можно безопасно использовать и как сущность Hibernate, и как DTO. Optional никак не влияет на хранимые данные. Он только оборачивает возможные null, чтобы корректно отрабатывать отсутствующие значения.

Однако у этого подхода есть один недостаток. Его нельзя полностью интегрировать с Lombok. Optional getters не подерживаются библиотекой и, судя по некоторым обсуждениям на Github, не будут.

Я писал статью по Lombok и я думаю, что это прекрасный инструмент. Тот факт, что он не интегрируются с Optional getters, довольно печален.

На текущий момент единственным выходом является ручное объявление необходимых геттеров.

Заключение

Это все, что я хотел сказать по поводу java.util.Optional. Я знаю, что это спорная тема. Если у вас есть какие-то вопросы или предложения, пожалуйста, оставляйте свои комментарии. Спасибо за чтение!

Подробнее..

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

02.03.2021 12:21:43 | Автор: admin


Познакомьтесь с Бобом


Боб чрезвычайно амбициозный и активный разработчик.

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

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

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

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

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

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

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

Это явление называется принципом Питера:

В иерархической системе каждый индивидуум имеет тенденцию подняться до уровня своей некомпетентности. [] Сливки поднимаются кверху, пока не прокиснут.

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

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

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

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

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

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

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

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

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

Творческая некомпетентность: способ быть профессионалом


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

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

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

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

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

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

Если вы думаете, что знаете всё, то ничего не знаете. Если вы думаете, что не знаете ничего, то кое-что знаете. Джейс О'Нил

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

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



На правах рекламы


Закажите сервер и сразу начинайте работать! Создание VDS любой конфигурации в течение минуты, в том числе серверов для хранения большого объёма данных до 4000 ГБ. Эпичненько :)

Подробнее..

Личный опыт подготовка к магистратуре JetBrains в Университете ИТМО и первые впечатления

16.05.2021 16:07:51 | Автор: admin

Всем привет! На связи Антон Клочков, студент первого курса корпоративной магистратуры JetBrains Разработка программного обеспечения на базе Университета ИТМО. Я хочу рассказать, как выбирал программу, и главное оправдались ли мои ожидания.

Northern Eurasia Regional Contests 2017. Антон в центре снимка [прим. ред.]Northern Eurasia Regional Contests 2017. Антон в центре снимка [прим. ред.]

Пара слов о себе

Я из небольшого города в Хабаровском крае Советской Гавани. В школьные годы участвовал в разных олимпиадах, но занимал только призовые места, и дальше региона мне пройти не удавалось. Пишу код с восьмого класса, разработкой занимаюсь с первого курса вуза.

Школу я закончил весьма успешно: золотая медаль и 276 баллов по ЕГЭ дали возможность поступить в Университет ИТМО, чем я и воспользовался. Выбрал бакалаврскую программу Программирование и интернет-технологии, где познакомился с множеством замечательных людей и расширил свой кругозор в области разработки программного обеспечения.

В конце второго курса я присоединился к 3D4Medical в роли R&D Engineer занимался оптимизациями графического движка, разрабатывал внутренние инструменты и реализовывал фичи для приложения компании Complete Anatomy. К началу четвертого курса я сменил акцент с разработки в сторону машинного обучения и перешел в другую компанию BrainGarden, где развивал проекты, связанные со SLAM (Simultaneous localization and mapping) и computer vision. Меня устраивал уровень зарплаты и был очевиден карьерный путь. Однако пробелы в области алгоритмов и в некоторых разделах математики не давали покоя. Я понимал, что магистратура возможность устранить эти пробелы, и решил выбрать для себя подходящую программу.

Выбор и поступление

На корпоративную программу JetBrains я подал документы после тщательного обдумывания и сравнения с аналогами. Критерии были следующие:

  1. Есть предметы, имеющие отношение и к разработке, и к машинному обучению. У меня был опыт работы с задачами по deep learning, и хотелось развиваться в этом направлении.

  2. Программа базируется в Санкт-Петербурге он мне очень нравится своим спокойствием и доступностью цивилизации.

  3. Большинство преподавателей являются очень хорошими специалистами в отрасли, либо работают в ней прямо сейчас.

  4. Программа актуальна. Мне жалко тратить свое время на учебу, когда полученными знаниями можно воспользоваться, только если работаешь с технологиями времен СССР.

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

В итоге в поле зрения оказалась моя нынешняя программа, а также Машинное обучение и анализ данных из петербургского кампуса НИУ ВШЭ. Знакомые студенты обеих программ заверили, что оба варианта очень сложные и интересные. Но Университет ИТМО был мне ближе по духу, поэтому я сделал выбор в его пользу.

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

Немного про учебу

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

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

Тем не менее, даже на дистанте преподаватели дают материал на высшем уровне. Например, мне кажется, что Александр Владимирович Смаль (ведет курс Алгоритмы и структуры данных) изобрёл великолепную технику преподавания. Когда требуется большое количество выкладок на доске, он использует планшет и стримит с него экран, на котором пишет. Выглядит это так:

Еще я столкнулся с совершенно новой для себя областью функциональным программированием. На этом курсе мы сначала изучали лямбда-исчисление и программирование на Haskell. Было интересно, сложно, но мы справились благодаря отличным лекциям от Дениса Николаевича Москвина и подробным практическим занятиям.

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

Научно-исследовательская работа

Кроме лекций и практик каждый семестр студенты выполняют научно-исследовательскую работу (НИР). Стоит сказать, что это один из самых интересных аспектов нашей магистратуры. Студенты получают темы от компаний: JetBrains, Яндекс, ВКонтакте и других, выбирают себе приглянувшиеся и под контролем сотрудников компании работают над ними. В частности, в первом семестре я взял тему Generative adversarial audio denoiser в компании MynaLabs.

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

Для обучения мне выделили отдельную видеокарту на корпоративных серверах и обеспечили необходимыми данными. В течение всего семестра я созванивался с научным руководителем: мы обсуждали проблемы, пути их решения, я описывал текущие результаты. В общем, мой НИР ничем не отличался от реальной работы в компании. В результате мне частично удалось обучить HiFi-GAN. Кажется, что за более длительное время я смог бы добиться и лучших результатов, но в целом я доволен проделанной работой.

Что в итоге

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

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

Оцените свою мотивацию. Эта программа реально сложная. Поэтому подумайте дважды, а то и трижды, зачем она вам. Я не пытаюсь отговорить, просто на одном желании долго не протянуть.

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

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

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


Что еще есть у нас в блоге на Хабре:


Подробнее..

Перевод Почему в мире так много отстойного ПО

17.05.2021 14:19:21 | Автор: admin
Мы буквально окружены отстойным программным обеспечением. Пенсионные фонды спотыкаются об написанные десятки лет назад пакетные скрипты с ошибочными допущениями. Из кредитных организаций утекает более сотни миллионов номеров социального обеспечения и других конфиденциальных данных. И это ещё не говоря о куче забагованного и раздражающего ПО, создаваемых и мелкими поставщиками, и крупными корпорациями.

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

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


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

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

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



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

Однако между нами и этой утопией стоят две проблемы.

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

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

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

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



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

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

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

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



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

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

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

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



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

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

image
Подробнее..

Мифический человеко-месяц 45 лет спустя

16.12.2020 16:22:14 | Автор: admin
Впервые о книге Фредерика Брукса я услышал лет десять назад, ещё учась в универе. Её настоятельно советовал почитать наш научный руководитель. Как часто бывает в таких случаях, когда кто-то вам советует что-то почитать, то вы вежливо говорите нечто вроде да-да, в скором времени, непременно этим займусь, заносите очередной пункт в свой grow list (в лучшем случае) и благополучно об этом забываете.



Через пару лет я вернулся к этой книге и наконец с ней ознакомился. К тому моменту у меня уже было несколько лет работы в IT-индустрии. И когда я начал читать, то удивился, насколько книга, написанная в 1975, да ещё и в сфере разработки ПО, по-прежнему актуальна!

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

Пара слов об авторе книги


Фредерик Брукс был менеджером проекта по созданию IBM OS/360 (группа операционных систем, разработанных IBM для мейнфреймов System/360). Для ускорения разработки он предпринял попытку привлечь ещё больше разработчиков, но решение это оказалось фатальным. Чтобы никто более не наступал на те же грабли, Брукс впоследствии написал серию очерков, которую мы теперь знаем как Мифический человеко-месяц. Считается, что каждому менеджеру проекта следует ознакомиться с этой книгой.


Высокая кухня требует времени


Именно такой эпиграф предваряет вторую главу книги, а начинается она со слов:
Все программисты оптимисты.



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

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

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

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

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

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

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

Поскольку Брукс сам погорел на этом при разработке IBM OS/360, он сформулировал такой закон:
Если проект не укладывается в сроки, то добавление рабочей силы задержит его ещё больше.

Хирургическая бригада


Третью главу Брукс начинает со следующего утверждения:
Очень хорошие профессиональные программисты в 10 раз продуктивнее слабых, при наличии того же уровня обучения и двухлетнего опыта.
Продолжая эту мысль, он говорит, что лучше всего небольшая высококлассная команда (не более 10 человек) чем меньше умов, тем лучше. На ваш взгляд, каков максимально допустимый размер команды?

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

Брукс даже приводит примерный состав команды, расписывая роль каждого её участника.

Итак, какой же состав dreamteam по Бруксу:
Хирург главный программист. Он лично задаёт спецификации, разрабатывает дизайн программы, реализует её и пишет документацию. Имеет большой талант, десятилетний опыт и значительные прикладные знания. В большинстве современных команд это тимлид или архитектор.
Второй пилот альтер эго хирурга, чуть менее опытный, но способен выполнять любой участок работы. Досконально знает весь код. Хирург обсуждает с ним свои идеи, но он не связан по рукам его советами. По описанию очень напоминает старшего программиста.
Администратор собственно, человек, который разгружает хирурга в части решения различных оргвопросов, которых в крупных компаниях бывает немало.
Редактор пишет документацию, беря за основу черновик, написанный хирургом. То есть правильно оформляет её, снабжает ссылками. Получается, это технический писатель. Брукс настаивает на том, что черновик документации должен писать именно тот, кто пишет код, то есть хирург.
Секретарь обрабатывает переписку, связанную с проектом, а также документы, не относящиеся к продукту. К слову, в нашей команде необходимость найма секретаря уже стала предметом многочисленных шуток.
Инструментальщик создаёт различные специализированные утилиты и скрипты. Очень похоже на роль девопса.
Тестировщик пишет тест-кейсы и тестирует код приложения. Также занимается созданием вспомогательных средств, необходимых для тестирования компонентов.
Языковой консультант разбирается в тонкостях используемого языка и умеет находить эффективные решения каких-нибудь каверзных задач. Часто проводит небольшие исследования в течение двух-трёх дней. Один такой консультант может работать сразу с несколькими командами. На мой взгляд, это некий разработчик единой платформы в рамках всей компании, которая позволяет командам сосредоточиться на написании самой бизнес-логики, специфичной для их проекта.
В этом распределении ролей Брукс находит два преимущества, которые выгодно отличают такую команду от обычной, где все равномерно распределяют между собой задачи. А именно концептуальная целостность и отсутствие конфликта интересов.

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

Эффект второй системы


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

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

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

Тут сложно что-либо добавить или опровергнуть. Я с автором полностью согласен. Решением данной проблемы является, на мой взгляд, принцип KISS (keep it simple, stupid). Потому что в современных реалиях любые попытки угадать дальнейшее развитие системы являются делом неблагодарным.

Пилотная установка

В этом мире нет ничего постоянного, кроме непостоянства.

Нельзя не согласиться


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

Планируйте выбросить первую версию вы всё равно это сделаете.

Как вы уже догадались, речь идёт об MVP (minimum viable product). Это некий прототип, позволяющий проверить ту или иную гипотезу бизнеса. Продолжая предыдущую тему, на этапе MVP следует выключить внутреннего перфекциониста. А уж если результат эксперимента окажется положительным, тогда получится быстрее с нуля написать вторую версию как положено.

Два шага вперёд и шаг назад


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

Все исправления тяготеют к разрушению структуры, увеличению энтропии и дезорганизации системы.

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

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

И грянул гром


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

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

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

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

Самодокументированные программы


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

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

Модель инкрементальной разработки


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



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

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

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

Зачем понадобилось переиздавать книгу


Тут хотелось бы просто привести цитату Брукса, в которой он иронизирует над самим собой. Он описывает случай, произошедший в самолёте:

Незнакомец, сидевший рядом со мной, читал Мифический человеко-месяц, и я ждал, как он отреагирует словом или знаком. Наконец, когда мы вырулили к выходу, я не выдержал:
Как вам эта книга? Рекомендуете?
Гм! Ничего такого, чего бы я уже не знал.

Я предпочёл не представляться.

Действительно, зачем переиздавать книгу, которой вот уже 45 лет и которая посвящена такой динамично развивающейся отрасли, как разработка ПО? Весь материал, который я привёл выше, показывает, что многие вопросы, рассматриваемые в книге, актуальны до сих пор. Да, у нас есть scrum, agile, стори-поинты, но если присмотреться, то все новое забытое, но проявившее себя вновь, старое.

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

AMA с разработчиками из SpaceX (часть 1)

18.05.2021 20:09:49 | Автор: admin
Упакованные спутники StarlinkУпакованные спутники Starlink

В субботу 15 мая компания SpaceX провела серию вопросов и ответов о разработке ПО в различных проектах компании. Я выделил и перевёл самые интересные из них.

В интервью участвовали:

  • Джарретт Фарнитано - работает над программным обеспечением корабля Dragon, включая дисплеи для экипажа.

  • Кристин Хуанг - ведет прикладное программное обеспечение для спутникового созвездия Starlink.

  • Жанетт Миранда - разрабатывает встроенное программное обеспечение для лазерной связи.

  • Ашер Данн - ведет программное обеспечение для Starship.

  • Натали Моррис - ведет инфраструктуру тестирования программного обеспечения для спутников.

Вопросы о ПО в целом

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

О: Работая [в SpaceX] как над критическим, так и над некритическим к безопасности программным обеспечением, вам абсолютно необходимо иметь другой менталитет. Самое главное - убедиться, что вы знаете, как поведет себя ваше программное обеспечение во всех различных сценариях. Это влияет на весь процесс разработки, включая проектирование, реализацию и тестирование. Проектирование и реализация будут иметь тенденцию к созданию более мелких компонентов с четкими границами. Это позволяет полностью протестировать эти компоненты, прежде чем они будут встроены в более широкую систему. Однако полная система все равно должна быть протестирована, что делает сквозное тестирование и наблюдаемость важной частью процесса. Выдавая информацию о решениях, которые принимает программное обеспечение, в виде телеметрии, мы можем автоматизировать мониторинг программного обеспечения. Эта автоматизация может быть использована при разработке, регрессионном тестировании, а также при тестировании программного обеспечения, работающего на реальных аппаратах во время полетов. Это позволяет нам быть уверенными в том, что программное обеспечение работает так, как ожидается, на протяжении всего жизненного цикла, особенно когда на борту находится экипаж. - Джаретт

В: Какие проблемы необходимо преодолеть, чтобы реализовать непрерывную развёртку и доставку для встроенных, орбитальных систем, таких как Starlink? Развертываете ли вы свое программное обеспечение в контейнерах? Какие трудности возникают при тестировании такой обширной сети? Продолжайте в том же духе!

О: Чтобы управлять большой спутниковой группировкой без необходимости привлечения сотен операторов-людей, мы полагаемся на автоматизацию программного обеспечения, работающего на земле и на спутниках. Для того чтобы полностью протестировать наши системы в сквозной конфигурации, это означает, что мы должны встроить сотни различных программных сервисов в среду разработки.
Еще одна проблема тестирования заключается в том, что не всегда возможно проверить все возможности одним тестом. Например, нам нужны автоматизированные тесты, которые проверяют каналы связи между спутниками и землей. У нас есть испытательные стенды HITL (hardware in the loop) для спутников, и мы можем установить макет наземной станции с фиксированной антенной. Мы можем провести тест, в котором имитируем пролет спутника над наземной станцией, но мы должны изменить программное обеспечение, чтобы спутник думал, что он всегда находится в контакте с нашей стационарной антенной. Это позволит нам протестировать весь радиочастотный и сетевой стек, но не позволит проверить логику наведения антенны. В качестве альтернативы мы можем запустить чисто программное моделирование для тестирования наведения антенны. Мы должны убедиться, что у нас достаточно поэтапного тестирования всех важных аспектов системы. - Натали

В: Насколько сильно любят Python в SpaceX? Очевидно, что он не может быть в роли пассажира первого класса, но развернут ли он где-нибудь в значительном объеме?
О: У нас в SpaceX очень много Python! Многие из наших наземных инструментов имеют значительные аспекты Python - такие системы, как наши службы анализа данных, инфраструктура тестирования и система CI/CD. Он не используется для управления космическими аппаратами, но мы очень часто обращаемся к Python для создания многих других систем. Одним из уникальных аспектов Python является то, что это отличный язык для изучения и работы на нем инженеров, не являющихся программистами (механики, двигателисты...). Мы добились больших успехов, используя его для написания тестовых примеров для программного и аппаратного обеспечения, автоматизированных конвейеров анализа данных и других подобных областей, где требуется вклад инженеров с различным образованием. - Кристин

В: Каков ваш стек решений? Языки, фреймворки, библиотеки и т.д.... Какие инструменты/редакторы/IDEs вы используете в повседневной работе? Как QA проверяет работу разработчиков, ведь вы не можете просто слетать туда и протестировать её?

О: В SpaceX мы не отделяем QA от разработки - каждый инженер, пишущий программное обеспечение, также должен участвовать в его тестировании. Как правило, мы стараемся проводить как можно больше испытаний до слияния на наших высокоточных аппаратных стендах. Наш тестовый код и результаты тестирования проходят экспертную оценку наряду с летным кодом, чтобы убедиться, что мы тестируем все правильные вещи. У нас также есть независимые инженеры, разрабатывающие сквозные тесты, которые подвергают нагрузке всю систему. Уникальность тестирования для большой спутниковой группировки заключается в том, что мы можем использовать некоторые спутники для тестирования новых функций. Мы проводим регрессионные тесты программного обеспечения, чтобы убедиться, что оно не нарушит критически важные функции, но затем мы можем выбрать спутник, развернуть новую функцию и наблюдать за ее поведением с минимальным риском для группировки. - Натали

В: Какой подход лучше для программного обеспечения полета ракеты - асинхронный или синхронный с большим количеством потоков? При выборе инструментов, стандартов, придерживаетесь ли вы консервативных решений (C++ pre '11 rev) или готовы пробовать новое (новые стандарты C++17/20, Rust)?

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


В: Как SpaceX удается использовать Linux вместо настоящей операционной системы реального времени на своих транспортных средствах? Я знаю, что патч PREEMPT_RT делает Linux более годящимся для realtime, но все же не полностью. Кажется, что полеты на ракетах и космических кораблях с экипажем - это то место, где жесткие гарантии реального времени необходимы постоянно.

О: Хотя я не могу вдаваться в подробности, мы разрабатываем наше программное обеспечение для работы без ОС реального времени. Мы также используем пользовательскую сборку Linux и полностью понимаем среду, в которой работает наше программное обеспечение и ОС. Работа в гораздо более ограниченной среде (по сравнению, скажем, с открытым интернетом) в сочетании с обширным инструментарием и тестированием аппаратного обеспечения в цикле означает, что мы можем быть уверены, что ОС будет вести себя на орбите так, как мы ожидаем". -Джарретт

В: Вы внедряете алгоритмы управления во встроенные системы?

О: Мы всегда ищем подходящее место для запуска процесса управления. Иногда лучшим местом для размещения алгоритма управления является встраиваемая система, расположенная близко к управляемому объекту. В других случаях процесс должен быть более централизованным. Если вы подумаете о таком сложном транспортном средстве, как Starship, то там нет единственного процесса управления, поскольку вам нужно управлять двигателями, закрылками, радиосистемами и т.д. - Ашер

В: Как выглядит ваш конвейер CI/CD? Устанавливаются ли новые сборки на "производственную" или "похожую на производственную" плату, подключенную к автоматизированной тестовой стойке, которая обеспечивает симулированные входы для датчиков, или все автоматизированные тесты проводятся в полностью симулированной среде?

О: У нас есть много различных типов тестовых сред. Некоторые из них являются чисто моделируемыми средами, которые мы называем HOOTL. Они могут работать в CI/CD, а также на рабочем столе разработчика для локальной проверки. В других случаях используются аппаратные средства, приближенные к реально летающим, которые мы называем HITL (Программно-аппаратные). Наши установки Starlink HITL - это просто спутники, которые мы снимаем с производства и встраиваем в наши системы CI. Мы настраиваем наши CI-конвейеры так, чтобы начать с быстрых, недорогих тестов для выявления основных ошибок. Затем, если они проходят, мы запускаем более длинные и сложные тесты. У нас также есть разные конвейеры для разных частей системы. Например, в Starlink у нас есть конвейер для тестирования программного обеспечения пользовательского терминала в изоляции. Если эти тесты пройдут, они будут включены в другие конвейеры, тестирующие интерфейс между программным обеспечением пользовательского терминала и спутниками". -Натали

В: Какие инструменты вы используете для тестирования и непрерывного развёртывания? И как вы моделируете оборудование ракеты и спутника?

О: Многое из этого создаётся самостоятельно. У нас есть целая команда, занимающаяся созданием инструментов CI/CD, а также основной инфраструктуры для тестирования и моделирования, которую используют наши транспортные средства. Запуск высокоточных физических симуляторов вместе с нашим программным обеспечением для его тестирования ставит перед нами интересные задачи, с которыми большинство готовых инструментов CI/CD справляются не очень хорошо. Моделирование не только требует много вычислений, но и может быть очень длительным (вспомните полет Dragon от взлета до стыковки), и нам нужно иметь возможность запускать как чисто программные, так и аппаратно-программные симуляции, когда мы загружаем программное обеспечение на тестовые стенды с копиями реальных компьютеров и электроники. Мы проделали большую работу, чтобы разработчики могли легко запускать эти тесты во время своей повседневной работы - мы можем запускать те же виды тестов на наших рабочих станциях, что и в кластере CI, и мы можем запускать примеры на тестовых стендах с аппаратным обеспечением в контуре еще до объединения изменений. Это позволяет нам быть уверенными в коде, который мы пишем. Мы также используем некоторые готовые решения; например, мы широко используем Bazel для сборки и модульного тестирования - Натали.


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

Подробнее..

Из песочницы AI, который не просит хлеба

17.11.2020 14:16:28 | Автор: admin
Статья о том, как мы шаг за шагом строили наш AI. Время чтения 10+ минут.



Введение. Стартап в области компьютерного зрения, используемый low-cost разработку в качестве базовой концепции. Команда вполне соответствует духу: 3 5 студентов разработчиков разного уровня и направления, в зависимости от дня недели и времени суток (от 0.25 до 1.25 ставки). Мой опыт игры в пятнашки здесь очень пригодился.

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

С технической стороны ограничений на железо не было, главное чтоб работало хорошо; а вот с финансовой были. На все про все ~500$. Разумеется только новые и современные комплектующие. Выбор их не велик, но есть!

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

Разработка фичей шла от простых и необходимых (работа с потоками и видео файлами) к сложным, с периодическим review. Собрали MVP, несколько спринтов оптимизации заметно приблизили нас к заветной цели выполнять все 4 пункта одновременно, а не по отдельности:

  1. 16+ ip-камер (FHD/25fps) трансляция, воспроизведение по событию или времени и запись
  2. Параллельная работа всех имеющихся CV алгоритмов
  3. Пользователь интенсивно пользуется интерфейсом без задержек смотрит стримы
  4. Загрузка ЦП менее 90% и все работает (!)

Немного о стеке, выбор пал на: С/С+, Python + TensorFlow, PHP, NodeJS, TypeScript, VueJS, PostgreSQL + Socket.io и прочие мелочи.

Реализованные фичи были сознательно скрыты, чтоб подробнее остановится, пожалуй, на самой интересной и восхитительной фичи из области CV и в некоторой степени ML.

Уникальные User


Пример использования собирать историю визитов каждого конкретного посетителя, причем сотрудников учитывать отдельно, даже если мы не знаем что это сотрудник (Пример ТРЦ).
И казалось бы, вроде как эта задача решена 100500+ раз и телефоны и все что угодно уже умеет распознавать лица и запоминать их, отправлять куда то, сохранять. Но 95% решений используются в СКУД, где сам пользователь стараясь быть распознанным, стоит перед камерой 5Мп на расстоянии 30-50см в течении нескольких секунд, пока его лицо не сверится с одним или несколькими лицами из БД.

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

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

Подход к решению: задача была декомпозированна на 2 задачи + структура БД.

Краткосрочная память


Отдельный сервис, где в основном протекает real-time процесс, на входе кадр с камеры (на самом деле другого сервиса), на выходе http запрос с нормированным 512-и мерным Х-вектором (face-id) и некоторыми мета-данными, например time stamp.
Внутри нее множество интересных решений в области логики и оптимизации, но на этом всё; пока что всё

Долгосрочная память


Отдельный сервис, где требования к real-time не стоит остро, но в некоторых случая это важно (например человек из стоп листа). В целом ограничились 3 секундами на обработку.
На входе в сервис http от краткосрочной памяти с 512-и мерным вектором внутри; на выходе Id посетителя.

Первые мысли очевидны, решение задачи достаточно просто: получил http сходил в БД, взял, что есть сравнил с начинкой http, если есть такой, то он и есть; если нет, то новый.
Плюсов такого решения не счесть, а минус только один оно не работает.

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

  1. Каждый вектор (а-вектор) будет принадлежать какому либо User; каждый кластер (не более М векторов, из коробки M =30) принадлежит какому либо User. Принадлежит ли а-вектор кластеру А не факт. Вектора в кластере определяют взаимодействие кластера, вектора в User определяют только историю User.
  2. Каждый кластер будет иметь центроид (по сути А-вектор) и собственный радиус (далее range) взаимодействия с другими векторами или кластерами.
  3. Центроид и range будут функцией кластера, а не статикой.
  4. Близость векторов определяется квадратом евклидова расстояния (в особых случаях иначе). Хотя здесь есть еще несколько других достойных методов, но мы просто остановились на этом.

Примечание: т.к. мы использовали нормированные вектора, то расстояние между ними было гарантированно от 0 до 2. Далее о алгоритме реализации концепции.

#1 Круг подозреваемых. Центроид, как хэш-функция


Х-вектор, полученный от краткосрочной памяти сравнивается с имеющимися в БД центроидами кластеров (А-вектор) на предмет близости, далекие, где range[X,A] > 1 отбрасывались. Если никого не оставалось создается новый кластер.

Далее ищется минимум между Х-вектором и всеми оставшимися а-векторами (min_range[X,a])

#2 Уникальные свойства кластера. Саморегулируемая сущность


Вычисляется собственный range_A кластера, чей вектор самый близкий к Х-вектору. Здесь используется обратная линейная функция от количества векторов (N), уже находящихся в этом кластере (const*(1 N/2M)); из коробки const =0,67).

#3 Валидация и непонимание. Если ни кто то кто !?


Если range_А > min_range[X,a], то Х-вектор помечается как принадлежащий к А-кластеру. Если нет то Ох Это чем то похоже на описание математической модели недопонимания.
Определились с тем, что в этом случае будем создавать новый кластер, тем самым сознательно шли на ошибку 1-го рода Пропуск цели.

#4 Дообучение. Как циферки формируют признаки


Субъективный опыт это когда данные становятся инструментом. Ранее мы распознали, но возможно с ошибкой. Стоит ли доверять Х-вектору, чтоб использовать его в следующем матчинге !? Проверяем! Х-вектор должен:

  • быть достаточно близок к центроиду А (range_А > range[X, А])
  • быть полезным и разнообразным, ведь с одной стороны мы минимизируем риск ошибки, с другой копии нам тоже не нужны (Config_Max[0,35] > range[X,a] > Config_Max[0,125]). Тем самым, конфиги определяют скорость и правильность обучения.

Выполняя эти условия, Х-вектор попадает в состав кластера А ( до этого он просто принадлежал User). Если векторов в кластере становится больше допустимого, то убираем самый центральный (min_range[А,a]) он вносит меньше всего разнообразия и является лишь функцией остальных; к тому же центроид и так участвует в матчинге.

#5 Работа над ошибками. Превращаем недостатки в достоинства


В каждом сложном выборе мы делали шаг в сторону ошибки Пропуск цели создавали новый кластер и User. Пришло время пересмотреть их все. После #4 мы имеем модифицированный кластер А. Далее мы пересчитываем его центроид (А-вектор) и ищем минимальное расстояние ко всем имеющимися центроидам в нашем 512-ти мерном пространстве. В этом случае расстояние считается более сложно, но это сейчас не так важно. Когда расстояние min_range[A,B] будет меньше, чем некоторая величина (из коробки range_unity=0,25) мы объединяем два множества, считаем новый центроид и избавляемся от менее полезных векторов, если их слишком много.
Другими словами: если существует 2+ кластера, в действительности, принадлежащих одному User, то они, спустя некоторую серию детекций, станут близки и объединяться в один вместе со своими историями.

#6 Комбинаторика признаков. Когда машина думает !?


Здесь стоит определить новый термин в этой статье. Фантомный вектор вектор, который был получен не в результате деятельности краткосрочной памяти, а в результате функции над N-шт векторов кластера (a1,a2,a3,a4). Разумеется, полученные таким образом вектора хранятся и учитываются отдельно и не представляют из себя ни какой ценности до тех пор, пока в результате матчинга не будут определены, как ближайшие (см #3). Основная польза фантомных векторов ускорение обучения кластера на его ранних этапах.

Система уже запущена в продакшен. Результат был получен на реальных данные вне тестовой среды на 5000+ User; так же там была замечена пачка слабых мест, которые были усилены и учтены в этом тексте.

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

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

Результат


Величина близости при распознании, основанном на долгосрочной памяти ~0,12-0,25 у умеренно обученного кластера (содержит 6-15 а-векторов). Далее обучение замедляется по причине повышения вероятности копий векторов, но в долгосрочной перспективе близость стремится к величинам ~0,04-0,12, когда кластер содержит уже 20+ а-векторов. Замечу, что внутри краткосрочной памяти, от кадра к кадру, этот же параметр имеет значение ~0,5-1,2, что звучит примерно как: Человек больше похож* на себя в очках 2 года назад, чем 100мс назад. Такие возможности открывает использование кластеризации в долгосрочной памяти.

Загадка


В результате одного из тестов получилось интересное наблюдение.

Начальные условия:

  • На двух абсолютно одинаковых ПК развернута абсолютно одинаковая система видео наблюдения с абсолютно одинаковыми настройками. Они подключены к одной единственной ip-камере, расположенной грамотно, согласно ТЗ.

Действие:

  • Системы запускают в одно и то же время и оставляют на неделю в покое со всеми работающими алгоритмами. Трафик соответствует нормальному трафику без изменений.

Результат:

  • Количество созданных User, кластеров и а-векторов одинаково, а центроиды разные, не значительно но разные. Вопрос почему? Кто знает пишите в комментариях или сюда)

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

В заключении скажу, что наблюдать изнутри, как автономная AI система классифицирует окружающее себя пространство, реализуя по пути различные заложенные в нее фичи очень интересно. Люди много чего не замечают в силу наличия накопленного опыта восприятия (шаг #4).


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

Может поменять способ хранения?

24.05.2021 20:06:11 | Автор: admin

Собрались однажды 2 разработчика. И нужно было им новую HTTP API реализовать для игрового магазина. Дошло дело до выбора БД, которую стоит применить в проекте:

- Слушай, а как мы выберем? Реляционную БД использовать или NoSQL. В частности, может нужна документоориентированная?

- Сперва нужно понять какие данные будут в нашей предметной области!

- Да, вот я уже набросал схемку:

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

- Выглядит так, что у нас уже есть четко описанная структурированная модель, да и опыт использования MySQL есть в компании. Предлагаю использовать его!


И реализовали разработчики успешно свою задумку. Аккуратно нормализовали данные, использовали ORM для работы с БД из приложения. Написали красивый и аккуратный код.

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

Корнем проблемы являлось большое (очень) количество запросов к БД - на каждое отношение между сущностями ORM генерировала дополнительный запрос.

Разработчики опустили руки и приуныли. Что же делать? Повезло им, ведь нашли они мудрость, увековеченную на бумажном носителе (милая книга с кабанчиком от Клеппмана).

А книга и порекомендовала еще раз внимательно взглянуть на свою предметную область... Ведь отношения между сущностями образуют дерево! А дерево можно уместить в одном документе (или представить с помощью одного JSON), что позволит избежать такого количества запросов.

Вооружились идеей разработчики и просто сериализовали сущность в JSON и сложили в 1 столбец MySQL (+ несколько генерируемых столбцов с индексами, для поиска):

95 перцентиль уменьшилась более чем в 3 раза, пропорционально увеличился и выдаваемый rps одного инстанса приложения.

А ведь просто поменяли формат хранимых данных, никак не затрагивая инфраструктуру и конфигурацию приложения

Какой вывод можно сделать?

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

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

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

Подробнее..

Из песочницы Введение в теорию компиляторов лексический анализ языка Pascal средствами C

17.08.2020 12:08:41 | Автор: admin

Введение


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

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

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

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

Реализация


Описание структуры


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

private string[] Words = { "program", "var", "integer", "real", "bool", "begin", "end", "if", "then", "else", "while", "do", "read", "write", "true", "false" };private string[] Delimiter = { ".", ";", ",", "(", ")", "+", "-", "*", "/", "=", ">", "<" };public List<Lex> Lexemes = new List<Lex>();


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

class Lex{    public int id;    public int lex;    public string val;    public Lex(int _id, int _lex, string _val)    {        id = _id;        lex = _lex;        val = _val;    }}

Наилучшим решением для обработки лексем будет служить некий конечный автомат. Это позволит избавиться от лишних if-ов, а также даст возможность легко вносить изменения в цикл. S начальное состояние, NUM, DLM, ASGN, ID состояния соответствующих видов лексем, ER будет использоваться для ошибки, а FIN для конечного состояния.

private string buf = ""; // буфер для хранения лексемыprivate char[] sm = new char[1];private int dt = 0;private enum States { S, NUM, DLM, FIN, ID, ER, ASGN, COM } // состояния state-машиныprivate States state; // хранит текущее состояниеprivate StringReader sr; // позволяет посимвольно считывать строку

Основными методами являются SearchLex, который ищет лексему в нашем массиве и возвращает ее id и значение в кортеже (да, кортежи тоже бывают полезными), а также PushLex, который добавляет новую лексему в словарь.

private (int, string) SerchLex(string[] lexes){    var srh = Array.FindIndex(lexes, s => s.Equals(buf));     if (srh != -1)        return (srh, buf);                 else return (-1, "");}private (int, string) PushLex(string[] lexes, string buf){    var srh = Array.FindIndex(lexes, s => s.Equals(buf));    if (srh != -1)        return (-1, "");    else    {        Array.Resize(ref lexes, lexes.Length + 1);        lexes[lexes.Length - 1] = buf;        return (lexes.Length - 1, buf);    }}

Реализация алгоритма


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

sr = new StringReader(text); // Получение исходного кода программыwhile (state != States.FIN){    switch (state)    {        case States.S:            if (sm[0] == ' ' || sm[0] == '\n' || sm[0] == '\t' || sm[0] == '\0' || sm[0] == '\r' )                GetNext();            else if (Char.IsLetter(sm[0]))            {                ClearBuf();                AddBuf(sm[0]);                state = States.ID;                GetNext();            }            else if (char.IsDigit(sm[0]))            {                dt = (int)(sm[0]-'0');                GetNext();                state = States.NUM;                            }            else if (sm[0] == '{')            {                state = States.COM;                GetNext();            }            else if (sm[0] == ':')            {                state = States.ASGN;                ClearBuf();                AddBuf(sm[0]);                GetNext();            }            else if (sm[0] == '.')            {                AddLex(Lexemes, 2, 0, sm[0].ToString());                state = States.FIN;            }            else            {                state = States.DLM;            }        break;    }  }

Метод GetNext позволяет получить следующий символ в строке, ClearBuf, соответственно, очищает буфер, хранящий в себе лексему

private void GetNext(){    sr.Read(sm, 0, 1);}

Особое внимание стоит уделить оператору присваивания ":=", который состоит из двух отдельных операторов. Самым простым способом определения данного оператора является добавление условия и запись промежуточного значения в буфер. Для этого было реализовано отдельное состояние ASGN (в переводе assing присваивание). В случае определения буфера как ":", алгоритм просто добавит новую лексему, а если следующим знаком является "=", то будет добавлен уже один оператор присваивания.

case States.ASGN:    if (sm[0] == '=')    {        AddBuf(sm[0]);        AddLex(Lexemes, 2, 4, buf);        ClearBuf();        GetNext();    }    else        AddLex(Lexemes, 2, 3, buf);    state = States.S;break;

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

case States.ER:    MessageBox.Show("Ошибка в программе");    state = States.FIN;    break;case States.FIN:    MessageBox.Show("Лексический анализ закончен");    break;

Тестирование


Протестировать алгоритм можно по-разному: указать напрямую путь .pas файла, программно создать строку или любой другой удобный вариант. Так как мы пишем на C#, не составит труда добавить форму в приложение, на которой будет 2 textBox-а, первый для ввода кода программы, второй выводит результат работы алгоритма.

По нажатию кнопки будем запускать анализ текста, а полученный результат будем обрабатывать с помощью switch конструкции: дополнительно выведем к какому типу относится найденная лексема.

private void button1_Click(object sender, EventArgs e){    textBox2.Clear();    TplMain tpl = new TplMain();    tpl.Analysis(textBox1.Text);        foreach(var lex in tpl.Lexemes)    {        switch (lex.id)        {            case 1:                textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " служебные слова "+ Environment.NewLine;                break;            case 2:                textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " ограничители " + Environment.NewLine;                break;            case 3:                textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " числа " + Environment.NewLine;                break;            case 4:                textBox2.Text += "id: " + lex.id + " lex: " + lex.lex + " val: " + lex.val + " |" + " идентификатор " + Environment.NewLine;                break;                        }         }       }

Входные данные


program hellohabr;var a, b, c : integer;beginc := a - b + 15;end.

Выходные данные


id: 1 lex: 0 val: program | служебные слова id: 4 lex: 1 val: hellohabr | идентификатор id: 2 lex: 1 val: ; | ограничители id: 1 lex: 1 val: var | служебные слова id: 4 lex: 1 val: a | идентификатор id: 2 lex: 2 val: , | ограничители id: 4 lex: 1 val: b | идентификатор id: 2 lex: 2 val: , | ограничители id: 4 lex: 1 val: c | идентификатор id: 2 lex: 3 val: : | ограничители id: 1 lex: 2 val: integer | служебные слова id: 2 lex: 1 val: ; | ограничители id: 1 lex: 5 val: begin | служебные слова id: 4 lex: 1 val: c | идентификатор id: 2 lex: 4 val: := | ограничители id: 4 lex: 1 val: a | идентификатор id: 2 lex: 6 val: - | ограничители id: 4 lex: 1 val: b | идентификатор id: 2 lex: 5 val: + | ограничители id: 3 lex: 1 val: 15 | числа id: 2 lex: 1 val: ; | ограничители id: 1 lex: 6 val: end | служебные слова id: 2 lex: 0 val: . | ограничители 

Полный алгоритм


public void Analysis(string text){    sr = new StringReader(text);    while (state != States.FIN)    {        switch (state)        {            case States.S:                if (sm[0] == ' ' || sm[0] == '\n' || sm[0] == '\t' || sm[0] == '\0' || sm[0] == '\r')                    GetNext();                else if (Char.IsLetter(sm[0]))                {                    ClearBuf();                    AddBuf(sm[0]);                    state = States.ID;                    GetNext();                }                else if (char.IsDigit(sm[0]))                {                    dt = (int)(sm[0] - '0');                    GetNext();                    state = States.NUM;                }                else if (sm[0] == '{')                {                    state = States.COM;                    GetNext();                }                else if (sm[0] == ':')                {                    state = States.ASGN;                    ClearBuf();                    AddBuf(sm[0]);                    GetNext();                }                else if (sm[0] == '.')                {                    AddLex(Lexemes, 2, 0, sm[0].ToString());                    state = States.FIN;                }                else                {                    state = States.DLM;                }                break;            case States.ID:                if (Char.IsLetterOrDigit(sm[0]))                {                    AddBuf(sm[0]);                    GetNext();                }                else                {                    var srch = SerchLex(Words);                    if (srch.Item1 != -1)                        AddLex(Lexemes, 1, srch.Item1, srch.Item2);                    else                    {                        var j = PushLex(TID, buf);                        AddLex(Lexemes, 4, j.Item1, j.Item2);                    }                    state = States.S;                }                break;            case States.NUM:                if (Char.IsDigit(sm[0]))                {                    dt = dt * 10 + (int)(sm[0] - '0');                    GetNext();                }                else                {                    var j = PushLex(TNUM, dt.ToString());                    AddLex(Lexemes, 3, j.Item1, j.Item2);                    state = States.S;                }                break;            case States.DLM:                ClearBuf();                AddBuf(sm[0]);                var r = SerchLex(Delimiter);                if (r.Item1 != -1)                {                    AddLex(Lexemes, 2, r.Item1, r.Item2);                    state = States.S;                    GetNext();                }                else                    state = States.ER;                break;            case States.ASGN:                if (sm[0] == '=')                {                    AddBuf(sm[0]);                    AddLex(Lexemes, 2, 4, buf);                    ClearBuf();                    GetNext();                }                else                    AddLex(Lexemes, 2, 3, buf);                state = States.S;                break;            case States.ER:                MessageBox.Show("Ошибка в программе");                state = States.FIN;                break;            case States.FIN:                MessageBox.Show("Лексический анализ закончен");                break;        }    }}

Заключение


Может показаться, что лексический анализатор штука не очень понятная, да и собственно не очень важная. Почему нельзя вынести все это в синтаксический анализатор? Как работать со сложными конструкциями? Да, способы реализации лексического анализатора разнятся от компилятора к компилятору, но при разборе всех этих принципов появится не только понимание работы языка программирования X, но и появится фундамент для разработки собственного языка программирования: второй Python, или язык для вашей предметной области все это можно реализовать при понимании всех специфик работы и устройства компилятора в общем виде.

С проектом можно ознакомиться по ссылке
Подробнее..

Active Restore С чего начать разработку в UEFI

20.07.2020 16:23:27 | Автор: admin

Всем привет. В рамках проекта от компании Acronis со студентами Университета Иннополис (подробнее о проекте мы уже описали это тут и тут) мы изучали последовательность загрузки операционной системы Windows. Появилась идея исполнять логику даже до загрузки самой ОС. Следовательно, мы попробовали написать что-нибудь для общего развития, для плавного погружения в UEFI. В этой статье мы пройдем по теории и попрактикуемся с чтением и записью на диск в pre-OS среде.


cover


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


Полезные ссылки


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


  • В первую очередь это UEFI and EDK II Learning and Development от tianocore. Прекрасно структурированный и иллюстрированный курс, который поможет понять что же происходит в момент загрузки и что такое UEFI. Если вы ищите точную теоретическую информацию по теме, вам туда. Если хочется поскорее перейти к написанию UEFI драйверов, то сразу в Lesson 3.
  • Статьи на хабре раз, два и три. Автору низкий поклон. Это отличное практическое руководство без лишних сложностей для начинающих. Частично в данной статье я буду цитировать эти шедевры, хоть и с небольшими изменениями. Без этих публикаций, было бы значительно тяжелее начать.
  • Для продолжающих рекомендую эту статью и другие этого же автора.
  • Так как мы планируем писать драйвер, очень поможет официальный гайдлайн по написанию драйвера. Наиболее правильные советы будут именно там.
  • Ну и на крайний случай спецификация UEFI.

Немного теории


Хочу напомнить требования и цели проекта Active Restore. Мы планируем приоритизировать файлы в системе для более эффективного восстановления. Для этого нужно запуститься на максимально раннем этапе загрузки ОС. Для понимания наших возможностей в мире UEFI стоит немного углубиться в теорию о том как проходит цикл загрузки. Информация для этой части полностью взята из этого источника, который я постараюсь популярно пересказать.


UEFI


UEFI или Unified Extensible Firmware Interface стал эволюцией Legacy BIOS. В модели UEFI тоже есть базовая система ввода-вывода для взаимодействия с железом, хотя процесс загрузки системы и стал отличаться. UEFI использует GPT (Guid partition table). GPT тесно связана со спецификацией и является более продвинутой моделью для хранения информации о разделах диска. Изменился процесс, но задачи остались прежними: инициализация устройств ввода-вывода и передача управления в код операционной системы. UEFI не только заменяет бльшую часть функций BIOS, но также предоставляет широкий спектр возможности для разработки в pre-OS среде. Хорошее сравнение Legacy BIOS и UEFI есть тут.


arch


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


В мире UEFI мы можем разрабатывать драйвера или приложения. Есть специальный подтип приложений загрузчики. Разница лишь в том, что эти приложения не завершаются привычным нам образом. Завершаются они вызовом функции ExitBootServices() и передают управление в операционную систему. Чтобы принять решение какой же драйвер нужен вам, рекомендую заглянуть сюда, чтобы расширить понимание о протоколах и рекомендациях по их использованию.


Dev kits


Небольшой список того, что мы будем использовать в нашей практике:


  • EDKII (Extensible Firmware Interface Development Kit) является свободно распространяемым проектом для разработки UEFI приложений и драйверов, которую и мы будем использовать, по началу не сильно в неё углубляясь.
  • VisualUEFI проект облегчающий разработку в Visual Studio. Больше не нужно заморачиваться с .inf файлами и ковыряться в 100500 скриптах на Python. Все это уже сделано за вас. Внутри можно найти QEMU для запуска нашего кода. В проекте представлены примеры приложения и драйверов.
  • Coreboot комплексный проект для firmware. Его задача помочь разработать решение для старта железа и передачи управления в payload (например UEFI или GRUB), который в свою очередь загрузит операционную систему. В данной статье мы не будем затрагивать coreboot. Оставим его для будущих экспериментов, когда набью руку с EDKII. Возможно правильным вектором развития будет Coreboot + Tianocore UEFI + Windows 7 x64.

Последовательность загрузки


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


boot_seq
Ссылка


Процесс с момента нажатия на кнопку питания на корпусе и до полной готовности UEFI интерфейса называется Platform Initialization и делится он на несколько фаз:


  • Security (SEC) зависит от платформы и процессора, обычно реализована ассемблерными командами, проводит первоначальную инициализацию временной памяти, проверку остальной части платформы на безопасность различными способами.
  • Pre EFI Initialization (PEI) в данной фазе уже начинается работа EFI кода, главная задача загрузка DXE Foundation который будет стартовать DXE драйверов на следующей фазе. На самом деле, тут происходит еще очень много всего, но то, что мы планируем разрабатывать, сюда не пролезет, так что двигаемся дальше.
  • Driver Execution Environment (DXE) на данном этапе начинают стартовать драйвера. Наиболее важная для нас фаза, потому, что наш драйвер тоже будет запущен тут. Данная среда исполнения драйверов и является основным преимуществом над Legacy BIOS. Тут код начинает исполняться параллельно. DXE ведет себя на манер операционной системы. Это позволяет различным компаниям имплементировать свои драйвера. DXE Foundation, развернутый на предыдущей фазе, поочередно находит драйвера, библиотеки и приложения, разворачивает их памяти и исполняет.
  • После этой фазы эстафету принимает Boot Device Selection (BDS). Вы наверняка лично видели данную фазу. Тут происходит выбор на каком устройстве искать приложение загрузчик операционной системы. После выбора начинается переход к операционной системе. DXE boot драйвера начинают выгружаться из памяти. Загрузчик операционной системы наоборот загружается в память с помощью блочного протокола ввода вывода BLOCK_IO. Здесь не все DXE драйвера завершают свою работу. Существуют так называемые runtime драйвера. Им придется на понятной для загруженной операционной системе нотации разметить память, которую они занимают. Иными словами виртуализировать свои адреса в адресное пространство Windows, когда произойдет вызов функции SetVirtualAddressMap(). Как только среда будет готова, Main функция ядра ОС начнет исполнение, а фаза EFI завершится вызовом ExitBootServices(). Контроль полностью передан в операционную систему. Дальше Windows будет решать какие и откуда загрузить дайвера, как читать и писать на диск и что за файловую систему использовать. Картинка обобщающая вышеуказанную последовательность:

image
Ссылка


Классный рассказ о этапах загрузки есть тут.


Подготовка проекта


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


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


Допускаю, что у вас уже есть Visual Studio. В моем случае у меня Visual Studio 2019. Для начала клонируем себе проект VisualUEFI:


git clone --recurse-submodules -j8 https://github.com/ionescu007/VisualUefi.git

Нам понадобится NASM (https://www.nasm.us/pub/nasm/releasebuilds/2.15.02/win64/). Переходим и скачиваем. На момент написания статьи актуальной версией является 2.15.02. После установки убедитесь, что в переменных средах у вас есть NASM_PREFIX, который указывает на папку, в которую был установлен NASM. В моем случае это C:\Program Files\NASM\.


NASM_PREFIX


Соберем EDKII. Для этого открываем EDK-II.sln из \VisualUefi\EDK-II, и просто жмем build на решении. Все проекты в решении должны успешно собраться, и можно переходить к уже готовым примерам. Открываем samples.sln из \VisualUefi\samples. Жмем build на приложении и драйвере, после чего можно запускать QEMU простым нажатием F5.


Shell


Проверяем наш UefiDriver и UefiApplication, именно так называются примеры в решении samples.sln.


Shell> fs1:FS1:\> load UefiDriver.efi

load


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


drivers


Если бы в коде мы не возвращали EFI_ACCESS_DENIED в функции UefiUnload, мы бы даже смогли выгрузить наш драйвер, выполнив команду:


FS1:\> unload BA

Теперь вызовем наше приложение:


FS1:\> UefiApplication.efi

app


Написание кода


Рассмотрим код предоставленного нам драйвера. Все начинается с функции UefiMain, которая находится в файле drvmain.c. Мы бы могли назвать точку входа и другим именем, если бы писали драйвер с нуля, указать это можно было бы в .inf файле.


EFI_STATUSEFIAPIUefiUnload (    IN EFI_HANDLE ImageHandle    ){    //    // Do not allow unload    //    return EFI_ACCESS_DENIED;}EFI_STATUSEFIAPIUefiMain (    IN EFI_HANDLE ImageHandle,    IN EFI_SYSTEM_TABLE *SystemTable    ){    EFI_STATUS efiStatus;    //    // Install required driver binding components    //    efiStatus = EfiLibInstallDriverBindingComponentName2(ImageHandle,                                                         SystemTable,                                                         &gDriverBindingProtocol,                                                         ImageHandle,                                                         &gComponentNameProtocol,                                                         &gComponentName2Protocol);    return efiStatus;}

В проекте от нас не требуют регистрировать Unload функцию, так как VisualUEFI это и так уже делает под капотом, нужно просто её объявить. В примере она в этом же файле и называется UefiUnload. В этой функции мы можем написать код, который освободит все занятые нами ресурсы, так как она будет вызвана при выгрузке драйвера. Регистрация Unload функции в проекте VisualUEFI происходит в файле DriverEntryPoint.c, в функции _ModuleEntryPoint.


// _DriverUnloadHandler manages to call UefiUnloadStatus = gBS->HandleProtocol (                    ImageHandle,                    &gEfiLoadedImageProtocolGuid,                    (VOID **)&LoadedImage              );ASSERT_EFI_ERROR (Status);LoadedImage->Unload = _DriverUnloadHandler;

В нашем примере, в функции UefiMain, происходит вызов функции EfiLibInstallDriverBindingComponentName2, которая регистрирует имя нашего драйвера и Driver Binding Protocol. Согласно модели драйверов UEFI, все драйвера устройств должны регистрировать этот протокол для предоставления контроллеру функций Support, Start, Stop. Функция Support отвечает, может ли наш драйвер работать с данным контроллером. Если да, то вызывается функция Start. Подробнее об этом хорошо описано в спецификации (раздел Protocols UEFI Driver Model). В нашем примере функции Support, Start и Stop устанавливают наш кастомный протокол. Его реализация в файле drvpnp.c:


//// EFI Driver Binding Protocol//EFI_DRIVER_BINDING_PROTOCOL gDriverBindingProtocol ={    SampleDriverSupported,    SampleDriverStart,    SampleDriverStop,    10,    NULL,    NULL};//// Install our custom protocol on top of a new device handle//efiStatus = gBS->InstallMultipleProtocolInterfaces(&deviceExtension->DeviceHandle,                                                       &gEfiSampleDriverProtocolGuid,                                                       &deviceExtension->DeviceProtocol,                                                       NULL);//// Bind the PCI I/O protocol between our new device handle and the controller//efiStatus = gBS->OpenProtocol(Controller,                                  &gEfiPciIoProtocolGuid,                                  (VOID**)&childPciIo,                                  This->DriverBindingHandle,                                  deviceExtension->DeviceHandle,                                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);

Фукнция EfiLibInstallDriverBindingComponentName2 реализована в файле UefiDriverModel.c, и, на самом деле, очень простая. Она вызывает InstallMultipleProtocolInterfaces из Boot Services (см. Спецификацию стр 210). Данная функция связывает handle (в нашем случае ImageHandle, который мы получили на точке входа) и протокол.


// install component name and bindingStatus = gBS->InstallMultipleProtocolInterfaces (                       &DriverBinding->DriverBindingHandle,                       &gEfiDriverBindingProtocolGuid, DriverBinding,                       &gEfiComponentNameProtocolGuid, ComponentName,                       &gEfiComponentName2ProtocolGuid, ComponentName2,                       NULL                       );

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

EFI_STATUSEFIAPIUefiUnload (    IN EFI_HANDLE ImageHandle    ){  gBS->UninstallMultipleProtocolInterfaces(    ImageHandle,    &gEfiDriverBindingProtocolGuid, &gDriverBindingProtocol,    &gEfiComponentNameProtocolGuid, &gComponentNameProtocol,    &gEfiComponentName2ProtocolGuid, &gComponentName2Protocol,    NULL  );    //    // Changed from access denied in order to unload in boot    //    return EFI_SUCCESS;}

Как вы могли заметить, в нашем коде мы взаимодействуем с UEFI через глобальное поле gBS (global Boot Services). Также, существует gRT (global Runtime Services), а вместе они являются частью структуры System Table. Источник.


gST = *SystemTable; gBS = gST->BootServices; gRT = gST->RuntimeServices;

Для работы с файлами нам понадобится Simple File System Protocol (см. Спецификацию стр 504). Вызвав функцию LocateProtocol, можно получить на него указатель, хотя более правильный способ перечислить все handles на устройства файловой системы с помощью функции LocateHandleBuffer, и, перебрав все протоколы Simple File System, выбрать подходящий, который позволит нам писать и читать в файл. Пример такого кода тут. А мы же воспользуемся способом проще. У протокола есть всего одна функция, которая позволит нам открыть том.


EFI_STATUSOpenVolume(  OUT EFI_FILE_PROTOCOL** Volume){  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fsProto = NULL;  EFI_STATUS status;  *Volume = NULL;  // get file system protocol  status = gBS->LocateProtocol(    &gEfiSimpleFileSystemProtocolGuid,    NULL,    (VOID**)&fsProto  );  if (EFI_ERROR(status))  {    return status;  }  status = fsProto->OpenVolume(    fsProto,    Volume  );  return status;}

Далее, нам необходимо уметь создавать файл и закрывать его. Воспользуемся EFI_FILE_PROTOCOL, в котором есть функции для работы с файловой системой (см. Спецификацию стр 506).


EFI_STATUSOpenFile(  IN  EFI_FILE_PROTOCOL* Volume,  OUT EFI_FILE_PROTOCOL** File,  IN  CHAR16* Path){  EFI_STATUS status;  *File = NULL;  //  from root file we open file specified by path  status = Volume->Open(    Volume,    File,    Path,    EFI_FILE_MODE_CREATE |    EFI_FILE_MODE_WRITE |    EFI_FILE_MODE_READ,    0  );  return status;}EFI_STATUSCloseFile(  IN EFI_FILE_PROTOCOL* File){  //  flush unwritten data  File->Flush(File);  //  close file  File->Close(File);  return EFI_SUCCESS;}

Для записи в файл нам придется вручную двигать каретку. Для этого будем спрашивать размер файла с помощью функции GetInfo.


EFI_STATUSWriteDataToFile(  IN VOID* Buffer,  IN UINTN BufferSize,  IN EFI_FILE_PROTOCOL* File){  UINTN infoBufferSize = 0;  EFI_FILE_INFO* fileInfo = NULL;  //  retrieve file info to know it size  EFI_STATUS status = File->GetInfo(    File,    &gEfiFileInfoGuid,    &infoBufferSize,    (VOID*)fileInfo  );  if (EFI_BUFFER_TOO_SMALL != status)  {    return status;  }  fileInfo = AllocatePool(infoBufferSize);  if (NULL == fileInfo)  {    status = EFI_OUT_OF_RESOURCES;    return status;  }  //    we need to know file size  status = File->GetInfo(    File,    &gEfiFileInfoGuid,    &infoBufferSize,    (VOID*)fileInfo  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    we move carriage to the end of the file  status = File->SetPosition(    File,    fileInfo->FileSize  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    write buffer  status = File->Write(    File,    &BufferSize,    Buffer  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    flush data  status = File->Flush(File);FINALLY:  if (NULL != fileInfo)  {    FreePool(fileInfo);  }  return status;}

Вызываем наши функции и пишем случайные данные в наш файл:


EFI_STATUSWriteToFile(  VOID){  CHAR16 path[] = L"\\example.txt";  EFI_FILE_PROTOCOL* file = NULL;  EFI_FILE_PROTOCOL* volume = NULL;  CHAR16 something[] = L"Hello from UEFI driver";  //  //  Open file  //  EFI_STATUS status = OpenVolume(&volume);  if (EFI_ERROR(status))  {    return status;  }  status = OpenFile(volume, &file, path);  if (EFI_ERROR(status))  {    CloseFile(volume);    return status;  }  status = WriteDataToFile(something, sizeof(something), file);  CloseFile(file);  CloseFile(volume);  return status;}

Есть альтернативный способ выполнить нашу задачу. В проекте VisualUEFI уже реализовано то, что мы написали выше. Мы можем просто подключить заголовочный файл ShellLib.h и вызвать в самом начале функцию ShellInitialize. Все необходимые протоколы для работы с файловой системой будут открыты, а функции ShellOpenFileByName, ShellWrite и ShellRead реализованы почти так же, как и у нас.


#include <Library/ShellLib.h>EFI_STATUSWriteToFile2(  VOID){  SHELL_FILE_HANDLE fileHandle = NULL;  CHAR16 path[] = L"fs1:\\example2.txt";  CHAR16 something[] = L"Hello from UEFI driver";  UINTN writeSize = sizeof(something);  EFI_STATUS status = ShellInitialize();  if (EFI_ERROR(status))  {    return status;  }  status = ShellOpenFileByName(path,    &fileHandle,    EFI_FILE_MODE_CREATE |    EFI_FILE_MODE_WRITE |    EFI_FILE_MODE_READ,    0);  if (EFI_ERROR(status))  {    return status;  }  status = ShellWriteFile(fileHandle, &writeSize, something);  ShellCloseFile(&fileHandle);  return status;}

Результат:


result


Код этого примера на github


Если мы хотим перейти в VMWare, то наиболее правильным будет модификация firmware с помощью UEFITool. Например тут демонстрируется как добавляют NTFS драйвер в UEFI.


Выводы


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


// just pseudo code...// open protocol to replace callbacksgBS->OpenProtocol(      Controller,      Guid,      (VOID**)&protocol,      DriverBindingHandle,      Controller,      EFI_OPEN_PROTOCOL_GET_PROTOCOL    );// raise Task Priority Level to max avaliablegBS->RaiseTPL(TPL_NOTIFY);VOID** protocolBase = EFI_FIELD_BY_OFFSET(VOID**, FilterContainer, 0);VOID** oldCallback = EFI_FIELD_BY_OFFSET(VOID**, *protocolBase, oldCallbackOffset);VOID** originalCallback = EFI_FIELD_BY_OFFSET(VOID**, FilterContainer, originalCallbackOffset);//  yes, I know that it is not super obvious//  but if first and third is equal (placeholder and function)//  then the first one is not the function it is offset!//  and function itself is by offset of third oneif ((UINTN) newCallback == originalCallbackOffset){  newCallback = *originalCallback;}PRINT_DEBUG(DEBUG_INFO, L"[UefiMonitor] 0x%x -> 0x%x\n", *oldCallback, newCallback);//saving original functions*originalCallback = *oldCallback;//replacing them by filter function*oldCallback = newCallback;// restore TPLgBS->RestoreTPL(oldTpl);

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


// event on exitgBS->CreateEvent(      EVT_SIGNAL_EXIT_BOOT_SERVICES,      TPL_NOTIFY,      ExitBootServicesNotifyCallback,      NULL,      &mExitBootServicesEvent    );

Но это это уже идеи для будущих статей. Спасибо за внимание.

Подробнее..

В МойОфис появилась поддержка средств российской криптографии

13.11.2020 12:14:13 | Автор: admin


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

Одновременно вышел новый релиз 2020.02.R2: таблицы стали открываться быстрее в 8 раз, улучшены почтовые и календарные системы, добавлены функции работы с фигурами и сводными таблицами, а также расширен состав комплекта средств для разработчиков. Читайте подробности под катом, и да, все скриншоты кликабельны.


Хабр, привет! Меня зовут Лина Удовенко, я возглавляю департамент коммуникаций МойОфис. Пару дней назад мы опубликовали новый релиз МойОфис, который уже доступен нашим партнерам и пользователям. За последние три месяца команда разработки провела большую работу и смогла существенно увеличить функциональность коммерческих продуктов. Теперь МойОфис поддерживает отечественные сертифицированные средства криптографической защиты информации (СКЗИ) в почтовых приложениях и редакторах документов на любых платформах настольных компьютерах, мобильных устройствах и в веб-браузерах.

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

Поддержка российской криптографии


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

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

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

Защита каналов связи


В продуктах МойОфис появилась поддержка российских криптографических алгоритмов при использовании протокола TLS (Transport Layer Security), который необходим для надежной защиты информации при передаче данных по публичным сетям.



Такая функция позволяет при использовании сертифицированных средств криптографической защиты информации реализовать выполнение требований законодательства РФ в части защиты каналов связи.

Шифрование и электронная подпись почтовых сообщений


Для обеспечения подтверждения авторства, целостности и конфиденциальности сообщений электронной почты применяется наиболее распространенный стандарт S/MIME, в котором была реализована поддержка российских криптоалгоритмов.



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



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



В профиле пользователей приложения МойОфис Контакты добавлен раздел Безопасность, где отображается информация о сертификате пользователя.

МойОфис Почта


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



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



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

Редакторы документов


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



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

Подробнее..

Как стать разработчиком беспилотного автомобиля?

01.02.2021 18:10:50 | Автор: admin

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

Но что нас ждет в будущем? Какие специалисты будут самыми востребованными и высокооплачиваемыми? Эксперты прогнозируют в скором будущем высокую потребность в следующих профессионалах: проектировщик 3D-печати, разработчик роботов, дизайнер виртуальной реальности, менеджер по космическому туризму, сити-фермер, онлайн-доктор, разработчик беспилотного транспорта.

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

Образование и компетенции

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

Сегодня на разработчика беспилотного транспорта не отучишься за пять лет в вузе или на дополнительных курсах. Но постепенно российские университеты начинают знакомить своих студентов с этой новой и перспективной профессией. Первую в России магистерскую программу по подготовке специалистов для работы с высокоавтоматизированными транспортными средствами уже запустил Университет ИТМО совместно с НПО СтарЛайн. Студенты изучают в университете программу Функциональная безопасность беспилотных транспортных средств, а для практики и закрепления знаний используют ресурсы НПО СтарЛайн. Молодые специалисты смогут попробовать свои силы и новые знание в проекте по созданию беспилотного автомобиля OSCAR. Студенты также могут устроиться на практику в компанию, занимающуюся разработкой беспилотников, получить бесценный опыт, а в дальнейшем и приглашение на работу.

Беспилотный автомобиль проекта OSCARБеспилотный автомобиль проекта OSCAR

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

Над созданием беспилотного автомобиля StarLine в первую очередь работают инженеры, инженеры-робототехники, инженеры в области Data Science и искусственного интеллекта. В нашей команде есть победители хакатона, который проводился в рамках Международного фестиваля робототехники РобоФинист. Увлечение робототехникой еще со школьных лет помогло ребятам не только получить первое место, но и приглашение на работу, - рассказывает Борис Иванов, руководитель проекта Беспилотный автомобиль StarLine.

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

Задачи и востребованность на рынке

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

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

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

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

Испытания беспилотного автомобиля StarLine на дорогах городаИспытания беспилотного автомобиля StarLine на дорогах города
Подробнее..

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

21.05.2021 08:10:50 | Автор: admin

Встречаются два эксперта-консультанта по конструированию программного обеспечения:
- Как написать сложное корпоративное приложение, поддерживать которое будет всегда легко и дешево.
- Могу рассказать...
- Рассказать и я могу! Написать-то как?..

Время чтения: 25 мин.

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

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

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

Введение в предметную область

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

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

В этой статье я хочу предложить технику написания программ, в основе которой лежит два паттерна проектирования ООП: декоратор и стратегия. Я уверен, что основная часть читающих статью наверняка не раз сталкивалась с этими паттернами (возможно, даже на практике). Но чтобы все чувствовали себя "в своей тарелке", обращусь к определениям из "Паттернов проектирования" Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона и Джона Влиссидеса (Банда четырех, Gang of Four, GoF):

  • Декоратор (Decorator, Wrapper) паттерн проектирования, позволяющий динамически добавлять объекту новые обязанности. Является гибкой альтернативой порождению подклассов с целью расширения функциональности.

  • Стратегия (Strategy, Policy) паттерн проектирования, который определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.

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

Декорирование стратегией, на мой взгляд, даёт великую пользу при поддержке приложений на очень большом жизненном цикле программного продукта. Компоненты в коде, написанные с применением данного подхода, соответствуют всем принципам дизайна SOLID из "Чистой архитектуры" Роберта Мартина. Каждый компонент, который мы напишем далее, будет отвечать только за одно действие; после написания нового компонента мы ни разу не модифицируем логику его методов, а лишь будем расширять ее в декорирующих компонентах; в силу паттерна "Декоратор" все расширяемые и расширяющие компоненты соответствуют одному контракту, следовательно их можно заменять друг другом; интерфейсы компонентов не содержат зависимостей, которые не используются; компоненты бизнес-логики ни в коей мере не зависят от деталей.

Я не раз сталкивался в обсуждениях с опытными разработчиками, которые говорят: "А вот всё, что связано с применением принципов SOLID, паттернов ООП на практике это миф!". Любезно обращаясь к скептически настроенным к применению теории разработки в реальных больших корпоративных проектах, хочу сказать: "А вот посмотрим!"

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

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

  • Должная обработка ошибок. В коде мы ограничимся оборачиванием ошибок дополнительным сообщением с помощью пакета "github.com/pkg/errors".

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

  • Комментарии и документирование кода.

  • Всё, что связано, с конкурентным выполнением задач и синхронизацией.

  • Структура файлов и директорий проекта.

  • Стили, линтеры и статический анализ.

  • Покрытие кода тестами.

  • Сквозь методы компонентов рекомендуется с первых этапов разработки "тянуть" context.Context, даже если он в тот момент не будет использоваться. Для упрощения повествования в примерах далее контекст также использоваться не будет.

Перейдём же наконец от скучной теории к занимательной практике!

Пролог. Закладываем фундамент

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

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

Первое, что нужно сделать определить интерфейс нашего первого компонента службы, которая будет представлять желаемый use-case SavePersonService. Но для этого нам нужно определить объекты нашей предметной области, а именно структуру данных, содержащую информацию о человеке PersonDetails. Создадим в корне проекта пакет app, далее создадим файл app/person.go, и оставим в нём нашу структуру:

// app/person.gotype PersonDetails struct {    Name string    Age  int}

Данный файл завершён, больше мы к нему в этой статье возвращаться не будем. Далее создаем файл app/save-person.go, и определяем в нём интерфейс нашего use-case:

// app/save-person.gotype SavePersonService interface {    SavePerson(id int, details PersonDetails) error}

Оставим сразу рядом с определением интерфейса его первую реализацию компонент noSavePersonService, который ничего не делает в теле интерфейсного метода:

// app/save-person.go// ... предыдущий код ...type noSavePersonService struct{}func (noSavePersonService) SavePerson(_ int, _ PersonDetails) error { return nil }

Поскольку объекты noSavePersonService не содержат состояния, можно гарантировать, что данный "класс" может иметь только один экземпляр. Напоминает паттерн проектирования Синглтон (Singleton ещё его называют Одиночка, но мне это название по ряду причин не нравится). Предоставим глобальную точку доступа к нему. В Golang легче всего это сделать, определив глобальную переменную:

/ app/save-person.go// ... предыдущий код ...var NoSavePersonService = noSavePersonService{}

Зачем мы написали ничего не делающий компонент? С первого взгляда он очень походит на заглушку. Это не совсем так. Далее поймём.

Эпизод 1. Будем знакомы, Декоратор Стратегией

Перейдём непосредственно к реализации бизнес-логики нашей задачи. Нам нужно в конечном счёте иметь хранилище, в котором содержатся данные о пользователях. С точки зрения выбора технологии мы сразу себе представляем, что будем использовать PostgreSQL, но правильно ли завязываться в коде нашей бизнес-логики на конкретную технологию. Вы правы конечно нет. Определить компонент нашего хранилища нам позволит паттерн Репозиторий (Repository). Создадим пакет с реализациями интерфейса нашего use-case save-person внутри app, и в нём создадим файл app/save-person/saving_into_repository.go реализации нашего use-case, которая обновляет данные в репозитории:

// app/save-person/saving_into_repository.gotype PersonRepository interface {    UpdatePerson(id int, details app.PersonDetails) error}type SavePersonIntoRepositoryService struct {    base app.SavePersonService    repo PersonRepository}func WithSavingPersonIntoRepository(base app.SavePersonService, repo PersonRepository) SavePersonIntoRepositoryService {    return SavePersonIntoRepositoryService{base: base, repo: repo}}func (s SavePersonIntoRepositoryService) SavePerson(id int, details app.PersonDetails) error {    err := s.base.SavePerson(id, details)    if err != nil {        return errors.Wrap(err, "save person in base in save person into repository service")    }    err = s.repo.UpdatePerson(id, details)    if err != nil {        return errors.Wrap(err, "update person in repo")    }    return nil}

В коде выше впервые появляется компонент, который выражает наш подход "Декорирование стратегией". Сам компонент представляет собой декоратор, реализующий интерфейс нашего use-case, который оборачивает любой компонент с таким же интерфейсом. В реализации метода изначально вызывается метод декорируемого объекта s.base; после этого происходит вызов стратегии обновления данных о человеке в хранилище s.repo. По сути, весь подход это конструирование компонентов-декораторов, которые содержат два объекта:

  1. Непосредственно декорируемый объект с таким же интерфейсом.

  2. Стратегия, логику которой мы добавляем в довесок к логике декорируемого объекта.

Структурная схема программы, собранной из декораторов стратегий может выглядеть примерно так:

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

Напомню, что бизнес-логика не должна содержать ненужные зависимости, зависимости от деталей и т.п. Другими словами, бизнес-логика должна быть "чистая, как слеза". Где тогда должны находиться зависимости от конкретных реализаций, зависимости от используемых технологий? Ответ в файле main.go. Следуя замечаниям Роберта Мартина, можно сделать умозаключение, что код компонентов файла, содержащего точку входа в программу, является самым "грязным" с точки зрения зависимостей от всего. Обозначим в main.go метод, который нам возвращает клиент к базе данных PostgreSQL. И собственно сборку объекта службы нашего use-case и вызов его метода на условных входных данных:

// main.gofunc NewPostgreSQLDatabaseClient(dsn string) savePerson.PersonRepository {    _ = dsn // TODO implement    panic("not implemented")}func run() error {    userService := savePerson.WithSavingPersonIntoRepository(        app.NoSavePersonService,        NewPostgreSQLDatabaseClient("postgres://user:pass@127.0.0.1:5432/users?sslmode=disable"))    err := userService.SavePerson(5, app.PersonDetails{        Name: "Mary",        Age:  17,    })    if err != nil {        return errors.Wrap(err, "save user Mary")    }    return nil}

В коде выше мы можем заметить, что в качестве стратегии репозитория выступает обозначенный конкретный компонент клиента к PostgreSQL. В качестве же декорируемого объекта выступает наша "фиктивная" реализация use-case app.NoSavePersonService, которая по сути ничего не делает. Зачем она нужна? Она ничего полезного ведь не делает? Не легче ли просто вызвать метод клиента к базе данных? Спокойно, звёздный час этой реализации сейчас настанет.

Ссылка на полный код эпизода

Эпизод 2. Магия начинается!

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

// main.go// ... предыдущий код ...func NewMemoryCache() savePerson.PersonRepository {    // TODO implement    panic("not implemented")}// ... последующий код ...

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

// main.go// внутри run()userService := savePerson.WithSavingPersonIntoRepository(    savePerson.WithSavingPersonIntoRepository(        app.NoSavePersonService,        NewPostgreSQLDatabaseClient("postgres://user:pass@127.0.0.1:5432/users?sslmode=disable")),    NewMemoryCache(),)err := userService.SavePerson(5, app.PersonDetails{    Name: "Mary",    Age:  17,})if err != nil {    return errors.Wrap(err, "save user Mary")}

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

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 3. Рефакторинг для здоровья

В предыдущем листинге кода создание сервиса выглядит достаточно громоздко. Нетрудно догадаться, применяя наш подход, мы продолжим и далее всё больше и больше оборачивать компонент, добавляя к логике новые стратегии. Поэтому мы, как опытные разработчики, замечаем эту потенциальную трудность и производим небольшой рефакторинг когда. Нам поможет паттерн Билдер (Builder опять же мне не очень нравится ещё одно его название Строитель). Это будет отдельный компонент, зона ответственности которого предоставить возможность сборки объекта службы нашего use-case. Файл app/save-person/builder.go:

// app/save-person/builder.gotype Builder struct {    service app.SavePersonService}func BuildIdleService() *Builder {    return &Builder{        service: app.NoSavePersonService,    }}func (b Builder) SavePerson(id int, details app.PersonDetails) error {    return b.service.SavePerson(id, details)}

Компонент Builder должен обязательно реализовывать интерфейс службы нашего use-case, так как именно он будет использоваться в конечном счёте. Поэтому мы добавляем метод SavePerson, который вызывает одноименный метод объекта в приватном поле service. Конструктор данного компонента называется BuildIdleService, потому что создаёт объект, который ничего не будет делать при вызове SavePerson (нетрудно заметить инициализацию поля service объектом app.NoSavePersonService). Зачем нам нужен этот бесполезный компонент? Чтобы получить всю истинную пользу, необходимо обогатить его другими методами. Эти методы будут принимать в параметрах стратегию и декорировать ею объект службы в поле service. Но вначале сделаем конструктор WithSavingPersonIntoRepository в app/save-person/saving_into_repository.go приватным, так как для создания службы мы теперь будем использовать только Builder:

// app/save-person/saving_into_repository.go// ... предыдущий код ...func withSavingPersonIntoRepository(base app.SavePersonService, repo PersonRepository) SavePersonIntoRepositoryService {    return SavePersonIntoRepositoryService{base: base, repo: repo}}// ... последующий код ...

Добавляем соответствующий метод для Builder:

// app/save-person/builder.go// ... предыдущий код ...func (b *Builder) WithSavingPersonIntoRepository(repo PersonRepository) *Builder {    b.service = withSavingPersonIntoRepository(b.service, repo)    return b}

И наконец производим рефакторинг в main.go:

// main.go// ... предыдущий код ...userService := savePerson.BuildIdleService().        WithSavingPersonIntoRepository(NewPostgreSQLDatabaseClient("postgres://user:pass@127.0.0.1:5432/platform?sslmode=disable")).        WithSavingPersonIntoRepository(NewMemoryCache())// ... последующий код ...

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 4. Больше заказчиков!

Через несколько дней успешной работы нашего кода в продакшне, к нам приходит другой заказчик от бизнеса и просит реализовать функциональность обновления информации о налогоплательщиках в отдельном хранилище. По неким причинам, обсуждение которых находится за пределами данной статьи, мы понимаем, что эту информацию лучше хранить в MongoDB. Клиент к базе добавляется в main.go:

// main.go// ... предыдущий код ...func NewMongoDBClient(dsn string) savePerson.PersonRepository {    _ = dsn // TODO implement    panic("not implemented")}// ... последующий код ...

Воспользуемся нашим билдером и просто добавим новый код в main.go под имеющийся фрагмент с userService:

// main.go// ... предыдущий код ...taxpayerService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewMongoDBClient("mongodb://user:pass@127.0.0.1:27017/tax_system")).    WithSavingPersonIntoRepository(NewMemoryCache())err = taxpayerService.SavePerson(1326423, app.PersonDetails{    Name: "Jack",    Age:  37,})if err != nil {    return errors.Wrap(err, "save taxpayer Jack")}

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

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 5. Путь в никуда

Проходит ещё время. Заказчик 2 ставит нам такую задачу. Так как все налогоплательщики должны быть совершеннолетними, необходимо в бизнес-логику добавить функциональность проверки возраста человека перед сохранением в хранилище. С этого момента начинаются интересные вещи. Мы можем добавить эту валидацию в метод SavePersonIntoRepositoryService.SavePerson в файле app/save-person/saving_into_repository.go. Но тогда при нескольких декорированиях стратегией сохранения информации в репозиторий эта валидация будет вызываться столько раз, сколько производилось таких декораций. Хотя и все проверки помимо первой никак не влияют на результат напрямую, всё-таки не хочется лишний раз вызывать один и тот же метод.

Мы можем добавить валидацию в Builder.SavePerson. Но есть проблема: заказчику 1 не нужна проверка возраста при сохранении. Придётся добавить if и дополнительный флаг в параметры конструктора, который будет определять необходимость валидации:

// app/save-person/builder.gotype Builder struct {    service           app.SavePersonService    withAgeValidation bool}func BuildIdleService(withAgeValidation bool) *Builder {    return &Builder{        service:           app.NoSavePersonService,        withAgeValidation: withAgeValidation,    }}func (b Builder) SavePerson(id int, details app.PersonDetails) error {    if b.withAgeValidation && details.Age < 18 {        return errors.New("invalid age")    }    return b.service.SavePerson(id, details)}// ... последующий код ...

И тогда в main.go нужно вызывать конструкторы билдера с разными значениями флага withAgeValidation:

// main.go// ... предыдущий код ... userService := savePerson.BuildIdleService(false).// ... код ...taxpayerService := savePerson.BuildIdleService(true).// ... последующий код ...

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

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 6. Путь истины

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

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

// app/save-person/validating.gotype PersonValidator interface {    ValidatePerson(details app.PersonDetails) error}type PreValidatePersonService struct {    base      app.SavePersonService    validator PersonValidator}func withPreValidatingPerson(base app.SavePersonService, validator PersonValidator) PreValidatePersonService {    return PreValidatePersonService{base: base, validator: validator}}func (s PreValidatePersonService) SavePerson(id int, details app.PersonDetails) error {    err := s.validator.ValidatePerson(details)    if err != nil {        return errors.Wrap(err, "validate person")    }    err = s.base.SavePerson(id, details)    if err != nil {        return errors.Wrap(err, "save person in base in pre validate person service")    }    return nil}

Опять ничего нового. PreValidatePersonService это очередной декоратор стратегией валидации перед последующим вызовом декорируемого метода.

Добавим соответствующий метод в Builder:

// app/save-person/builder.go// ... предыдущий код ...func (b *Builder) WithPreValidatingPerson(validator PersonValidator) *Builder {    b.service = withPreValidatingPerson(b.service, validator)    return b}

Добавление каждого нового декоратора стратегией требует добавление нового метода в наш билдер.

Добавим реализацию валидатора, проверяющую возраст человека:

// main.go// ... предыдущий код ...type personAgeValidator struct{}func (personAgeValidator) ValidatePerson(details app.PersonDetails) error {    if details.Age < 18 {        return errors.New("invalid age")    }    return nil}var PersonAgeValidator = personAgeValidator{}// ... последующий код ...

Так как personAgeValidator не имеет состояния, можем сделать для компонента единую точку доступа PersonAgeValidator. Далее просто вызываем новый метод в main.go только для taxpayerService:

// main.go// ... предыдущий код ...taxpayerService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewMongoDBClient("mongodb://user:pass@127.0.0.1:27017/tax_system")).    WithSavingPersonIntoRepository(NewMemoryCache()).    WithPreValidatingPerson(PersonAgeValidator)// ... последующий код ...

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 7. А ну-ка закрепим

Уверен, к данному эпизоду вы поняли смысл подхода "Декорирование стратегией". Чтобы закрепить, давайте добавим ещё один такой компонент. Представим, технический руководитель требует от нас покрыть метриками время выполнения сохранения данных в хранилище. Мы могли бы замерить это время, просто добавив пару строчек кода в SavePersonIntoRepositoryService. Но как бы не так! Мы же не изменяем уже работающий в продакшне код, а можем его только расширить. Давайте же так и сделаем. Добавим новый декоратор стратегией отправки метрики времени:

// app/save-person/sending_metric.gotype MetricSender interface {    SendDurationMetric(metricName string, d time.Duration)}type SendMetricService struct {    base         app.SavePersonService    metricSender MetricSender    metricName   string}func withMetricSending(base app.SavePersonService, metricSender MetricSender, metricName string) SendMetricService {    return SendMetricService{base: base, metricSender: metricSender, metricName: metricName}}func (s SendMetricService) SavePerson(id int, details app.PersonDetails) error {    startTime := time.Now()    err := s.base.SavePerson(id, details)    s.metricSender.SendDurationMetric(s.metricName, time.Since(startTime))    if err != nil {        return errors.Wrap(err, "save person in base in sending metric service")    }    return nil}

Помимо компонента стратегии, отправляющего метрики, мы в конструкторе также передаем название метрики, которую мы хотим замерять. Добавляем новый метод в Builder:

// app/save-person/builder.go// ... предыдущий код ...func (b *Builder) WithMetricSending(metricSender MetricSender, metricName string) *Builder {    b.service = withMetricSending(b.service, metricSender, metricName)    return b}

И наконец обозначаем в main.go функцию, возвращающую savePerson.MetricSender и добавляем вызов нового метода Builder в сборку наших сервисов:

// main.go// ... предыдущий код ...func MetricSender() savePerson.MetricSender {    // TODO implement    panic("not implemented")}// ... код ...userService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewPostgreSQLDatabaseClient("postgres://user:pass@127.0.0.1:5432/platform?sslmode=disable")).    WithMetricSending(MetricSender(), "save-into-postgresql-duration").    WithSavingPersonIntoRepository(NewMemoryCache())// ... код ...taxpayerService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewMongoDBClient("mongodb://user:pass@127.0.0.1:27017/tax_system")).    WithMetricSending(MetricSender(), "save-into-mongodb-duration").    WithSavingPersonIntoRepository(NewMemoryCache()).    WithPreValidatingPerson(PersonAgeValidator)// ... последующий код ...

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

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 8. Результаты ясновидения

Проходит время. Заказчик 2 ставит новую задачу. Он желает знать, как долго выполняется сохранение данных о налогоплательщике, но с небольшой оговоркой: учитывать нужно всё, кроме валидации. Похоже на замер времени, который мы недавно реализовали для своих целей, не правда ли? Чтобы решить задачу, всё что нам требуется это добавить вызов метода для новой метрики в main.go:

// main.go// ... предыдущий код ...taxpayerService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewMongoDBClient("mongodb://user:pass@127.0.0.1:27017/tax_system")).    WithMetricSending(MetricSender(), "save-into-mongodb-duration").    WithSavingPersonIntoRepository(NewMemoryCache()).    WithMetricSending(MetricSender(), "save-taxpayer-duration").    WithPreValidatingPerson(PersonAgeValidator)

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 9. Укрощение капризов

Мы вот только недавно произвели релиз последней задачи от заказчика 2, но он захотел изменить начальные требования. Такие изменения часто возникают на стороне заказчика, которые заставляют нас "перелопатить" весь код. Знакомо? На этот раз заказчик желает отказаться от оговорки из предыдущего эпизода и производить замер полного цикла сохранения данных о налогоплательщике вместе с валидацией. Если бы мы конструировали нашу бизнес-логику в виде сценария транзакции (transaction script), то это повлекло бы за собой непосредственное вмешательство в тело метода, copy-paste кода, что требует приложить силы, в том числе в процессе ревью, тестирования и т.п. В нашем же случае нам достаточно просто подвинуть вызов метода WithMetricSending в цепочке методов создания объекта службы в main.go:

// main.go// ... предыдущий код ...taxpayerService := savePerson.BuildIdleService().    WithSavingPersonIntoRepository(NewMongoDBClient("mongodb://user:pass@127.0.0.1:27017/tax_system")).    WithMetricSending(MetricSender(), "save-into-mongodb-duration").    WithSavingPersonIntoRepository(NewMemoryCache()).    WithPreValidatingPerson(PersonAgeValidator).    WithMetricSending(MetricSender(), "save-taxpayer-duration")

В коде выше мы поменяли местами второй WithMetricSending и WithPreValidatingPerson.

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

Ссылка на diff эпизода
Ссылка на полный код эпизода

Эпизод 10. Взгляд в будущее

Этот заключительный эпизод всего лишь подчеркивает потенциал дальнейших доработок логики данного кода. Что ещё может пожелать заказчик от бизнеса или с технической стороны? Вариантов более чем достаточно. Может потребоваться функциональность отправки асинхронных событий об изменении информации о человеке (полезно при ведении журнала аудита, коммуникации с другими сервисами и т.д.). Может понадобиться введение механизма гомогенных и даже гетерогенных транзакций. Возможно, потребуется добавить запрос данных к соседнему микросервису. По техническим соображениям возможно будет нужен предохранитель (circuit-breaker) для таких запросов к другим сервисам. Наверняка нужно будет добавлять механизм трассировки (tracing). И многое-многое другое.

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

Эпилог. Подводим итоги

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

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

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

Что есть что на этом графике? Почему на осях нет чисел? Всё потому что график абстрактный. Он отражает качественный смысл содержимого, не количественный. По горизонтальной оси у нас время, прошедшее с момента начала разработки продукта. Или если желаете, количество добавлений новой функциональности в изначально разработанный продукт. Меру по вертикальной оси тоже можно выразить различными способами. Это может быть цена добавления новой строчки кода функционала в денежном эквиваленте; может быть время добавления новой функциональности; может быть количество потраченных нервных клеток разработчиком, ревьювером или тестировщиком. Красный график демонстрирует зависимость этих величин для подхода разработки, который называется сценарием транзакции (Transaction Script) последовательно следующие друг за другом инструкции. Синий график показывает эту зависимость для подхода модели предметной области (Domain Model).

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

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

Содержание статьи изложено на основе моего субъективного понимания. Любые замечания с удовольствием готов обсуждать в комментариях. Использовать "Декорирование стратегией" или нет личное решение каждого. Главное, я считаю, нужно помнить о том, что мы как разработчики должны в первую очередь уделять внимание не бизнесу, не пользователю, не выделенным машинным ресурсам, а нашему коллеге такому же разработчику, который через несколько лет будет добавлять в наш код новую функциональность.

Литература

  1. Макконнелл С. Совершенный код. Мастер-класс., 2020.

  2. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования., 2020.

  3. Мартин Р. Чистая архитектура. Искусство разработки программного обеспечения., 2020

  4. Фаулер, Мартин. Шаблоны корпоративных приложений., 2020.

Подробнее..

JDK 17 новые функции в Java 17

01.06.2021 00:14:15 | Автор: admin
  • Всегда строгая семантика с плавающей запятой

  • API сторонних функций и памяти

  • Унифицированный API для генераторов псевдослучайных чисел

Версия Java 17, которая должна выйти в сентябре, продолжает набирать форму, на данный момент запланировано девять функций для обновления до стандартной Java, а также удаление двух функций и две функции прекращают поддерживаться. В последних изменениях, на 24 мая, было добавлено сопоставление с образцом для switch выражений и восстановлена всегда строгая семантика с плавающей запятой.

Java Development Kit (JDK) 17 будет релизом с долгосрочной поддержкой (LTS), с расширенной поддержкой Oracle, ожидаемой в течение нескольких лет. Функции, представленные как часть OpenJDK's JDK 17, включают следующее:

  • С восстановлением всегда строгой семантики с плавающей запятой, операции с плавающей запятой будут постоянно строгими, вместо того, чтобы иметь как строгую семантику с плавающей запятой (strictfp), так и слегка отличающуюся семантику с плавающей запятой по умолчанию. Это восстанавливает исходную семантику с плавающей запятой для языка и виртуальной машины, соответствуя семантике до введения строгих режимов и режимов с плавающей запятой по умолчанию в Java Standard Edition 1.2. Цели этих затрат включают облегчение разработки библиотек, чувствительных к числовым значениям, включая java.lang.Math и java.lang.StrictMath. Стимул к изменению семантики с плавающей запятой по умолчанию в конце 1990-х гг. был вызван плохим взаимодействием между исходным языком Java и семантикой JVM, а также некоторыми особенностями набора команд сопроцессора с плавающей запятой x87 популярной архитектуры x86. Соответствие точной семантике с плавающей запятой во всех случаях, включая субнормальные операнды и результаты, требовало больших накладных расходов дополнительных инструкций. Сопоставление результатов при отсутствии переполнения или потери значимости может быть выполнено с меньшими накладными расходами, и это примерно то, что позволяет пересмотренная семантика с плавающей запятой по умолчанию, представленная в Java SE 1.2. Но расширения SSE2 (Streaming SIMD Extensions 2), поставляемые в процессорах Pentium 4 и более поздних версиях, начиная примерно с 2001 г., могли напрямую поддерживать строгие операции с плавающей запятой JVM без чрезмерных накладных расходов. Поскольку Intel и AMD поддерживают SSE2 и более поздние расширения, которые позволяют естественную поддержку строгой семантики с плавающей запятой, технической мотивации для использования семантики с плавающей запятой по умолчанию, отличной от строгой, больше не существует.

  • Прекращение поддержки Security Manager, подготовка к удалению в следующем выпуске. Начиная с Java 1.0, Security Manager был основным средством защиты кода Java на стороне клиента и редко использовался для защиты кода на стороне сервера. Цель предложения - оценить, нужны ли новые API или механизмы для решения конкретных узких вариантов использования, для которых использовался Security Manager, например, для блокировки System::exit. Планы предусматривают прекращение поддержки Security Manager для удаления вместе с устаревшим Applet API, который также планируется исключить в JDK 17.

  • Сопоставление с образцом для switch расширяет язык шаблонов в Java, позволяя тестировать выражения и операторы switch по ряду шаблонов, каждый из которых имеет определенное действие. Это позволяет кратко и безопасно выражать сложные запросы, ориентированные на данные. В число целей этой функции входит расширение выразительности и применения выражений и операторов switch путем включения шаблонов в метки case, ослабление исторической враждебности к нулю при необходимости switch и введение двух типов шаблонов: защищенных шаблонов, которые позволяют уточнять логику сопоставления должна быть уточнена с помощью произвольных логических выражений и шаблонов в скобках, которые разрешают некоторые неоднозначности синтаксического анализа. В JDK 16 оператор instanceof был расширен, чтобы принимать образец типа и выполнять сопоставление с образцом. Предлагаемое скромное расширение позволяет упростить знакомую идиому instanceof-and-cast.

  • Строгая инкапсуляция для внутренних компонентов JDK, за исключением критически важных внутренних API, таких как misc.unsafe, сделает невозможным ослабление строгой инкапсуляции внутренних элементов с помощью единственной опции командной строки, как это было возможно в JDK 9 - JDK 16. Цели плана включают повышение безопасности и удобства сопровождения JDK, а также поощрение разработчиков к переходу от внутренних элементов к стандартным API.

  • Удаление механизма активации удаленного вызова метода (RMI) с сохранением остальной части RMI. Механизм активации RMI устарел, не используется и был объявлен устаревшим для удаления в JDK 15.

  • Внешняя функция и API памяти, представленные на стадии зарождения, позволяют программам Java взаимодействовать с кодом и данными вне среды выполнения Java. За счет эффективного вызова внешних функций, т.е. кода вне JVM, и безопасного доступа к внешней памяти, т.е. памяти, не управляемой JVM, API позволяет программам Java вызывать собственные библиотеки и обрабатывать собственные данные без хрупкости и риска JNI (Java Native Interface). Предлагаемый API представляет собой эволюцию двух API - API доступа к внешней памяти и API внешнего компоновщика. API доступа к внешней памяти был нацелен на Java 14 в 2019 году как инкубирующий API и возрожден в Java 15 и Java 16. API внешнего компоновщика был нацелен на Java 16 как зарождающийся API в конце 2020 года. Цели плана API включают простоту использования, производительность, универсальность и безопасность.

  • Внедренный в JDK 16 в качестве зарождающегося API, независимый от платформы vector API будет снова возрожден в JDK 17, обеспечивая механизм для выражения векторных вычислений, которые надежно компилируются во время выполнения в оптимальные векторные инструкции на поддерживаемых архитектурах ЦП. Это обеспечивает лучшую производительность, чем эквивалентные скалярные вычисления. В JDK 17 vector API был улучшен для повышения производительности и реализации, включая улучшения для преобразования байтовых векторов в логические массивы и наоборот.

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

  • Удаление экспериментального компилятора AOT и JIT, который мало использовался, но требует значительных усилий по обслуживанию. План призывает поддерживать интерфейс компилятора JVM на уровне Java, чтобы разработчики могли продолжать использовать созданные извне версии компилятора для JIT-компиляции. Компиляция AOT (инструмент jaotc) была включена в JDK 9 в качестве экспериментальной функции. Инструмент использует компилятор Graal, который сам написан на Java, для компиляции AOT. Эти экспериментальные функции не были включены в сборки JDK 16, опубликованные Oracle, и никто не жаловался. Согласно предписанному плану, три модуля JDK будут удалены: jdk.aot (инструмент jaotc); internal.vm.compiler, компилятор Graal; и jdk.internal.vm.compiler.management, MBean Graal. Также будет удален код HotSpot, связанный с компиляцией AOT.

  • Перенос JDK на MacOS / AArch64 в ответ на план Apple по переходу своих компьютеров Macintosh с x64 на AArch64. Порт AArch64 для Java уже существует для Linux, и в настоящее время ведутся работы для Windows. Разработчики Java рассчитывают повторно использовать существующий код AArch64 из этих портов, применяя условную компиляцию, как это обычно бывает в портах JDK, чтобы учесть различия в низкоуровневых соглашениях, таких как двоичный интерфейс приложения и набор зарезервированных регистров процессора. Изменения для MacOS / AArch64 могут привести к поломке существующих портов Linux / AArch64, Windows / AArch64 и MacOS / x64, но риск будет снижен за счет тестирования перед интеграцией.

  • Устарело использование Applet API для удаления. Этот API по сути не имеет значения, поскольку все поставщики веб-браузеров либо удалили поддержку подключаемых модулей браузера Java, либо объявили о планах сделать это. Applet API ранее был объявлен устаревшим, но не подлежал удалению в Java 9 в сентябре 2017 года.

  • Новый конвейер рендеринга для MacOS, использующий Apple Metal API в качестве альтернативы существующему конвейеру, который использует устаревший API OpenGL. Это предложение предназначено для обеспечения полнофункционального конвейера рендеринга для Java 2D API, который использует структуру MacOS Metal, и быть готовым на случай, если Apple удалит OpenGL API из будущей версии MacOS. Предполагается, что конвейер будет иметь функциональный паритет с существующим конвейером OpenGL, с производительностью, такой же или более высокой в отдельных приложениях и тестах. Будет создана чистая архитектура, которая вписывается в текущую 2D-модель Java. Этот конвейер будет сосуществовать с конвейером OpenGL до тех пор, пока он не станет устаревшим. Целью предложения не является добавление каких-либо новых API-интерфейсов Java или JDK.

  • Усовершенствованные генераторы псевдослучайных чисел, которые будут предоставлять новые типы интерфейсов и реализации для генераторов псевдослучайных чисел (PRNG), включая изменяемые PRNG и дополнительный класс алгоритмов разделяемого PRNG (LXM). Новый интерфейс RandomGenerator предоставит единый API для всех существующих и новых PRNG. Будет предоставлено четыре специализированных интерфейса RandomGenerator. Мотивация плана - сосредоточение внимания на нескольких областях для улучшения в области генерации псевдослучайных чисел в Java. Эти усилия не требуют реализации множества других алгоритмов PRNG. Но были добавлены три общих алгоритма, которые уже широко используются в других средах языков программирования. Цели плана включают:

    • Упрощение взаимозаменяемого использования различных алгоритмов PRNG в приложениях.

    • Улучшена поддержка потокового программирования, предоставляющего потоки объектов PRNG.

    • Устранение дублирования кода в существующих классах PRNG.

    • Сохранение существующего поведения класса java.util.Random.

14 сентября намечено сделать JDK 17 общедоступной. Производственному выпуску будут предшествовать этапы свертывания в июне и июле, а выпуск кандидатов - в августе. Сборки JDK 17 с открытым исходным кодом для раннего доступа можно найти на jdk.java.net.

Релизы LTS, такие как JDK 17, появляются каждые три года. Последний релиз LTS, JDK 11, был опубликован в сентябре 2018 года. Новые версии Java появляются каждые шесть месяцев.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru