From 6e7938cc80874da1e6d971422933921366b4007c Mon Sep 17 00:00:00 2001 From: Arity-T Date: Wed, 2 Apr 2025 22:35:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D0=B0=D1=82=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab3/.gitignore | 7 + lab3/programm/passgen.py | 178 ++++++ lab3/programm/passgen_refactor.py | 173 ++++++ lab3/report.tex | 865 ++++++++++++++++++++++++++++++ 4 files changed, 1223 insertions(+) create mode 100644 lab3/.gitignore create mode 100644 lab3/programm/passgen.py create mode 100644 lab3/programm/passgen_refactor.py create mode 100644 lab3/report.tex diff --git a/lab3/.gitignore b/lab3/.gitignore new file mode 100644 index 0000000..8bbef0e --- /dev/null +++ b/lab3/.gitignore @@ -0,0 +1,7 @@ +**/* +!.gitignore +!report.tex +!img +!img/** +!programm +!programm/*.py \ No newline at end of file diff --git a/lab3/programm/passgen.py b/lab3/programm/passgen.py new file mode 100644 index 0000000..b731c44 --- /dev/null +++ b/lab3/programm/passgen.py @@ -0,0 +1,178 @@ +import random +import string + +max_password_length = 100 # Максимальная длина пароля + + +def get_valid_int(prompt, min_value=0, max_value=None): + while True: + user_input = input(prompt).strip() + if not user_input: + print("Ошибка: ввод не должен быть пустым. Попробуйте снова.") + continue + try: + value = int(user_input) + if value < min_value: + print( + f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова." + ) + continue + if max_value and value > max_value: + print( + f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова." + ) + continue + return value + except ValueError: + print("Ошибка: введите корректное целое число.") + + +def get_yes_no(prompt): + while True: + user_input = input(prompt).strip().lower() + if user_input in ["yes", "y"]: + return True + if user_input in ["no", "n"]: + return False + print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').") + + + +def get_user_input(): + """Запрашивает у пользователя параметры генерации пароля с проверкой ввода.""" + global max_password_length + + length = get_valid_int( + f"Введите длину пароля (1-{max_password_length}): ", + min_value=1, + max_value=max_password_length, + ) + + use_lower = get_yes_no("Использовать строчные буквы? (yes/y, no/n): ") + use_upper = get_yes_no("Использовать заглавные буквы? (yes/y, no/n): ") + use_digits = get_yes_no("Использовать цифры? (yes/y, no/n): ") + use_special = get_yes_no("Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): ") + + # Проверяем, что хотя бы один тип символов выбран + if not (use_lower or use_upper or use_digits or use_special): + print("Ошибка: необходимо выбрать хотя бы один тип символов.") + return get_user_input() # Повторный ввод всех данных + + # Запрашиваем минимальное количество каждого типа символов + min_lower = ( + get_valid_int("Минимальное количество строчных букв: ", 0) if use_lower else 0 + ) + min_upper = ( + get_valid_int("Минимальное количество заглавных букв: ", 0) if use_upper else 0 + ) + min_digits = get_valid_int("Минимальное количество цифр: ", 0) if use_digits else 0 + min_special = ( + get_valid_int("Минимальное количество спецсимволов: ", 0) if use_special else 0 + ) + + return ( + length, + use_lower, + use_upper, + use_digits, + use_special, + min_lower, + min_upper, + min_digits, + min_special, + ) + + +def validate_input(length, min_lower, min_upper, min_digits, min_special): + """Проверяет, что длина пароля больше суммы минимальных значений.""" + total_required = min_lower + min_upper + min_digits + min_special + if total_required > length: + print( + f"Ошибка: сумма минимальных значений ({total_required}) превышает длину пароля ({length})." + ) + return False + return True + + +def generate_mandatory_chars( + min_lower, + min_upper, + min_digits, + min_special, + lower_chars, + upper_chars, + digit_chars, + special_chars, +): + """Генерирует обязательные символы пароля.""" + password = ( + random.choices(lower_chars, k=min_lower) + + random.choices(upper_chars, k=min_upper) + + random.choices(digit_chars, k=min_digits) + + random.choices(special_chars, k=min_special) + ) + return password + + +def fill_password(password, length, all_chars): + """Дополняет пароль случайными символами до нужной длины.""" + remaining_length = length - len(password) + password += random.choices(all_chars, k=remaining_length) + return password + + +def shuffle_password(password): + """Перемешивает символы пароля случайным образом.""" + random.shuffle(password) + return "".join(password) + + +def generate_password( + length, + use_lower, + use_upper, + use_digits, + use_special, + min_lower, + min_upper, + min_digits, + min_special, +): + """Генерирует пароль с учётом заданных параметров.""" + lower_chars = string.ascii_lowercase if use_lower else "" + upper_chars = string.ascii_uppercase if use_upper else "" + digit_chars = string.digits if use_digits else "" + special_chars = "!@#$%^&*" if use_special else "" + + all_chars = lower_chars + upper_chars + digit_chars + special_chars + + while not validate_input(length, min_lower, min_upper, min_digits, min_special): + print("Пожалуйста, введите параметры заново.") + return generate_password(*get_user_input()) + + password = generate_mandatory_chars( + min_lower, + min_upper, + min_digits, + min_special, + lower_chars, + upper_chars, + digit_chars, + special_chars, + ) + password = fill_password(password, length, all_chars) + password = shuffle_password(password) + + return password + + +def main(): + """Основная функция программы.""" + max_password_length = 100 + user_data = get_user_input() + password = generate_password(*user_data) + print("Сгенерированный пароль:", password) + + +if __name__ == "__main__": + main() diff --git a/lab3/programm/passgen_refactor.py b/lab3/programm/passgen_refactor.py new file mode 100644 index 0000000..00ffa97 --- /dev/null +++ b/lab3/programm/passgen_refactor.py @@ -0,0 +1,173 @@ +""" +Модуль для генерации безопасных паролей с разными настройками. +Пользователь может задавать длину, типы символов и минимальное количество каждого типа. +""" + +import random +import string + +MAX_PASSWORD_LENGTH = 100 # Максимальная длина пароля + + +def get_valid_int(prompt, min_value=0, max_value=None): + """Запрашивает у пользователя целое число, проверяя корректность ввода.""" + while True: + user_input = input(prompt).strip() + if not user_input: + print("Ошибка: ввод не должен быть пустым. Попробуйте снова.") + continue + try: + value = int(user_input) + if value < min_value: + print( + f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова." + ) + continue + if max_value and value > max_value: + print( + f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова." + ) + continue + return value + except ValueError: + print("Ошибка: введите корректное целое число.") + + +def get_yes_no(prompt): + """Запрашивает у пользователя 'yes'/'y' или 'no'/'n', проверяя корректность ввода.""" + while True: + user_input = input(prompt).strip().lower() + if user_input in ["yes", "y"]: + return True + if user_input in ["no", "n"]: + return False + print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').") + + +def get_user_input(): + """Запрашивает у пользователя параметры генерации пароля с проверкой ввода.""" + settings = { + "length": get_valid_int( + f"Введите длину пароля (1-{MAX_PASSWORD_LENGTH}): ", + min_value=1, + max_value=MAX_PASSWORD_LENGTH, + ), + "use_lower": get_yes_no("Использовать строчные буквы? (yes/y, no/n): "), + "use_upper": get_yes_no("Использовать заглавные буквы? (yes/y, no/n): "), + "use_digits": get_yes_no("Использовать цифры? (yes/y, no/n): "), + "use_special": get_yes_no( + "Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): " + ), + } + + # Проверяем, что хотя бы один тип символов выбран + if not any( + [ + settings["use_lower"], + settings["use_upper"], + settings["use_digits"], + settings["use_special"], + ] + ): + print("Ошибка: необходимо выбрать хотя бы один тип символов.") + return get_user_input() # Повторный ввод всех данных + + # Запрашиваем минимальное количество каждого типа символов + settings["min_lower"] = ( + get_valid_int("Минимальное количество строчных букв: ", 0) + if settings["use_lower"] + else 0 + ) + settings["min_upper"] = ( + get_valid_int("Минимальное количество заглавных букв: ", 0) + if settings["use_upper"] + else 0 + ) + settings["min_digits"] = ( + get_valid_int("Минимальное количество цифр: ", 0) + if settings["use_digits"] + else 0 + ) + settings["min_special"] = ( + get_valid_int("Минимальное количество спецсимволов: ", 0) + if settings["use_special"] + else 0 + ) + + return settings + + +def validate_input(settings): + """Проверяет, что длина пароля больше суммы минимальных значений.""" + total_required = sum( + [ + settings["min_lower"], + settings["min_upper"], + settings["min_digits"], + settings["min_special"], + ] + ) + if total_required > settings["length"]: + print( + f"Ошибка: сумма минимальных значений ({total_required})" + f" превышает длину пароля ({settings['length']})." + ) + return False + return True + + +def generate_mandatory_chars(settings, char_sets): + """Генерирует обязательные символы пароля.""" + password = ( + random.choices(char_sets["lower"], k=settings["min_lower"]) + + random.choices(char_sets["upper"], k=settings["min_upper"]) + + random.choices(char_sets["digits"], k=settings["min_digits"]) + + random.choices(char_sets["special"], k=settings["min_special"]) + ) + return password + + +def fill_password(password, length, all_chars): + """Дополняет пароль случайными символами до нужной длины.""" + remaining_length = length - len(password) + password += random.choices(all_chars, k=remaining_length) + return password + + +def shuffle_password(password): + """Перемешивает символы пароля случайным образом.""" + random.shuffle(password) + return "".join(password) + + +def generate_password(settings): + """Генерирует пароль с учётом заданных параметров.""" + char_sets = { + "lower": string.ascii_lowercase if settings["use_lower"] else "", + "upper": string.ascii_uppercase if settings["use_upper"] else "", + "digits": string.digits if settings["use_digits"] else "", + "special": "!@#$%^&*" if settings["use_special"] else "", + } + + all_chars = "".join(char_sets.values()) + + while not validate_input(settings): + print("Пожалуйста, введите параметры заново.") + return generate_password(get_user_input()) + + password = generate_mandatory_chars(settings, char_sets) + password = fill_password(password, settings["length"], all_chars) + password = shuffle_password(password) + + return password + + +def main(): + """Основная функция программы.""" + user_settings = get_user_input() + password = generate_password(user_settings) + print("Сгенерированный пароль:", password) + + +if __name__ == "__main__": + main() diff --git a/lab3/report.tex b/lab3/report.tex new file mode 100644 index 0000000..b7d7de7 --- /dev/null +++ b/lab3/report.tex @@ -0,0 +1,865 @@ +\documentclass[a4paper, final]{article} +%\usepackage{literat} % Нормальные шрифты +\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта +\usepackage{tabularx} +\usepackage[T2A]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[russian]{babel} +\usepackage{amsmath} +\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry} +\usepackage{ragged2e} %для растягивания по ширине +\usepackage{setspace} %для межстрочно го интервала +\usepackage{moreverb} %для работы с листингами +\usepackage{indentfirst} % для абзацного отступа +\usepackage{moreverb} %для печати в листинге исходного кода программ +\usepackage{pdfpages} %для вставки других pdf файлов +\usepackage{tikz} +\usepackage{graphicx} +\usepackage{afterpage} +\usepackage{longtable} +\usepackage{float} + + + +% \usepackage[paper=A4,DIV=12]{typearea} +\usepackage{pdflscape} +% \usepackage{lscape} + +\usepackage{array} +\usepackage{multirow} + +\renewcommand\verbatimtabsize{4\relax} +\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге +\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице +\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы +\usepackage{listings} %листинги +\usepackage{xcolor} % цвета +\usepackage{hyperref}% для гиперссылок +\usepackage{enumitem} %для перечислений + +\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}} + + +\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях + +\hypersetup{colorlinks, + allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные) + +% подгружаемые языки — подробнее в документации listings (это всё для листингов) +\lstloadlanguages{ SQL} +% включаем кириллицу и добавляем кое−какие опции +\lstset{tabsize=2, + breaklines, + basicstyle=\footnotesize, + columns=fullflexible, + flexiblecolumns, + numbers=left, + numberstyle={\footnotesize}, + keywordstyle=\color{blue}, + inputencoding=cp1251, + extendedchars=true +} +\lstdefinelanguage{MyC}{ + language=SQL, +% ndkeywordstyle=\color{darkgray}\bfseries, +% identifierstyle=\color{black}, +% morecomment=[n]{/**}{*/}, +% commentstyle=\color{blue}\ttfamily, +% stringstyle=\color{red}\ttfamily, +% morestring=[b]", +% showstringspaces=false, +% morecomment=[l][\color{gray}]{//}, + keepspaces=true, + escapechar=\%, + texcl=true +} + +\textheight=24cm % высота текста +\textwidth=16cm % ширина текста +\oddsidemargin=0pt % отступ от левого края +\topmargin=-1.5cm % отступ от верхнего края +\parindent=24pt % абзацный отступ +\parskip=5pt % интервал между абзацами +\tolerance=2000 % терпимость к "жидким" строкам +\flushbottom % выравнивание высоты страниц + + +% Настройка листингов +\lstset{ + language=python, + extendedchars=\true, + inputencoding=utf8, + keepspaces=true, + % captionpos=b, % подписи листингов снизу +} + +\begin{document} % начало документа + + + + % НАЧАЛО ТИТУЛЬНОГО ЛИСТА + \begin{center} + \hfill \break + \hfill \break + \normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\ + федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]} + \normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt] + \normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt] + \normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\ + + \hfill \break + \hfill \break + \hfill \break + \hfill \break + \large{Лабораторная работа №3}\\ + \large{<<Статический анализ кода приложений>>}\\ + \large{по дисциплине}\\ + \large{<<Методы тестирования программного обеспечения>>}\\ + \hfill \break + + % \hfill \break + \hfill \break + \end{center} + + \small{ + \begin{tabular}{lrrl} + \!\!\!Студент, & \hspace{2cm} & & \\ + \!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\ + \!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Курочкин М. А. \\\\ + &&\hspace{4cm} + \end{tabular} + \begin{flushright} + <<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г. + \end{flushright} + } + + \hfill \break + % \hfill \break + \begin{center} \small{Санкт-Петербург, 2025} \end{center} + \thispagestyle{empty} % выключаем отображение номера для этой страницы + + % КОНЕЦ ТИТУЛЬНОГО ЛИСТА + \newpage + + \tableofcontents + + + \newpage + + \section{Постановка задачи} + \subsection{Задачи лабораторной работы} + Задачи лабораторной работы: + \begin{itemize} + \item изучить методы статического тестирование; + \item провести статическое тестирование программы; + \item проанализировать полученный результат; + \item рассмотреть рекомендации статического анализатора и при необходимости внести изменения в программу. + \end{itemize} + + + \newpage + \section {Статическое тестирование} + Статическое тестирование — это оценка элемента тестирования, при которой не происходит выполнения кода, и которая может быть проведена вручную или с помощью инструментариев. Объектом тестирования может быть документация или исходный код, а сам процесс возможен на любом этапе жизненного цикла ПО (Согласно ГОСТ Р 56920-2024 (раздел 5.5.2)). + + Статическое тестирование включает в себя: + \begin{itemize} + \item Проверку документации: требования, спецификации, архитектурные решения. + \item Анализ исходного кода: поиск синтаксических ошибок, нарушений стандартов + кодирования и потенциальных уязвимостей. + \end{itemize} + + Цель статического тестирования — выявление дефектов на ранних стадиях разработки, что снижает затраты на их исправление. Как отмечает Гленфорд Майерс + в книге «Искусство тестирования», до 60\% ошибок можно обнаружить до запуска + программы. Это делает статическое тестирование критически важным инструментом для повышения качества ПО. + + \subsection{Основные формы статического тестирования} + Используются специальные инструменты -- «статические анализаторы», которые автоматически: + + \begin{itemize} + \item Проверяют соответствие кода стандартам кодирования (Code Style, Coding + Guidelines). + \item Ищут потенциальные ошибки (например, неиспользуемые переменные, некорректные приведения типов, опасные конструкции). + \item Указывают на потенциальные уязвимости в безопасности (например, возможности для SQL-инъекций, потенциальные переполнения буфера). + \item Анализируют потоки данных, чтобы понять, где значения могут принимать + нежелательные (NullPointerException и др.) значения. + \end{itemize} + + + \subsection{Разница между статическим анализатором и инспекцией кода за столом} + \subsubsection*{Способ выполнения} + \begin{itemize} + \item Статический анализатор: запускается автоматически на исходном коде и выдает отчёт об обнаруженных проблемах. Анализатор следует набору заранее + заданных правил (линейный и/или межпроцедурный анализ, анализ потока + данных и т. д.). + \item Инспекция кода за столом: проводится людьми (разработчиками, тестировщиками). Участники встречи просматривают код построчно (или анализируют его логические куски) и обсуждают архитектурные, логические, стилевые и + другие аспекты. + \end{itemize} + + \subsubsection*{Область охвата} + \begin{itemize} + \item Статический анализатор: + \begin{itemize} + \item Ориентирован в основном на типичные ошибки и «сигнализирует» о + потенциальных проблемах, отклонениях от правил кодирования, уязвимостях в безопасности. + \item Хорошо справляется с рутинным поиском большого количества распространённых проблем (например, неиспользуемые переменные, неочевидные «if» без «else», выход за границы массива и т. п.). + \end{itemize} + + \item Инспекция кода: + \begin{itemize} + \item Позволяет вскрыть более сложные логические ошибки, несоответствие + требованиям, некорректную бизнес-логику. + \item Во время обсуждения могут выявиться проблемы, которые невозможно уловить статическим анализатором: «Почему этот алгоритм выбран + именно так?», «Соответствует ли это бизнес-требованиям?», «Оптимальна ли структура данных?», «Легко ли будет поддерживать этот код?». + \end{itemize} + \end{itemize} + + \subsubsection*{Глубина и виды обнаруживаемых дефектов} + \begin{itemize} + \item Статический анализатор: находит скорее «структурные» и «синтаксические» + дефекты и уязвимости (неиспользуемые переменные, неправильные операции + с памятью, отсутствие проверок). Может не понимать, «хорош ли» сам алгоритм. + \item Инспекция кода: ориентирована на логику, архитектуру, читаемость, потенциальные проблемы взаимодействия модулей. Тут важны не только дефекты + самого кода, но и соответствует ли он требованиям или лучшим практикам + проектирования. + \end{itemize} + + \subsubsection*{Скорость и автоматизация} + \begin{itemize} + \item Статический анализатор: работает быстро (особенно если хорошо интегрирован в CI/CD); выдаёт отчёты сразу после запуска. + \item Инспекция кода: процесс требует участия людей и времени на обсуждение. + Однако именно в этом процессе выявляются «глубинные» проблемы, которые + не найдёт автоматизированный инструмент. + \end{itemize} + + \subsubsection*{Результаты и интерпретация} + \begin{itemize} + \item Статический анализатор: + \begin{itemize} + \item Даёт отчёты (логи, списки ошибок/предупреждений) -- но они нуждаются в + интерпретации человеком, поскольку есть ложные срабатывания (false + positives). + \item Для принятия решения о серьёзности проблемы часто всё равно приходится просматривать код. + \end{itemize} + \item Инспекция кода: + \begin{itemize} + \item Часто приводит не только к обнаружению ошибок, но и к улучшению + совместной экспертизы в команде. + \item Может завершиться рекомендациями по рефакторингу, изменению архитектуры, или даже пересмотром требований. + \end{itemize} + \end{itemize} + + + \newpage + \section {Описание приложения и среды разработки} + Название программы: Генератор паролей. + + Задача программы: Сгенерировать пароль с параметрами, заданными пользователем. + + Дано: + \begin{itemize} + \item число -- длина генерируемого пароля; + \item строка <> или <>, если пользователь введёт строку <, то в пароле будут использоваться строчные буквы; + \item строка <> или <>, если пользователь введёт строку <, то в пароле будут использоваться заглавные буквы; + \item строка <> или <>, если пользователь введёт строку <, то в пароле будут использоваться цифры; + \item строка <> или <>, если пользователь введёт строку <, то в пароле будут использоваться спецсимволы; + \item число -- минимальное количество строчных букв в пароле; + \item число -- минимальное количество заглавных букв в пароле; + \item число -- минимальное количество цифр в пароле; + \item число -- минимальное количество спецсимволов; + \end{itemize} + + Ограничения: + \begin{itemize} + \item допустимая длина пароля -- от 1 до 100 символов; + \item минимальное количество строчных букв -- 0; + \item минимальное количество заглавных букв -- 0; + \item минимальное количество цифр -- 0; + \item минимальное количество спецсимволов -- 0; + \item сумма минимального количества строчных букв, заглавных букв, цифр и спецсимволов не может превышать длину пароля. + \end{itemize} + + Программа была написана на языке Python. В качестве среды разработки использовалось лицензионное программное обеспечение для редактирования текста -- Microsoft Visual Studio Code. + + + \newpage + \subsection{Спецификация тестируемой программы} + + Спецификация программы представлена в таблице~\ref{tbl:spec}. + + \begin{table}[h!] + \centering + \caption{Спецификация.} + \footnotesize + \begin{tabularx}{\textwidth}{|X|X|X|} + \hline + \textbf{Входные данные} & \textbf{Выходные данные} & \textbf{Комментарий} \\ + \hline + 10 y y y y 1 1 1 1 & 6KoL4Tfn*M & Программа сгенерировала и вывела на экран пароль с заданными параметрами. \\ + \hline + -10 y y y y 1 1 1 1 & Ошибка: значение должно быть не меньше 1. Попробуйте снова. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\ + \hline + 10 abc y y y 1 1 1 1 & Ошибка: введите 'yes' (или 'y') или 'no' (или 'n'). & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\ + \hline + 10 abc y y y -1 1 1 1 & Ошибка: значение должно быть не меньше 0. Попробуйте снова. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\ + \hline + 10 abc y y y 5 5 5 5 & Ошибка: сумма минимальных значений (103) превышает длину пароля (10). Пожалуйста, введите параметры заново. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\ + \hline + \end{tabularx} + \label{tbl:spec} + \end{table} + + \subsection{Исходный код программы тестируемой программы} + + Исходный код программы представлен в листинге~\ref{lst:code}. + +\begin{lstlisting}[caption={Исходный код.}, label={lst:code}] +import random +import string + +max_password_length = 100 # Максимальная длина пароля + + +def get_valid_int(prompt, min_value=0, max_value=None): + while True: + user_input = input(prompt).strip() + if not user_input: + print("Ошибка: ввод не должен быть пустым. Попробуйте снова.") + continue + try: + value = int(user_input) + if value < min_value: + print( + f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова." + ) + continue + if max_value and value > max_value: + print( + f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова." + ) + continue + return value + except ValueError: + print("Ошибка: введите корректное целое число.") + + +def get_yes_no(prompt): + while True: + user_input = input(prompt).strip().lower() + if user_input in ["yes", "y"]: + return True + if user_input in ["no", "n"]: + return False + print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').") + + + +def get_user_input(): + """Запрашивает у пользователя параметры генерации пароля с проверкой ввода.""" + global max_password_length + + length = get_valid_int( + f"Введите длину пароля (1-{max_password_length}): ", + min_value=1, + max_value=max_password_length, + ) + + use_lower = get_yes_no("Использовать строчные буквы? (yes/y, no/n): ") + use_upper = get_yes_no("Использовать заглавные буквы? (yes/y, no/n): ") + use_digits = get_yes_no("Использовать цифры? (yes/y, no/n): ") + use_special = get_yes_no("Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): ") + + # Проверяем, что хотя бы один тип символов выбран + if not (use_lower or use_upper or use_digits or use_special): + print("Ошибка: необходимо выбрать хотя бы один тип символов.") + return get_user_input() # Повторный ввод всех данных + + # Запрашиваем минимальное количество каждого типа символов + min_lower = ( + get_valid_int("Минимальное количество строчных букв: ", 0) if use_lower else 0 + ) + min_upper = ( + get_valid_int("Минимальное количество заглавных букв: ", 0) if use_upper else 0 + ) + min_digits = get_valid_int("Минимальное количество цифр: ", 0) if use_digits else 0 + min_special = ( + get_valid_int("Минимальное количество спецсимволов: ", 0) if use_special else 0 + ) + + return ( + length, + use_lower, + use_upper, + use_digits, + use_special, + min_lower, + min_upper, + min_digits, + min_special, + ) + + +def validate_input(length, min_lower, min_upper, min_digits, min_special): + """Проверяет, что длина пароля больше суммы минимальных значений.""" + total_required = min_lower + min_upper + min_digits + min_special + if total_required > length: + print( + f"Ошибка: сумма минимальных значений ({total_required}) превышает длину пароля ({length})." + ) + return False + return True + + +def generate_mandatory_chars( + min_lower, + min_upper, + min_digits, + min_special, + lower_chars, + upper_chars, + digit_chars, + special_chars, +): + """Генерирует обязательные символы пароля.""" + password = ( + random.choices(lower_chars, k=min_lower) + + random.choices(upper_chars, k=min_upper) + + random.choices(digit_chars, k=min_digits) + + random.choices(special_chars, k=min_special) + ) + return password + + +def fill_password(password, length, all_chars): + """Дополняет пароль случайными символами до нужной длины.""" + remaining_length = length - len(password) + password += random.choices(all_chars, k=remaining_length) + return password + + +def shuffle_password(password): + """Перемешивает символы пароля случайным образом.""" + random.shuffle(password) + return "".join(password) + + +def generate_password( + length, + use_lower, + use_upper, + use_digits, + use_special, + min_lower, + min_upper, + min_digits, + min_special, +): + """Генерирует пароль с учётом заданных параметров.""" + lower_chars = string.ascii_lowercase if use_lower else "" + upper_chars = string.ascii_uppercase if use_upper else "" + digit_chars = string.digits if use_digits else "" + special_chars = "!@#$%^&*" if use_special else "" + + all_chars = lower_chars + upper_chars + digit_chars + special_chars + + while not validate_input(length, min_lower, min_upper, min_digits, min_special): + print("Пожалуйста, введите параметры заново.") + return generate_password(*get_user_input()) + + password = generate_mandatory_chars( + min_lower, + min_upper, + min_digits, + min_special, + lower_chars, + upper_chars, + digit_chars, + special_chars, + ) + password = fill_password(password, length, all_chars) + password = shuffle_password(password) + + return password + + +def main(): + """Основная функция программы.""" + max_password_length = 100 + user_data = get_user_input() + password = generate_password(*user_data) + print("Сгенерированный пароль:", password) + + +if __name__ == "__main__": + main() +\end{lstlisting} + + + \section{Статический анализ кода приложения} + \subsection{Описание выбранного инструмента для статического анализа кода} + + В качестве инструмента статического анализа кода проекта был выбран Pylint + версии 3.3.6. Pylint - это программа для проверки исходного кода, ошибок и качества + для языка программирования Python. Он назван в соответствии с общепринятым в + Python соглашением о префиксе «py» и отсылкой к программе lint для программирования на C. Он следует стилю, рекомендованному PEP 8, руководством по стилю + Python. Он похож на Pychecker и Pyflakes, но включает в себя следующие функции: + \begin{itemize} + \item Проверка длины каждой строки; + \item Проверка правильности формирования имен переменных в соответствии со + стандартом кодирования проекта; + \item Проверка того, что заявленные интерфейсы действительно реализованы. + \end{itemize} + + Pylint классифицирует свои сообщения об ошибках и предупреждениях по категориям, каждая из которых обозначается соответствующим префиксом. Основные + виды сообщений следующие + + \begin{itemize} + \item C (Convention): Сообщения, связанные со стилем оформления кода (например, + нарушение соглашений PEP8), именованием переменных, форматированием и + т.д. + \item R (Refactor): Рекомендации по рефакторингу кода для улучшения читаемости, + структуры и поддерживаемости. Эти сообщения помогают улучшить архитектуру кода. + \item W (Warning): Предупреждения о потенциальных проблемах, которые могут + привести к ошибкам или неожиданному поведению во время выполнения. Например, возможное использование необъявленной переменной. + \item E (Error): Ошибки, которые скорее всего приведут к сбоям выполнения программы, такие как неправильное использование синтаксиса, отсутствие необходимых атрибутов или функций. + \item F (Fatal): Критические ошибки, при обнаружении которых анализ кода прерывается. Обычно это ошибки синтаксиса или другие проблемы, которые делают + дальнейший анализ невозможным. + \end{itemize} + + \subsection{Разбор Pylint правил из группы «Convention»} + Эти проверки нацелены на то, чтобы код соответствовал принятым соглашениям о стиле (например, PEP8) и был легко читаемым. + \begin{itemize} + \item Именование: + \begin{itemize} + \item Проверяется, чтобы имена классов, функций, переменных, модулей и + констант соответствовали принятым стандартам. + \item Например, классы должны именоваться в стиле CamelCase (например, MyClass), а функции и переменные – в snake\_case (например, + my\_function, my\_variable). + \item Также проверяются длина имен и их осмысленность, чтобы они точно + отражали назначение объекта в коде. + \end{itemize} + \item Стиль оформления: + \begin{itemize} + \item Длина строк: Pylint следит за тем, чтобы строки не превышали установленную длину (обычно 79 или 99 символов в зависимости от конфигурации). + \item Отступы и пробелы: Контролируется корректное использование отступов (обычно 4 пробела), пробелов вокруг операторов, после запятых и + т.д. + \item Разбиение на строки: Рекомендуется правильно разбивать длинные выражения или вызовы функций на несколько строк для лучшей читаемости. + \item Пустые строки: Проверяется количество пустых строк между функциями и классами для поддержания визуальной структуры кода. + \end{itemize} + \item Документация: + \begin{itemize} + \item Docstrings: Pylint обращает внимание на наличие строк документации + (docstrings) в модулях, классах, функциях и методах. + \item Формат документации: Документация должна быть оформлена согласно принятым стандартам (например, в формате reStructuredText или + Google style), чтобы обеспечить понятное описание функционала и параметров. + \end{itemize} + \end{itemize} + + \subsection{ Разбор Pylint правил из группы «Refactor»} + Эта группа сообщений направлена на улучшение структуры кода, его упрощение + и повышение поддерживаемости + + \begin{itemize} + \item Сложность функций: + \begin{itemize} + \item Цикломатическая сложность: Анализируется количество ветвлений, + циклов и условных операторов. Функции с высокой сложностью могут + быть трудными для тестирования и отладки. + \item Слишком длинные функции: Если функция слишком большая или содержит множество аргументов, Pylint может рекомендовать её разбить + на более мелкие части. + \end{itemize} + \item Дублирование кода: + \begin{itemize} + \item Проверка на повторяющиеся участки кода, что может указывать на возможность объединения логики в одну функцию или класс. + \item Цель – уменьшить количество повторений, чтобы изменение в одной + части кода не требовало повторения исправлений в нескольких местах. + \end{itemize} + \item Структурные проблемы: + \begin{itemize} + \item Обнаружение слишком больших классов или методов, которые выполняют сразу несколько задач + \item Рекомендации по разделению ответственности (например, принцип + единственной ответственности из SOLID) для улучшения модульности + и тестируемости кода. + \end{itemize} + \end{itemize} + + \subsection{Разбор Pylint правил из группы «Warning»} + Эта категория охватывает сообщения, которые указывают на потенциальные + проблемы, не являющиеся критическими ошибками, но способными привести к + неожиданному поведению. + + \begin{itemize} + \item Неиспользуемые элементы: + \begin{itemize} + \item Переменные: Если переменная объявлена, но не используется, Pylint сообщает об этом, что помогает избежать загромождения кода. + \item Импорты: Неиспользуемые модули или функции, импортированные в + начале файла, могут быть отмечены для удаления. + \item Аргументы функций: Иногда функция принимает аргументы, которые + не используются в теле, что может быть сигналом к тому, что интерфейс + функции следует пересмотреть. + \end{itemize} + \item Подозрительные конструкции: + \begin{itemize} + \item Использование переменных до объявления: Если переменная используется до того, как ей было присвоено значение, это может привести к + ошибкам. + \item Использование изменяемых значений по умолчанию: Применение изменяемых объектов (например, списков или словарей) в качестве значений + по умолчанию в параметрах функций может привести к неожиданным эффектам. + \end{itemize} + \item Ошибки логики: + \begin{itemize} + \item Порой конструкция кода может быть синтаксически корректной, но её + поведение может быть неочевидным или потенциально приводить к логическим ошибкам (например, некорректное сравнение или неверное использование операторов). + \end{itemize} + \end{itemize} + + \newpage + \section{Процесс тестирования} + \subsection{Подготовка} + + Перед использованием PyLint необходимо установить Python и PIP с официального сайта. Затем создать виртуальное окружение с помощью команды \texttt{virtualenv venv}. + + Чтобы установить PyLint, достаточно выполнить команду \texttt{pip install pylint} внутри виртуального окружения. + + Для запуска статического анализа достаточно выполнить команду \texttt{pylint file.py}, где \texttt{file.py} -- это название файла с исходным кодом. + + + \subsection{Результат работы анализатора} + + Полный вывод команды \texttt{pylint passgen.py} представлен в листинге~\ref{lst:res}. Статический анализатор вывел 12 сообщений о различных проблемах в коде. Они связаны с несоответствием стиля именования, отсутствием документации в модуле и функциях, а также слишком длинными строками. Кроме того, есть предупреждения о неинициализированной глобальной переменной, переопределении имени переменной во внутренней области видимости, а также о слишком большом количестве аргументов в функциях. PyLint также даёт общую оценку кода. Для моей программы он вывел оценку 8.48 из 10, что означает, что код в целом неплох, но его можно улучшить. + +\begin{lstlisting}[caption={Результат выполнения команды \texttt{pylint passgen.py}.}, label={lst:res}] +************* Module passgen +passgen.py:91:0: C0301: Line too long (103/100) (line-too-long) +passgen.py:1:0: C0114: Missing module docstring (missing-module-docstring) +passgen.py:4:0: C0103: Constant name "max_password_length" doesn't conform to UPPER_CASE naming style (invalid-name) +passgen.py:7:0: C0116: Missing function or method docstring (missing-function-docstring) +passgen.py:30:0: C0116: Missing function or method docstring (missing-function-docstring) +passgen.py:43:4: W0602: Using global for 'max_password_length' but no assignment is done (global-variable-not-assigned) +passgen.py:97:0: R0913: Too many arguments (8/5) (too-many-arguments) +passgen.py:97:0: R0917: Too many positional arguments (8/5) (too-many-positional-arguments) +passgen.py:130:0: R0913: Too many arguments (9/5) (too-many-arguments) +passgen.py:130:0: R0917: Too many positional arguments (9/5) (too-many-positional-arguments) +passgen.py:171:4: W0621: Redefining name 'max_password_length' from outer scope (line 4) (redefined-outer-name) +passgen.py:171:4: W0612: Unused variable 'max_password_length' (unused-variable) + +------------------------------------------- +Your code has been rated at 8.48/10 (previous run: 8.48/10, +0.00) +\end{lstlisting} + + \subsection{Результат улучшения кода} + + В соответствии с рекомендациями PyLint в исходный код были добавлены комментарии с документацией для всего модуля и функций, слишком длинные строки были разбиты на более короткие и удобочитаемые, неинициализированная глобальная переменная была удалена, была удалена одна переменная из локальной области видимости, перекрывавшая глобальную переменную, имена всех переменных были приведены в соответствие с принятыми соглашениями языка Python. + + После внесения перечисленных выше изменений статический анализатор PyLint был запущен ещё раз на обновлённом файле с исходным кодом. Результат запуска представлен в листинге~\ref{lst:res-new}. В этот раз PyLint не вывел никаких сообщений. + +\begin{lstlisting}[caption={Результат работы PyLint на обновлённом файле с исходным кодом.}, label={lst:res-new}] +------------------------------------- +Your code has been rated at 10.00/10 +\end{lstlisting} + + Обновлённый код программы представлен в листинге~\ref{lst:code-new}. + +\begin{lstlisting}[caption={Обновлённый код программы.}, label={lst:code-new}] +""" +Модуль для генерации безопасных паролей с разными настройками. +Пользователь может задавать длину, типы символов и минимальное количество каждого типа. +""" + +import random +import string + +MAX_PASSWORD_LENGTH = 100 # Максимальная длина пароля + + +def get_valid_int(prompt, min_value=0, max_value=None): + """Запрашивает у пользователя целое число, проверяя корректность ввода.""" + while True: + user_input = input(prompt).strip() + if not user_input: + print("Ошибка: ввод не должен быть пустым. Попробуйте снова.") + continue + try: + value = int(user_input) + if value < min_value: + print( + f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова." + ) + continue + if max_value and value > max_value: + print( + f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова." + ) + continue + return value + except ValueError: + print("Ошибка: введите корректное целое число.") + + +def get_yes_no(prompt): + """Запрашивает у пользователя 'yes'/'y' или 'no'/'n', проверяя корректность ввода.""" + while True: + user_input = input(prompt).strip().lower() + if user_input in ["yes", "y"]: + return True + if user_input in ["no", "n"]: + return False + print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').") + + +def get_user_input(): + """Запрашивает у пользователя параметры генерации пароля с проверкой ввода.""" + settings = { + "length": get_valid_int( + f"Введите длину пароля (1-{MAX_PASSWORD_LENGTH}): ", + min_value=1, + max_value=MAX_PASSWORD_LENGTH, + ), + "use_lower": get_yes_no("Использовать строчные буквы? (yes/y, no/n): "), + "use_upper": get_yes_no("Использовать заглавные буквы? (yes/y, no/n): "), + "use_digits": get_yes_no("Использовать цифры? (yes/y, no/n): "), + "use_special": get_yes_no( + "Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): " + ), + } + + # Проверяем, что хотя бы один тип символов выбран + if not any( + [ + settings["use_lower"], + settings["use_upper"], + settings["use_digits"], + settings["use_special"], + ] + ): + print("Ошибка: необходимо выбрать хотя бы один тип символов.") + return get_user_input() # Повторный ввод всех данных + + # Запрашиваем минимальное количество каждого типа символов + settings["min_lower"] = ( + get_valid_int("Минимальное количество строчных букв: ", 0) + if settings["use_lower"] + else 0 + ) + settings["min_upper"] = ( + get_valid_int("Минимальное количество заглавных букв: ", 0) + if settings["use_upper"] + else 0 + ) + settings["min_digits"] = ( + get_valid_int("Минимальное количество цифр: ", 0) + if settings["use_digits"] + else 0 + ) + settings["min_special"] = ( + get_valid_int("Минимальное количество спецсимволов: ", 0) + if settings["use_special"] + else 0 + ) + + return settings + + +def validate_input(settings): + """Проверяет, что длина пароля больше суммы минимальных значений.""" + total_required = sum( + [ + settings["min_lower"], + settings["min_upper"], + settings["min_digits"], + settings["min_special"], + ] + ) + if total_required > settings["length"]: + print( + f"Ошибка: сумма минимальных значений ({total_required})" + f" превышает длину пароля ({settings['length']})." + ) + return False + return True + + +def generate_mandatory_chars(settings, char_sets): + """Генерирует обязательные символы пароля.""" + password = ( + random.choices(char_sets["lower"], k=settings["min_lower"]) + + random.choices(char_sets["upper"], k=settings["min_upper"]) + + random.choices(char_sets["digits"], k=settings["min_digits"]) + + random.choices(char_sets["special"], k=settings["min_special"]) + ) + return password + + +def fill_password(password, length, all_chars): + """Дополняет пароль случайными символами до нужной длины.""" + remaining_length = length - len(password) + password += random.choices(all_chars, k=remaining_length) + return password + + +def shuffle_password(password): + """Перемешивает символы пароля случайным образом.""" + random.shuffle(password) + return "".join(password) + + +def generate_password(settings): + """Генерирует пароль с учётом заданных параметров.""" + char_sets = { + "lower": string.ascii_lowercase if settings["use_lower"] else "", + "upper": string.ascii_uppercase if settings["use_upper"] else "", + "digits": string.digits if settings["use_digits"] else "", + "special": "!@#$%^&*" if settings["use_special"] else "", + } + + all_chars = "".join(char_sets.values()) + + while not validate_input(settings): + print("Пожалуйста, введите параметры заново.") + return generate_password(get_user_input()) + + password = generate_mandatory_chars(settings, char_sets) + password = fill_password(password, settings["length"], all_chars) + password = shuffle_password(password) + + return password + + +def main(): + """Основная функция программы.""" + user_settings = get_user_input() + password = generate_password(user_settings) + print("Сгенерированный пароль:", password) + + +if __name__ == "__main__": + main() +\end{lstlisting} + + \newpage + \section*{Заключение} + \addcontentsline{toc}{section}{Заключение} + + В ходе выполнения данной лабораторной работы было проведено статистическое тестирование для программы: <<Генератор паролей>>. + + Были найдены следующие проблемы: + \begin{itemize} + \item 5 нарушений правил оформления исходного кода; + \item 3 предупреждения о возможных ошибках в исходном коде; + \item 4 рекомендации по рефакторингу исходного кода. + \end{itemize} + + В результате проделанной работы программный код был исправлен в соответствии с рекомендациями статистического анализатора. На примере небольшой программы была наглядно продемонстрирована польза от использования статических анализаторов кода. Были сделаны выводы о том, что статистическое тестирование позволяет выявить некорректность как в логике работы программы, так и в стиле её оформления. На примере PyLint был получен первый опыт использования статических анализаторов кода. + + В процессе статистического тестирования были выявлены некоторые недостатки, которых не удалось обнаружить во время инспекции за столом. Поэтому статистическое тестирование является хорошим дополнением к инспекции за столом. + К тому же статическое тестирование не столь трудозатрано и его можно автоматизировать. + +\newpage +\section*{Список литературы} +\addcontentsline{toc}{section}{Список литературы} + +\vspace{-1.5cm} +\begin{thebibliography}{0} + \bibitem{mayers} + Майерс, Г. Искусство тестирования программ. -- Санкт-Петербург: Диалектика, 2012 г. +\end{thebibliography} + +\end{document} \ No newline at end of file