Нияз Хадимуллин | Ментор по GO – Telegram
Нияз Хадимуллин | Ментор по GO
1.17K subscribers
123 photos
1 video
35 links
Авторский канал ментора Нияза про Go, базы данных и разработку

Если хочешь записаться на моё менторство и начать получать офферы, не стесняйся писать мне https://mentor-niyaz.ru
Download Telegram
🔄 Посредник (Mediator): упрощение взаимодействия компонентов

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

💡 Основные концепции:
- Централизованное управление:
1. Объект-посредник управляет взаимодействием между компонентами.
2. Компоненты общаются только через посредника.

❗️Ключевые особенности:
- Снижение зависимости:
1. Уменьшение количества связей между компонентами.
2. Упрощение изменения и расширения системы.

- Упрощение кода:
1. Централизованное управление логикой взаимодействия.
2. Улучшение читаемости и поддержки кода.

🔍 Типичные сценарии использования:
1. Управление сложными взаимодействиями между компонентами.
2. Упрощение кода и улучшение его поддержки.
3. Обеспечение гибкости и расширяемости системы.

🎯 Преимущества использования Posrednik:
- Уменьшение зависимости между компонентами.
- Упрощение кода и улучшение его поддержки.
- Повышение гибкости и расширяемости системы.
🔗 Цепочка обязанностей (Chain of Responsibility): передача запросов

Что такое паттерн Цепочка обязанностей?
Цепочка обязанностей (Chain of Responsibility) — это поведенческий паттерн, который позволяет передавать запросы последовательно по цепочке обработчиков до тех пор, пока один из них не обработает запрос.

💡 Основные концепции:
- Цепочка обработчиков:
1. Каждый обработчик решает, может ли он обработать запрос.
2. Если нет, запрос передаётся следующему обработчику в цепочке.

❗️Ключевые особенности:
- Гибкость:
1. Возможность динамического изменения цепочки обработчиков.
2. Упрощение добавления новых обработчиков.

- Разделение обязанностей:
1. Каждый обработчик отвечает только за свою часть логики.
2. Улучшение модульности и поддержки кода.

🔍 Типичные сценарии использования:
1. Обработка запросов с неопределённым набором операций.
2. Реализация систем авторизации и аутентификации.
3. Управление событиями и уведомлениями.

🎯 Преимущества использования Chain of Responsibility:
- Упрощение добавления новых обработчиков.
- Улучшение модульности и поддержки кода.
- Повышение гибкости системы.
📋 Шаблонный метод (Template Method): определение алгоритма

Что такое паттерн Шаблонный метод?
Шаблонный метод (Template Method) — это поведенческий паттерн, который определяет скелет алгоритма в методе, оставляя реализацию отдельных шагов подклассам.

💡 Основные концепции:
- Скелет алгоритма:
1. Базовый класс определяет структуру алгоритма.
2. Подклассы реализуют конкретные шаги алгоритма.

❗️Ключевые особенности:
- Избежание дублирования кода:
1. Общая логика алгоритма определяется в базовом классе.
2. Подклассы реализуют только уникальные части.

- Гибкость и расширяемость:
1. Возможность изменения отдельных шагов алгоритма без изменения его структуры.
2. Упрощение добавления новых реализаций.

🔍 Типичные сценарии использования:
1. Реализация алгоритмов с общей структурой, но различной логикой шагов.
2. Упрощение поддержки и расширения кода.
3. Обеспечение согласованности в реализации алгоритмов.

🎯 Преимущества использования Template Method:
- Избежание дублирования кода.
- Упрощение поддержки и расширения алгоритмов.
- Обеспечение согласованности в реализации.
🚫 Anti-SRP: принцип размытой обязанности

Что такое Anti-SRP?
Anti-SRP (Anti-Single Responsibility Principle) — это антипаттерн, при котором классы разбиты на множество мелких классов, в результате чего логика "размазывается" по нескольким классам или модулям, что затрудняет понимание и поддержку кода.

💡 Основные концепции:
- Размытая логика:
1. Логика приложения распределена по множеству мелких классов.
2. Трудности в отслеживании и понимании потока выполнения.

❗️Ключевые особенности:
- Усложнение поддержки:
1. Трудности в нахождении и исправлении ошибок.
2. Увеличение времени на внесение изменений.

- Снижение читаемости:
1. Необходимость понимания множества классов для выполнения одной задачи.
2. Усложнение документирования и обучения новых разработчиков.

