DNK_C_C++_Go_Rust
45 subscribers
14 photos
45 links
DNK - дневник кодера С и С++
Download Telegram
#416_Cpp_МTH_PkS

Как создать поток с помощью std::thread?

Создание потока в C++ с использованием std::thread довольно просто.

Базовый пример создания потока и передачи ему функции для выполнения:
#include <iostream>
#include <thread>

/* Функция, которую будет выполнять новый поток */
void myFunction(int value) {
std::cout << "Hello from thread! Value: " << value << std::endl;
}

int main() {
int someValue = 42;

/* Создание нового потока и передача функции и аргументов */
std::thread newThread(myFunction, someValue);

// Ожидание завершения потока
newThread.join();

return 0;
}


Создание потока:
std::thread newThread(myFunction, someValue);

Здесь мы создаем новый поток newThread, передавая функцию myFunction и аргумент someValue.
Аргументы передаются по значению, поэтому любые изменения, сделанные в потоке, не повлияют на исходную переменную.

Ожидание завершения потока:
newThread.join();

Метод join() заставляет основной поток ожидать завершения созданного потока. Без этого основного потока программа может завершиться до того, как созданный поток успеет завершить свою задачу.

Передача объектов в поток:
Если нужно передать объект в поток, можно воспользоваться ссылкой или указателем:
#include <iostream>
#include <thread>

class MyClass {
public:
void doSomething(int value) {
std::cout << "Doing something with value: " << value << std::endl;
}
};

int main() {
MyClass obj;
int someValue = 10;

/* Создаём поток, передавая ссылку на объект и аргумент */
std::thread newThread(&MyClass::doSomething, &obj, someValue);

newThread.join();

return 0;
}

Здесь мы передаем ссылку на объект obj и аргумент someValue в поток. Важно помнить, что ссылка должна оставаться действительной на протяжении всего времени жизни потока.

Завершение программы без join():
Если нужно, чтобы поток продолжал выполнение после завершения основного потока, можно использовать метод detach():
std::thread newThread(myFunction, someValue);
newThread.detach();

Метод detach() отделяет поток от текущего объекта std::thread, позволяя ему продолжать выполнение даже после выхода из области видимости. Однако будьте осторожны, так как это может привести к утечкам ресурсов и другим проблемам, если поток не завершится должным образом.


Cоздание потоков с помощью std::thread в C++ достаточно простое и гибкое, позволяя передавать функции и объекты для выполнения в отдельном потоке.
#417_МTH_PkS_TP

На сколько потоков лучше разбить задачу?
От чего это зависит?


Определение оптимального количества потоков для задачи — важный аспект разработки многопоточных приложений.
Количество потоков влияет на производительность, использование ресурсов и сложность реализации.


Факторы, определяющие количество потоков:

Количество ядер процессора — количество физических или логических ядер процессора.
Оптимальное количество потоков часто соответствует количеству ядер, поскольку каждый поток может быть назначен своему ядру, что максимизирует производительность.
Например, если процессор имеет 8 ядер, разумно запустить 8 потоков для максимальной загрузки всех ядер.

Тип задачи — различные задачи требуют разного подхода к распараллеливанию:
— вычислительные задачи — обычно выгодно запускать столько потоков, сколько есть ядер, так как каждая единица вычислительных мощностей будет занята полезной работой;
— задачи ввода-вывода (I/O-bound) — часто включают ожидание внешних событий (например, чтение файлов, сетевые запросы). В таких случаях может потребоваться больше потоков, чем ядер, чтобы компенсировать время простоя, связанное с I/O операциями.

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

Размер задачи — большие задачи обычно легче разделить на множество потоков, тогда как маленькие задачи могут выиграть от меньшего количества потоков, чтобы избежать лишних накладных расходов на управление потоками.

Платформа и окружение — некоторые платформы и окружения могут накладывать ограничения на максимальное количество потоков.
Например,
ОС могут ограничивать количество потоков, которые могут быть запущены одновременно.

Параллельная архитектура приложения — архитектура приложения играет важную роль. Если приложение спроектировано таким образом, что разные части могут выполняться параллельно, возможно, потребуется больше потоков для оптимизации работы.

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


Практические рекомендации:
Начните с простого — начните с количества потоков, равного числу ядер процессора. Это хороший стартовый пункт для большинства задач.
Экспериментируйте — проведите тесты с разным количеством потоков, измеряя производительность и ресурсы.
Найдите баланс между производительностью и затратами на управление потоками.

Используйте динамическое масштабирование — современные библиотеки и фреймворки, такие как std::async и std::future, позволяют динамически распределять задачи между потоками, основываясь на текущей нагрузке и доступности ресурсов.
Избегайте избыточности — не создавайте слишком много потоков, особенно если они большую часть времени простаивают.
Это может привести к ненужной загрузке системы и ухудшению производительности.



Оптимальное количество потоков зависит от множества факторов, включая архитектуру процессора, тип задачи, взаимодействие потоков и платформу.
Начинайте с простых решений и экспериментируйте, чтобы найти наилучший подход для вашей конкретной задачи.
#418_Cpp_МTH_PkS

Как работать с std::async?

Функция std::async из стандартной библиотеки C++ используется для асинхронного запуска функций или лямбда-выражений.
Она возвращает объект типа std::future, который позволяет получить результат выполнения задачи позже.


Основные аспекты работы с std::async:

std::async — функция, принимающая функцию или лямбду и возвращающая std::future.
std::future — объект, позволяющий получить результат выполнения задачи, запущенной через std::async.
std::launch — перечисление, определяющее политику запуска задачи (синхронно или асинхронно).

Пример использования:
#include <iostream>
#include <future>

int calculate(int a, int b) {
return a + b;
}

int main() {
// Запускаем асинхронную задачу
auto future = std::async(calculate, 10, 20);

/* Получаем результат выполнения задачи */
int result = future.get();

std::cout << "Result: " << result << std::endl;

return 0;
}


Запуск задачи:
auto future = std::async(calculate, 10, 20);

Используем std::async для запуска функции calculate с аргументами 10 и 20.
Функция std::async возвращает объект типа std::future<int>, который хранится в переменной future.

Получение результата:
int result = future.get();

Метод get() объекта future блокирует текущий поток до тех пор, пока задача не завершится, а затем возвращает результат выполнения функции calculate.


Политика запуска.
По умолчанию std::async выбирает политику запуска автоматически, но можно явно указать, как должна быть выполнена задача, используя параметры std::launch:
std::launch::async — задача гарантированно будет выполнена асинхронно в новом потоке.
std::launch::deferred — задача будет отложена до вызова метода get(), и выполнится синхронно в текущем потоке.


Пример явного указания политики:
auto future = std::async(std::launch::async, calculate, 10, 20);



Использование лямбды.
Также можно передавать лямбда-функции в std::async:
auto future = std::async([](int a, int b) { return a * b; }, 5, 10);



Исключения.

Если в асинхронной задаче возникает исключение, оно будет передано в объект future и выброшено при вызове метода get().


