Java | Фишки и трюки
7.21K subscribers
182 photos
29 videos
6 files
40 links
Java: примеры кода, интересные фишки и полезные трюки

Купить рекламу: https://telega.in/c/java_tips_and_tricks

✍️По всем вопросам: @Pascal4eg
Download Telegram
☕️Использование аспектно-ориентированного программирования с помощью фреймворка AspectJ

Данная фишка используется для реализации перехватов событий и внедрения дополнительной логики в код без изменения его основной логики.

Пример кода:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;

@Aspect
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void beforeServiceMethod(JoinPoint joinPoint) {
System.out.println("Логирование: Вызов метода " + joinPoint.getSignature().getName());
}
}
🔈В этом примере мы создаем аспект LoggingAspect, который использует фреймворк AspectJ для перехвата вызовов методов в пакете com.example.service. С помощью аспектно-ориентированного программирования мы можем внедрять логику логирования или другие операции в код без изменения самого кода, улучшая его модульность и поддерживаемость.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥21👨‍💻1
☕️Использование трассировки стека:

Отлов багов – это, возможно, самая трудоемкая составляющая процесса разработки на Java. Трассировка стека позволяет отследить, в каком именно месте проекта было выброшено исключение.

public class StackTraceExample {

public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace(); // Вывод трассировки стека
}
}

public static void method1() {
method2();
}

public static void method2() {
method3();
}

public static void method3() {
throw new RuntimeException("Custom exception message");
}
}
🔈В этом примере показано как генерировать и обрабатывать трассировку стека в Java. Когда метод method3 вызывает исключение, это исключение перехватывается в методе main, и затем с помощью вызова e.printStackTrace() выводится трассировка стека, которая содержит информацию о вызове каждого метода от точки, где произошло исключение.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍42👏2
☕️Использование Java Reflection API для динамической инспекции и манипуляции с классами, методами, полями и другими элементами программы во время выполнения.

Java Reflection API позволяет получать информацию о классах, создавать экземпляры классов, вызывать методы, получать и устанавливать значения полей, а также работать с аннотациями и конструкторами.

Пример использования Java Reflection API:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class MyClass {
private String name;

public MyClass(String name) {
this.name = name;
}

public void printInfo() {
System.out.println("Name: " + name);
}
}

public class Main {
public static void main(String[] args) throws Exception {
MyClass obj = new MyClass("John");

// Получение класса
Class<?> clazz = obj.getClass();

// Получение и установка значения поля
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
System.out.println("Original value: " + field.get(obj));
field.set(obj, "Alice");
System.out.println("Updated value: " + field.get(obj));

// Вызов метода
Method method = clazz.getDeclaredMethod("printInfo");
method.invoke(obj);
}
}


🔔В этом примере используется Java Reflection API для получения доступа к полю name, изменения его значения, вызова метода printInfo и вывода информации об объекте класса MyClass. Reflection API предоставляет возможность обращаться к классам и их элементам динамически во время выполнения программы, что делает его мощным инструментом для реализации различных функций в профессиональной разработке на Java.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥1
☕️Использование аннотаций @SuppressWarnings

Аннотация @SuppressWarnings в Java используется для подавления предупреждений компилятора или IDE. Это может быть полезно, если вы уверены, что определенное предупреждение не является критическим и хотите, чтобы компилятор пропустил его. Однако следует использовать эту аннотацию осторожно, поскольку она может скрыть проблемы в коде.

Вот пример использования аннотации @SuppressWarnings:

public class DeprecatedExample {

@SuppressWarnings("deprecation")
public void useDeprecatedMethod() {
// Используем устаревший метод
DeprecatedClass deprecatedClass = new DeprecatedClass();
deprecatedClass.deprecatedMethod();
}

public static void main(String[] args) {
DeprecatedExample example = new DeprecatedExample();
example.useDeprecatedMethod();
}
}
✔️В данном примере метод useDeprecatedMethod() использует метод deprecatedMethod() из класса DeprecatedClass, который помечен аннотацией @Deprecated как устаревший. Аннотация @SuppressWarnings("deprecation") подавляет предупреждение компилятора о том, что используется устаревший метод, и позволяет успешно скомпилировать код без предупреждений.

