Виш-лист разработчика: работа в стартапе и зарплата в долларах.
Пост рекламный, но о важных вещах.
В карьере разработчика сложны только первые шаги — войти в индустрию и набрать 3-4 года опыта. Спрос на программистов сильно превышает предложение, поэтому в IT большие возможности для самореализации.
Можно продвигаться в карьере, увеличивать зарплату или устраивать себе челленджи, например:
🔸️ Пройти путь от джуниора до архитектора за 5 лет.
🔸️ Разобраться в какой-нибудь теме, выступить на конференции и носить футболку с шуткой, которую поймут только 3 человека в мире.
🔸️ Пройти 8 этапов собеседований в Яндексе и отказаться от оффера.
Участвовать в молодых проектах — тоже интересная цель. Работа в стартапе совсем не похожа на уютный корпоративный мир. Это:
✅ супер увлечённые люди,
✅ ответственность и дисциплина,
✅ быстро видны результаты работы,
✅ топовые технологии.
Конечно, также велика вероятность:
▪️ ненормированного рабочего дня,
▪️ истеричных коллег,
▪️ закрытия проекта через 3 месяца.
Многие только после работы в стартапе поняли, что значить РАБОТАТЬ. Не закрывать задачи в джире и лениво отчитываться на стендапах, а решать проблемы здесь и сейчас, адаптироваться к изменениям, быстро внедрять новые технологии.
Работа с иностранными заказчиками — тоже специфический опыт. Удалённая работа, высокая зарплата, иностранный язык и иногда совсем другой менталитет.
Главная сложность — найти такие вакансии. На HeadHunter они если и появляются, то под названием «ИП Иванов А.И» или «ООО НьюСофт». Чаще всего их там нет🤷♂️
@profunctor_jobs — телеграм канал с супергорячими вакансиями и важной информацией — стек, зарплата, локация, график и контакт. Там ищут не только java разработчиков, но это реальный шанс найти интересные предложения. Среди их партнёров:
🤳Snap - компания владеет мессенджером Snapchat, умными очками Spectacles, сервисами Bitmoji и Zenly.
🚗Bolt – транспортная платформа, работающая в 30 странах.
💲Revolut – мультивалютные карты без процентов за конвертацию.
Рекомендуем подписаться и следить за вакансиями: @profunctor_jobs
Пост рекламный, но о важных вещах.
В карьере разработчика сложны только первые шаги — войти в индустрию и набрать 3-4 года опыта. Спрос на программистов сильно превышает предложение, поэтому в IT большие возможности для самореализации.
Можно продвигаться в карьере, увеличивать зарплату или устраивать себе челленджи, например:
🔸️ Пройти путь от джуниора до архитектора за 5 лет.
🔸️ Разобраться в какой-нибудь теме, выступить на конференции и носить футболку с шуткой, которую поймут только 3 человека в мире.
🔸️ Пройти 8 этапов собеседований в Яндексе и отказаться от оффера.
Участвовать в молодых проектах — тоже интересная цель. Работа в стартапе совсем не похожа на уютный корпоративный мир. Это:
✅ супер увлечённые люди,
✅ ответственность и дисциплина,
✅ быстро видны результаты работы,
✅ топовые технологии.
Конечно, также велика вероятность:
▪️ ненормированного рабочего дня,
▪️ истеричных коллег,
▪️ закрытия проекта через 3 месяца.
Многие только после работы в стартапе поняли, что значить РАБОТАТЬ. Не закрывать задачи в джире и лениво отчитываться на стендапах, а решать проблемы здесь и сейчас, адаптироваться к изменениям, быстро внедрять новые технологии.
Работа с иностранными заказчиками — тоже специфический опыт. Удалённая работа, высокая зарплата, иностранный язык и иногда совсем другой менталитет.
Главная сложность — найти такие вакансии. На HeadHunter они если и появляются, то под названием «ИП Иванов А.И» или «ООО НьюСофт». Чаще всего их там нет🤷♂️
@profunctor_jobs — телеграм канал с супергорячими вакансиями и важной информацией — стек, зарплата, локация, график и контакт. Там ищут не только java разработчиков, но это реальный шанс найти интересные предложения. Среди их партнёров:
🤳Snap - компания владеет мессенджером Snapchat, умными очками Spectacles, сервисами Bitmoji и Zenly.
🚗Bolt – транспортная платформа, работающая в 30 странах.
💲Revolut – мультивалютные карты без процентов за конвертацию.
Рекомендуем подписаться и следить за вакансиями: @profunctor_jobs
Сколько лет Вы пишете на java?
Anonymous Poll
39%
Меньше года
35%
1-2 года
12%
3-4 года
14%
Больше 4 лет
Какие темы Вам интересны?
Anonymous Poll
49%
Вопросы с собеседований
64%
Java core: обзор и тонкости использования
61%
Принципы разработки и лучшие практики
39%
Теория - JVM, компиляторы, детали реализации
24%
Soft skills - чему и где учиться, как эффективно работать и продвигаться в карьере и деньгах
23%
Саммари книжек и конференций по java
39%
Не совсем java - фреймворки, котлин, БД, архитектура
Привет! Чтобы писать понятные и полезные посты, мы хотим знать больше о Вас и о том, что Вам интересно. Ответьте, пожалуйста, на 2 вопроса выше☝️
Ещё 4 коротких вопроса в https://forms.gle/Vqdfpjfm59BmPQBa6
Ещё 4 коротких вопроса в https://forms.gle/Vqdfpjfm59BmPQBa6
Google Docs
Опрос аудитории Java: fill the gaps
Функциональный стиль в java: когда он нужен?
Большинство задач в энтерпрайзе делятся на две группы:
1️⃣ Одиночный запрос.
Покупка в интернет-магазине, вывод курса доллара — такие запросы затрагивают мало данных и много компонентов. Эти бизнес-процессы хорошо описываются объектами и отношениями между ними.
2️⃣ Обработка данных.
Статистика покупок, поиск рекомендаций, прогноз погоды - в центре внимания обработка данных, а не взаимодействие сущностей. Задача разработчика — чтобы обработка была быстрой. Нужно по максимуму использовать ресурсы процессора и доступную память, снизить время простоя и количество блокировок.
Вот здесь пригодятся некоторые подходы функционального программирования(ФП) :
1️⃣ Понятие чистой функции.
В многопоточном программировании главная проблема — работа с общими переменными. Чтобы безопасно их обновлять и всегда читать актуальные знвчения используются средства синхронизации. Большинство из них снижают общую пропускную способность.
Чистые функции - методы, которые характеризуются только входными и выходными данными. У них нет сайд-эффектов, Они не меняют внешних объектов и не бросают эксепшены. Для одинаковых входных данных результат всегда будет один и тот же.
✅ Нет общих переменных — нет синхронизации.
✅ Можно кэшировать некоторые результаты.
2️⃣ Неизменяемые данные
Исходные данные не должны меняться. Меняться могут только локальные переменные.
✅ Синхронизация не нужна — общие переменные не меняются и всегда актуальны.
Пример: добавление элемента в список.
Подход ООП: добавляем в исходный список - меняем внешний объект.
❌ Для запуска в многопоточной среде нужна синхронизация.
❌ Если метод прервался, то нужно выяснять, что он успел изменить, а что нет.
Подход ФП: создаём новый список на основе исходного и добавляем туда элемент.
✅ Если что-то пошло не так, просто ещё раз вызываем метод.
❌ Большой расход памяти.
3️⃣ Декларативный стиль написания кода.
Обход коллекции в цикле — это императивный подход. Для каждого элемента задаётся чёткая последовательность действий.
Stream API — декларативный. Благодаря большому количеству встроенных функций можно просто описать то, что нужно: отфильтровать, сгруппировать, просуммировать.
Лучшая читаемость - не единственное преимущество такого подхода. Чистые функции и неизменяемые данные сами по себе тоже не сделают программу эффективной. Но их использование в Stream API дают компилятору и JVM большие возможности для оптимизаций. Cамая мощная из них — опция parallel(), она автоматически распределяет данные по параллельным подзадачам.
Начиная с 8 версии java можно применять и другие идеи ФП — функции высших порядков, currying, pattern matching и т.д. Некоторые подходы были доступны и раньше, например, использовать хвостовую рекурсию вместо итерации. Но наибольшую практическую пользу можно извлечь из обработки больших коллекций с помощью Stream API.
Писать программы в функциональном стиле на чистой java можно, но на практике это пока не оптимально. Для обработки потоков данных больше подходят библиотеки для реактивного программирования и стрим-фреймворки.
Большинство задач в энтерпрайзе делятся на две группы:
1️⃣ Одиночный запрос.
Покупка в интернет-магазине, вывод курса доллара — такие запросы затрагивают мало данных и много компонентов. Эти бизнес-процессы хорошо описываются объектами и отношениями между ними.
2️⃣ Обработка данных.
Статистика покупок, поиск рекомендаций, прогноз погоды - в центре внимания обработка данных, а не взаимодействие сущностей. Задача разработчика — чтобы обработка была быстрой. Нужно по максимуму использовать ресурсы процессора и доступную память, снизить время простоя и количество блокировок.
Вот здесь пригодятся некоторые подходы функционального программирования(ФП) :
1️⃣ Понятие чистой функции.
В многопоточном программировании главная проблема — работа с общими переменными. Чтобы безопасно их обновлять и всегда читать актуальные знвчения используются средства синхронизации. Большинство из них снижают общую пропускную способность.
Чистые функции - методы, которые характеризуются только входными и выходными данными. У них нет сайд-эффектов, Они не меняют внешних объектов и не бросают эксепшены. Для одинаковых входных данных результат всегда будет один и тот же.
✅ Нет общих переменных — нет синхронизации.
✅ Можно кэшировать некоторые результаты.
2️⃣ Неизменяемые данные
Исходные данные не должны меняться. Меняться могут только локальные переменные.
✅ Синхронизация не нужна — общие переменные не меняются и всегда актуальны.
Пример: добавление элемента в список.
Подход ООП: добавляем в исходный список - меняем внешний объект.
public void add(List list, Integer value)
{ list.add(value); }
✅ Минимальный расход памяти.❌ Для запуска в многопоточной среде нужна синхронизация.
❌ Если метод прервался, то нужно выяснять, что он успел изменить, а что нет.
Подход ФП: создаём новый список на основе исходного и добавляем туда элемент.
public List add(List list, Integer value {
List newList=new List();
newList.addAll(list);
newList.add(value);
return list;
}
✅ Можно использовать в многопоточной среде.✅ Если что-то пошло не так, просто ещё раз вызываем метод.
❌ Большой расход памяти.
3️⃣ Декларативный стиль написания кода.
Обход коллекции в цикле — это императивный подход. Для каждого элемента задаётся чёткая последовательность действий.
Stream API — декларативный. Благодаря большому количеству встроенных функций можно просто описать то, что нужно: отфильтровать, сгруппировать, просуммировать.
Лучшая читаемость - не единственное преимущество такого подхода. Чистые функции и неизменяемые данные сами по себе тоже не сделают программу эффективной. Но их использование в Stream API дают компилятору и JVM большие возможности для оптимизаций. Cамая мощная из них — опция parallel(), она автоматически распределяет данные по параллельным подзадачам.
Начиная с 8 версии java можно применять и другие идеи ФП — функции высших порядков, currying, pattern matching и т.д. Некоторые подходы были доступны и раньше, например, использовать хвостовую рекурсию вместо итерации. Но наибольшую практическую пользу можно извлечь из обработки больших коллекций с помощью Stream API.
Писать программы в функциональном стиле на чистой java можно, но на практике это пока не оптимально. Для обработки потоков данных больше подходят библиотеки для реактивного программирования и стрим-фреймворки.
👍1
java.util.Set: сравнение реализаций.
Set - интерфейс для коллекций из уникальных элементов. В стандартной библиотеке java 6 реализаций:
HashSet, TreeSet, LinkedHashSet, CopyOnWriteArraySet, ConcurrentSkipListSet и EnumSet.
В этой статье рассмотрим и сравним первые три из них.
Детали реализации:
1️⃣ TreeSet базируется на TreeMap, а он - на основе красно-чёрного дерева. Элементы коллекции должны реализовать интерфейс Comparable, либо экземпляр Comparable нужно передать в конструкторе, потому что compareTo - основной метод для вставки и поиска элементов.
✅ Отсортированные значения.
✅ Быстрая ребалансировка.
✅ Можно искать по ближайшему ключу - доступны функции lower(e), floor(e), ceiling(e), higher(e).
2️⃣ HashSet внутри себя содержит HashMap. Он реализован так:
Есть массив корневых элементов - их ещё называют бакетами. Размер массива по умолчанию 16.
Если нужно добавить элемент в коллекцию, то сначала вычисляется его хэш. Далее ищется индекс корневого элемента - остаток от деления хэша на размер массива. Если по этому индексу ничего нет, корнем становится новый элемент. Если есть - добавляется в структуру под ним.
В java 7 этой структурой был список, с java 8 - бинарное дерево.
Эффективность HashSet зависит от равномерности распределения хэшей. В энтерпрайз приложениях хэш вычисляется по набору полей, и равномерность часто не достигается. Поэтому на практике HashSet не всегда является оптимальной структурой данных. Если известно примерное количество элементов, можно задать loadFactor и capacity в конструкторе.
✅ Возможность оптимизации.
✅ Быстрый поиск.
❌ Значения не отсортированы.
❌ Долгая перестройка при увеличении размера коллекции.
3️⃣ LinkedHashSet расширяет класс HashSet. Помимо основной структуры новые элемент также добавляются в двусвязный список.
✅ Значения отсортированы по порядку вставки.
✅ Возможность оптимизации.
Знание деталей реализации даёт простые правила выбора:
1️⃣ Частый поиск - выбираем TreeSet.
2️⃣ Порядок не важен - HashSet.
3️⃣ Порядок вставки важен - LinkedHashSet.
Мы проверили, насколько велика разница между реализациями:
1️⃣ Добавить в Set миллион чётных чисел.
2️⃣ Вставить миллион нечётных.
3️⃣ Найти миллион случайных.
Запустили несколько раз для HashSet, TreeSet, LinkedHashSet, а также для HashSet и LinkedHashSet с заранее заданной ёмкостью в 2 миллиона. Проверили на java 7 и 11.
В качестве типов данных использовали Integer(идеальное распределение хэшей) и класс с несколькими полями (неравномерное распределение).
Результаты на картинке внизу поста. Тест не претендует на точность, но теперь можно добавить ещё несколько рекомендаций:
✅ В новых версиях java все операции всех реализаций работают на 5-20% быстрее.
✅ Выбор реализации имеет значение.
✅ Знаете будущий размер коллекции - передавайте его в конструкторе.
✅ Для чисел лучше использовать HashSet.
✅ Для экземпляров классов всё неоднозначно, имеет смысл попробовать разные варианты.
Set - интерфейс для коллекций из уникальных элементов. В стандартной библиотеке java 6 реализаций:
HashSet, TreeSet, LinkedHashSet, CopyOnWriteArraySet, ConcurrentSkipListSet и EnumSet.
В этой статье рассмотрим и сравним первые три из них.
Детали реализации:
1️⃣ TreeSet базируется на TreeMap, а он - на основе красно-чёрного дерева. Элементы коллекции должны реализовать интерфейс Comparable, либо экземпляр Comparable нужно передать в конструкторе, потому что compareTo - основной метод для вставки и поиска элементов.
✅ Отсортированные значения.
✅ Быстрая ребалансировка.
✅ Можно искать по ближайшему ключу - доступны функции lower(e), floor(e), ceiling(e), higher(e).
2️⃣ HashSet внутри себя содержит HashMap. Он реализован так:
Есть массив корневых элементов - их ещё называют бакетами. Размер массива по умолчанию 16.
Если нужно добавить элемент в коллекцию, то сначала вычисляется его хэш. Далее ищется индекс корневого элемента - остаток от деления хэша на размер массива. Если по этому индексу ничего нет, корнем становится новый элемент. Если есть - добавляется в структуру под ним.
В java 7 этой структурой был список, с java 8 - бинарное дерево.
Эффективность HashSet зависит от равномерности распределения хэшей. В энтерпрайз приложениях хэш вычисляется по набору полей, и равномерность часто не достигается. Поэтому на практике HashSet не всегда является оптимальной структурой данных. Если известно примерное количество элементов, можно задать loadFactor и capacity в конструкторе.
✅ Возможность оптимизации.
✅ Быстрый поиск.
❌ Значения не отсортированы.
❌ Долгая перестройка при увеличении размера коллекции.
3️⃣ LinkedHashSet расширяет класс HashSet. Помимо основной структуры новые элемент также добавляются в двусвязный список.
✅ Значения отсортированы по порядку вставки.
✅ Возможность оптимизации.
Знание деталей реализации даёт простые правила выбора:
1️⃣ Частый поиск - выбираем TreeSet.
2️⃣ Порядок не важен - HashSet.
3️⃣ Порядок вставки важен - LinkedHashSet.
Мы проверили, насколько велика разница между реализациями:
1️⃣ Добавить в Set миллион чётных чисел.
2️⃣ Вставить миллион нечётных.
3️⃣ Найти миллион случайных.
Запустили несколько раз для HashSet, TreeSet, LinkedHashSet, а также для HashSet и LinkedHashSet с заранее заданной ёмкостью в 2 миллиона. Проверили на java 7 и 11.
В качестве типов данных использовали Integer(идеальное распределение хэшей) и класс с несколькими полями (неравномерное распределение).
Результаты на картинке внизу поста. Тест не претендует на точность, но теперь можно добавить ещё несколько рекомендаций:
✅ В новых версиях java все операции всех реализаций работают на 5-20% быстрее.
✅ Выбор реализации имеет значение.
✅ Знаете будущий размер коллекции - передавайте его в конструкторе.
✅ Для чисел лучше использовать HashSet.
✅ Для экземпляров классов всё неоднозначно, имеет смысл попробовать разные варианты.
Что выведет следующий код?
List list=new ArrayList();
list.add(1);
list.add(2);
Stream stream=list.stream();
list.remove(0);
println(stream.count());Stream API: обзор и основные методы.
Stream API - удобный инструмент обработки данных, который появился в java 8. Два принципа, на которых строится Stream API - composition & single responsibility. Действия собираются из простых независимых друг от друга блоков без сайд-эффектов. Этим достигается удобство чтения и поддержки:
✅ Стандартные названия.
✅ Нет локальных переменных.
✅ Линейная последовательность действий.
Стрим состоит из 3 частей:
1️⃣ Источник данных.
2️⃣ Преобразования.
3️⃣ Конечная операция.
Основные способы создать стрим:
🔸 Из готовой коллекции
🔸 Из набора элементов
🔸 Из массива
🔸 Задать первый элемент и правило вывода последующего, получится бесконечный стрим:
🔸 Генерировать независимые друг от друга элементы:
🔹
🔹
🔹
🔹
🔹
Вычисления выполняются не сразу, а только когда вызывается конечная операция. Конечная операция описывает желаемый результат. Результатом может быть:
◾️ Структура данных:
◾️ Результат поиска:
◾️ Сайд-эффект:
Стрим - не структура хранения данных. Он обходит источник данных (или сам его генерирует) и сохраняет промежуточные результаты. По этой причине нельзя копировать стримы и исполнять их несколько раз - пришлось бы контролировать этапы исполнения, консистентность данных и добавить синхронизацию для работы в многопоточной среде. При попытке переиспользования стрима выбрасывается
✅ Исходные данные не меняются.
✅ Стрим нельзя переиспользовать.
✅ Пока не вызвана конечная операция, стрим можно менять как угодно.
Пример из опроса выше вернёт 1.
Если Вас раздражает, что метод
Stream API - удобный инструмент обработки данных, который появился в java 8. Два принципа, на которых строится Stream API - composition & single responsibility. Действия собираются из простых независимых друг от друга блоков без сайд-эффектов. Этим достигается удобство чтения и поддержки:
✅ Стандартные названия.
✅ Нет локальных переменных.
✅ Линейная последовательность действий.
Стрим состоит из 3 частей:
1️⃣ Источник данных.
2️⃣ Преобразования.
3️⃣ Конечная операция.
Основные способы создать стрим:
🔸 Из готовой коллекции
🔸 Из набора элементов
🔸 Из массива
🔸 Задать первый элемент и правило вывода последующего, получится бесконечный стрим:
Stream.iterate(T, BinaryOperator)
🔸 Задать первый элемент, правило вывода последующего и условие остановки: Stream.iterate(T, Predicate, BinaryOperator) (java 9) 🔸 Генерировать независимые друг от друга элементы:
Stream.generate(Supplier)
🔸 Из диапазона с включением граничных значений: IntStream.range(..)
🔸 Диапазон без граничных значений: IntStream.rangeClosed(..)
🔸 Из строк: BufferedReader.lines()
🔸 Из символов строки: CharSequence.chars()
Названия большинства методов преобразований говорят сами за себя: filter, distinct, limit, sorted. Есть менее понятные:🔹
map: применить функцию к каждому элементу.🔹
flatmap: положить элементы из "списка списков" в единый стрим. Также с его помощью можно уменьшить размерность массива.🔹
takeWhile(Predicate): брать элементы из стрима, пока выполняется условие. Метод доступен c java 9.🔹
dropWhile(Predicate): пропускать элементы, пока условие не нарушится. Метод доступен c java 9.🔹
peek(Consumer): сделать что-то с каждым элементом стрима, не меняя исходный стрим. Часто используется при дебаге, например, вывести в консоль текущие элементы.Вычисления выполняются не сразу, а только когда вызывается конечная операция. Конечная операция описывает желаемый результат. Результатом может быть:
◾️ Структура данных:
toArray, collect.◾️ Результат поиска:
anyMatch, allMatch, nonMatch, findFirst, findAny.
◾️ Результат агрегации: min, max, count, reduce.◾️ Сайд-эффект:
forEach, forEachOrdered.Стрим - не структура хранения данных. Он обходит источник данных (или сам его генерирует) и сохраняет промежуточные результаты. По этой причине нельзя копировать стримы и исполнять их несколько раз - пришлось бы контролировать этапы исполнения, консистентность данных и добавить синхронизацию для работы в многопоточной среде. При попытке переиспользования стрима выбрасывается
IllegalStateException.✅ Исходные данные не меняются.
✅ Стрим нельзя переиспользовать.
✅ Пока не вызвана конечная операция, стрим можно менять как угодно.
Пример из опроса выше вернёт 1.
list.remove(0) удаляет элемент с индексом 0, поэтому на момент старта вычислений stream.count() в источнике данных остаётся всего один элемент.Если Вас раздражает, что метод
list.remove принимает не сам элемент, а индекс, то некоторые методы Stream API Вас тоже разочаруют. Например, такой код компилируется:Stream.of(-1, 0, 1).max(Math::max).get();Но в результате получается -1, потому что входной параметр метода
max - Comparator. Math.max по сигнатуре на него похож, поэтому получается неверный результат😒👍8
Stream API: забудьте про peek()
Один из недостатков Stream API по сравнению с циклами - неудобный дебаг. До сих пор метод
Один из недостатков Stream API по сравнению с циклами - неудобный дебаг. До сих пор метод
peek() был единственным способом посмотреть элементы внутри стрима:list.stream().filter(..)Недавно в Intellij IDEA вышел потрясающий апдейт, который выводит дебаг стримов на новый уровень:
.peek(e->println("filt:"+e))
.map(..)
.peek(e->println("map:"+e))
...
🔥6
10 правил безопасных java приложений.
Скорость, масштабируемость, удобство использования — такие требования обычно ставят заказчики. Устойчивость к атакам и защита данных редко проговаривается и подразумевается по умолчанию.
Для кода на java есть много рекомендаций, например, Secure Coding Guidelines for Java SE. На практике большинство ошибок можно избежать очень просто:
1️⃣ Понятный код и однозначная бизнес-логика - уязвимости легко пропустить в чём-то сложном и запутанном. Сужайте и скрывайте доступную функциональность на всех уровнях:
🔸 Модификаторы полей и классов: всё, что может быть private, делайте private. Если для задачи удобно поменять private на public, подумайте о других вариантах.
🔸 Reflection и проверка имён классов: используйте осторожно, т.к такой код не адаптируется к изменениям. Поменяется имя метода, добавится ещё один класс, а рефлекшн останется тем же.
🔸 Внешний API: чем проще, тем лучше.
2️⃣ Избегайте сериализации: чтобы хранить и передавать объекты внутри системы используйте облегчённые протоколы с поддержкой версионирования: gRPC, Avro, Thrift и т.д.
3️⃣ Шифруйте персональную информацию: пароли, номера карточек, кодовые слова. Не записывайте её в лог! В 2019 году перехват незащищённых данных всё ещё входит в топ-3 опасностей по версии OWASP.
4️⃣ Проверяйте входные данные от пользователей и сторонних систем. Даже если похожие проверки есть на фронтенде. Ограничивайте размер пользовательского ввода и результатов: не позволяйте загружать 5 ГБ картинку и запрашивать триллион записей.
5️⃣ Используйте PreparedStatement для запросов к БД. Для большинства NoSQL они тоже есть.
6️⃣ Скрывайте технические детали в сообщениях об ошибке: не показывайте стек трейс конечному пользователю и в консоли браузера.
7️⃣ Обновляйте библиотеки, не игнорируйте авто-апдейты.
8️⃣ Проверяйте open-source библиотеки, которые используются на проекте, например, с помощью OWASP Dependency-Check. Коммит вредоносного кода в open-source - реальная угроза.
9️⃣ Записывайте и анализируйте активность пользователей - как минимум неудачные попытки авторизации.
🔟 Тестируйте методы на переполнение, оно случается.
Скорость, масштабируемость, удобство использования — такие требования обычно ставят заказчики. Устойчивость к атакам и защита данных редко проговаривается и подразумевается по умолчанию.
Для кода на java есть много рекомендаций, например, Secure Coding Guidelines for Java SE. На практике большинство ошибок можно избежать очень просто:
1️⃣ Понятный код и однозначная бизнес-логика - уязвимости легко пропустить в чём-то сложном и запутанном. Сужайте и скрывайте доступную функциональность на всех уровнях:
🔸 Модификаторы полей и классов: всё, что может быть private, делайте private. Если для задачи удобно поменять private на public, подумайте о других вариантах.
🔸 Reflection и проверка имён классов: используйте осторожно, т.к такой код не адаптируется к изменениям. Поменяется имя метода, добавится ещё один класс, а рефлекшн останется тем же.
🔸 Внешний API: чем проще, тем лучше.
2️⃣ Избегайте сериализации: чтобы хранить и передавать объекты внутри системы используйте облегчённые протоколы с поддержкой версионирования: gRPC, Avro, Thrift и т.д.
3️⃣ Шифруйте персональную информацию: пароли, номера карточек, кодовые слова. Не записывайте её в лог! В 2019 году перехват незащищённых данных всё ещё входит в топ-3 опасностей по версии OWASP.
4️⃣ Проверяйте входные данные от пользователей и сторонних систем. Даже если похожие проверки есть на фронтенде. Ограничивайте размер пользовательского ввода и результатов: не позволяйте загружать 5 ГБ картинку и запрашивать триллион записей.
5️⃣ Используйте PreparedStatement для запросов к БД. Для большинства NoSQL они тоже есть.
6️⃣ Скрывайте технические детали в сообщениях об ошибке: не показывайте стек трейс конечному пользователю и в консоли браузера.
7️⃣ Обновляйте библиотеки, не игнорируйте авто-апдейты.
8️⃣ Проверяйте open-source библиотеки, которые используются на проекте, например, с помощью OWASP Dependency-Check. Коммит вредоносного кода в open-source - реальная угроза.
9️⃣ Записывайте и анализируйте активность пользователей - как минимум неудачные попытки авторизации.
🔟 Тестируйте методы на переполнение, оно случается.
❤5
Обход коллекций в Java.
Чтобы ответить на вопросы выше, посмотрим на обход более сложных сущностей, например, графа. Граф - структура данных из вершин и рёбер. В графах часто встречаются циклические пути. Найти гамильтонов путь, то есть обойти вершины графа по одному разу - непростая задача. Алгоритм ведёт учёт посещённых вершин и перебирает разные варианты прежде, чем утвердить один из них. В computer science обход структуры данных называется traverse. Основная идея — выводим текущий элемент, следующий пока неизвестен и считается отдельно.
Вернёмся в мир java. Здесь нет зацикленных графов, все просто — списки, множества, очереди, деревья. При обходе следующий элемент всегда однозначен, а его отсутствие означает конец работы. Поэтому обход выглядит так:
❌ Так как следующий элемент известен заранее, итератор может показать удалённый элемент. Или не вывести только что добавленный.
ArrayList, HashMap, HashSet и тд. не синхронизированы. Если одновременно итерировать и менять коллекцию разными потоками, можно нарушить целостность данных. Есть два способа этого избежать:
1️⃣ Fail-fast итераторы бросают ConcurrentModificationException при изменениях во время итерации.
2️⃣ Fail-safe итераторы работают с неизменяемой структурой.
Большинство однопоточных коллекций реализуют fail-fast подход.
Метод
❓Почему нельзя использовать траверс по умолчанию?
➡️ Потому что итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
❓Зачем нужно несколько вариантов?
➡️
Итого: при выводе элементов
❗️Cинтаксис
Чтобы ответить на вопросы выше, посмотрим на обход более сложных сущностей, например, графа. Граф - структура данных из вершин и рёбер. В графах часто встречаются циклические пути. Найти гамильтонов путь, то есть обойти вершины графа по одному разу - непростая задача. Алгоритм ведёт учёт посещённых вершин и перебирает разные варианты прежде, чем утвердить один из них. В computer science обход структуры данных называется traverse. Основная идея — выводим текущий элемент, следующий пока неизвестен и считается отдельно.
Вернёмся в мир java. Здесь нет зацикленных графов, все просто — списки, множества, очереди, деревья. При обходе следующий элемент всегда однозначен, а его отсутствие означает конец работы. Поэтому обход выглядит так:
Iterator it=list.iterator();Метод next() возвращает текущий элемент и сразу сдвигает указатель на следующий. Метод hasNext() проверяет, ссылается ли этот указатель куда-нибудь. Этот паттерн повторяется снова и снова и называется Iteration. Важно - указатель на следующий элемент вычисляется заранее. Итератор лежит в основе синтаксиса
while(it.hasNext())
int result = it.next();
for (T e: collection).❌ Так как следующий элемент известен заранее, итератор может показать удалённый элемент. Или не вывести только что добавленный.
ArrayList, HashMap, HashSet и тд. не синхронизированы. Если одновременно итерировать и менять коллекцию разными потоками, можно нарушить целостность данных. Есть два способа этого избежать:
1️⃣ Fail-fast итераторы бросают ConcurrentModificationException при изменениях во время итерации.
2️⃣ Fail-safe итераторы работают с неизменяемой структурой.
Большинство однопоточных коллекций реализуют fail-fast подход.
ConcurrentHashMap потокобезопасен, поэтому изменения во время обхода разрешены. Обход через for реализован через итератор. Указатель на следующий элемент вычисляется заранее. Более подходящий новый элемент не отображается, и выводится 2 элемента.Метод
forEach в ConcurrentHashMap использует подход траверс и вычисляет следующий элемент только когда он запрашивается. Поэтому новый ключ подхватывается и выводится 3 элемента.❓Почему нельзя использовать траверс по умолчанию?
➡️ Потому что итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
❓Зачем нужно несколько вариантов?
➡️
ConcurrentHashMap может перестраиваться во время обхода. Чтобы во время перестройки не выводить пользователю дубликаты, используется траверс со сложной логикой.Итого: при выводе элементов
ConcurrentHashMap через for и forEach используются разные алгоритмы обхода, поэтому результат вывода тоже разный.❗️Cинтаксис
forEach реализован и для однопоточных коллекций, но там используется итератор, поэтому разницы с for нет.Intellij IDEA: пишем код быстрее.
1️⃣ Live templates.
Аббревиатуры для популярных синтаксических конструкций. Вводите и нажимаете Enter: 4 символа разворачиваются в 40, а курсор приходит в нужную позицию.
Полный список сокращений в File/Settings/Editor/Live Templates. Есть для Java, Kotlin, JS, Groovy, для разработки под Android и React.
Самые популярные для Java:
▫️St →
▫️ fori
Дополнение имен классов, методов и полей на основе контекста. Варианты появляются в выпадающем списке. Можно писать только начало:
▫️Int →
▫️NPE →
▫️count == 4.if
1️⃣ Live templates.
Аббревиатуры для популярных синтаксических конструкций. Вводите и нажимаете Enter: 4 символа разворачиваются в 40, а курсор приходит в нужную позицию.
Полный список сокращений в File/Settings/Editor/Live Templates. Есть для Java, Kotlin, JS, Groovy, для разработки под Android и React.
Самые популярные для Java:
▫️St →
String
▫️sout System.out.println();
▫️main public static void main(String[] args){}
▫️prsf private static final
Некоторые сокращения разворачиваются в методы с параметрами для автозаполнения. Перемещаться между ними можно с помощью Tab:▫️ fori
for (int i=0; i< ; i++) {}
▫️ifn if (args == null) {}
▫️mx = Math.max(, );
▫️lazy
if (obj == null)2️⃣ Code completion.
{ obj = new Integer(); }
Дополнение имен классов, методов и полей на основе контекста. Варианты появляются в выпадающем списке. Можно писать только начало:
▫️Int →
Integer
▫️Cust → Customer
Можно писать заглавные буквы в названиях классов:▫️NPE →
NullPointerException
▫️CHM → ConcurrentHashMap
Можно оборачивать код в синтаксические конструкции:▫️count == 4.if
if (count == 4) {}
▫️list.for for(Integer i : list) {}
▫️obj.opt
Optional.of(obj)
▫️answer.switch switch (answer) {}
Полный список таких дополнений в File/Settings/Editor/General/Postfix Completion. Есть варианты не только для Java, но и для Kotlin и JS.🔥2