std::async — удобный инструмент для запуска асинхронных задач и получения их результатов позже.
Он упрощает работу с многозадачностью и помогает избегать сложных манипуляций с потоками вручную.
#419_Cpp_МTH_PkS_UB

Thread-safe гарантии контейнеров в С++?
В чем недостаток интерфейса front() + pop_front()?


Контейнеры стандартной библиотеки C++ предоставляют различные гарантии безопасности при работе в многопоточном окружении.
Эти гарантии зависят от конкретного контейнера и выполняемых операций.


Основные аспекты thread-safety для стандартных контейнеров.

Стандартная библиотека C++ гарантирует, что все контейнеры безопасны для одновременного чтения из разных потоков, но не для записи.
То есть, если несколько потоков читают данные из одного контейнера, это безопасно, однако, если хотя бы один поток пытается модифицировать контейнер, необходимо использовать механизмы синхронизации (например, мьютексы).


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


Недостатки Интерфейса front() + pop_front().
Комбинация методов front() и pop_front() может вызывать проблемы в многопоточной среде, потому что эти две операции выполняются последовательно, а не атомарно:
std::deque<int> queue;

void producer() {
// Добавляет элемент в очередь
queue.push_back(42);
}

void consumer() {
if (!queue.empty()) {
// Чтение значения
int value = queue.front();
// Удаление элемента
queue.pop_front();
// Обработка значения
process(value);
}
}

Проблема заключается в том, что между вызовами front() и pop_front() может произойти переключение контекста, и другой поток может изменить содержимое очереди. Это приведет к тому, что первый поток получит неверное значение или попытается удалить несуществующий элемент, что вызовет неопределенное поведение.

Чтобы решить эту проблему, необходимо защитить обе операции одной блокировкой:
std::mutex mtx;
std::deque<int> queue;

void producer() {
std::lock_guard<std::mutex> lock(mtx);
queue.push_back(42);
}

void consumer() {
std::lock_guard<std::mutex> lock(mtx);
if (!queue.empty()) {
int value = queue.front();
queue.pop_front();
process(value);
}
}

Теперь оба потока защищены одним мьютексом, что исключает возможность гонки данных.


Альтернативы:
Некоторые контейнеры, такие как std::queue и std::stack, предлагают методы push() и pop(), которые работают атомарно, что делает их более удобными для использования в многопоточных приложениях. Однако даже в этих случаях необходимо использовать синхронизацию для обеспечения безопасности.


Контейнеры стандартной библиотеки C++ обеспечивают безопасность для чтения из разных потоков, но требуют синхронизации для модификаций.
Комбинирование методов front() и pop_front() без синхронизации может привести к гонке данных и неопределенному поведению.
Для безопасной работы с контейнерами в многопоточной среде следует использовать мьютексы или другие механизмы синхронизации.
#420_NTWK_PkS

Что такое TCP handshake?

TCP handshake (или трёхэтапное рукопожатие) — процедура установления соединения между двумя хостами в сети, использующая протокол управления передачей (Transmission Control Protocol, TCP).
Этот процесс необходим для инициализации надёжного двунаправленного канала связи, обеспечивающего доставку данных в правильном порядке и без потерь.


Процесс TCP handshake состоит из трёх этапов:
SYN (Synchronize Sequence Numbers) — клиент отправляет серверу пакет SYN, содержащий начальный номер последовательности (ISN, Initial Sequence Number).
Этот пакет сообщает серверу, что клиент хочет установить соединение.
SYN-ACK (Synchronize-Acknowledge) — сервер отвечает клиенту пакетом SYN-ACK, содержащим свой ISN и подтверждение (ACK) полученного номера последовательности клиента. Это подтверждает, что сервер готов принять соединение.
ACK (Acknowledge) — клиент отправляет последний пакет ACK, подтверждая получение пакета SYN-ACK от сервера. Теперь соединение установлено, и обе стороны готовы обмениваться данными.

Эти три шага можно кратко описать следующим образом:
Клиент: SYN →
Сервер: ← SYN-ACK
Клиент: ACK →



Детали каждого этапа:
SYN клиент инициирует соединение, отправляя пакет SYN с начальным номером последовательности (ISN).
Пакет содержит информацию о номере порта назначения и источнике, а также некоторые опции, например, размер окна (Window Size), максимальный сегмент данных (MSS) и другие параметры.
SYN-ACK — сервер получает пакет SYN и отвечает пакетом SYN-ACK.
В ответе содержится собственный ISN сервера и подтверждение (ACK) номера последовательности клиента.
Сервер также включает свои параметры, такие как MSS и окно приёма.
ACK — клиент получает пакет SYN-ACK и отправляет финальный пакет ACK, подтверждающий успешное получение ответа от сервера.
На этом этапе соединение считается установленным, и обе стороны могут начинать передачу данных.


Почему TCP использует трёхэтапное рукопожатие?

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


TCP handshake — ключевой механизм, обеспечивающий надёжное и устойчивое соединение между двумя узлами в сети.
Эта процедура гарантирует, что обе стороны готовы к передаче данных и что данные будут доставлены в нужном порядке и без потерь.
#421_NTWK_PkS

Какая разница между TCP и UDP?

Протоколы TCP (Transmission Control Protocol) и UDP (User Datagram Protocol) являются основными транспортными протоколами, используемыми в Интернете.
Они оба работают поверх IP-протокола (Internet Protocol), но имеют существенные различия в способах передачи данных и уровне предоставляемых услуг.


Ключевые отличия.

Надёжность:
TCP — надёжен и обеспечивает гарантированную доставку данных путём подтверждения получения каждой порции данных (пакета).
Если пакет теряется или повреждается, он пересылается повторно.
UDP — ненадёжен и не гарантирует доставку данных. Пакеты отправляются без подтверждения их получения, и нет механизма повторной отправки потерянных или повреждённых пакетов.

Порядок доставки:
TCP — обеспечивает доставку данных в правильном порядке.
Если пакеты приходят в неправильном порядке,
TCP восстанавливает их последовательность перед передачей приложению.
UDP — не заботится о порядке доставки. Пакеты могут приходить в произвольном порядке, и приложение само должно заботиться об их сортировке, если это требуется.

Задержка:
TCP — имеет большую задержку из-за необходимости подтверждения получения каждого пакета и повторного отправления потерянных пакетов.
UDP — меньшая задержка, так как не требует подтверждения и повторной отправки пакетов.

Управление потоком:
TCP — включает механизмы управления потоком, такие как скользящее окно (sliding window), для регулирования скорости передачи данных в зависимости от пропускной способности сети и возможностей получателя.
UDP — не имеет встроенных механизмов управления потоком. Приложение должно самостоятельно регулировать скорость передачи данных, если это необходимо.

Концепция соединения:
TCP — ориентирован на соединение. Перед началом обмена данными устанавливается TCP-соединение посредством трёхэтапного рукопожатия (handshake).
UDP — без установки соединения. Данные отправляются напрямую, без предварительного согласования параметров соединения.

