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

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

Недавно я написал приложение от начала до конца и хотел ответить на этот вопрос. Проект был мечтой разработчиков, так как он был полностью с нуля, виноват был только один человек (я), и у меня был очень конкретный список функций, которые нужно было сделать, и вряд ли они изменятся или увеличатся. Я делал полный стек (Kotlin, Spring, Bootstrap, Angular) — все, от создания проекта до его развертывания в облаке в Docker-контейнере. Это заняло около двух месяцев календарного времени, в основном час или два здесь и там, когда мой новорожденный позволял это (= спал).

Стек был типичным в том смысле, что он содержал что-то очень знакомое мне (Spring), некоторые с ограниченным знанием (Kotlin, Angular, Bootstrap) и совершенно новую территорию, такую ​​как Google Cloud и Spring Webflux. В нем также были некоторые элементы, которые я добавил для чисто академического интереса (токены JWT, Exposed).

Так куда уходит время на создание чего-то подобного? В основном все задачи программирования начинаются с принципа «разделяй и властвуй» — вы проектируете общую архитектуру, а затем разбиваете проблему на более управляемые части и начинаете решать их одну за другой. База данных. Просмотры пользовательского интерфейса. Обработка запросов REST. Аутентификация. CSS. На на. Этот тип разделения кажется второй натурой для любого опытного программиста, и жизнь кажется стоящей того, чтобы жить, когда вы до нее доберетесь. Затем идут сюрпризы. «Где мой аварийный виски» — вещи, которые заставляют вас рвать на себе волосы (я лысый).

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

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

Токены JWT с Spring Boot не работали

Там есть инструкции, но данные сеанса не сохраняются внутри Spring. Согласно Spring, я всегда был анонимным пользователем, независимо от того, насколько я аутентифицировал себя. Я долго отлаживал внутреннюю часть обработки запросов Spring, но безрезультатно, пока не разобрался. Оказывается, использовать @Autowire для передачи Spring SecurityContext классам — плохая идея. Используйте SecurityContextHolderкаждый раз.

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

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

Как вернуть данные из открытых транзакций?

(Exposed — это ORM для Kotlin). В рамках вы оборачиваете операции базы данных в транзакции следующим образом:

transaction {
 val dao = UserDao.find{ name eq someName }
 dao.password = newPass
 return toPojo(dao) // Error
}

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

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

transaction {
 val dao = UserDao.find{ name eq someName }
 dao.password = newPass
 toPojo(dao)
}

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

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

Может быть, есть возможность в пивоварении?

Ошибки Angular Routing в Spring Routing

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

Извлеченный урок: оказывается, вам нужно что-то вроде этого:

Это гарантирует, что все запросы (которые не соответствуют другим правилам маршрутизации Spring) перенаправляются на index.html, а оттуда — на правильный контроллер Angular. Google предоставит вам все виды устаревших, более не работающих версий этого.

Может сантехник?

Весенние проблемы с тестированием JUnit

У меня было две проблемы одновременно: загрузка Spring Context работала не совсем так, как я хотел, а автосвязывание полей было нестабильным. Например, контекст не загружал файлы конфигурации, написанные на YML, особенно в интеграционных тестах. И внедрение поля не совсем работало с тестовым контекстом.

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

Мусорщик?

Конфигурация через ConfigurationProperties

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

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

Как насчет дистилляции?

Многотабличные запросы в Exposed

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

Извлеченный урок: магия фреймворка DAO, которая генерирует SQL-запросы, по своей сути является неудачной. Я чувствовал это еще до того, как начал, и вот я снова здесь. Может быть, на этот раз они просто сработают, сказал я себе. Неа. У вас в голове чистый и быстрый SQL-запрос, но вы тратите часы на бесконечное массирование уровня DAO в надежде, что он сгенерирует его правильно и не будет ужасно медленным. Такие вещи, как Android Room, просто превосходны.

Возможно виноделие?

Karma отказалась работать с PhantomJS

Модульные тесты терпят неудачу, если я запускаю браузерный движок PhantomJS. Я бы получил что-то вроде [object ErrorEvent], и тесты случайно не прошли.

Извлеченный урок. В экосистеме JavaScript царит беспорядок. Но я это уже знал.

Как насчет организованной преступности?

Уязвимость безопасности в Хуке