Помните, что использование аннотации @SuppressWarnings должно быть обосновано, и важно следить за качеством кода, чтобы не упустить потенциальные проблемы в вашем приложении.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🎉1🤩1🍾1
😁27🤣9😱2👏1
☕️Использование библиотеки Lombok для автоматической генерации геттеров, сеттеров и конструкторов в Java.

Пример кода:

import lombok.Getter;
import lombok.Setter;

public class User {

@Getter @Setter
private String name;

@Getter @Setter
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}
}

⚙️С помощью аннотаций @Getter и @Setter от Lombok, не нужно писать многочисленные геттеры и сеттеры для каждого поля класса. Библиотека автоматически их сгенерирует во время компиляции. Это значительно упрощает процесс программирования и делает код более чистым и понятным.
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍3👏1
☕️Пример Java кода для реализации алгоритма быстрой сортировки


public class QuickSort {
// Метод для обмена элементов
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}

// Основной метод быстрой сортировки
public static void quickSort(int[] array, int low, int high) {
if (array == null || array.length == 0){
return;
}
if (low >= high){
return;
}

// Выбор опорного элемента (pivot)
int middle = low + (high - low) / 2;
int pivot = array[middle];

// Разделение на подмассивы
int i = low, j = high;
while (i <= j) {
while (array[i] < pivot) {
i++;
}
while (array[j] > pivot) {
j--;
}
if (i <= j) {
swap(array, i, j);
i++;
j--;
}
}

// Рекурсивные вызовы для подмассивов
if (low < j){
quickSort(array, low, j);
}
if (high > i){
quickSort(array, i, high);
}
}

// Тестирование алгоритма
public static void main(String[] args) {
int[] array = {9, -3, 5, 2, 6, 8, -6, 1, 3};
System.out.println("Исходный массив: " + Arrays.toString(array));
quickSort(array, 0, array.length - 1);
System.out.println("Отсортированный массив: " + Arrays.toString(array));
}
}


🔈 Реализация алгоритма включает в себя метод quickSort, осуществляющий сортировку массива чисел, включающий в себя выбор опорного элемента, разделение на подмассивы и рекурсивные вызовы. Также, есть метод swap для обмена элементов и метод main для тестирования алгоритма.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52🔥1👏1😁1
☕️Использование Optional.

🌐Optional - это класс, представляющий контейнер, который может содержать или не содержать значение. Он помогает избежать NullPointerException.

Пример:


List<String> names = Arrays.asList("Alice", "Bob", "Charlotte", "David");

Optional<String> nameOptional = names.stream()
.filter(name -> name.startsWith("A"))
.findFirst();

nameOptional.ifPresent(name -> System.out.println("Найдено имя, начинающееся на 'A': " + name));

✔️В этом примере мы используем Optional для поиска первого имени в списке, которое начинается на букву "A". Мы применяем метод filter для фильтрации и метод findFirst для поиска первого элемента. Затем мы используем метод ifPresent для выполнения действия, если значение было найдено.

⚙️Использование Optional помогает обработать ситуации, когда значение может отсутствовать, и предотвратить ошибки NullPointerException. Эта фишка улучшает безопасность кода и делает его более надежным, что является важным для профессиональных разработчиков на Java.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
⌨️Метод parallelStream() / parallel()

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

Вот пример использования метода parallel() для вычисления суммы квадратов всех чисел от 1 до 100:


import java.util.stream.IntStream;

public class Main {
public static void main(String[] args) {
int sumOfSquares = IntStream.rangeClosed(1, 100)
.parallel() // запускаем параллельные операции
.map(x -> x * x) // применяем квадратное преобразование к каждому числу
.sum(); // находим сумму всех чисел

System.out.println("Sum of squares: " + sumOfSquares);
}
}


В данном примере метод parallel() позволяет выполнять вычисления одновременно для нескольких чисел параллельно, что может значительно ускорить время выполнения программы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41🤡1
Java | Фишки и трюки
Чистый код: создание, анализ и рефакторинг Автор: Роберт Мартин Эту книгу должен прочитать каждый разработчик. А затем перечитывать каждый год! 😁 Плюс для джавистов в том что в книге все примеры написаны на Java. "Чистый код" Роберта Мартина – это практическое…
📚 Чистый код. Функции