🔍 Типичные сценарии возникновения:
1. Чрезмерное разделение логики на мелкие классы.
2. Отсутствие чёткого разделения обязанностей между классами.
3. Попытка следовать SRP без понимания контекста.

🎯 Как избежать Anti-SRP:
- Объединяйте связанную логику в одном классе.
- Используйте принцип единственной ответственности (SRP) с учётом контекста.
- Упрощайте структуру кода для повышения его читаемости и поддержки.
🏭 Anti-OCP: принцип фабрики фабрик

Что такое Anti-OCP?
Anti-OCP (Anti-Open/Closed Principle) — это антипаттерн, при котором дизайн системы становится слишком обобщённым, с большим количеством уровней абстракции, что затрудняет его понимание и расширение.

💡 Основные концепции:
- Чрезмерная абстракция:
1. Слишком много уровней абстракции и обобщений.
2. Трудности в понимании и расширении системы.

❗️Ключевые особенности:
- Усложнение расширения:
1. Трудности в добавлении новой функциональности.
2. Необходимость изменения множества классов для внесения изменений.

- Снижение производительности:
1. Увеличение накладных расходов на вызовы и управление абстракциями.
2. Усложнение отладки и тестирования.

🔍 Типичные сценарии возникновения:
1. Попытка создать универсальную систему для всех случаев.
2. Чрезмерное использование паттернов проектирования.
3. Отсутствие чёткого понимания требований системы.

🎯 Как избежать Anti-OCP:
- Используйте абстракции только там, где это необходимо.
- Следуйте принципу открытости/закрытости (OCP) с учётом конкретных требований.
- Упрощайте архитектуру для повышения её понятности и расширяемости.
👥 Anti-LCP: принцип непонятного наследования

Что такое Anti-LCP?
Anti-LCP (Anti-Liskov Substitution Principle) — это антипаттерн, при котором наследование используется неправильно, либо чрезмерно, либо отсутствует, что затрудняет понимание и поддержку кода.

💡 Основные концепции:
- Неправильное наследование:
1. Чрезмерное использование наследования.
2. Отсутствие наследования там, где оно необходимо.

❗️Ключевые особенности:
- Усложнение поддержки:
1. Трудности в понимании иерархии классов.
2. Увеличение времени на внесение изменений.

- Снижение гибкости:
1. Трудности в расширении и изменении функциональности.
2. Увеличение зависимости между классами.

🔍 Типичные сценарии возникновения:
1. Использование наследования вместо композиции.
2. Отсутствие наследования там, где оно уместно.
3. Неправильное понимание принципа подстановки Лисков (LSP).

🎯 Как избежать Anti-LCP:
- Используйте наследование только там, где это уместно.
- Рассмотрите возможность использования композиции вместо наследования.
- Следуйте принципу подстановки Лисков (LSP) с учётом контекста.
🌉 Мост (Bridge): разделение абстракции и реализации

Что такое паттерн Мост?
Мост (Bridge) — это структурный паттерн проектирования, который разделяет абстракцию и реализацию, позволяя им изменяться независимо друг от друга.

💡 Основные концепции:
- Разделение абстракции и реализации:
1. Абстракция определяет интерфейс для клиента.
2. Реализация предоставляет конкретные классы для выполнения задач.

❗️Ключевые особенности:
- Гибкость:
1. Возможность изменения реализации без изменения абстракции.
2. Упрощение добавления новых реализаций.

- Уменьшение зависимости:
1. Снижение связанности между абстракцией и реализацией.
2. Улучшение модульности и поддержки кода.

🔍 Типичные сценарии использования:
1. Разработка кроссплатформенных приложений.
2. Управление несколькими реализациями одного интерфейса.
3. Упрощение изменения и расширения системы.

🎯 Преимущества использования Bridge:
- Уменьшение зависимости между абстракцией и реализацией.
- Упрощение изменения и расширения системы.
- Повышение гибкости и модульности кода.
🧩 Компоновщик (Composite): объединение объектов в древовидные структуры

Что такое паттерн Компоновщик?
Компоновщик (Composite) — это структурный паттерн, который позволяет создавать древовидные структуры объектов и работать с ними так, как будто это единичные объекты.

💡 Основные концепции:
- Древовидная структура:
1. Объекты могут быть как листьями, так и композитами, содержащими другие объекты.
2. Единый интерфейс для работы с объектами и композитами.

❗️Ключевые особенности:
- Единообразие:
1. Клиент работает с объектами и композитами через единый интерфейс.
2. Упрощение добавления новых типов объектов.

- Гибкость:
1. Возможность создания сложных иерархий объектов.
2. Упрощение управления древовидными структурами.