Использование ресурсов:
TCP — требует больше системных ресурсов для поддержания состояния соединения и обработки подтверждений.
UDP — более лёгкий и менее требовательный к ресурсам, так как не занимается управлением состоянием соединения и подтверждением доставки.

Применение:
TCP — используется для приложений, требующих высокой надёжности передачи данных, таких как веб-браузеры, электронная почта, файловые трансферы.
UDP — применяется в приложениях, где важнее низкая задержка и высокая скорость передачи, например, в онлайн-играх, потоковом видео и аудио, VoIP (Voice over IP).


Характеристики TCP:
надёжность — высокая (гарантирует доставку данных);
порядок доставки — поддерживается (данные всегда поступают в правильном порядке);
задержка — большая (из-за подтверждений и повторных отправлений);
управление потоком — есть (скользящие окна и др.);
концепция соединения — ориентирован на соединение (установка соединения перед передачей данных);
использование ресурсов — высокое;
применение — веб, электронная почта, файлы.

Характеристики UDP:
недежность — низкая (не гарантирует доставку);
порядок доставки — не поддерживается;
задержка — маленькая;
управление потоком — нет;
концепция соединения — без установки соединения;
использование ресурсов — низкое;
применение — онлайн-игры, потоковое видео/аудио.


Выбор между TCP и UDP зависит от требований конкретного приложения.
Если нужна высокая надёжность и правильная доставка данных, предпочтительнее использовать TCP.
Если важнее низкая задержка и высокая скорость передачи, лучше выбрать UDP.
#422_NTWK_PkS

Протоколы верхнего уровня.

Протоколы верхнего уровня относятся к уровню приложений в модели OSI (Open Systems Interconnection) и представляют собой набор правил и стандартов, используемых для взаимодействия между различными приложениями и сервисами в компьютерных сетях.
Эти протоколы определяют, как данные должны быть представлены, переданы и интерпретированы между конечными пользователями или приложениями.
Протоколы верхнего уровня работают поверх транспортных протоколов, таких как TCP и UDP, и обеспечивают более высокий уровень абстракции для конкретных типов сервисов и приложений.


Наиболее известные и используемые протоколы верхнего уровня:

HTTP (Hypertext Transfer Protocol) — основной протокол, используемый для передачи гипертекстовых документов в Интернете.
Он работает поверх TCP и используется для передачи веб-страниц, изображений, видеофайлов и других видов контента между веб-сервером и клиентом (обычно браузером).
HTTP основан на запросе-ответе — клиент отправляет запрос на сервер, а сервер отвечает соответствующим содержимым.

FTP (File Transfer Protocol) — предназначен для передачи файлов между компьютерами через сеть.
FTP использует два отдельных TCP-соединения: одно для команд и другое для передачи данных.
FTP часто применяется для загрузки и выгрузки файлов на удалённые серверы.

SMTP (Simple Mail Transfer Protocol) — используется для отправки электронной почты между серверами.
SMTP определяет формат сообщений и способы их передачи.
Большинство почтовых клиентов используют SMTP для отправки писем, а POP3 или IMAP — для их получения.

DNS (Domain Name System) служит для преобразования доменных имён (например, example.com) в IP-адреса, которые используются для маршрутизации трафика в Интернете.
DNS-запросы обычно передаются через UDP, хотя могут использоваться и TCP для больших ответов.

SSH (Secure Shell) — протокол для безопасного удалённого входа в компьютерные системы.
SSH шифрует весь трафик между клиентом и сервером, предотвращая перехват данных злоумышленниками.
SSH часто используется для администрирования серверов и передачи файлов через SFTP (SSH File Transfer Protocol).

RTP (Real-time Transport Protocol) — используется для передачи мультимедийных данных в реальном времени, таких как голос и видео.
RTP работает поверх UDP и обеспечивает синхронизацию и контроль качества передачи данных.
Часто используется вместе с RTCP (Real-time Transport Control Protocol) для мониторинга качества обслуживания.

SIP (Session Initiation Protocol) — используется для настройки, изменения и завершения сеансов связи, таких как звонки через Интернет (VoIP).
SIP управляет установлением, поддержанием и завершением сессий мультимедиа, включая голосовые и видеозвонки.

HTTPS (Hypertext Transfer Protocol Secure) — защищённая версия HTTP, которая использует SSL/TLS для шифрования данных, передаваемых между клиентом и сервером.
HTTPS широко используется для защиты конфиденциальной информации, такой как пароли и банковские данные.

POP3 (Post Office Protocol version 3) — используется для получения электронных писем с почтового сервера. Он позволяет клиентам скачивать сообщения на локальную машину и удалять их с сервера.

IMAP (Internet Message Access Protocol) — также используется для получения электронных писем, но в отличие от POP3, он позволяет пользователям оставлять письма на сервере и управлять ими удаленно.
IMAP предоставляет более широкие возможности для работы с письмами, такими как организация по папкам и метаданные.


Протоколы верхнего уровня играют ключевую роль в обеспечении функциональности и удобства современных сетей.
Каждый из них ориентирован на решение специфической задачи, будь то передача веб-контента, отправка электронной почты, обеспечение безопасности или поддержка мультимедийных коммуникаций.
Выбор правильного протокола зависит от потребностей конкретного приложения и требований к безопасности, надёжности и производительности.
#423_NTWK_PkS

Какая разница между HTTP и HTTPS?

HTTP (Hypertext Transfer Protocol) и HTTPS (Hypertext Transfer Protocol Secure) — два основных протокола, используемые для передачи данных в Интернете.
Основное различие между ними заключается в уровне безопасности, который они обеспечивают.


HTTP (Hypertext Transfer Protocol) — стандартный протокол, используемый для передачи гипертекстовых документов (веб-страниц) между веб-сервером и клиентом (браузером).
HTTP работает на основе модели запроса-ответаклиент отправляет запрос на сервер, а сервер отвечает соответствующим содержимым.

Особенности:
Не зашифрован — все данные, передаваемые по HTTP, передаются в открытом текстовом формате, что делает их уязвимыми для перехвата и анализа злоумышленниками.
Простота — HTTP прост в использовании и легко интегрируется с большинством веб-сервисов.
Порт — по умолчанию HTTP использует порт 80.
Когда использоватьHTTP подходит для передачи общедоступной информации, которая не требует защиты конфиденциальности, например, статического контента на веб-сайтах.


HTTPS (Hypertext Transfer Protocol Secure) — расширенная версия HTTP, которая добавляет слой безопасности с использованием криптографических протоколов SSL (Secure Sockets Layer) или TLS (Transport Layer Security).
Это обеспечивает защиту данных, передаваемых между клиентом и сервером.