Я понятия не имел, что такое Hoek, но Github пожаловался, что в нем открыта уязвимость, поэтому я потратил часы и часы, чтобы понять, как исправить это. Эта проблема закрыта, но я не уверен, что она была исправлена.

Усвоенный урок:JavaScript — это беспорядок. Проблемы безопасности в ваших зависимостях — это всегда и ваши проблемы.

Жить под мостом и жить на собес?

Угловой 5-›6

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

  • Я активно использовал потоковые операторы Observable.of и Rx, оба не были обновлены должным образом. Особенно болезненно было изменение stream pipe().
  • С импортом повозились. Я думаю, что они просто издеваются над нами. Они понимают, что это ломает все, верно?
  • WebStorm лишился возможности запускать тесты

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

Сельское хозяйство?

Ошибка компиляции TypeScript

Внезапно все на стороне Angular сломалось. Я получил:

ERROR in src/app/auth.interceptor.ts(6,2): error TS2345: Argument of type 'typeof AuthInterceptor' is not assignable to parameter of type '({ providedIn: Type<any> | "root"; } & ValueSansProvider)
| ({ providedIn: Type<any> | "root"; } ...'.
Type 'typeof AuthInterceptor' is not assignable to type '{ providedIn: Type<any> | "root"; } & ClassSansProvider'.
Type 'typeof AuthInterceptor' is not assignable to type '{ providedIn: Type<any> | "root"; }'.
Property 'providedIn' is missing in type 'typeof AuthInterceptor'.

Гугл не предложил никакой помощи. Да, ошибка была в строке 6 в файле auth.interceptor.ts, но я ее не увидел. Я переписал класс и сравнил результаты, чтобы найти ошибку. Я пропустил круглые скобки в аннотации @Injectable.

Извлеченный урок: я сразу увидел ошибку после перерыва. Проверьте свои основы, когда «Hello World!» уровень не работает.

Хобо наверное?

Типы списков Json в Kotlin

У меня были проблемы с сопоставлением списков JSON с Kotlin Pojos. В основном эта проблема. Пока я разбирался, это выглядело настолько уродливым и ненадежным, что я выбрал план Б и вместо этого просто изменил JSON.

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

Смотритель парка?

Интеграционные тесты Spring с Gradle 4

Мне не повезло с тестами Spring Integration, все, что я получил, это:

java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing

Куда бы я ни посмотрел, все было по-прежнему в Gradle 3 и не помогало.

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

configurations {
   integrationTestImplementation.extendsFrom testImplementation 
   integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}

Это все еще магия. Я даже не знаю, что прочитать, чтобы разочаровать его.

Профессиональный игрок в гольф? Это все еще вариант в 37 лет, верно?

Мой контроллер Rest возвращает HTML

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

Я пропустил, что класс был помечен @Controller вместо правильного @RestController. Вздох.

Извлеченный урок: еще раз об основах.

Строительство? Я слышал, что они пользуются спросом.

Как вернуть 403, если у пользователя нет доступа

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

Извлеченный урок:провал возможен всегда.

Няня? О, подождите, я уже это делаю.

Компьютерный кризис

Мой старый Surface Pro 3 начал гнуться под нагрузкой, поэтому я купил новый компьютер. Время выполнения моего модульного теста сократилось с 15 до 2 секунд. Больше никаких зависаний IntelliJ. Я мог одновременно запускать WebStorm, IDEA и приложение в контейнере Docker, и при этом оставался запас памяти.

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

Параметры углового маршрута

Мне нужно было отправить параметры между контроллерами. Конечно, есть проводники, но снова магический фактор был слишком высок. Такое ощущение, что отклонился на миллиметр от примеров и вдруг route.params опустел или что-то в этом роде. Это было вдвойне верно с тестами кармы. Почему этот RouterTestingModule теперь требуется, если раньше его не было. Особенно то, как задавать параметры для контроллеров, казалось, сильно отличалось. Наконец, я решил, что это что-то вроде этого, чтобы передать параметры контроллерам:

TestBed.configureTestingModule({
  declarations: [GroupComponent],
  imports: [FormsModule, RouterTestingModule.withRoutes([]), HttpClientTestingModule],
  providers: [GroupsService, UserService, AlertService, AuthenticationService,
    [GroupComponent, {
      provide: ActivatedRoute,
      useValue: {snapshot: {params: {'group': '123'}}}
    }]]
}).compileComponents();

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

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

Было еще что-то, но я думаю, вы поняли суть.