🔍 Типичные сценарии использования:
1. Работа с иерархическими структурами данных.
2. Управление графическими объектами в интерфейсах.
3. Реализация файловых систем и меню.

🎯 Преимущества использования Composite:
- Упрощение работы с древовидными структурами.
- Унификация интерфейса для объектов и композитов.
- Повышение гибкости и расширяемости системы.
🎨 Декоратор (Decorator): динамическое добавление поведения объектам

Что такое паттерн Декоратор?
Декоратор (Decorator) — это структурный паттерн, который позволяет динамически добавлять объектам новое поведение, оборачивая их в объекты-декораторы.

💡 Основные концепции:
- Оборачивание объектов:
1. Декораторы реализуют тот же интерфейс, что и декорируемые объекты.
2. Декораторы добавляют новое поведение, не изменяя код объектов.

❗️Ключевые особенности:
- Гибкость:
1. Возможность динамического добавления и удаления поведения.
2. Упрощение расширения функциональности объектов.

- Модульность:
1. Разделение ответственности между объектами и декораторами.
2. Улучшение поддержки и тестируемости кода.

🔍 Типичные сценарии использования:
1. Добавление нового поведения объектам без изменения их кода.
2. Реализация дополнительных функций, таких как логирование или кэширование.
3. Управление графическими объектами с дополнительными свойствами.

🎯 Преимущества использования Decorator:
- Упрощение добавления нового поведения объектам.
- Улучшение модульности и поддержки кода.
- Повышение гибкости и расширяемости системы.
🔗 Заместитель (Proxy): контроль доступа к объекту

Что такое паттерн Заместитель?
Заместитель (Proxy) — это структурный паттерн проектирования, который предоставляет заместитель объекта для контроля доступа к нему.

💡 Основные концепции:
- Контроль доступа:
1. Заместитель управляет доступом к основному объекту.
2. Возможность добавления дополнительной логики при обращении к объекту.

❗️Ключевые особенности:
- Ленивая инициализация:
1. Отложенная загрузка ресурсов до момента их фактического использования.
2. Улучшение производительности за счёт экономии ресурсов.

- Защита и безопасность:
1. Контроль доступа и проверка прав.
2. Логирование и мониторинг обращений к объекту.

🔍 Типичные сценарии использования:
1. Управление доступом к ресурсам (файлы, базы данных).
2. Реализация ленивой инициализации объектов.
3. Добавление уровня безопасности при работе с объектами.

🎯 Преимущества использования Proxy:
- Улучшение производительности за счёт ленивой инициализации.
- Повышение безопасности и контроля доступа.
- Упрощение добавления дополнительной логики при обращении к объекту.
🔌 Адаптер (Adapter): совместимость интерфейсов

Что такое паттерн Адаптер?
Адаптер (Adapter) — это структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе, преобразуя интерфейс одного класса в интерфейс, ожидаемый клиентом.

💡 Основные концепции:
- Совместимость интерфейсов:
1. Адаптер преобразует вызовы одного интерфейса в вызовы другого.
2. Обеспечение совместимости между несовместимыми классами.

❗️Ключевые особенности:
- Гибкость:
1. Возможность интеграции сторонних библиотек и компонентов.
2. Упрощение изменения и расширения системы.

- Повторное использование:
1. Возможность использования существующих классов без их изменения.
2. Улучшение модульности и поддержки кода.

🔍 Типичные сценарии использования:
1. Интеграция сторонних библиотек с несовместимыми интерфейсами.
2. Использование старых классов с новыми интерфейсами.
3. Обеспечение совместимости между различными версиями системы.

🎯 Преимущества использования Adapter:
- Упрощение интеграции несовместимых компонентов.
- Улучшение модульности и поддержки кода.
- Повышение гибкости и расширяемости системы.
🌊 Object Pool: повторное использование объектов

Что такое паттерн Object Pool?
Object Pool — это поведенческий паттерн, который управляет пулом объектов, позволяя их повторное использование вместо создания новых, что улучшает производительность приложений.

💡 Основные концепции:
- Пул объектов:
1. Хранение и управление объектами для их повторного использования.
2. Уменьшение накладных расходов на создание и уничтожение объектов.

❗️Ключевые особенности:
- Повышение производительности:
1. Уменьшение времени на создание объектов.
2. Снижение нагрузки на систему сборки мусора.

- Экономия ресурсов:
1. Уменьшение использования памяти и других ресурсов.
2. Улучшение масштабируемости приложений.