Особенности:
Шифрование — HTTPS шифрует все данные, передаваемые между клиентом и сервером, что предотвращает их перехват и анализ злоумышленниками.
Сертификаты — для использования HTTPS сервер должен иметь цифровой сертификат, выданный доверенным центром сертификации.
Этот сертификат удостоверяет подлинность сервера и позволяет устанавливать безопасное соединение.
Порт — по умолчанию HTTPS использует порт 443.
Когда использовать — HTTPS обязателен для передачи конфиденциальной информации, такой как пароли, платежные данные, личные сообщения и другая чувствительная информация.
Также HTTPS улучшает SEO-рейтинг сайта и доверие пользователей.


Основные различия.

Безопасность:
HTTP — не зашифрован, данные могут быть перехвачены и прочитаны злоумышленниками.
HTTPS — зашифрован с использованием SSL/TLS, что обеспечивает защиту данных от перехвата и несанкционированного доступа.

Цифровые сертификаты:
HTTP — не требует цифровых сертификатов.
HTTPS — требуется цифровой сертификат, выданный доверенным центром сертификации.

Порт:
HTTP — использует порт 80.
HTTPS — использует порт 443.

SEO и доверие:
HTTP — может негативно влиять на рейтинг сайта в поисковых системах и снижать доверие пользователей.
HTTPS — улучшает SEO-рейтинг и увеличивает доверие пользователей, так как показывает, что сайт безопасен.


Разница между HTTP и HTTPS заключается главным образом в уровне безопасности, который они обеспечивают.
HTTPS предлагает значительно более высокую степень защиты данных, делая его обязательным для сайтов, работающих с конфиденциальной информацией.
В современном Интернете использование HTTPS стало стандартом де-факто для большинства веб-ресурсов.
#424_NTWK_PkS

Расскажите об SSL/TLS handshake.

SSL/TLS handshake — процесс установления безопасного соединения между клиентом (например, веб-браузером) и сервером, использующим протоколы SSL (Secure Sockets Layer) или TLS (Transport Layer Security).
Этот процесс включает в себя несколько шагов, направленных на аутентификацию участников, согласование параметров шифрования и установку безопасного канала связи.


Рассмотрим шаги процесса SSL/TLS handshake подробно.


1. Client Hello — клиент начинает процесс, отправляя сообщение Client Hello на сервер. В этом сообщении содержатся следующие данные:
— версия протокола (например, TLS 1.2 или TLS 1.3);
— список поддерживаемых клиентом алгоритмов шифрования (cipher suites);
— случайное число (random number), генерируемое клиентом;
— идентификатор сессии (session ID), если клиент ранее устанавливал соединение с этим сервером;
— другие параметры, такие как список поддерживаемых расширений.

2. Server Hello — сервер отвечает сообщением Server Hello, включающим:
— выбранную версию протокола (чаще всего самую новую из предложенных клиентом);
— выбранный алгоритм шифрования (cipher suite) из списка, предоставленного клиентом;
— случайное число, сгенерированное сервером;
— сертификат сервера, подписанный доверенным центром сертификации
(CA);
— идентификатор сессии, если она была возобновлена.

3. Сертификация и проверка — после получения сертификата сервера клиент проверяет его:
— проверяет подпись сертификата, чтобы убедиться, что он был выпущен доверенным CA;
— проверяет срок действия сертификата;
— проверяет, совпадает ли имя хоста в сертификате с именем хоста, к которому подключается клиент.

Если проверка проходит успешно, клиент продолжает процесс. В противном случае соединение разрывается.

4. Предварительный мастер-ключ (Pre-Master Key) — клиент генерирует предварительный мастер-ключ (pre-master key) и шифрует его с использованием открытого ключа сервера, полученного из сертификата.
Затем этот ключ отправляется обратно на сервер.

5. Мастер-ключ (Master Key) — клиент и сервер используют предварительный мастер-ключ, случайные числа, полученные на предыдущих шагах, и выбранный алгоритм шифрования для генерации общего мастер-ключа (master key).
Этот ключ будет использоваться для шифрования дальнейшего общения.

6. Change Cipher Spec — клиент и сервер обмениваются сообщениями Change Cipher Spec, сигнализируя о переходе к использованию согласованных параметров шифрования.

7. Завершающие сообщения — клиент и сервер отправляют друг другу сообщения Finished, содержащие хэш всех предыдущих сообщений, зашифрованный с использованием мастер-ключа.
Это позволяет проверить целостность данных и завершить процесс рукопожатия.

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


Особенности TLS 1.3.
В версии TLS 1.3 процесс рукопожатия был оптимизирован для повышения эффективности и безопасности:
— сокращено количество раундов обмена сообщениями;
— упрощенный выбор алгоритма шифрования;
— улучшенная защита от атак, основанных на анализе времени и мощности.


SSL/TLS handshake — сложный, но необходимый процесс для обеспечения безопасности интернет-коммуникаций.
Он гарантирует, что данные, передаваемые между клиентом и сервером, защищены от перехвата и несанкционированного доступа.
Современные браузеры и серверы поддерживают последние версии TLS, что делает интернет-соединения более безопасными и надежными.
#425_DvOp_PkS

Процессы CI.

CI (Continuous Integration) — практика разработки ПО, которая подразумевает регулярное объединение изменений кода от разных разработчиков в общую кодовую базу с целью минимизации конфликтов и быстрого выявления ошибок.


Основные процессы CI:
Автоматизация сборки — каждый раз, когда разработчик отправляет изменения в репозиторий, система CI автоматически запускает сборку проекта.
Это включает компиляцию исходного кода, выполнение тестов и создание артефактов (например, бинарных файлов).
Тестирование — после успешной сборки проект проходит автоматическое тестирование.
Обычно это юнит-тесты, интеграционные тесты и другие виды автоматизированных проверок.
Если хотя бы один тест не пройден, процесс CI завершается ошибкой, а разработчики получают уведомление об этом.
Анализ кода — в процессе CI может проводиться статический анализ кода для поиска потенциальных проблем, таких как уязвимости безопасности, нарушения стандартов кодирования и другие ошибки.
Обратная связь — разработчики должны получать быстрые уведомления о результатах сборки и тестирования.
Это позволяет им оперативно исправлять проблемы до того, как они станут частью основной ветки разработки.

Непрерывные улучшения — процессы CI направлены на постоянное улучшение качества кода и процесса разработки.
Например, если выявляются частые ошибки при сборке или тестировании, можно добавить дополнительные проверки или улучшить инфраструктуру CI/CD.
Интеграция с другими инструментами — СI часто интегрируется с системами управления версиями (Git, SVN), системами отслеживания задач (Jira, Trello) и платформами для развертывания (Docker, Kubernetes).
Это позволяет создавать комплексные решения для автоматизации всего жизненного цикла разработки ПО.


Преимущества CI:
Быстрое обнаружение ошибок — ошибки выявляются сразу после внесения изменений, что упрощает их устранение.
Снижение риска конфликтов — регулярное слияние кода уменьшает вероятность возникновения сложных конфликтов при объединении веток.
Повышение качества кода — автоматизированные тесты и статический анализ помогают поддерживать высокий уровень качества кода.
Ускорение разработки — благодаря автоматизации рутинных процессов, разработчики могут сосредоточиться на создании новых функций и улучшении продукта.


