Зачем учить Java и как делать это эффективно. Доклад Яндекса
Чем Java отличается от других популярных языков? Почему именно Java может быть первым языком для изучения? Давайте составим план, который поможет выучить Java как с нуля, так и с применением навыков программирования на других языках. Перечислим отличия между созданием продакшен-кода на Java и разработкой на других языках. Михаил Затепякин прочитал этот доклад на встрече для будущих участников стажировки Яндекса и других начинающих разработчиков — Java Junior meetup.
— Всем привет, меня зовут Миша. Я разработчик из Яндекс.Маркета, и сегодня я расскажу вам, зачем учить Java и как делать это эффективно. Вы можете задать резонный вопрос: почему это буду рассказывать я, а не какой-нибудь сильный разработчик с кучей лет стажа? Дело в том, что я сам изучал Java недавно, года полтора назад, поэтому еще помню, как это примерно было и какие есть подводные камни.
Год назад я попал на стажировку в Яндекс.Маркет. Разрабатывал бэкенд для «Беру», для самого Маркета, вы наверняка им пользовались. Сейчас продолжаю там работать, в другой команде. Мы делаем аналитическую платформу Яндекс.Маркета для бизнес-партнеров.
Давайте приступим. Зачем учить Java с практической точки зрения? Дело в том, что Java — это очень известный язык программирования. У него очень большое комьюнити.
Например, есть такой TIOBE-индекс, популярный индекс популярности языков программирования, и Java там занимает первое место. Также на сайтах с вакансиями вы наверняка заметите, что большинство вакансий — как раз таки про Java, то есть разрабатывая на Java, вы всегда сможете найти себе работу.
Поскольку комьюнити очень большое, на любой ваш вопрос найдется ответ на каком-нибудь Stack Overflow или других сайтах. А еще, разрабатывая на Java, вы, на самом деле, пишете код по JVM, поэтому сможете легко пересесть на Kotlin, Scala и другие языки, использующие JVM.
Чем хороша Java с идеологической точки зрения? Есть разные языки программирования. Они решают разные задачи, вы это знаете. Например, на Python очень здорово писать однострочные скрипты для решения быстрых задач.
На плюсах можно полностью контролировать исполняемый код. Например, у нас ездят машинки, беспилотные автомобили Яндекса, их код написан на плюсах. Почему? У Java есть такая штука — Garbage Collector. Она очищает оперативную память от ненужных объектов. Эта штука запускается спонтанно и делает stop-the-world, то есть останавливает всю остальную программу и идет считать объекты, очищать память от объектов. Если такая штука будет работать в беспилотнике, это не круто. Ваш беспилотник будет ехать прямо, в этот момент очищать память и никак не смотреть на дорогу. Поэтому беспилотник написан на плюсах.
Какие задачи решает Java? Это в первую очередь язык для разработки больших программ, которые пишутся годами, десятками или сотнями людей. В частности, много бэкенда в Яндекс.Маркете написано на Java. У нас распределенная команда в нескольких городах, по десять человек в каждом. И код легко поддерживать, он поддерживается уже десять и более лет, и при этом приходят новые люди, разбираются в этом коде.
Какими характеристиками должен обладать язык, чтобы код на нем был легко поддерживаемый и чтобы его легко было разрабатывать в больших командах. Это в первую очередь должен быть читаемый код, и на нем должно быть легко реализовывать сложные архитектурные решения. То есть на нем должно быть легко писать высокоуровневые абстракции и т. д. Все это как раз и предоставляет нам Java. Это объектно-ориентированный язык. На нем действительно просто реализовывать высокоуровневые абстракции и сложные архитектуры.
Также существует очень много фреймворков и библиотек для Java, потому что языку уже больше 15 лет. За это время на нем было написано все, что могло быть написано, поэтому есть куча библиотек для всего, что может вам пригодиться.
Какими основными скиллами, на мой взгляд, должен обладать начинающий джавист? В первую очередь, это знание языка Java core. Далее это какой-нибудь Dependency Injection фреймворк. Про это более полно будет рассказывать следующий докладчик Кирилл. Я особо углубляться не буду. Далее это архитектура и паттерны проектирования. Нам нужно уметь писать архитектурно красивый код, чтобы писать большие приложения. И это какой-нибудь SQL или ORM для задач работы с базой. И это больше касается бэкенда.
Поехали! Java core. Тут я особо Америку не открою — нужно знать сам язык. На что стоит обратить внимание. Во-первых, Java вышло очень много версий за последние годы, то есть в 2014-2015 году вышла седьмая, потом восьмая, девятая, десятая, очень много новых версий, и в них вводили очень много новых крутых штук, например, Java Stream API, лямбда и т. д. Очень клевые свежие крутые штуки, которые используются в production-коде, о чем спрашивают на собеседованиях и которые нужно знать. Поэтому не стоит брать книгу с полки в библиотеке Java-4 и идти ее учить. Такой себе план: учим Java-8 или выше.
Внимательно обращаем внимание на нововведения, такие как Stream API, var, и т. д. Их спрашивают на собеседованиях, постоянно используют в production. То есть в Stream API сильно круче циклов, вообще, очень крутая штука. Обратите внимание обязательно.
И есть еще всякие штуки вроде итераторов, Exceptions и так далее. То, что кажется вам неважным, пока вы пишете какой-то маленький код сами. Вам не нужны эти Exceptions, кому они вообще нужны? Но их точно будут спрашивать на собеседованиях, они точно вам пригодятся в продакшене. В общем, стоит обратить внимание на Exceptions, итераторы и прочие штуки.
Структуры данных. Без структур никуда, при этом будет здорово, если вы будете не просто знать, что бывают set, dictionary, листы. А еще разные реализации структур. Например, у той же dictionary в Java много реализаций, в том числе HashMap и TreeMap. У них разные асимптотики, они по-разному устроены внутри. Нужно знать, чем они отличаются, когда какую использовать.
Также очень здорово будет, если вы будет знать, как работают эти структуры данных внутри. То есть не просто знать их асимптотики — за сколько работает ставка, за сколько работает проход, а как работает структура внутри — например, что такое бакет в HashMap.
Также стоит обратить внимание на деревья и графы. Это такие штуки, которых не очень много в продакшен-коде, но их любят на собеседованиях. Соответственно, нужно уметь обходить деревья, графы в ширину, глубину. Это все простенькие алгоритмы.
Как только вы начнете писать сколь-нибудь большой код, сложный, с использованием библиотек, многоклассовый код, вы поймете, что вам тяжко без систем сборки и resolve-зависимости. Это, в первую очередь Maven и Gradle. Они позволяют вам импортировать библиотеки в ваш проект реально в одну строчку. То есть вы пишете однострочный xml и импортируете в проект библиотеки. Отличные системы. Они примерно одинаковые, используйте любую — Maven или Gradle.
Далее — какая-нибудь система контроля версий. Я рекомендую Git, потому что он популярный, есть куча туториалов. Почти все пользуются Git, клевая штука, без нее никуда.
И — какая-нибудь среда разработки. Я рекомендую IntelliJ Idea. Она очень сильно ускоряет процесс разработки, сильно вам помогает, пишет за вас всякий boilerplate-код, в общем, клевая.
SQL. Чуть-чуть про бэкендеров. Здесь, на самом деле, был забавный кейс. Мне за два дня до моего второго собеседования на стажировку позвонила девушка-HR и сказала, что меня через два дня будут спрашивать SQL и HTTP, нужно выучить. А я не знал ни про SQL, ни про HTTP примерно ничего. И я нашел такой клевый сайт — SQLZOO. На нем я выучил SQL часов за 12, в смысле, синтаксис SQL, как писать SELECT-запросы, JOIN и т. д. Очень клевый сайт, очень рекомендую. Реально за 12 часов выучил 90% того, что я знаю сейчас.
И еще здорово знать архитектуру баз данных. Это всякие ключи, индексы, нормализация. Про это есть серия постов на Хабре.
В Java, кроме SQL, есть всякие Object-relational mapping-системы типа JPA. Есть некий код. В первом методе некий SQL-код — SELECT id name FROM info.users WHERE id IN userIds. Из базы данных users, из таблицы, получают их айдишники, имена.
Далее есть некий маппер, который превращает объект из базы в джавовый объект. И есть ниже третий метод, который, собственно, выполняет этот код. Все это с помощью JPA можно заменить на одну строчку, которая написана ниже. Она делает все то же самое, — find All ByIdIn. То есть по названию метода он генерирует вам SQL-запрос.
Очень клевая штука. Я сам, когда не знал SQL, использовал JPA. В общем, обратите внимание. Если лень учить SQL, — огонь. И, вообще, огонь!
Spring. Кто слышал про такую штуку, как фреймворк Spring? Видите, как вас много? Неспроста. Spring есть в требованиях каждой второй вакансии по бэкенду на Java. Без него реально никуда в большой разработке. Что такое Spring? В первую очередь это Dependency Injection-фреймворк. Про это тоже будет рассказывать следующий докладчик. Но если вкратце, это штука, которая позволяет вам облегчить импортирование зависимостей одних классов на другие. То есть упрощается знание зависимостей.
Spring Boot — такой кусок Spring, который позволяет вам поднимать свое серверное приложение одной кнопкой. Вы заходите на THID, нажимаете пару кнопок, и вот у вас на localhost 8080 уже поднято ваше серверное приложение. То есть вы еще ни строчки кода не написали, а оно уже работает. Очень клевая штука. Если пишете что-то свое, — огонь!
Spring — очень большой фреймворк. Он не только поднимает вам серверное приложение и резолвит Dependency Injection. Он позволяет делать кучу всего, в том числе создавать REST API-методы. То есть вы написали какой-то метод, повесили на него аннотацию Get mapping. И вот у вас на localhost уже есть какой-то метод, который пишет вам Hello world. Две строчки кода, и работает. Крутая штука.
Также Spring упрощает написание тестов. Без тестирования в большой разработке никак. Код нужно тестировать. Для этого в Java есть клевая библиотека JUnit 5. И вообще JUnit, но последняя версия пятая. Там есть все для тестирования, всякие assertions и прочие штуки.
И есть офигенный фреймворк Mockito. Представьте, что у вас есть некая функциональность, которую вы хотите протестировать. Функциональность делает много чего, в том числе, где-то в серединке она входит во «ВКонтакте» с вашим, например, айдишником, и получает по айдишнику имя и фамилию юзера «ВКонтакте». Наверное, вы не будете в тестах входить «ВКонтакте», это странно. Но функциональность протестировать вам нужно, поэтому вы этот класс, с помощью Mockito, сделали его mok, его имитацию.
Вы скажете, что когда в этот класс приходит запрос с айдишником таким-то, возвращает какую-нибудь фамилию, например, Вася Пупкин. И это будет работать. То есть вы будете тестировать всю функциональность за mok один какой-то класс. Очень клевая штука.
Паттерны проектирования. Что это такое? Это шаблоны для решения типичных проблем, возникающих в разработке. В разработке частенько возникают одинаковые, или схожие какие-то задачи, которые было бы здорово как-то хорошо решать. Поэтому люди придумали best practices, некие шаблоны, как решать эти проблемы.
Есть веб-сайт с большинством популярных паттернов — refactoring.guru, можно почитать, узнать, какие бывают паттерны, прочитать кучу теории. Проблема в том, что это практически бесполезно. Фактически паттерны без практики особой пользы не несут.
Вы услышите про какие-нибудь паттерны типа Singletone или Builder. Кто слышал эти слова? Очень много людей. Есть такие простые паттерны, которые вы можете реализовать сами. Но большинство паттернов: стратегия, фабрика, фасад — не понятно, где их применять.
И пока вы не увидите на практике в каком-то чужом коде место, к которому применен этот паттерн, применять его самим не получится. Поэтому с паттернами очень важна практика. И просто прочитать о них на refactoring.guru не супер полезно, но это обязательно нужно сделать.
Зачем нужны паттерны? Пусть у вас есть некий класс User. У него есть Id и Name. У каждого User обязательно должен быть и Id, и Name. Слева сверху — класс.
Какие есть способы инициализировать User? Два варианта — либо конструктор, либо setter. Какие есть минусы у обоих подходов?
Конструктор. new User (7, «Bond»), окей. Теперь допустим, что у нас не класс User, а некий другой, с семью числовыми полями. У вас будет конструктор, в котором семь подряд идущих чисел. Непонятно, что это за числа, какое из них к какому property относится. Конструктор — это не здорово.
Второй вариант — setter. Вы четко пишете: setId(7), setName(«Bond»). Вы понимаете, какая property к какому полю относится. Но у setter есть проблема. Во-первых, вы можете забыть засетить что-то, а во-вторых, ваш объект получается изменяемым. Это не потокобезопасно и чуть-чуть понижает читаемость кода. Поэтому люди придумали клевый паттерн — Builder.
Про что это? Постараемся собрать плюсы обоих подходов — и setter, и конструктора — в одном. Делаем некий объект, Builder, у которого тоже будут поля Id и Name, который сам будет строиться на основе setter, и у которого будет метод Build, который возвращает вам нового User со всеми параметрами. У нас получается неизменяемый объект и setter. Клево!
Какие есть проблемы? Вот у нас есть классический Builder. Проблема — мы все еще можем забыть засетить какое-то поле. И если мы забыли засетить айдишник, в данном случае в Builder он проинициализируется нулем, потому что тип int — не nullable. И если мы сделаем Name «Bond» и забудем посетить айдишник, у нас будет новый User с id «0» и именем «Bond». Не клево.
Попробуем с этим побороться. В Builder поменяем int на int же, чтобы он был nullable. Теперь все здорово.
Если мы пытаемся сделать User с именем «Bond», забыв проставить ему айдишник, у нас упадет null pointer exception, потому что айдишник не nullable, а у Builder — null, конкретно pointer exception.
Но мы все еще можем забыть поставить имя, поэтому мы навешиваем object replay на null. Теперь, когда мы строим из Builder наш объект, он проверяет, что поле не nullable. И это еще не все.
Посмотрим на последний пример. В данном случае если мы в айдишном рантайме каким-то образом положили null, и было бы здорово сразу узнать, что ты это сделал и это не клево, что ты сейчас делаешь ошибку.
Нужно кинуть ошибку не в момент создания User, а когда ты сетишь в айдишник null. Поэтому мы в Builder поменяем в setter Integer на int, и он сразу здесь ругнется, что выкинули null.
Короче, в чем суть? Есть простейший паттерн Builder, но даже в его реализации есть какие-то тонкости, поэтому очень клево посмотреть на разные реализации паттернов. У каждого паттерна есть десятки реализаций. Это все очень интересно.
Как мы пишем Builder в продакшен-коде? Вот наш User. На него вешаем ротацию Builder из библиотеки Lombok, и она сама генерирует нам Builder. То есть мы не пишем кода, но Java уже считает, что у этого класса есть Builder, и мы его можем вот так вызвать.
Я уже говорил, что в Java есть библиотеки практически для всего, в том числе Lombok, клевая библиотека, которая позволяет вам не писать boilerplate. Builder, GET.
Паттерны бывают архитектурные — относящиеся не только к одному классу, а к системе в целом. Есть такой клевый принцип в проектировании систем: Single Responsibility Principle. О чем он говорит? О том, что каждый класс должен отвечать за какую-то свою функциональность. В данном случае у нас есть Controller, который общается с пользователями, JSON-объектами. Есть Facade, который преобразует JSON-объекты в модели, с которыми далее будет работать Java приложение. Есть Service, в котором есть сложная логика, работающая с этими моделями. Есть Data Access Object, которая эти модели кладет в базу и достает из базы. И есть сама база данных. Другими словами, не все это находится в одном классе, а мы делаем пять разных классов, и это еще один паттерн.
Когда вы более-менее выучили Java, здорово написать какой-нибудь собственный проект, в котором будет база данных, работа с другими API, и который будет предоставлять REST API-клиентам ваше серверное приложение. Такую штуку будет здорово вставить в резюме, это клевое окончание вашего обучения. С этим можно идти и устраиваться на работу.
Вот пример моего серверного приложения. На втором курсе я с ребятами писал курсовую работу. Они писали мобильное приложение для организации мероприятий. Там пользователи могли заходить через «ВКонтакте», ставить на карте точки, создавать мероприятия, звать на них своих друзей, сохранять изображение мероприятий и т. д.
Что сделал в проекте я? Написал серверное приложение на Spring Boot, не используя SQL. Я его не знал, использовал JPA. Что оно умело делать? Авторизоваться в VK через OAuth-2. Брать токен пользователя, идти с ним в VK, проверять, что это настоящий пользователь. Получать информацию о пользователях через «ВКонтакте». Оно умело сохранять информацию в базе данных, также через JPA. Умело сохранять картинки и прочие файлы в памяти компьютера, и сохранять ссылки на них в базе. Я тогда еще не знал, что есть CLOB-объекты в базе, поэтому делал так. Там был REST API для пользователей, клиентских приложений. И были юнит-тесты на основные функциональности.
[…] Небольшой пример моего успешного изучения Java. На первом курсе в университете мне преподавали C# и дали понимание про ООП-программирование — что такое классы, интерфейсы, абстракция, зачем они нужны. Мне это очень помогло. Без этого изучать Java довольно сложно, непонятно, зачем нужны классы.
На втором курсе в университете опять же давали Java core, но я на этом не остановился, пошел изучать Spring сам и написал курсовую работу, свой проект, о котором я говорил выше. И с этим всем я пошел на стажировку в Яндекс, прошел собеседование, попал в Яндекс.Маркет. Там я писал бэкенд для Беру, это наш маркетплейс, и для самого Яндекс.Маркета.
После этого, полгода назад, я перевелся в другую команду внутри того же Маркета. Мы делаем аналитику для бизнес-партнеров. Мы в аналитической платформе, нас на бэкенде трое, поэтому у меня очень большая доля влияния на проект. Это очень интересно, на самом деле. То есть, мы реально предоставляем данные по рынку — какие продажи, в каких категориях, в каких моделях, для бизнес-партнеров, больших известных компаний. И нас всего трое, мы пишем этот код, и это очень прикольно.