DNK_C_C++_Go_Rust
45 subscribers
14 photos
45 links
DNK - дневник кодера С и С++
Download Telegram
#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.


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

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


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

Сколько тестов нужно написать на одну функцию?

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


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

Пример:
Допустим, у вас есть функция, которая вычисляет факториал числа:
int factorial(int n) {
if (n < 0) {
throw std::invalid_argument("n must be non-negative");
}
int result = 1;
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}



Рекомендации по тестированию:

Тест на корректные входные данные — проверить, что функция возвращает правильный результат для небольшого положительного числа, например, factorial(5) == 120.

Тест на граничные случаи — проверить, что функция возвращает 1 для n = 0 и n = 1.
Проверить, что функция возвращает правильный результат для большого числа, например, factorial(10) == 3628800.

Тест на обработку ошибок — проверить, что функция бросает исключение std::invalid_argument при передаче отрицательного числа.

Тест на граничные случаи обработки ошибок — проверить, что функция корректно обрабатывает минимальное отрицательное число, например, factorial(-1).

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


Пример тестов с использованием Google Test:
#include <gtest/gtest.h>

int factorial(int n);

TEST(FactorialTest, CorrectInput) {
EXPECT_EQ(factorial(5), 120);
}

TEST(FactorialTest, BoundaryCases) {
EXPECT_EQ(factorial(0), 1);
EXPECT_EQ(factorial(1), 1);
EXPECT_EQ(factorial(10), 3628800);
}

TEST(FactorialTest, ErrorHandling) {
EXPECT_THROW(factorial(-1), std::invalid_argument);
}

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



Количество тестов зависит от специфики функции и требований к тестовому покрытию.
В общем случае, старайтесь покрыть все возможные сценарии использования функции, включая нормальные, граничные и ошибочные случаи. Это поможет обеспечить надёжность и стабильность вашего кода.
#435_Cpp_PkS_TP

Что такое побочный эффект, идемпотентность и чистые функции?

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


Побочный эффект (Side Effect) — любое изменение состояния программы, которое происходит в результате выполнения функции, помимо её основного результата.
Другими словами, это когда функция изменяет что-то за пределами своей области видимости (например, глобальные переменные, файлы, базы данных и т.д.) или взаимодействует с внешним миром (например, выводит данные на экран, отправляет запросы в сеть).

Примеры:
int global_variable = 0;

void increment_global() {
/* Побочный эффект: изменение глобальной переменной */
global_variable++;
}

void print_message(const std::string& message) {
// Побочный эффект: вывод на экран
std::cout << message << std::endl;
}



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

Примеры:
x = 5; — идемпотентная операция, так как повторное выполнение не изменит значение x.
Удаление файла — идемпотентная операция, так как повторное удаление уже удалённого файла не изменит состояние системы.

Неидемпотентные операции:
x++; — неидемпотентная операция, так как повторное выполнение изменит значение x.
Добавление записи в базу данных — неидемпотентная операция, так как повторное выполнение добавит дублирующую запись.


Чистые функции (Pure Functions) — функция, которая:
Не имеет побочных эффектов: не изменяет состояние программы и не взаимодействует с внешним миром.
Детерминированна: всегда возвращает один и тот же результат для одних и тех же входных данных.

Пример:
int add(int a, int b) {
/* Чистая функция: не имеет побочных эффектов и детерминирована */
return a + b;
}


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


Понимание этих концепций помогает писать более предсказуемый и надёжный код.
Чистые функции облегчают тестирование и отладку, а идемпотентные операции полезны в распределённых системах и сценариях с возможностью повторных запросов.
#436_ADM_DvOp_PkS

Что такое контейнеризация и в чем преимущества и недостатки?
Что такое Docker или иной инструмент контейнеризации?


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


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


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


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

Преимущества Docker:
Простота использования — Docker предоставляет интуитивно понятный интерфейс для работы с контейнерами.
Широкая поддержка — Docker поддерживается большинством облачных платформ и операционных систем.
Большое сообщество — Docker имеет большое и активное сообщество разработчиков, что обеспечивает доступность документации, примеров и решений для различных задач.
Интеграция с другими инструментами — Docker легко интегрируется с другими инструментами DevOps, такими как Kubernetes, Jenkins и др.

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


Альтернативы Docker.
Существуют и другие инструменты контейнеризации, такие как:
Podman — альтернатива Docker, которая предоставляет аналогичные функции, но без использования демона.
LXC/LXD — инструмент для создания и управления контейнерами, который фокусируется на изоляции и безопасности.
Rocket (rkt) — проект, разработанный компанией CoreOS, который предлагает альтернативный подход к контейнеризации.


Каждый из этих инструментов имеет свои особенности и подходит для различных сценариев использования.
Выбор конкретного инструмента зависит от конкретных требований и предпочтений команды разработчиков и администраторов.
#437_ADM_DvOp_PkS

Что такое CI/CD и какие преимущества приносит для разработчика?

CI/CD (Continuous Integration/Continuous Delivery или Continuous Deployment) — методология и набор практик, направленных на автоматизацию процессов разработки, тестирования, интеграции и развертывания программного обеспечения.
CI/CD позволяет разработчикам быстрее и надежнее выпускать новые версии приложений, минимизируя ручные операции и снижая вероятность ошибок.

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

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


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


Инструменты CI/CD:
Jenkins — один из самых популярных инструментов для CI/CD, предоставляющий гибкие возможности настройки и интеграции с различными системами.
GitLab CI/CD — встроенный в GitLab инструмент для CI/CD, который интегрируется с системой контроля версий.
CircleCI — облачный сервис для CI/CD, который поддерживает различные языки программирования и платформы.
Travis CI — популярный облачный сервис для CI/CD, особенно среди проектов с открытым исходным кодом.
Bamboo — инструмент от Atlassian, который интегрируется с Jira и Bitbucket.


Выбор конкретного инструмента зависит от требований проекта, инфраструктуры и предпочтений команды.