Инструменты для реализации CI: Jenkins, GitLab CI, Travis CI, CircleCI, TeamCity и др.
👌1
#426_DvOp_GIT_PkS

Как отредактировать коммит?

Редактирование коммита в системе контроля версий Git возможно несколькими способами в зависимости от того, какой именно коммит нужно изменить и где он находится относительно вашей текущей работы.


Распространенные сценарии:


Изменение последнего коммита — если нужно исправить последний коммит (например, забыли добавить файл или хотите изменить сообщение коммита), используйте команду git commit --amend:
# Откроется редактор, чтобы изменить сообщение коммита
git commit --amend

# Или добавьте параметр -m, чтобы указать новое сообщение напрямую
git commit --amend -m "Новое сообщение"

Эта команда изменяет последний коммит, позволяя внести правки в файлы и/или изменить сообщение коммита.


Редактирование более ранних коммитов с помощью интерактивного ребейза (git rebase -i) — если нужно изменить коммит, который был сделан ранее, но еще не отправлен в удаленный репозиторий, можно использовать интерактивный режим команды rebase.
Для этого выполните следующую команду:
# Укажите хеш коммита, начиная с которого начнется редактирование
git rebase -i <хэш_коммита>

Откроется текстовый редактор с перечнем всех коммитов, начиная с указанного.
Увидите список команд напротив каждого коммита. Чтобы изменить конкретный коммит, замените слово pick на edit рядом с этим коммитом.
После сохранения и выхода из редактора Git перейдет к указанному коммиту, и можно будет его изменить:
# Добавьте нужные изменения в индекс
git add .

# Зафиксируйте изменения
git commit --amend

# Продолжайте ребейс
git rebase --continue

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


Изменение сообщения коммита с помощью git filter-branch — если нужно изменить только сообщение коммита без изменения содержимого файла, можно воспользоваться командой filter-branch:
# Заменяем старое сообщение на новое во всех коммитах
git filter-branch -f --msg-filter 'sed "s/старое сообщение/новое сообщение/g"' HEAD

Внимание!!! Использование этой команды требует осторожности, так как она переписывает историю коммитов и может привести к проблемам при совместной работе над проектом.


Важно!
Помните, что изменение истории коммитов может быть опасно, особенно если эти коммиты уже были отправлены в общий репозиторий.
В таком случае лучше создать новый коммит с исправлениями вместо изменения старых коммитов.
#427_DvOp_GIT_PkS

Интерактивный rebase.

Интерактивный ребейс (interactive rebase) в Git — инструмент, позволяющий изменять историю коммитов в локальном репозитории.
Он предоставляет возможность редактировать, перемещать, удалять и комбинировать коммиты перед тем, как отправить их в удалённый репозиторий.
Интерактивный ребейс полезен, когда необходимо очистить историю коммитов, сделать её более понятной или исправить ошибки.


Как работает интерактивный ребейс?
Когда выполняется команда git rebase -i, Git открывает текстовый редактор с перечислением всех коммитов, которые будут пересмотрены.
Рядом с каждым коммитом указаны действия, которые можно с ним совершить.
По умолчанию указано действие
pick, которое означает, что коммит будет оставлен без изменений.


Основные доступные действия:
pickоставить коммит без изменений;
reword — изменить сообщение коммита;
edit — остановить процесс ребейза на данном коммите, чтобы внести изменения вручную;
squash — объединить текущий коммит с предыдущим;
fixupаналогично squash, но сообщение текущего коммита игнорируется;
drop — удалить коммит полностью.


Пример списка коммитов в редакторе:
pick a123456 Сообщение первого коммита
pick b234567 Сообщение второго коммита
pick c345678 Сообщение третьего коммита

Можно изменить порядок строк, чтобы поменять последовательность коммитов, или заменить pick на одно из других действий.

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

Выполняем команду:
git rebase -i HEAD~3


Открывается редактор с таким содержанием:
pick a123456 Сообщение первого коммита
pick b234567 Сообщение второго коммита
pick c345678 Сообщение третьего коммита


Меняем строки на следующие:
pick a123456 Сообщение первого коммита
pick b234567 Сообщение второго коммита
squash c345678 Сообщение третьего коммита

Сохраняем и закрываем редактор. Git откроет ещё один редактор, чтобы задать итоговое сообщение нового комбинированного коммита.
Здесь можно оставить оба сообщения или написать своё собственное.
После завершения редактирования сообщений Git завершит процесс ребейза, и история коммитов изменится соответствующим образом.


Важные моменты:
Изменение истории — использование интерактивного ребейза меняет историю коммитов, поэтому будьте осторожны, если коммиты уже были отправлены в удалённый репозиторий. Это может привести к конфликтам при следующей попытке push'а.
Резервные копии — перед началом интерактивного ребейза рекомендуется создать резервную копию своей работы, например, с помощью команды git branch backup.
Команды отмены — если что-то пошло не так, всегда можно отменить ребейс с помощью команды git rebase --abort.


Интерактивный ребейс — инструмент для очистки и реорганизации истории коммитов.
Однако важно помнить, что его следует применять осторожно, особенно если работаете в команде.
Правильное использование этого инструмента поможет поддерживать чистую и понятную историю изменений проекта.
#428_DBG_PkS_TP

Какие существуют способы дебаггинга кода?

Дебаггинг (debugging) — процесс нахождения и устранения ошибок в программном коде.
Существует множество методов и подходов к дебаггингу, каждый из которых имеет свои особенности и применяется в зависимости от конкретной ситуации.


Распространённые способы дебаггинга:

Логирование (Logging) — запись информации о выполнении программы в лог-файл или консоль.
Логирование помогает отслеживать состояние приложения в определённые моменты времени и понять, какие операции выполняются и какие данные обрабатываются.

Примеры:
Использование встроенных средств логирования, таких как console.log() в JavaScript или print() в Python.
Применение специализированных библиотек для логирования, таких как logging в Python или log4j в Java.

Плюсы:
Простота использования — возможность записывать информацию в разные уровни детализации (info, warning, error и т.д.).
Минусы:
Может замедлить работу программы, если используется слишком много логов.
Требует анализа большого объёма данных.


Отладка с использованием точек останова (Breakpoints)точки останова позволяют приостановить выполнение программы в определённой строке кода и исследовать текущее состояние переменных, стек вызовов и другие аспекты выполнения.

Инструменты:
Встроенные отладчики IDE, такие как Visual Studio, PyCharm, IntelliJ IDEA и др.
Внешние отладчики, такие как GDB для C/C++.

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

Минусы:
Требуют использования специальных инструментов.
Не всегда применимо в высоконагруженных системах или в условиях реального времени.


Печать значений переменных (Print Debugging) — простой способ дебаггинга, заключающийся в выводе значений переменных прямо в коде.
Хотя этот метод считается устаревшим, он всё ещё широко используется, особенно в ситуациях, когда нет доступа к полноценным отладчикам.

Примеры:
printf("Value of x: %d", x); // в C.
System.out.println("Value of x: " + x); // в Java.


