\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): Ошибки, которые приведут к сбоям выполнения программы, такие как: синтаксические ошибки, вызов функций с неккоректным списком параметров или с неккоректными типами параметров. \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} \subsection{Разбор Pylint правил из группы «Error»} Эти проверки сигнализируют о проблемах, которые приведут к сбоям выполнения программы. \begin{itemize} \item Синтаксические ошибки: \begin{itemize} \item Ошибки в написании кода (например, забытые двоеточия, скобки, неправильное построение конструкции) могут привести к невозможности интерпретировать код. \item Такие ошибки выявляются на этапе статического анализа до выполнения программы. \end{itemize} \item Ошибки времени выполнения: \begin{itemize} \item Обращение к несуществующим атрибутам: Если код пытается получить доступ к атрибуту, которого нет у объекта, Pylint укажет на эту проблему. \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} -- это название файла с исходным кодом. При запуске PyLint без дополнительных параметров, статический анализатор будет искать все типы ошибок, перечисленные в предыдущем разделе. \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}