🔍 Типичные сценарии использования:
1. Управление соединениями с базой данных.
2. Работа с потоками и сетевыми ресурсами.
3. Оптимизация производительности высоконагруженных приложений.

🎯 Преимущества использования Object Pool:
- Улучшение производительности за счёт повторного использования объектов.
- Экономия ресурсов и улучшение масштабируемости.
- Уменьшение нагрузки на систему сборки мусора.
🔍 Указатели на элементы map в Go

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

💡 Почему нельзя:

1. Изменяемая структура:
- Описание: Внутренняя структура map может изменяться, например, при добавлении или удалении элементов, что может привести к перераспределению памяти.
- Преимущества: Позволяет map эффективно управлять памятью.
- Недостатки: Указатели на элементы могут стать недействительными.

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

🔍 Как работать с элементами map:
1. Используйте ключи для доступа к элементам map.
2. Если нужно сохранить ссылку на элемент, рассмотрите возможность использования других структур данных, таких как слайсы, если это подходит для вашей задачи.
🔍 Режим транзакции по умолчанию в PostgreSQL

Какой режим транзакции по умолчанию в PostgreSQL?
В PostgreSQL режим транзакции по умолчанию — это READ COMMITTED. Это означает, что каждая транзакция видит только те данные, которые были зафиксированы (committed) до её начала.

💡 Основные характеристики READ COMMITTED:

1. Изоляция:
- Описание: Транзакции изолированы друг от друга. Изменения, сделанные одной транзакцией, не видны другим транзакциям до тех пор, пока они не будут зафиксированы.
- Преимущества: Обеспечение целостности данных.
- Недостатки: Возможны конфликты при одновременном доступе к данным.

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

Что такое PgBouncer?
PgBouncer — это лёгкий и быстрый пул соединений для PostgreSQL. Он управляет соединениями с базой данных, позволяя повторно использовать их и уменьшая нагрузку на сервер базы данных.

💡 Основные функции PgBouncer:

1. Пул соединений:
- Описание: Повторное использование соединений для уменьшения накладных расходов на их создание и закрытие.
- Преимущества: Снижение нагрузки на сервер базы данных.
- Недостатки: Необходимость настройки и управления пулом соединений.

2. Управление нагрузкой:
- Описание: Ограничение количества одновременных соединений с базой данных.
- Преимущества: Предотвращение перегрузки сервера базы данных.
- Недостатки: Возможные задержки при большом количестве запросов.

3. Поддержка транзакций:
- Описание: Возможность работать в режиме транзакций, сессий или выполнения отдельных запросов.
- Преимущества: Гибкость в управлении соединениями.
- Недостатки: Сложность настройки для различных режимов работы.

🎯 Преимущества использования PgBouncer:
- Уменьшение нагрузки на сервер базы данных.
- Повышение производительности приложений за счёт повторного использования соединений.
- Упрощение управления соединениями и нагрузкой.
🔧 Императивное программирование и Go

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

💡 Основные концепции:
- Управление потоком:
1. Использование циклов, условий и функций для управления выполнением программы.
2. Изменение состояния переменных и структур данных.

❗️Ключевые особенности:
- Простота и ясность:
1. Чёткое описание последовательности действий.
2. Легкость отладки и тестирования.

- Гибкость:
1. Возможность точного контроля над выполнением программы.
2. Поддержка сложных алгоритмов и логики.

🔍 Типичные сценарии использования:
1. Реализация алгоритмов и бизнес-логики.
2. Работа с данными и их преобразование.
3. Управление ресурсами и состоянием приложения.

🎯 Преимущества использования императивного программирования в Go:
- Чёткость и предсказуемость выполнения программы.
- Высокая производительность и эффективность.
- Поддержка сложных вычислений и алгоритмов.
👍2019🔥15
📜 Декларативное программирование и SQL

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

💡 Основные концепции:
- Описание результата:
1. Фокус на том, какие данные нужно получить, а не на том, как их получить.
2. Использование запросов для описания операций над данными.

❗️Ключевые особенности:
- Удобство и лаконичность:
1. Простота написания и чтения запросов.
2. Снижение вероятности ошибок за счёт абстракции.

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

🔍 Типичные сценарии использования:
1. Выполнение запросов к базам данных для получения и обработки данных.
2. Определение правил и ограничений для данных.
3. Анализ и отчётность на основе данных.

🎯 Преимущества использования декларативного программирования в SQL:
- Удобство и лаконичность написания запросов.
- Автоматическая оптимизация и повышение производительности.
- Снижение вероятности ошибок за счёт абстракции.
18👍18🔥17
🔍 Линтеры и Go: обеспечение качества кода

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