Плюсы:

Очень простой и быстрый способ получить информацию о значениях переменных.
Минусы:
Загромождает код.
Трудно масштабируемо для больших проектов.


Анализ дампов памяти (Memory Dumps) — в случае аварийного завершения программы (crash) можно проанализировать дамп памяти, чтобы найти причину сбоя.
Дамп содержит всю информацию о состоянии программы на момент аварии, включая значения переменных, стек вызовов и т.д.

Инструменты:
WinDbg для Windows.
GDB
для Linux.

Плюсы:
Полезен для анализа критических сбоев.
Позволяет восстановить состояние программы на момент аварии.

Минусы:
Сложность анализа дампа.
Требуется специализированное оборудование и навыки.


Профилирование (Profiling) — процесс измерения производительности программы, включая время выполнения различных частей кода, использование памяти и другие ресурсы.
Помогает выявить узкие места и оптимизировать производительность.

Инструменты:
VisualVM для Java.
Chrome DevTools
для веб-приложений.
Valgrind для C/C++.

Плюсы:
Помогает находить неэффективные участки кода.
Можно использовать для оптимизации производительности.

Минусы:
Может замедлить работу программы во время профилирования.
Требует дополнительных знаний и навыков.


.
Тестирование (Testing) — помогает обнаружить ошибки до их попадания в продакшн-среду.
Существуют различные виды тестирования, такие как модульное тестирование, интеграционное тестирование и регрессионное тестирование.

Инструменты:
JUnit для Java.
pytest для Python.
Mocha
для JavaScript.

Плюсы:
Обнаруживает ошибки на ранней стадии разработки.
Повышает качество кода.

Минусы:
Требует написания дополнительного кода для тестов.
Не гарантирует полное отсутствие ошибок.


Рефакторинг (Refactoring) — процесс улучшения структуры и читаемости кода без изменения его функциональности.
Часто приводит к устранению скрытых ошибок и улучшению поддерживаемости кода.

Методы:
Извлечение метода.
Переименование переменной.
Разделение класса.

Плюсы:
Улучшает читаемость и поддержку кода.
Может устранить скрытые ошибки.

Минусы:
Требует времени и усилий.
Необходимо тщательно проверять функциональность после рефакторинга.


Выбор способа дебаггинга зависит от многих факторов, включая ЯП, среду выполнения, сложность задачи и доступное время.
Комбинируя различные методы, можно значительно повысить эффективность поиска и устранения ошибок в коде.
#429_Cpp_DBG

Вывод типов в С++.


Вывод типов в C++ — процесс определения типа переменных или выражений во время компиляции.
В языке есть несколько механизмов для автоматического выведения типов, начиная с версии C++11 и далее.


Основные способы вывода типов в C++:


Ключевое слово auto было введено в C++11 и позволяет компилятору автоматически определять тип переменной на основании инициализатора:
auto x = 5;         // x будет иметь тип int
auto y = 6.7f; // y будет иметь тип float
auto z = true; // z будет иметь тип bool

Здесь типы переменных x, y и z определяются автоматически исходя из их инициализаторов.


decltype — используется для получения типа выражения.
decltype возвращает тип, который выражение имело бы в контексте программы:
int a = 10;
decltype(a) b = 20; // b будет иметь тип int



Шаблонные параметры — шаблоны позволяют выводить типы параметров функций или классов:
template<typename T>
void print(T value) {
std::cout << "Тип: " << typeid(T).name() << ", Значение: " << value << std::endl;
}

int main() {
// Тип: int, Значение: 42
print(42);
// Тип: double, Значение: 3.14
print(3.14);
// Тип: char const*, Значение: Привет
print("Привет");
return 0;
}

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


std::initializer_list — для работы с инициализационными списками используется std::initializer_list.
Его тип может быть выведен автоматически:
void func(std::initializer_list<int> list) {
for (auto item : list) {
std::cout << item << ' ';
}
std::cout << std::endl;
}

int main() {
func({1, 2, 3}); // Выведет: 1 2 3
return 0;
}



decltype(auto) — комбинация decltype и auto позволяет более точно управлять типом возвращаемого значения.
Она полезна, когда требуется сохранить ссылку или тип выражения:
int arr[] = {1, 2, 3};
decltype(auto) ref = arr;
// ref будет иметь тип int (&)[3]



Автоматическое выведение типа возвращаемого значения функции — начиная с C++14, можно опустить указание типа возвращаемого значения функции, и компилятор выведет его автоматически:
auto add(int a, int b) {
return a + b;
}

int main() {
// result будет иметь тип int
auto result = add(5, 10);
std::cout << result << std::endl; // Выведет: 15
return 0;
}



СЛЕДУЕТ ЗАПОМНИТЬ!!!
— в процессе вывода типа шаблона аргументы, являющиеся ссылками, рассматриваются как ссылками не являющиеся, т.е. их "ссылочность" игнорируется;
— при выводе типов для параметров функции, являющихся универсальными ссылками
(&&), lvalue-аргументы рассматриваются специальным образом;
— при выводе типов для параметров, передаваемых по значению, аргументы, объявленные как
const и/или volatile,
рассматриваются как не являющиеся ни const, ни volatile;
— в процессе вывода типа шаблона аргументы, являющиеся именами массивов или функций, преобразуются в указатели, если только они не использованы для инициализациии ссылок;
— вывод типа
auto обычно такой же, как и вывод типа шаблона, но вывод типа auto, в отличие от вывода типа шаблона, предполагает, что инициализатор в фигурных скобках представляет std::initializer_list;
— auto в возвращаемом типе функции или параметре лямбда-выражения влечет применение вывода типа шаблона, а не вывода типа auto;

decltype почти всегда дает тип переменной или выражения без каких-либо изменений;
— для
lvalue-выражений типа Т, отличных от имени,
decltype всегда дает тип T&;
— C++14 поддерживает конструкцию decltype(auto), которая, подобно auto, выводит тип из его инициализатора, но выполняет вывод типа с использованием правил decltype.


Механизмы выведения типов значительно упрощают программирование на C++, делая код более лаконичным и удобным для чтения.
Особенно полезны они при работе с шаблонами и обобщенным программированием.
#430_Cpp_DBG

Как посмотреть выведенные типы в С++?

Просмотр выведенных типов в C++ полезен для отладки и понимания поведения программы.
Самый простой способ — использование оператора typeid, возвращающего информацию о типе объекта или выражения.


Примеры:

Использование typeid typeid возвращает объект типа std::type_info, который содержит информацию о типе.
Метод name() этого объекта возвращает строку, представляющую имя типа.
Однако строковое представление имени типа зависит от конкретной реализации компилятора и может быть трудночитаемым.
#include <iostream>
#include <typeinfo>

int main() {
auto x = 5;
std::cout << "Тип x: " << typeid(x).name() << std::endl;

decltype(3.14) y;
std::cout << "Тип y: " << typeid(y).name() << std::endl;

return 0;
}


