\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{Постановка задачи} Задачи лабораторной работы: \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}