💡 Основные концепции:
- Анализ кода:
1. Проверка синтаксиса и стиля кода.
2. Обнаружение потенциальных ошибок и уязвимостей.

❗️Ключевые особенности:
- Повышение качества кода:
1. Раннее выявление ошибок и проблем.
2. Обеспечение соблюдения стандартов и лучших практик.

- Интеграция в процесс разработки:
1. Возможность использования в CI/CD пайплайнах.
2. Поддержка различных IDE и редакторов кода.

🔍 Типичные сценарии использования:
1. Проверка кода перед коммитом в систему контроля версий.
2. Обеспечение соблюдения стандартов кодирования в команде.
3. Автоматизация проверок кода в процессе разработки.

🎯 Преимущества использования линтеров в Go:
- Повышение качества и надёжности кода.
- Раннее выявление ошибок и проблем.
- Обеспечение соблюдения стандартов и лучших практик.
🔥24👍1910
🔍 Виды валидации кэша

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

💡 Основные виды валидации кэша:

1. Cache-Aside (Lazy Loading):
- Описание: Данные загружаются в кэш только при первом запросе.
- Преимущества: Уменьшение нагрузки на источник данных, так как данные кэшируются только по запросу.
- Недостатки: Возможны задержки при первом запросе.

2. Write-Through:
- Описание: Данные записываются в кэш и основное хранилище одновременно.
- Преимущества: Гарантирует, что данные в кэше всегда актуальны.
- Недостатки: Увеличение времени записи из-за необходимости обновления двух источников.

3. Write-Behind (Write-Back):
- Описание: Данные сначала записываются в кэш, а затем асинхронно обновляются в основном хранилище.
- Преимущества: Ускорение записи за счёт асинхронного обновления.
- Недостатки: Риск потери данных в случае сбоя.

4. Cache-Aside with TTL (Time-To-Live):
- Описание: Данные хранятся в кэше в течение определённого времени, после чего считаются недействительными.
- Преимущества: Простота реализации и управления.
- Недостатки: Возможность получения устаревших данных, если TTL слишком велик.

🔍 Типичные сценарии использования:
1. Ускорение доступа к часто запрашиваемым данным.
2. Снижение нагрузки на основное хранилище.
3. Обеспечение актуальности данных в распределённых системах.

🎯 Преимущества использования валидации кэша:
- Улучшение производительности приложений.
- Снижение нагрузки на основное хранилище.
- Обеспечение актуальности данных.
20🔥19👍16
🧪 Юнит-тесты: основа надежного кода

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

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

- Повторяемость:
1. Возможность многократного выполнения тестов с одинаковым результатом.
2. Автоматизация тестирования в процессе разработки.

❗️Ключевые особенности:
- Раннее выявление ошибок:
1. Обнаружение проблем на этапе написания кода.
2. Уменьшение времени на отладку и исправление ошибок.

- Поддержка рефакторинга:
1. Уверенность в том, что изменения не нарушают существующую функциональность.
2. Упрощение улучшения и оптимизации кода.

🔍 Типичные сценарии использования:
1. Проверка корректности работы отдельных функций и методов.
2. Обеспечение надёжности кода при внесении изменений.
3. Автоматизация тестирования в CI/CD пайплайнах.

🎯 Преимущества использования юнит-тестов:
- Раннее выявление ошибок и улучшение качества кода.
- Поддержка рефакторинга и улучшения кода.
- Автоматизация тестирования и повышение надёжности приложения.
🔥2116👍16
🤖 Авто-тесты: обеспечение качества на каждом этапе

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

💡 Основные концепции:
- Автоматизация:
1. Выполнение тестов без участия человека.
2. Интеграция в процессы разработки и сборки.

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

❗️Ключевые особенности:
- Быстрота и эффективность:
1. Выполнение тестов быстрее, чем вручную.
2. Возможность проверки большого объёма функциональности.

- Повторяемость и надёжность:
1. Одинаковые результаты при каждом запуске.
2. Уменьшение вероятности человеческих ошибок.

🔍 Типичные сценарии использования:
1. Проверка функциональности приложения на различных этапах разработки.
2. Регрессионное тестирование для выявления новых ошибок.
3. Интеграция в CI/CD пайплайны для автоматического выполнения тестов.

🎯 Преимущества использования авто-тестов:
- Ускорение процесса тестирования и повышение его эффективности.
- Обеспечение высокого уровня качества и надёжности кода.
- Уменьшение ручного труда и вероятности ошибок.
25👍17🔥12