Собственные исключения в Java: как управлять ошибками в вашем приложении
Исключения являются важной частью любой программы, позволяя элегантно обрабатывать ошибки и исключительные ситуации. В Java, помимо стандартных исключений, разработчики также могут создавать собственные исключения, которые отражают специфические ситуации в их приложениях или библиотеках. В этой статье мы рассмотрим, для чего нужны собственные исключения в Java и как их использовать.
Статья может быть полезна для новичков или тем, кто не так давно в мире Java. В ней мы не касаемся типов исключений, а рассматриваем создание собственных.
Зачем нужны собственные исключения?
Этот вопрос может возникнуть у тех кто только входит в ИТ, и вопрос даже вполне корректный, ведь имеется масса своих исключений, но собственные исключения позволяют разработчикам более гибко управлять ошибками и обрабатывать их в соответствии с требованиями конкретного приложения. Вот несколько причин, по которым собственные исключения в Java могут быть полезны:
1. Отражение специфических ситуаций: Собственные исключения могут быть созданы для отображения конкретных ошибок или ситуаций, которые могут возникнуть в приложении. Например, вы можете создать исключение InvalidInputException , чтобы указать, что получен недопустимый ввод от пользователя.
2. Локализация и обработка ошибок: Собственные исключения позволяют локализовать и обрабатывать ошибки на определенном уровне в коде. Каждое исключение может быть обработано отдельно, что упрощает отладку и обработку ошибок.
3. Гибкая обработка и восстановление: При использовании собственных исключений вы можете предоставить гибкую обработку ошибок, включая восстановление после ошибки или выполнение альтернативных действий. Например, вы можете перехватить исключение FileNotFoundException и попробовать открыть альтернативный файл.
4. Инкапсуляция ошибок: Собственные исключения позволяют инкапсулировать информацию об ошибке и передавать ее на более высокий уровень в коде. Это помогает упростить код и улучшить его читаемость, так как обработка ошибок может быть сосредоточена в одном месте.
5. Расширение стандартных исключений: Собственные исключения могут расширять стандартные исключения Java, такие как Exception или RuntimeException , чтобы добавить дополнительную информацию или функциональность при обработке ошибок. Например, вы можете создать исключение DatabaseException , которое расширяет Exception и содержит дополнительные методы для работы с базой данных.
Примеры создания собственных исключений
Пример 1
Для создания собственного исключения в Java необходимо создать класс, который расширяет стандартный класс исключений или другое собственное исключение.
public class InvalidInputException extends Exception < public InvalidInputException(String message) < super(message); >>
В этом примере класс InvalidInputException расширяет класс Exception и имеет конструктор, который принимает сообщение об ошибке в качестве параметра. В конструкторе вызывается конструктор суперкласса Exception , передавая ему сообщение.
Теперь, когда у вас есть собственное исключение, вы можете использовать его в своем коде для обработки конкретных ошибок.
public class Example < public static void main(String[] args) < try < validateInput("abc"); >catch (InvalidInputException e) < System.out.println("Invalid input: " + e.getMessage()); >> public static void validateInput(String input) throws InvalidInputException < if (input == null || input.isEmpty()) < throw new InvalidInputException("Input cannot be null or empty"); >> >
В этом примере метод validateInput() проверяет, является ли входная строка пустой или равной null . Если это так, то метод выбрасывает исключение InvalidInputException с сообщением. В методе main() мы вызываем validateInput() с недопустимым вводом и перехватываем исключение с помощью блока catch , где выводим сообщение об ошибке.
Пример 2
// Создаем собственное исключение class CustomException extends Exception < public CustomException(String message) < super(message); >> // Класс, который использует собственное исключение class Example < // Метод, который может выбросить собственное исключение public static void doSomething(int value) throws CustomException < if (value < 0) < throw new CustomException("Значение должно быть больше или равно нулю"); >> public static void main(String[] args) < try < // Вызываем метод, который может выбросить исключение doSomething(-5); >catch (CustomException e) < // Обрабатываем исключение System.out.println("Ошибка: " + e.getMessage()); >> >
В этом примере у нас есть метод doSomething , который может выбросить собственное исключение CustomException , если значение аргумента меньше нуля. В методе main мы вызываем этот метод с отрицательным значением и перехватываем исключение с помощью блока catch . Затем мы выводим сообщение об ошибке.
Этот пример показывает, как правильно объявлять методы, которые могут выбрасывать собственные исключения, и как перехватывать их при вызове этих методов
Пример 3
// Создаем собственное исключение class InvalidEmailException extends Exception < public InvalidEmailException(String message) < super(message); >> // Класс, который использует собственное исключение class EmailValidator < // Метод, который может выбросить собственное исключение public static void validateEmail(String email) throws InvalidEmailException < if (!email.contains("@")) < throw new InvalidEmailException("Некорректный адрес электронной почты"); >> public static void main(String[] args) < try < // Вызываем метод, который может выбросить исключение validateEmail("example.com"); >catch (InvalidEmailException e) < // Обрабатываем исключение System.out.println("Ошибка: " + e.getMessage()); >> >
В этом примере у нас есть метод validateEmail , который может выбросить собственное исключение InvalidEmailException , если переданный адрес электронной почты не содержит символа «@». В конструкторе исключения мы передаем сообщение об ошибке. В методе main мы вызываем этот метод с некорректным адресом электронной почты и перехватываем исключение с помощью блока catch . Затем мы выводим сообщение об ошибке.
Вот вроде бы и все, достаточно легко и просто, как всегда, есть одно большое но! можно создать исключения, которые только усложнят жизнь разработку
Пример 4 (некорректное создание исключений)
// Некорректное создание собственного исключения class MyException extends Exception < public MyException() < super(); >>
В этом примере мы создаем собственное исключение MyException , но не передаем ему сообщение об ошибке через конструктор. Это может привести к непонятным или неинформативным сообщениям об ошибках при перехвате исключения.
Корректным подходом будет передача сообщения об ошибке через конструктор, как показано в предыдущем примере:
class MyException extends Exception < public MyException(String message) < super(message); >>
Таким образом, при выбрасывании и перехвате исключения MyException будет доступно информативное сообщение об ошибке.
Пример 5 (некорректное использование)
Примером неправильного использования собственного исключения может быть его применение без реальной необходимости или создание избыточных иерархий исключений.
public class MyCustomException extends Exception < // . >public class Example < public static void main(String[] args) < try < throw new MyCustomException(); >catch (MyCustomException e) < System.out.println("Caught MyCustomException"); >catch (Exception e) < System.out.println("Caught Exception"); >> >
В этом примере мы создаем собственное исключение MyCustomException , но в блоке catch обрабатываем его так же, как и любое другое исключение Exception . Такое использование собственного исключения делает его бессмысленным, поскольку нет никакой разницы между обработкой MyCustomException и обработкой любого другого исключения.
Инструмент создания собственных исключений может иметь как плюсы, так и минусы, поэтому подход к его использованию должен быть обоснован архитектурой кода.
Плюсы создания собственных исключений:
1. Отражение специфических ситуаций: Собственные исключения позволяют точно отражать конкретные ошибки или ситуации, которые могут возникнуть в вашем приложении. Это упрощает понимание и обработку ошибок другими разработчиками, которые работают с вашим кодом.
2. Гибкая обработка и восстановление: Использование собственных исключений позволяет предоставить гибкую обработку ошибок, включая восстановление после ошибки или выполнение альтернативных действий. Это помогает повысить надежность и стабильность вашего приложения.
3. Инкапсуляция информации об ошибке: Собственные исключения позволяют инкапсулировать информацию об ошибке, включая дополнительные детали или контекст ошибки. Это помогает упростить код и улучшить его читаемость, так как обработка ошибок может быть сосредоточена в одном месте.
4. Расширение стандартных исключений: Вы можете расширять стандартные исключения Java, такие как Exception или RuntimeException , с добавлением дополнительной функциональности или информации для обработки ошибок. Это позволяет вам более точно определить тип ошибки и предоставить дополнительные методы для работы с ней.
Минусы создания собственных исключений:
1. Усложнение кода: Создание собственных исключений может привести к усложнению кода, особенно если их необходимо обрабатывать на разных уровнях в приложении. Слишком много разных исключений может сделать код сложным для чтения и понимания.
2. Переусиление иерархии исключений: Если неправильно спроектировать иерархию собственных исключений, это может привести к избыточности или неправильной обработке ошибок. Следует тщательно продумать, какие исключения создавать и как они связаны между собой.
3. Неоднозначность исключений: Если вы создаете собственные исключения, которые перекрывают или имеют схожие имена с существующими стандартными исключениями, это может вызывать путаницу при обработке ошибок. Разработчики могут испытывать трудности в определении, какое исключение выбрать для обработки конкретной ситуации.
4. Проблемы совместимости: Если вы используете собственные исключения в библиотеке или API, это может создать проблемы совместимости, если другие разработчики не знакомы с вашими исключениями или не знают, как с ними работать.
Заключение
Собственные исключения в Java предоставляют разработчикам возможность более гибко управлять ошибками и исключительными ситуациями в своих приложениях или библиотеках. Они позволяют отразить специфические ситуации, локализовать и обработать ошибки, предоставить гибкую обработку и восстановление, инкапсулировать ошибки и расширить стандартные исключения.
9 лучших практик для обработки исключений в Java
Независимо от того, новичок вы или профессионал, всегда полезно освежить в памяти методы обработки исключений, чтобы убедиться, что вы и ваша команда можете справиться с проблемами.
Обработка исключений в Java — непростая тема. Новичкам сложно понять, и даже опытные разработчики могут часами обсуждать, как и какие исключения следует создавать или обрабатывать.
Вот почему у большинства команд разработчиков есть собственный набор правил их использования. И если вы новичок в команде, вас может удивить, насколько эти правила могут отличаться от тех, которые вы использовали раньше.
Тем не менее, есть несколько передовых практик, которые используются большинством команд. Вот 9 самых важных из них, которые помогут вам начать работу или улучшить обработку исключений.
1. Освободите ресурсы в блоке finally или используйте инструкцию «Try-With-Resource»
Довольно часто вы используете ресурс в своем блоке try, например InputStream, который вам нужно закрыть позже. Распространенной ошибкой в таких ситуациях является закрытие ресурса в конце блока try.
public void doNotCloseResourceInTry() < FileInputStream inputStream = null; try < File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // используем inputStream для чтения файла // не делайте этого inputStream.close(); >catch (FileNotFoundException e) < log.error(e); >catch (IOException e) < log.error(e); >>
Проблема в том, что этот подход работает отлично до тех пор, пока не генерируется исключение. Все операторы в блоке try будут выполнены, и ресурс будет закрыт.
Но вы не зря добавили блок try. Вы вызываете один или несколько методов, которые могут вызвать исключение, или, может быть, вы сами вызываете исключение. Это означает, что вы можете не дойти до конца блока try. И как следствие, вы не закроете ресурсы.
Поэтому вам следует поместить весь код очистки в блок finally или использовать оператор try-with-resource.
Используйте блок Finally
В отличие от последних нескольких строк вашего блока try, блок finally всегда выполняется. Это происходит либо после успешного выполнения блока try, либо после обработки исключения в блоке catch. Благодаря этому вы можете быть уверены, что освободите все захваченные ресурсы.
public void closeResourceInFinally() < FileInputStream inputStream = null; try < File file = new File("./tmp.txt"); inputStream = new FileInputStream(file); // используем inputStream для чтения файла >catch (FileNotFoundException e) < log.error(e); >finally < if (inputStream != null) < try < inputStream.close(); >catch (IOException e) < log.error(e); >> > >
Оператор Java 7 «Try-With-Resource»
Другой вариант — это оператор try-with-resource, который я объяснил более подробно во введении в обработку исключений Java.
Вы можете использовать его, если ваш ресурс реализует интерфейс AutoCloseable. Это то, что делает большинство стандартных ресурсов Java. Когда вы открываете ресурс в предложении try, он автоматически закрывается после выполнения блока try или обработки исключения.
public void automaticallyCloseResource() < File file = new File("./tmp.txt"); try (FileInputStream inputStream = new FileInputStream(file);) < // используем inputStream для чтения файла >catch (FileNotFoundException e) < log.error(e); >catch (IOException e) < log.error(e); >>
2. Конкретные исключения предпочтительнее
Чем конкретнее исключение, которое вы генерируете, тем лучше. Всегда помните, что коллеге, который не знает вашего кода, а может быть, и вам через несколько месяцев, необходимо вызвать ваш метод и обработать исключение.
Поэтому постарайтесь предоставить им как можно больше информации. Это упрощает понимание вашего API. В результате вызывающий ваш метод сможет лучше обработать исключение или избежать его с помощью дополнительной проверки.
Поэтому всегда старайтесь найти класс, который лучше всего подходит для вашего исключительного события, например, генерируйте NumberFormatException вместо IllegalArgumentException. И избегайте создания неспецифического исключения.
public void doNotDoThis() throws Exception < . >public void doThis() throws NumberFormatException
3. Документируйте определенные вами исключения
Каждый раз, когда вы определяете исключение в сигнатуре вашего метода, вы также должны задокументировать его в своем Javadoc. Это преследует ту же цель, что и предыдущая передовая практика: предоставить вызывающему как можно больше информации, чтобы он мог избежать или обработать исключение.
Итак, не забудьте добавить объявление @throws в свой Javadoc и описать ситуации, которые могут вызвать исключение.
/** * Этот метод делает что-то чрезвычайно полезное . * * @param input * @throws MyBusinessException, если . происходит */ public void doSomething(String input) throws MyBusinessException
4. Генерирование исключений с описательными сообщениями
Идея, лежащая в основе этой передовой практики, аналогична двум предыдущим. Но на этот раз вы не предоставляете информацию вызывающей стороне вашего метода. Сообщение об исключении читают все, кто должен понимать, что произошло, когда исключение было зарегистрировано в файле журнала или в вашем инструменте мониторинга.
Следовательно, он должен как можно точнее описать проблему и предоставить наиболее актуальную информацию для понимания исключительного события.
Не поймите меня неправильно; вы не должны писать абзац текста. Но вам следует объяснить причину исключения в 1-2 коротких предложениях. Это помогает вашей группе эксплуатации понять серьезность проблемы, а также упрощает анализ любых инцидентов, связанных с обслуживанием.
Если вы выберете конкретное исключение, его имя класса, скорее всего, уже будет описывать тип ошибки. Таким образом, вам не нужно предоставлять много дополнительной информации. Хорошим примером этого является NumberFormatException. Оно вызывается конструктором класса java.lang.Long, когда вы предоставляете String в неправильном формате.
try < new Long("xyz"); >catch (NumberFormatException e)
Название класса NumberFormatException уже говорит вам о типе проблемы. Его сообщение должно содержать только строку ввода, которая вызвала проблему. Если имя класса исключения не так выразительно, вам необходимо предоставить необходимую информацию в сообщении.
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5. Сначала перехватите наиболее конкретное исключение
Большинство IDE помогут вам в этой лучшей практике. Они сообщают о недостижимом блоке кода, когда вы сначала пытаетесь перехватить менее конкретное исключение.
Проблема в том, что выполняется только первый блок catch, соответствующий исключению. Итак, если вы сначала поймаете IllegalArgumentException, вы никогда не достигнете блока catch, который должен обрабатывать более конкретное NumberFormatException, потому что это подкласс IllegalArgumentException.
Всегда сначала перехватывайте наиболее конкретный класс исключения и добавляйте менее конкретные блоки перехвата в конец вашего списка.
Пример такого оператора try-catch представлен в следующем фрагменте кода. Первый блок catch обрабатывает все NumberFormatException, а второй — все IllegalArgumentException, которые не являются NumberFormatException.
public void catchMostSpecificExceptionFirst() < try < doSomething("Сообщение"); >catch (NumberFormatException e) < log.error(e); >catch (IllegalArgumentException e) < log.error(e) >>
6. Не перехватывайте Throwable
Throwable — это суперкласс всех исключений и ошибок. Вы можете использовать его в предложении catch, но никогда не должны этого делать!
Если вы используете Throwable в предложении catch, он не только перехватит все исключения; он также перехватит все ошибки. JVM выдает ошибки, чтобы указать на серьезные проблемы, которые не предназначены для обработки приложением. Типичными примерами этого являются OutOfMemoryError или StackOverflowError. И то, и другое вызвано ситуациями, которые находятся вне контроля приложения и не могут быть обработаны.
Итак, лучше не перехватывайте Throwable, если вы не абсолютно уверены, что находитесь в исключительной ситуации, в которой вы можете или обязаны обрабатывать ошибку.
public void doNotCatchThrowable() < try < // делает что-нибудь >catch (Throwable t) < // не делает этого! >>
7. Не игнорируйте исключения
Вы когда-нибудь анализировали отчет об ошибке, в котором выполнялась только первая часть вашего сценария использования?
Часто это вызвано игнорируемым исключением. Разработчик, вероятно, был уверен, что оно никогда не будет вызвано, и добавил блок catch, который не обрабатывает и не регистрирует его. И когда вы найдете этот блок, вы, скорее всего, даже найдете один из известных комментариев «Этого никогда не будет».
public void doNotIgnoreExceptions() < try < // делает что-нибудь >catch (NumberFormatException e) < // это никогда не выполнится >>
Что ж, возможно, вы анализируете проблему, в которой произошло невозможное.
Поэтому, пожалуйста, никогда не игнорируйте исключения. Вы не знаете, как код изменится в будущем. Кто-то может удалить проверку, которая предотвратила исключительное событие, не осознавая, что это создает проблему. Или код, который генерирует исключение, изменяется и теперь генерирует несколько исключений одного и того же класса, а вызывающий код не предотвращает их все.
Вы должны хотя бы написать сообщение в журнале, сообщающее всем, что произошло немыслимое и что кто-то должен это проверить.
public void logAnException() < try < // делает что-нибудь >catch (NumberFormatException e) < log.error("Это никогда не должно происходить: " + e); >>
8. Не пишите в лог сгенерированные исключения
Это, вероятно, наиболее часто игнорируемая передовая практика в списке. Вы можете найти множество фрагментов кода и даже библиотек, в которых исключение перехватывается, регистрируется и повторно генерируется.
try < new Long("xyz"); >catch (NumberFormatException e)
Может показаться интуитивно понятным регистрировать исключение, когда оно произошло, а затем повторно генерировать его, чтобы вызывающий мог обработать его соответствующим образом. Но, в таком случае, приложение будет писать в лог несколько сообщений об ошибках для одного и того же исключения.
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz" Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.(Long.java:965) at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63) at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
Повторные сообщения также не добавляют никакой информации. Как объясняется в лучшей практике №4, сообщение об исключении должно описывать исключительное событие. А трассировка стека сообщает вам, в каком классе, методе и строке было сгенерировано исключение.
Если вам нужно добавить дополнительную информацию, вы должны перехватить исключение и обернуть его в пользовательское. Но обязательно следуйте передовой практике номер 9.
public void wrapException(String input) throws MyBusinessException < try < // делает что-нибудь >catch (NumberFormatException e) < throw new MyBusinessException("Сообщение с описанием ошибки.", e); >>
Итак, перехватывайте исключение, только если вы хотите его обработать. В противном случае укажите это в сигнатуре метода и позвольте вызывающей стороне позаботиться об этом.
9. Оберните исключение, не обрабатывая его
Иногда лучше поймать стандартное исключение и превратить его в настраиваемое. Типичным примером такого исключения является бизнес-исключение для конкретного приложения или платформы. Это позволяет вам добавлять дополнительную информацию, а также вы можете реализовать специальную обработку для вашего класса исключения.
Когда вы это сделаете, обязательно установите исходное исключение в качестве причины. Класс Exception предоставляет определенные методы конструктора, которые принимают Throwable в качестве параметра. В противном случае вы потеряете трассировку стека и сообщение об исходном исключении, что затруднит анализ исключительного события, вызвавшего ваше исключение.
public void wrapException(String input) throws MyBusinessException < try < // делает что-нибудь >catch (NumberFormatException e) < throw new MyBusinessException("Сообщение с описанием ошибки.", e); >>
Резюме
Как вы видели, есть много разных вещей, которые вы должны учитывать, когда генерируете или перехватываете исключение. Большинство из них имеют цель улучшить читаемость вашего кода или удобство использования вашего API.
Чаще всего исключения являются одновременно механизмом обработки ошибок и средством связи. Поэтому вам следует обязательно обсудить передовые практики и правила, которые вы хотите применять, со своими коллегами, чтобы все понимали общие концепции и использовали их одинаково.
Как вызвать исключение в java
правильно ли понимаю, что когда я работаю с проектом, в котором есть несколько потоков исполнения, может быть вот такая ситуация. Один из этих потоков запускается и завершается успешно, а затем выбрасывает исключение внутри блока try-catch. Оставшиеся потоки исполнения продолжают свою работу, но никакой код в блоке finally не выполняется. Тогда блок finally при обработке исключений не будет выполнен?
Иван Уровень 23
29 мая 2023
Отличная статья! Большое Вам спасибо за разъяснение!
Tural Уровень 13
16 мая 2023
Отлавливают ли Error ? в OCS пишется что нельзя их отлавливать
Dok3R73 Уровень 42
26 марта 2023
Спасибо за труды, очень доступно написано
Art Sch Уровень 13
11 января 2023
я читаю про исключения на 1м и в принципе понимаю, но не очень. ps: зачем только я начал с java core. pss: если вы это читаете, и я до сих пор на первом, то либо я прохожу другой курс, либо читаю книгу по джаве, параллельно проходя этот курс, либо решил взять перерыв на неопределенный срок времени. никогда не сдамся)
Уровень 23
6 ноября 2021
Есть подозрение, что так будет правильнее.
Mark Vladimirovich Уровень 16
25 марта 2021
обращу внимание на некоторую неточность. цитата «Создание исключения При исполнении программы исключение генерируется JVM или вручную, с помощью оператора throw» в java исключения это тоже объекты поэтому создается исключение так же как объект new Exception. а бросается в программе с помощью оператора throw. обычно эти операции объединяют в одну throw new Exception(«aaa»);
19 сентября 2020
если что я пишу это с 3 уровня. Под конец лекций я читал статью про бафридер, после нашел там ссылку на потоки вводов, а потом чтобы понять что там говориться ввел гугл про исключение и нашел эту статью, спасибо автору, это статья очень помогла. PS если ты читаешь этот комментарий и видишь что у меня нет прогресса(то есть если я все еще на 3 уровне или чуточку больше), то скажи мне, что я нуб и не дошел до 40 лвла
Как вызвать исключение в java
можно Вас попросить побольше писать статей? так понятно еще не было)) и книгу читала и другие статьи — только еще больше путаницы. спасибо!
Сергей Уровень 28
19 октября 2023
Отлично! Разложил все по полочкам, лучше чем в курсе.
ElenaN Уровень 26
19 октября 2023
Эта лекция написана намного лучше, чем та, что в курсе, все просто и четко объяснено! Спасибо автору!
Denis Gritsay Уровень 23
2 октября 2023
Anton Voskresensky Уровень 13
6 сентября 2023
Хотелось бы дополнения к статье в виде try-with-resources
Dmitry Vidonov Уровень 29 Expert
16 августа 2023
Спасибо за статью! Наконец-то дошло, как работает механизм «пробросов». В обычных лекциях про это упоминалось, но тогда было ничего непонятно.
conquEror Уровень 21
29 июля 2023
Теория разобрана лучше чем в курсе
Alexander Rozenberg Уровень 28
20 июля 2023
Артем Уровень 29 Expert
17 июля 2023
Реюят, я правильно понимаю, что для непроверяемыхх исключений, пробрасывание наверх выглядит вот так: throw new ArithmeticExeption; А для проверяемых через сигнатуру метода? но для проверяемых можно также пробросить с помощью throw new?
Сообщество
JavaRush — это интерактивный онлайн-курс по изучению Java-программирования c нуля. Он содержит 1200 практических задач с проверкой решения в один клик, необходимый минимум теории по основам Java и мотивирующие фишки, которые помогут пройти курс до конца: игры, опросы, интересные проекты и статьи об эффективном обучении и карьере Java‑девелопера.
Подписывайтесь
Язык интерфейса
«Программистами не рождаются» © 2023 JavaRush
Скачивайте наши приложения
«Программистами не рождаются» © 2023 JavaRush
Этот веб-сайт использует данные cookie, чтобы настроить персонально под вас работу сервиса. Используя веб-сайт, вы даете согласие на применение данных cookie. Больше подробностей — в нашем Пользовательском соглашении.