Функции должны быть короткими и компактными.

Функции должны быть очень короткими и очень компактными.

Приблизительный максимум 20 строк и 150 символов в одной строке, если не влезает, то нужно разделять.

Функция должна выполнять только одну операцию.

- Она должна выполнять её хорошо и ничего другого она делать не должна.

- Если функция выполняет только те действия, которые находятся на одном уровне абстракции, то функция выполняет одну операцию.

- Чтобы определить выполняет ли функция более одной операции, попробуй извлечь из нее другую функцию, которая не будет являться простой переформулировкой реализации.

Чтобы определить выполняет ли функция более одной операции, попробуй извлечь из нее другую функцию, которая не будет являться простой переформулировкой реализации.

If, else, while и т.д. должны содержать вызов одной функции. Так будет читабельнее, понятнее и проще.

Идеальное количество входных аргументов для функции = 0. Если входных аргументов больше трех, то стоит задуматься каким образом лучше от них избавиться, например, создать класс для этих аргументов.

Чем больше входных аргументов, тем тяжелее понимается функция.

Функция в которую передается аргумент-флаг, от которого зависит работа функции говорит о том, что функция выполняет более одной операции. Такие функции следует разбить на две и вызывать их уровнем выше.

Функция, которая изменяет входной аргумент, должна отдавать ссылку на измененный объект, а не просто изменять без возврата. String transform(String text)

Если функция, должна изменять входной аргумент, то пусть она изменяет состояние своего объекта-владельца.

Если входной аргумент функции не должен меняться (и используется дальше в коде), то следует скопировать значение аргумента и внутри функции работать с копией.

Вместо return null лучше использовать пустой объект — Collection.empty() или null-объект -EmptyObject().

Всегда старайся использовать нестатические функции. Если это невозможно, то используй статические.

Если есть код, который должен следовать один за другим, то передавай результаты первой функции во вторую, чтобы кто-нибудь не изменил последовательность вызовов.

Используй полиморфизм вместо if/else или switch/case или when.

Избегай отрицательных условий.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16🤣3👏2🔥1👨‍💻1
⌨️ JUnit

Библиотека JUnit предназначена для написания и запуска юнит-тестов в Java. Она позволяет программистам проверять правильность работы отдельных частей и методов их приложений. Вот некоторые основные методы JUnit с их объяснениями и примерами кода:

1. @Test - аннотация, указывающая, что метод является тестовым.


import org.junit.Test;
import static org.junit.Assert.*;

public class MyTest {

@Test
public void testSum() {
assertEquals(5, Calculator.sum(2, 3));
}
}


2. assertEquals(expected, actual) - метод, который проверяет, что ожидаемое значение совпадает с фактическим значением.


import static org.junit.Assert.*;

public class MyTest {

@Test
public void testMultiply() {
assertEquals(6, Calculator.multiply(2, 3));
}
}


3. assertTrue(condition) - метод, который проверяет, что условие истинно.


import static org.junit.Assert.*;

public class MyTest {

@Test
public void testIsEven() {
assertTrue(Calculator.isEven(4));
}
}


4. assertFalse(condition) - метод, который проверяет, что условие ложно.


import static org.junit.Assert.*;

public class MyTest {

@Test
public void testIsPrime() {
assertFalse(Calculator.isPrime(4));
}
}


5. @Before и @After - аннотации, которые используются для выполнения определенных методов перед и после каждого теста соответственно.


import org.junit.Before;
import org.junit.After;

public class MyTest {

@Before
public void setUp() {
// код инициализации
}

@After
public void tearDown() {
// код очистки
}
}


JUnit облегчает процесс тестирования кода и помогает обнаруживать дефекты на ранних стадиях разработки. Его методы позволяют программистам писать понятные и эффективные тесты для своих приложений.
Please open Telegram to view this post
VIEW IN TELEGRAM
💯12👍1
⌨️ ShedLock - это библиотека для Java, предназначенная для предотвращения параллельного выполнения задач в распределенной среде. Она гарантирует, что задача будет выполнена только один раз, независимо от того, сколько экземпляров приложения запущено.