На выходе получим строки, соответствующие именам типов, например:
Тип x: i
Тип y: d

Где i обозначает int, а d — double.
Имена могут отличаться в зависимости от компилятора.


Деманглирование имен типов — если нужны более читаемые имена типов, можно воспользоваться деманглером.
Для GCC и Clang встроенный деманглер доступен через функцию abi::__cxa_demangle:
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

/* Функция для вывода имени типа в удобочитаемом формате */
std::string demangledTypeName(const char* mangledName) {
int status;
char* demangled = abi::__cxa_demangle(mangledName, nullptr, nullptr, &status);
if (demangled == nullptr || status != 0) {
return mangledName;
}
std::string result(demangled);
free(demangled);
return result;
}

int main() {
auto x = 5;
std::cout << "Тип x: " << demangledTypeName(typeid(x).name()) << std::endl;

decltype(3.14) y;
std::cout << "Тип y: " << demangledTypeName(typeid(y).name()) << std::endl;

return 0;
}


Теперь результат будет выглядеть так:
Тип x: int
Тип y: double

Это гораздо удобнее для восприятия.


Отладка с помощью IDE — многие современные IDE, такие как Visual Studio, CLion, Eclipse и другие, предоставляют удобные инструменты для просмотра типов переменных прямо во время отладки.
Например, в Visual Studio можно поставить точку останова и просмотреть типы всех переменных в окне Watch или Locals.
Важно помнить!!! При работе со сложными типами, информация, выводимая IDE, может оказаться не точной и не особенно полезной.


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


Предположим, что хотим узнать типы, выведенные для х и у из следующего примера:
const int theAnswer = 42 ;
auto х theAnswer;
auto у = &theAnswer;


Сначала объявим шаблон класса, но не определим его:
template<typename Т>
class TD;
// Только объявление TD;


Попытки инстанцировать этот шаблон приведут к сообщению об ошибке, поскольку инстанцируемый шаблон отсутствует.
Чтобы увидеть типы х и у, просто попробуем инстанцировать TD с их типами:
 /* Сообщение об ошибке будет содержать типы х и у */
TD<decltype ( x ) > хТуре ;
TD<decltype ( y ) > уТуре ;


Используем имена переменных вида
variaЫeNameType, чтобы проще найти интересующую информацию в сообщении об ошибке.
В зависимости от компилятора сообщения об ошибке могут отличаться:

error: aggregate ' TD<int> хТуре ' has incomplete type and
cannot Ье def ined
error: aggregate ' TD<const int *> уТуре ' has incomplete type
and cannot Ье defined


или например:
error: ' хТуре ' uses undefined class ' TD<int> '
error : ' уТуре ' uses unde fined class ' TD<const int * > '


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

.
Сторонние библиотеки — там, где std::tуре_info::name и IDE могут ошибаться, библиотека Boost Typelndex (часто именуемая как Boost.Typelndex) приведет к успеху.
Boost.TypeIndex
не является частью стандарта С++, но точно так же частью стандарта не являются ни IDE, ни шаблоны наподобие рассмотренного выше TD.
Библиотеки Boost (доступные по адресу boost.org) являются кроссплатформенными, с открытым исходным кодом и с лицензией, разработанной так, чтобы быть приемлемой даже для самых параноидальных юристов, означает, что код с применением библиотек Boost переносим практически так же хорошо, как и код, основанный на стандартной библиотеке.

Рассмотрим более сложный пример:
/* Шаблонная функция, вызываемая далее */
template<typename T>
void f(const T& param);

// Фабричная функция
std::vector<Widget> createVec();
/* Инициализация vw возвратом фабричной функции */
const auto vw = createVec();
if (!vw.empty()) {
// Вызов f
f(&vw[0]);
}

Этот код, включает пользовательский тип Widget, контейнер STL std::vector и переменную auto vw, является более представительным и интересным примером.
Интересно узнать, какие типы выводятся для параметра типа шаблона T и для параметра param функции f.
Вот как функция f может выдать точную информацию о типах с использованием Boost.Typelndex:
#include <boost/type_index.hpp>

template<typename T>
void f(const T& param) {
using std::cout;
using boost::typeindex::type_id_with_cvr;

// Вывод информации о T
cout << "T: "
<< type_id_with_cvr<T> ().pretty_name()
<< '\n';

// Вывод информации о типе param
cout << "param: "
<< type_id_with_cvr<decltype (param)>().pretty_пame()
<< '\n' ;
}


Как это работает?

Шаблон функции boost::typeindex::type_id_with_cvr получает аргумент типа (тип, о котором мы хотим получить информацию) и не удаляет const, volatile или квалификатор ссылки (о чем и говорит "with_cvr" в имени шаблона).
Результатом является объект boost::typeindex::type_index, функция-член pretty_name которого дает std::string с удобочитаемым представлением типа.
При такой реализации f обратимся к вызову, который дает неверную информацию о типе param при использовании typeid:
// Фабричная функция
std::vector<Widget> createVec();
/* Инициализация vw с помощью фабричной функции */
const auto vw = createVec();
if (!vw.empty()) {
// Вызов f
f(&vw[0]);
}


После компиляции с помощью компиляторов GNU и Clang Boost.Typelndex дает следующий (точный) результат:
Т: Widget const *
param: Widget const * const &


Применение компилятора Microsoft дает по сути то же самое:
T = class Widget const *
param = class Widget const * const &



Такое единообразие - это хорошо, но важно помнить, что редакторы
IDE, сообщения об ошибках компилятора и библиотеки наподобие Boost.Typelпdex являются всего
лишь инструментами, которые можно использовать для выяснения того, какие типы выводит компилятор.

Это может быть полезно, но не может заменить понимания информации о выводе типов в С++.
Просмотр выведенных типов в C++ может быть полезен для диагностики ошибок и лучшего понимания работы программы.
Используйте typeid и деманглеры для получения информации о типах, а также возможности IDE для удобной отладки.

.
СЛЕДУЕТ ЗАПОМНИТЬ!!!
— выводимые типы часто можно просмотреть с помощью редакторов IDE, сообщений об ошибках компиляции и с использованием библиотеки Boost.Typelпdex.
— результаты, которые выдают некоторые инструменты, могут оказаться как неточными, так и бесполезными, так что понимание правил вывода типов в С++ является совершенно необходимым.
#431_Cpp_DBG_LIB_PkS

Для чего нужны Unit test?
Чем отличается от Functional Test?


Unit tests и functional tests являются важными частями процесса тестирования ПО, однако у них разные цели и подходы к тестированию.


Unit Tests (Юнит-тесты) — предназначены для проверки отдельных компонентов (модулей, классов, методов) программы в изоляции друг от друга.
Основная цель юнит-тестов — убедиться, что каждая часть системы работает правильно и выполняет свои задачи согласно спецификации.