Пример кода с использованием аннотации @SchedulerLock:

import net.javacrumbs.shedlock.core.LockConfiguration;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ExampleScheduledTask {

@Scheduled(cron = "0 * * * * ?")
@SchedulerLock(name = "exampleScheduledTask", lockAtLeastFor = "PT10S", lockAtMostFor = "PT50S")
public void executeTask() {
// Код выполнения задачи
System.out.println("Выполнение задачи: " + System.currentTimeMillis());
}
}


В этом примере метод executeTask() будет вызываться раз в минуту. Так как метод проаннотирован @SchedulerLock, это гарантирует, что этот метод будет выполняться только одним экземпляром приложения. Когда в одном экземпляре приложения вызывается метод executeTask(), происходит блокировка и все другие экземпляры не могут выполнить этот метод. Блокировка снимается после выполнения метода. lockAtLeastFor указывает минимальное время блокировки, а lockAtMostFor максимальное.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥32
☕️Использование библиотеки Apache Camel

🔔Apache Camel - это легковесная библиотека, которая предоставляет возможность создания интеграционных приложений с использованием шаблонов маршрутизации и преобразования данных. Она поддерживает множество протоколов и технологий, таких как HTTP, JMS, JDBC, FTP, и многие другие.

Использовать Apache Camel можно для создания сложных интеграционных решений, объединяя различные компоненты и системы в одно целое. Вот несколько методов библиотеки Apache Camel:

1. from() - этот метод определяет начальную точку маршрута, откуда начинается обработка сообщений. Например, можно указать источник данных для получения сообщений.

Пример кода:

from("file:/inputFolder")
.to("log:incomingMessages")
.to("direct:processMessages");


2. to() - метод, который определяет конечную точку маршрута, куда отправляются обработанные сообщения. Например, можно указать назначение для сохранения или передачи данных.

Пример кода:

from("direct:processMessages")
.process(exchange -> {
String body = exchange.getIn().getBody(String.class);
// обработка сообщения
})
.to("jms:queue:processedMessages");


3. choice() - метод, который позволяет реализовать условную логику в маршруте, выбирая альтернативные пути в зависимости от содержания сообщения.

Пример кода:

from("jms:queue:incomingMessages")
.choice()
.when(header("type").isEqualTo("order"))
.to("direct:processOrder")
.when(header("type").isEqualTo("payment"))
.to("direct:processPayment")
.otherwise()
.to("log:unknownMessageType");


✔️ Apache Camel упрощает процесс интеграции различных систем и компонентов, делая его более гибким и масштабируемым.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍131❤‍🔥1💯1🏆1💔1
🤣26😁6🔥4👍21🤗1
☕️Использование библиотеки Spring Framework

⚙️Spring Framework - это один из самых популярных и широко используемых фреймворков для разработки приложений на Java. Он предоставляет множество инструментов и возможностей для упрощения создания и управления приложениями, таких как внедрение зависимостей, управление транзакциями, аспектно-ориентированное программирование и многое другое.

Ниже представлены некоторые из основных методов и функциональностей Spring Framework

1. Внедрение зависимостей (Dependency Injection):

public class ExampleService {
private ExampleRepository exampleRepository;

@Autowired
public ExampleService(ExampleRepository exampleRepository) {
this.exampleRepository = exampleRepository;
}
}
В данном примере класс ExampleService использует внедрение зависимостей для инъекции зависимости ExampleRepository. Аннотация @Autowired указывает Spring Framework на то, что нужно внедрить экземпляр ExampleRepository.

2. Управление транзакциями (Transaction Management):

@Transactional
public void saveData(Data data) {
// Сохранение данных в базу
}
Аннотация @Transactional позволяет Spring Framework управлять транзакциями в методе saveData. После выполнения метода, транзакция будет завершена автоматически, и любые изменения будут сохранены или откатаны в зависимости от результата метода.

3. Аспектно-ориентированное программирование (Aspect-Oriented Programming):

@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
}
В данном примере класс LoggingAspect используется для создания аспекта логирования, который будет выполняться перед выполнением методов в пакете com.example.service. Аннотация @Aspect указывает Spring Framework, что данный класс является аспектом.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32🔥2👏1
😱7🤯5🎉21🏆1
☕️Использование библиотеки Elasticsearch

Elasticsearch - это распределенный поисковый и аналитический движок, основанный на Apache Lucene. Он предоставляет возможность хранить, искать и анализировать большие объемы данных в реальном времени. Elasticsearch имеет открытый и гибкий API на основе HTTP, что делает его легко интегрируемым с различными языками программирования, включая Java.

Основные методы Elasticsearch на Java:

1. Создание индекса:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));

CreateIndexRequest request = new CreateIndexRequest("my_index");
client.indices().create(request, RequestOptions.DEFAULT);
client.close();

2. Добавление документа в индекс:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));

IndexRequest request = new IndexRequest("my_index").id("1")
.source("field1", "value1",
"field2", "value2");
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
client.close();

3. Получение документа из индекса:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));

GetRequest request = new GetRequest("my_index", "1");
GetResponse response = client.get(request, RequestOptions.DEFAULT);
String document = response.getSourceAsString();
client.close();

4. Поиск документов в индексе:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));

SearchRequest request = new SearchRequest("my_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("field1", "value1"));
request.source(sourceBuilder);

SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
client.close();

5. Удаление документа из индекса:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));

DeleteRequest request = new DeleteRequest("my_index", "1");
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
client.close();

🔖Это лишь небольшой набор методов, которые предоставляет Elasticsearch на Java. Он также имеет множество других методов для выполнения различных операций, таких как обновление документа, агрегации данных, управление индексами и многое другое. Elasticsearch обладает широкими возможностями для работы с данными и поиска, что делает его популярным инструментом в различных областях программирования и аналитики.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥41
☕️Использование библиотеки Apache Kafka

Apache Kafka - это распределенная система обмена сообщениями, которая позволяет создавать высокопроизводительные приложения, обрабатывающие и потребляющие потоковые данные. Он предоставляет надежную, масштабируемую и устойчивую платформу для обработки данных в реальном времени.

Ниже приведены некоторые основные методы Apache Kafka и примеры их использования на Java:

1. Producer API используется для отправки сообщений в топики Kafka. Пример кода для отправки сообщения:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<String, String>("my_topic", "key", "value"));
producer.close();


2. Consumer API используется для чтения сообщений из топиков Kafka. Пример кода для чтения сообщений:

Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my_group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("my_topic"));

while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}


3. Streams API используется для обработки и анализа данных в реальном времени. Пример кода для обработки данных:

Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "my_stream_app");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> inputStream = builder.stream("input_topic");
KStream<String, String> outputStream = inputStream.mapValues(value -> value.toUpperCase());
outputStream.to("output_topic");

KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();


Apache Kafka предоставляет разнообразные API и методы для работы с данными в реальном времени. Представленные выше примеры кода помогут начать использовать Apache Kafka для создания высокопроизводительных потоковых приложений.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍131😱1
☕️Использование библиотеки Mockito

Mockito - это фреймворк для создания и использования динамических мок-объектов (mock objects) в юнит-тестах Java. Он позволяет заменить зависимости вашего класса на мок-объекты, что позволяет вам изолировать код и тестировать его отдельно.

Mockito предоставляет множество методов для создания и использования мок-объектов. Некоторые из наиболее часто используемых методов:

1. mock() - создает мок-объект класса или интерфейса.

List<String> mockedList = Mockito.mock(List.class);


2. when() - определяет поведение мок-объекта при вызове определенного метода.

Mockito.when(mockedList.size()).thenReturn(10);


3. verify() - проверяет, был ли вызван определенный метод мок-объекта.

Mockito.verify(mockedList).add("test");


4. any() - используется для указания любого аргумента при вызове метода.

Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("element");


5. spy() - создает мок-объект, который делегирует вызовы реальным методам переданного объекта.

List<String> list = new ArrayList<>();
List<String> spyList = Mockito.spy(list);


6. doReturn() - используется для указания возвращаемого значения метода мок-объекта.

Mockito.doReturn("element").when(mockedList).get(0);


🔥Mockito является мощным инструментом для написания эффективных и надежных юнит-тестов в Java. Он помогает программистам создавать изолированные тесты и проверять поведение кода в контролируемой среде.
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍5🔥2
1👍1👏1