Особенности:
Изоляция — каждый юнит-тест проверяет только одну единицу кода, изолируя её от других частей системы. Это достигается за счет моков (mocks) и заглушек (stubs).
Быстрота выполнения — юнит-тесты обычно выполняются очень быстро, поскольку они работают с небольшими фрагментами кода.
Покрытие кода — хороший набор юнит-тестов должен покрывать большинство возможных путей выполнения кода, включая нормальные сценарии, граничные случаи и исключения.
Автоматизация — юнит-тесты легко автоматизируются и могут запускаться часто, даже после каждого изменения в коде.

Примеры инструментов:
C++: GoogleTest, Catch2, Boost.Test
Java: JUnit, TestNG
Python: unittest, pytest
JavaScript: Jest, Mocha


Functional Tests (Функциональные тесты) — проверяют поведение всей системы или её крупных подсистем с точки зрения пользователя или другого внешнего интерфейса.
Они оценивают, соответствует ли система функциональным требованиям и спецификациям.

Особенности:
Интеграция — функциональные тесты проверяют взаимодействие между различными компонентами системы, включая базы данных, внешние сервисы и пользовательские интерфейсы.
Реалистичность — функциональные тесты имитируют реальные сценарии использования системы, чтобы проверить её функциональность в условиях, близких к реальным.
Время выполнения — функциональные тесты обычно занимают больше времени, чем юнит-тесты, так как они охватывают большие части системы.
Тестовые сценарии — функциональные тесты часто основаны на сценариях использования (use cases), описывающих последовательность действий пользователя.

Примеры инструментов:
Web: Selenium, Cypress
API: Postman, Rest Assured
Desktop applications: Sikuli, AutoIt
Mobile apps: Appium, Espresso


Отличия между Unit Tests и Functional Tests:

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

Уровень абстракции:
Юнит-тесты работают на уровне кода, проверяя отдельные методы и классы.
Функциональные тесты работают на уровне взаимодействия пользователя с системой.

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

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

Инструменты:
Для юнит-тестов используются специализированные фреймворки, такие как GoogleTest или JUnit.
Для функциональных тестов применяются инструменты автоматизации, такие как Selenium или Appium.


Оба вида тестов важны для обеспечения качества программного продукта.
Юнит-тесты помогают разработчикам находить ошибки на ранних этапах разработки, а функциональные тесты гарантируют, что система удовлетворяет требованиям пользователей и работает корректно в реальных условиях.
Комбинируя эти подходы, можно достичь высокого уровня надежности и стабильности ПО.
#432_Cpp_DBG_LIB_PkS

Как тестировать код?
Какой используете фреймворк для С и С++?


Тестирование кода — важный этап разработки ПО, который помогает убедиться, что код работает корректно и соответствует требованиям.


Вот несколько шагов, которые можно использовать для тестирования кода:

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

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


Системное тестирование — тестирование всей системы в целом.
Это включает в себя проверку всех функций и возможностей системы.

Регрессионное тестирование — тестирование, направленное на проверку того, что изменения в коде не привели к появлению новых ошибок.

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

Тестирование безопасности — тестирование на наличие уязвимостей и проверка того, что система защищена от атак.

Тестирование удобства использования — тестирование интерфейса пользователя на удобство и простоту использования.


Для тестирования кода на языках C и C++ можно использовать различные фреймворки:
Google Test (gtest) — популярный фреймворк для модульного тестирования на C++. Он предоставляет широкий набор функций для написания тестов и поддерживает как C, так и C++.
Catch2 — легкий и удобный фреймворк для модульного тестирования на C++. Он прост в использовании и не требует сложной настройки.
CMake — инструмент для сборки и тестирования проектов на C и C++. Он поддерживает интеграцию с различными тестовыми фреймворками.
Boost.Test — фреймворк для модульного тестирования на C++, который является частью библиотеки Boost.
Unity — легкий фреймворк для модульного тестирования на C. Он прост в использовании и подходит для небольших проектов.
CppUnit — фреймворк для модульного тестирования на C++, который является портом JUnit для C++.


Выбор фреймворка зависит от конкретных требований проекта и предпочтений разработчика.
#433_Cpp_DBG_LIB_PkS

Что такое mock?

Mock (или "мок-объект") — объект-заместитель, который имитирует поведение реального объекта в тестируемом коде.
Моки используются в модульном тестировании для замены зависимостей тестируемого объекта, чтобы изолировать его от внешних систем и проверить его поведение в контролируемых условиях.


Основные цели использования моков:
Изоляция тестируемого кода — моки позволяют изолировать тестируемый объект от внешних зависимостей, таких как базы данных, внешние API, файловые системы и т.д.
Это делает тесты более стабильными и независимыми от состояния внешних систем.

Контроль поведения зависимостей — моки позволяют задавать определённое поведение для зависимостей, что помогает тестировать различные сценарии, такие как успешные и неудачные вызовы, исключения и т.д.
Ускорение тестов — моки могут значительно ускорить выполнение тестов, так как они не требуют реальных внешних систем, которые могут быть медленными или сложными в настройке.

Пример использования мока на C++ с использованием Google Test:
#include <gtest/gtest.h>
#include "gmock/gmock.h"

/* Пример интерфейса для зависимостей */
class Database {
public:
virtual ~Database() {}
virtual bool SaveData(const std::string& data) = 0;
};

// Тестируемый класс
class MyClass {
public:
MyClass(Database* db) : db_(db) {}
bool Save(const std::string& data) {
return db_->SaveData(data);
}
private:
Database* db_;
};

// Мок для Database
class MockDatabase : public Database {
public:
MOCK_METHOD(bool, SaveData, (const std::string& data), (override));
};

// Тесты
TEST(MyClassTest, SaveSuccess) {
MockDatabase mockDb;
ON_CALL(mockDb, SaveData("test data")).WillByDefault(Return(true));

MyClass myClass(&mockDb);
EXPECT_TRUE(myClass.Save("test data"));
}

TEST(MyClassTest, SaveFailure) {
MockDatabase mockDb;
ON_CALL(mockDb, SaveData("test data")).WillByDefault(Return(false));

MyClass myClass(&mockDb);
EXPECT_FALSE(myClass.Save("test data"));
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}


Объяснение:
Database — интерфейс, который используется для работы с базой данных.
MyClass — класс, который использует объект базы данных для сохранения данных.
MockDatabase — мок-объект, который имитирует поведение базы данных.
В тестах SaveSuccess и SaveFailure настраиваем мок-объект так, чтобы он возвращал либо true, либо false при вызове метода SaveData.


Преимущества использования моков:
Изоляция — тестируемый код изолирован от внешних зависимостей.
Повторяемость — тесты всегда дают одинаковые результаты, так как моки контролируют поведение зависимостей.
Скорость — тесты выполняются быстрее, так как не требуется взаимодействие с реальными внешними системами.

Недостатки:
Сложность — настройка моков может быть сложной, особенно для больших и сложных систем.
Поддержка — моки требуют регулярного обновления и поддержки, чтобы соответствовать изменениям в реальном коде.


Моки являются важным инструментом в арсенале разработчика, особенно при разработке сложных систем, где требуется высокое покрытие тестами и надёжность.