Pyflakes e что это
Перейти к содержимому

Pyflakes e что это

  • автор:

7 пакетов Python для анализа и улучшения кода

• isort — это Python-утилита/библиотека для сортировки импорта по алфавиту, а также автоматического разделения его на разделы и по типам.

• black — это бескомпромиссный форматировщик кода на языке Python. Используя его, вы соглашаетесь отказаться от контроля над тонкостями ручного форматирования. Взамен Black дает вам скорость, детерминизм и свободу от придирок pycodestyle к форматированию. Вы сэкономите время и умственную энергию для решения более важных задач.

• flake8 — это инструмент линтинга для Python, который проверяет кодовую базу Python на наличие ошибок, проблем со стилем и сложностью. Библиотека Flake8 построена на базе трех инструментов: PyFlakes — проверяет кодовую базу Python на наличие ошибок. McCabe — проверяет кодовую базу Python на сложность. pycodestyle — проверяет кодовую базу Python на проблемы со стилем в соответствии с PEP8..

• interrogate — interrogate проверяет вашу кодовую базу на наличие отсутствующих документальных строк. Документация должна быть столь же важна, как и сам код. И она должна находиться внутри кода.

• Whispers — это инструмент статического анализа кода, предназначенный для разбора различных распространенных форматов данных в поисках жестко закодированных учетных данных и опасных функций. Whispers может работать в CLI или интегрироваться в конвейер CI/CD.

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

• pylint — Pylint анализирует ваш код без его реального выполнения. Он проверяет наличие ошибок, соблюдает стандарты кодирования, ищет «запахи» кода и может дать рекомендации по его рефакторингу.

Spyder — Invalid syntax pyflakes e — Only on Spyder

I am trying to replicate code from a youtube video tutorial, but I am getting the error «Invalid syntax pyflakes E», but I am not sure why. I am using Spyder IDE. This is the tutorial link: https://www.youtube.com/watch?v=bM50i7sKwwM&ab_channel=TechWithTim

import requests import json def login(mail, password): s = requests.Session() payload = < 'email': mail, 'password': password >res = s.post('http://api.giggl.app/v1/auth'), json=payload) s.headers.update() print(res.content) return s #user/pw session = login('@gmail.com', 'q1231312123') r = session.patch('https://api.giggl.app/v1/users/@me', json=) print(r.content) 

asked Mar 3, 2022 at 15:31
59 2 2 silver badges 12 12 bronze badges

If look carefully you will see that the code highlighting is broken from some point on. There’s your error, a missing ‘ .

Mar 3, 2022 at 15:38
Ah..oops ty. I added that ‘ in front of the URL but I still have the same error. Any ideas?
Mar 3, 2022 at 15:45
@KlausD. The code highlighter itself doesn’t seem to handle scrollable text very well.
Mar 3, 2022 at 15:49

You do, however, have a stray ) following the URL in your call to post . Aside from that, there are no syntax errors in the code shown.

Подход к обнаружению ошибок несоответствия типов в коде на динамических языках программирования Текст научной статьи по специальности «Компьютерные и информационные науки»

СТАТИЧЕСКИЙ АНАЛИЗ / ОБНАРУЖЕНИЕ ДЕФЕКТОВ / ДИНАМИЧЕСКАЯ ТИПИЗАЦИЯ ДАННЫХ / НЕСООТВЕТСТВИЕ ТИПОВ / ТРАССЫ ДЕФЕКТОВСТАТИЧЕСКИЙ АНАЛИЗ / PYTHON / RUBY / JAVASCRIPT / STATIC ANALYSIS / DEFECTS DETECTION / DYNAMIC TYPING / TYPES INCONSISTENCY / DEFECTS TRACES

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Бронштейн И. Е.

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

i Надоели баннеры? Вы всегда можете отключить рекламу.

Похожие темы научных работ по компьютерным и информационным наукам , автор научной работы — Бронштейн И. Е.

Вывод типов для языка Python

Получение типов данных в языках с динамической типизацией для статического анализа исходного кода с помощью универсального классового представления

Численное моделирование анализа исходного кода с использованием промежуточных представлений

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

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

i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Approach to detecting types inconsistency errors in a program code in dynamic languages

The paper deals with detection of defects in a program code written in dynamic languages. At first, overview of static analyzers for programs in Python , Ruby and JavaScript is done. After this, the paper shows that most of existing tools are not able to detect entire class of defects: types inconsistency errors. Such errors are defined, the paper proves that the errors are prevailing and rather critical in the opinion of software developers. It presents an approach to type inference for dynamic languages and to implementation of checkers based on output from type inference. Concept of defect trace is introduced and construction of such trace is described then.

Текст научной работы на тему «Подход к обнаружению ошибок несоответствия типов в коде на динамических языках программирования»

Подход к обнаружению ошибок несоответствия типов в коде на динамических языках программирования

Бронштейн И. Е. ibronstein@ispras. ги

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

Ключевые слова: статический анализ; обнаружение дефектов; динамическая

типизация данных; Python; Ruby; JavaScript; несоответствие типов; трассы дефектов.

Среди существующих языков программирования можно выделить большую группу динамических языков. Главный фактор, определяющий, является ли язык динамическим, — используемая в языке типизация данных [1]. Статическая типизация данных означает, что контроль типов осуществляется во время компиляции. Напротив, при динамической типизации проверки производятся в процессе выполнения программы. В этом случае тип имеется у тех или иных значений, однако у переменных он как таковой отсутствует. В процессе работы программы одной и той же переменной могут быть присвоены значения различных типов [2].

Несмотря на отсутствие единого определения динамических языков программирования, обычно к ним относят языки, которые поддерживают: (1) динамическую типизацию данных; (2) изменение кода программы во время выполнения (например, добавление полей класса, изменение иерархии типов и

т. д.), а также выполнение произвольного кода «на лету» (функция eval). Как правило, динамические языки являются интерпретируемыми.

В последнее время такие языки получили большое распространение. Согласно индексу ТЮВЕ по состоянию на август 2013 года [3], в десятку самых популярных языков программирования входят РНР, Python, JavaScript и Ruby, являющиеся динамическими языками. Perl также не сильно уступает им в популярности. Всё чаще используются фреймворки, включающие в себя динамические языки: AJAX (включает JavaScript), LAMP (Perl, РНР или Python), Ruby on Rails (Ruby). В последние годы динамические языки стали поддерживаться основными производителями интегрированных средств разработки (IDE). Популярность динамических языков можно проследить на примере языка Python: в последнее время он стал использоваться не только для написания небольших скриптов, но и для создания крупных программных продуктов. Исходный код таких проектов, как TACTIC, Twisted, Django и SQLAlchemy, содержит более сотни тысяч строк, что весьма велико, учитывая, насколько высокоуровневыми являются программные конструкции языка Python.

2 Обзор статических анализаторов для динамических языков

В связи со всё возрастающей сложностью программ, разрабатываемых на динамических языках, остро стоит проблема их надёжности. Чтобы решить эту проблему для программ на статических языках (например, Си/Си++ и Java), активно используются средства, позволяющие проводить анализ программ с целью обнаружения тех или иных дефектов. Для выявления ошибок реализации чаще всего используются методы статического анализа, осуществляемого по исходному коду анализируемой программы без её запуска. Исследования на конкретных программных продуктах показывают, что сложность исправления ошибки, обнаруженной при помощи статического анализа, ниже, чем если бы эта ошибка была найдена на более поздних этапах разработки [4].

Так, для программ на языках Си и Си++ существует множество профессиональных статических анализаторов (Svace [5], Coverity Prevent, Klocwork Insight, PVS-Studio и другие). Эти средства включают в себя набор автоматических обнаружителей дефектов (чекеров), которые могут: (1) выполняться после стадии семантического анализа путём обхода синтаксического дерева (AST) и анализа навешанных на это дерево атрибутов (без анализа путей выполнения); (2) осуществлять анализ потоков данных (dataflow) по промежуточному представлению программы. Эти две основные группы можно назвать простыми и, соответственно, сложными чекерами. С помощью сложных чекеров возможно обнаруживать критические дефекты: выход за границы массива, разыменование нулевого указателя, использование освобождённой памяти или значения неинициализированной переменной.

Для программ на динамических языках программирования также, хотя и в меньшем количестве, существуют статические анализаторы. Подавляющее большинство среди них составляют программные средства, не использующие вывод типов: например, Pyflakes [6] и Pylint [7] (для языка Python), reek [8] и ruby-lint [9] (для языка Ruby), JSLint [10] и JSHint [11] (для языка JavaScript). Все эти инструменты объединяет то, что в них реализован набор чекеров, выполняющихся путём обхода синтаксического дерева без навешанных на него атрибутов. В качестве примера можно привести нахождение операторов без побочного эффекта. Ниже приведены код на языке Python и предупреждение, которое выводится при анализе данного кода анализатором Pylint:

W: 2, 0: Statement seems to have no effect

В целом, реализованные в вышеперечисленных средствах чекеры направлены на поиск в анализируемом коде некоторых анти-паттернов программирования. Иногда также проверяется, соответствует ли программа принятым в языке стандартам кодирования (например, РЕР8 [12] для языка Python). Проводя аналогию с Си++, описанные чекеры можно назвать простыми. Они чаще всего соответствуют не сообщениям об ошибках, а предупреждениям, который выдавал бы компилятор статического языка программирования.

Хотя про выше названные инструменты сказано, что в них не реализован вывод типов, это не совсем так. В Pylint частично реализован вывод типов, на основе которого осуществляется проверка, можно ли использовать объект в выражении «вызов функции». Однако, вывод типов в Pylint является локальным (производится лишь в рамках одной функции) и неполным (поддерживает не все возможные выражения). Так, для следующего примера инструмент выдаст сообщение об ошибке:

E: 2, 0: x is not callable (not-callable)

Однако, если пример немного усложнить (назовём получившийся пример f alse_negative . ру), ошибка не будет обнаружена, поскольку вывод типов в Pylint не сопоставляет параметры функции и аргументы её вызова:

def foo(р): р() х = 0 foo(х)

3 Ошибки несоответствия типов

Как видно, код в false_negative. ру содержит ошибку, которая не обнаруживается ни одним из средств, представленных выше. Обобщая ситуацию, можно заметить, что поскольку в этих средствах не реализован вывод типов, они не могут обнаруживать ошибки несоответствия типов. Говоря неформально, под такими ошибками мы будем понимать непредусмотренный разработчиком вызов функции или операции (например, доступа к атрибуту объекта) с аргументами (операндами) типов, которые функцией (операцией) не поддерживаются. Ошибка в false_negative . ру подходит под это определение, поскольку выражение х () можно считать

неявным обращением к х._call_(аналог метода operator () в Си++). К

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

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

Номер Тип дефектов Описание Исправление

010 Документация Комментарии, сообщения Изменение документации, комментариев и т. д.

020 Синтаксис Синтаксические ошибки, опечатки в коде и т. д. Исправление синтаксической или иной ошибки во время компиляции

030 Сборка, пакеты Подключение библиотек, контроль версий и т. д. Создание или использование правильной версии

040 Присваивания Декларации, дубликаты имён, области видимости имён и т. д. Исправление оператора

050 Интерфейс Вызовы процедур, ввод/вывод, пользовательские форматы Изменение интерфейса

060 Проверки Сообщения об ошибках, некорректные проверки Добавление или исправление обработки ошибок

070 Данные Структура, содержимое Изменение программы для поддержания целостности данных

080 Функция Логика, указатели, циклы, рекурсия, вычисления ИТ. д. Изменение совокупности операторов

090 Система Конфигурация, хронометраж, память Адаптация программы к внешним факторам (например, ОС)

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

Таблица 1. Общая DTS-классификация.

Рассмотрим стандарт Personal Software Process Defect Type Standard (PSP DTS) — распространённую классификацию программных дефектов [13,14]. В ней описаны десять различных видов ошибок, которые обычно встречаются в программных продуктах. Для каждого вида перечислены его номер (от 010 до 100), название (тип дефектов), описание, а также исправление, которое необходимо внести для устранения ошибок данного вида. Общая (независимая от языка программирования) DTS-классификация представлена в таблице 1.

Номер Тип дефектов Описание

010 Документация Ошибки в документационных строках и комментариях

020 Синтаксис SyntaxError, IndentationError

030 Стандарт кодирования Предупреждения и ошибки, связанные с РЕР8

040 Присваивание NameError (нет объявления), IndexError/KeyError,

UnboundLocalError (область видимости), ImportError

050 Интерфейс TypeError, AttributeError: неправильные параметры и методы

060 Проверки AssertionError (ошибка в assert), ошибки в тестах (doctest или unittest)

070 Данные ValueError (неправильные данные), ArithmeticError,

080 Функция RuntimeError и логические ошибки

090 Система SystemError (MemoryError, ReferenceError)

100 Окружение EnvironmentError: ошибки ОС и сторонних библиотек, ошибки ввода/вывода (ЮЕггог)

Таблица 2. DTS-классификация для языка Python.

Как следует из общей DTS-классификации и определения ошибок несоответствия типов, последние соответствуют DTS-ошибкам вида 050 («интерфейс»). В терминах DTS несоответствие типов — это неправильное использование интерфейса, а исправление такой ошибки — это изменение либо интерфейсной функции, либо способа её использования (передаваемых в функцию аргументов).

Также существуют модификации DTS применительно к различным языкам программирования. В этой связи интерес представляет DTS-классификация для языка Python [15], представленная в таблице 2. В качестве описания для большинства видов дефектов приведены типы (классы) исключений, соответствующие данному виду дефектов. Ошибкам вида «интерфейс»

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

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

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

• долю отчётов об ошибках несоответствия типов среди всех отчётов об ошибках (распространённость);

• долю критичных отчётов среди отчётов об ошибках несоответствия типов по сравнению с долей критичных отчётов целом (критичность).

В качестве источника отчётов выберем системы отслеживания ошибок известных проектов с открытым исходным кодом на языке Python: Exaile [16], calibre [17], SQLAlchemy [18], Bazaar [19], Twisted [20], Trac [21] и Django [22].

Название проекта Все отчёты об ошибках AttributeError и ТуреЕггог Доля, %

Exaile 1350 86 6,37

calibre 1357 77 5,67

SQLAlchemy 2826 230 8,14

Bazaar 4646 167 3,46

Twisted 6664 197 2,96

Trac 10472 627 5,99

Django 20664 969 4,69

Таблица 3. Распространённость ошибок несоответствия типов.

Данные по распространённости ошибок несоответствия типов приведены в таблице 3. Из таблицы видно, что доля отчётов, посвящённых именно ошибкам несоответствия типов, для некоторых проектов превышает 8 %. При этом необходимо учитывать, что тема значительной части отчётов (более 90 %) —

не дефекты, а логические ошибки, которые обнаружить автоматическими средствами невозможно [2]. Таким образом, отчёты об ошибках несоответствия типов составляют для некоторых проектов большинство отчётов об ошибках, которые можно найти автоматически.

Название проекта Критичность всех отчётов, % Критичность АЕ/ТЕ, % Более критичны

Exaile 8,88 11,63 д

Bazaar 4,72 4,25 н

Trac 5,36 10,03 д

Django 1,95 2,68 д

Таблица 4. Критичность ошибок несоответствия типов.

Данные по критичности ошибок несоответствия типов приведены в таблице 4. Критичными считались отчёты с большим значением поля «критичность», если таковое присутствовало в системе отслеживания ошибок: «Critical» и «High» в поле «Importance» (для Exaile), «Critical» в поле «Importance» (для Bazaar), «blocker» и «critical» в поле «Severity» (для Trac), «Release blocker» в поле «Severity» (для Django). Для всех проектов, кроме Bazaar, критичность ошибок несоответствия типов выше, чем в среднем. Заметим, однако, что 15 % кода Bazaar написано на языке Си. Эго могло послужить причиной более низкой критичности ошибок несоответствия типов для этого проекта.

4 Обнаружение ошибок в программах на динамических языках

Таким образом, ошибки несоответствия типов широко распространены и, по мнению самих разработчиков проектов, достаточно критичны. Для обнаружения таких ошибок необходимо наличие глобального и полного (в смысле поддержки программных конструкций того или иного динамического языка) вывода типов. Отметим, что, несмотря на отсутствие такого вывода типов в перечисленных выше статических анализаторах, исследовательские проекты по реализации вывода типов для различных динамических языков всё же существуют. К подобным проектам можно отнести DRuby [23] (для языка Ruby) и TAJS [24] (для языка JavaScript).

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

выражений [25]. Однако, на наш взгляд, представление ограничений именно в виде графа значительно упрощает построение трасс дефектов, о которых будет сказано несколько позже.

Рис. 1. Общая схема работы статического анализатора.

Общая схема работы статического анализатора в рамках предлагаемого подхода представлена на рис. 1. Ниже мы более подробно разберём все этапы работы за исключением парсинга исходного кода. Задача парсинга, как правило, не представляет особой сложности и может быть решена как стандартными средствами языка (например, модуль ast для языка Python), так и при помощи сторонних библиотек (например, jraby-parser [26] для языка Ruby или Esprima [27] для языка JavaScript).

5 Вывод типов для выражений

В предлагаемом подходе к построению статического анализатора для вывода типов используется представление программы в виде графа типовых переменных. Каждому выражению в программе ставится в соответствие свой узел графа: типовая переменная, то есть переменная, значением которой является множество типов. Изначально типовые переменные хранят в себе пустые множества типов, за исключением констант и строковых литералов, которые в качестве значения получают множество из одного единственного типа: типа константы (литерала). Множество возможных типов для выражения ехрг обозначается type (ехрг).

Рис. 2. Ситуация до и после добавления дуги и пропагации по ней типов.

Г раф типовых переменных является ориентированным. Дуги графа, называемые также ограничениями на типы, создаются при обходе тех или иных языковых конструкций в программном коде. Дуги помечаются названием: видом ограничения, который зависит от того, какая конструкция анализируется (например, для присваивания соответствующее ограничение будет иметь вид ASSIGN). Типы распространяются (пропагируются) по графу типовых переменных по определённым правилам, которые устанавливаются ограничениями. Так, если в программе выполняется присваивание х = у, то любой тип, который может принимать выражение у, может также иметь и переменная х. Поэтому ограничение вида AS SIGN пропагирует все типы из у в х, отражая тот факт, что type (у) ctype (х). Эго иллюстрирует рис. 2 (типы изображены на нём в виде чёрных точек).

Добавление в граф новых ограничений создаёт необходимость в дальнейшей пропагации типов. Верно и обратное: например, для вызова некоторого метода в результате пропагации может быть вычислен новый тип объекта. Значит, возможно, будет необходимо анализировать тела методов в этом объекте, а при анализе тел методов могут быть созданы новые дуги в графе (ограничения) и так далее. В алгоритмах, которые работают с графом типовых переменных, циклически создаются новые ограничения и пропагируются типы до тех пор, пока эти действия вносят изменения в граф. Работа с графом типовых переменных зависят от семантики того или иного динамического языка программирования. Виды ограничений, создаваемых для программ на языке Python, подробно описаны в статье [2], которая служит основой для исследовательского проекта TIRPAN [28].

i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

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

на выходе по ним строится тот тип, который имело бы возвращаемое значение исходной функции. Также, возможно, моделируется внесение изменений в типы входных параметров (например, для функции append в языке Python или аналогичной ей функции push в языке Ruby тип первого параметра меняется в ходе выполнения).

6 Чекеры для ошибок несоответствия типов

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

Фреймворк осуществляет итерирование по AST, при этом пропускаются участки кода, для которых выше по стеку есть обработчики исключений, соответствующих ошибкам несоответствия типов для данного языка. Например, для следующего кода на языке Ruby фреймворк пропустит код с вызовом getTranslation, иначе это могло бы привести к многочисленным ложным срабатываниям чекера, проверяющего доступ к несуществующему методу:

translation = message.getTranslation() rescue NoMethodError

Фреймворк предоставляет чекерам возможность зарегистрировать произвольное число функций обратного вызова (callback), которые будет вызываться при входе в узлы или выходе из узлов определённого типа. Предположим, что для кода на языке Python необходимо реализовать чекер basenameChecker, проверяющий, что в функцию os . path . basename было передано значение, отличное от строки. Регистрация такого чекера может выглядеть следующим образом:

registerCallback(Event.ON_ENTER, ast.Call, processBasenameCall)

Основная функциональность фреймворка — это предоставление интерфейсных функций следующих видов: (1) функций, возвращающих множество типов для узла (например, getNodeTypes); (2) предикатов для проверки типа (например, isFunction); (3) функций для работы с внутренней структурой типа или для вывода его внешнего представления (например, getListElements или getQualifiedName); (4) функций формирования сообщения об ошибке (например, reportError). Такой интерфейс позволяет реализовать каждую из функций, из которых состоит чекер несоответствия типов, в компактном виде. Например, реализация processBasenameCall с использованием интерфейсных функций может выглядеть так:

return (isFunction(element) and

functionTypes = node.func.getNodeTypes() if any(isBasenameFunction(type) for type in functionTypes):

argTypes = node.args[0].getNodeTypes() for type in argTypes:

if not isString(type):

7 Генерация трасс дефектов

Предлагаемый подход к построению статического анализатора предполагает генерацию трасс для найденных ошибок несоответствия типов. Говоря неформально, трасса дефекта — это описание последовательности шагов, из которой можно понять, как неправильный тип попадает в точку несоответствия типов. Такое описание должно помочь пользователю статического анализатора определить, является ли обнаруженный дефект истинным. Чтобы понять важность наличия трассы, рассмотрим ошибку несоответствия типов из проекта Gramps [2, 29], которая заключается в том, что в функцию os . path. basename вместо строки передаётся пустой список:

def set_css_filename(self, css_filename) : if os.path.basename(css_filename):

self.css_filename = css_filename else:

self.doc = HtmlDoc(. ) self.CSS =

PLUGMAN.process_plugin_data(«WEBSTUFF») self.css_filename = self.CSS[id][«filename»]

При обнаружениив set_css_filename дефекта несоответствия типов чекер basenameChecker может предоставить пользователю трассировку стека (stack trace), включающую в себя функции set_css_filename, make_document, on_ok_clicked и так далее. Однако, такая трассировка малоинформативна, поскольку не позволяет проследить, откуда у css_filename появилось значение, не являющееся строкой. Эту информацию можно получить путём «обратного разбора» функции process_plugin_data, что, с свою очередь, потребует дальнейшего анализа. Подобный анализ и является генерацией трассы дефекта.

Так как типовые переменные являются вершинами ориентированного графа, понятие трассы дефекта можно определить более формально, рассмотрев новый граф, в котором все вершины соединены обратными дугами. Эго означает, что, если в исходном графе имелось ограничение вида AS SIGN от переменной А к переменной В, в новом графе будет присутствовать дуга от В к А. Будем считать, что обратные дуги имеют вид «конкатенация ! и названия вида первоначальной дуги», например, ! AS SIGN.

После того, как мы ввели новый граф, трассу можно определить как набор путей в нём, причём каждый путь начинается в переменной, соответствующей месту дефекта (например, аргументу os. path. basename для Defect. BASENAME) и строится по определённым правилам, которые задаются дугами в графе. Рассмотрим небольшой участок кода и покажем на примере, как при помощи его «обратного» графа (показан на рис. 3) для дефекта Defect. BASENAME строится трасса, состоящая из ровно одного пути:

if condition(): f = у

Построение пути начинается с типовой переменной для f, которая становится текущей вершиной для алгоритма. Для каждого узла в алгоритме задаётся текущее множество типов, поиск которых осуществляется. Первоначально (для первой вершины пути) это множество равно множеству «неправильных» типов для дефекта. Для данного примера множество равно (list(int)>. Далее производится добавление в трассу новых вершин графа в соответствии с ограничениями и обход вершин в глубину. Ограничение вида ! AS SIGN добавляет в граф вершину на конце дуги (назовём её V, ASSIGN)в случае, если

пересечение множества искомых типов для текущего узла и

type (V, ASSIGN) не пусто. Когда vis j станет текущей вершиной,

множеством искомых типов для неё будет это пересечение. Согласно этому правилу, переменная для «abc» в трассу не добавляется (поэтому она и дуга к ней изображены на рис. 3 пунктиром), а переменные для у и [ х ] добавляются. Правило для вершин на конце ! ELEMENT также вычисляет пересечение типов, но при этом «раскрываются» коллекции (например, (list(int)>

превращается в ). По этому правилу в трассу добавляется переменная для х и, следовательно, для 1. На этом построение трассы заканчивается, поскольку вершин для добавления в неё больше нет.

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

Перечислим возможные направления дальнейших исследований по рассматриваемой тематике:

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

2. Добавление поддержки инкрементального анализа, при котором небольшие изменения в тексте программы вызывают пересчёт небольшого числа типов выражений, что позволило бы выполнять статический анализ при наборе кода в IDE.

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

[1] L. D. Paulson. Developers shift to dynamic programming languages. IEEE Computer, vol. 40, issue 2, February 2007, pp. 12-15.

[2] Бронштейн И. E. Вывод типов для языка Python. Труды Института системного программирования РАН, том 24, 2013, стр. 161-190.

[3] ТЮВЕ Programming Community Index for August 2013: http://tinyurl.com/tiobe-201308

[4] Савицкий В. О., Сидоров Д. В. Инкрементальный анализ исходного кода на языках C/C++. Труды Института системного программирования РАН, том 22, 2012, стр. 119—129.

[5] Аветисян Арутюн, Белеванцев Андрей, Бородин Алексей, Несов Владимир. Использование статического анализа для поиска уязвимостей и критических ошибок в исходном коде программ. Труды Института системного программирования РАН, том 21, 2011, стр. 23-38.

[6] Руflakes: https://launchpad.net/pyflakes

[7] Pylint — code analysis for Python: http://www.pylint.org

[8] reek: https://github.com/troessner/reek

[9] ruby-lint: http://code.yorickpeterse.com/ruby-lint/latest

[10] JSLint: http://www.jslint.com

[11] JSHint, a JavaScript Code Quality Tool: http://www.jshint.com

[12] PEP 8 — Style Guide for Python Code: http://www.python.org/dev/peps/pep-0008

[13] R. Chillarege, I. Bhandari, J. Chaar, M. Halliday, D. Moebus, B. Ray, M. Y. Wong. Orthogonal Defect Classification — A Concept of In-Process Measurements (1992).

[14] W. Humprey. A discipline for software engineering (1997).

[15] M. Reingart. Rad2py: Platafonna de Trabajo para el Desarrollo Rapido de Aplicaciones bajo un Proceso de Software Personal, pp. 152-153: http://code.google.eom/p/rad2py/wiki/DefectTypeStandard

[16] Bugs : Exaile: https://bugs.launchpad.net/exaile

[17] Bugs : calibre: https://bugs.launchpad.net/calibre

[18] sqlalchemy: http://www.sqlalchemy.org/trac

[19] Bugs : Bazaar: https://bugs.launchpad.net/bzr

[20] Twisted: http://twistedmatrix.com/trac

[21] The Trac Project: http://trac.edgewall.org

[22] Django: https://code.djangoproject.com

[23] DRuby — Home: http://www.cs.umd.edu/projects/PL/druby

[24] Type Analysis for JavaScript: http://www.brics.dk/TAJS

[25] Michael Furr, Jong-hoon (David) An, Jeffrey S. Foster, Michael Flicks. Static type inference for Ruby. SAC ’09 Proceedings of the 2009 ACM symposium on Applied Computing, pp. 1859-1866.

[26] jruby-parser: https://github.com/jruby/jruby-parser

[27] esprima: https://github.com/ariya/esprima

[28] tirpan: https://github.com/bronikkk/tirpan

[29] Gramps Bug Report 005023: http://www.gramps-project.org/bugs/view.php?id=5023

Approach to detecting types inconsistency errors in a program code in dynamic languages

I. E. Bronshteyn ibronstein@ispras.ru

Abstract. The paper deals with detection of defects in a program code written in dynamic languages. At first, overview of static analyzers for programs in Python, Ruby and JavaScript is done. After this, the paper shows that most of existing tools are not able to detect entire class of defects: types inconsistency errors. Such errors are defined, the paper proves that the errors are prevailing and rather critical in the opinion of software developers. It presents an approach to type inference for dynamic languages and to implementation of checkers based on output from type inference. Concept of defect trace is introduced and construction of such trace is described then.

Keywords: static analysis; defects detection; dynamic typing; Python; Ruby; JavaScript; types inconsistency; defects traces.

Сравнение линтеров для кода на Python и советы по их применению

В первой части статьи, опубликованной на pythonist.ru (с которой мы уже знакомили вас ранее), разбиралось, почему качество кода имеет такое большое значение, какой код можно считать качественным и на какие стандарты можно ориентироваться.

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

Для сравнения пропустим одинаковый код через несколько линтеров с дефолтными настройками.

Проверять будем следующий код. В нем есть целый ряд логических и стилистических ошибок:

""" code_with_lint.py Example Code with lots of lint! """ import io from math import * from time import time some_global_var = 'GLOBAL VAR NAMES SHOULD BE IN ALL_CAPS_WITH_UNDERSCOES' def multiply(x, y): """ This returns the result of a multiplation of the inputs """ some_global_var = 'this is actually a local variable. ' result = x* y return result if result == 777: print("jackpot!") def is_sum_lucky(x, y): """This returns a string describing whether or not the sum of input is lucky This function first makes sure the inputs are valid and then calculates the sum. Then, it will determine a message to return based on whether or not that sum should be considered "lucky" """ if x != None: if y is not None: result = x+y; if result == 7: return 'a lucky number!' else: return( 'an unlucky number!') return ('just a normal number') class SomeClass: def __init__(self, some_arg, some_other_arg, verbose = False): self.some_other_arg = some_other_arg self.some_arg = some_arg list_comprehension = [((100/value)*pi) for value in some_arg if value != 0] time = time() from datetime import datetime date_and_time = datetime.now() return

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

ЛИНТЕР КОМАНДА ВРЕМЯ
Pylint pylint code_with_lint.py 1,16 с
PyFlakes pyflakes code_with_lint.py 0,15 с
pycodestyle pycodestyle code_with_lint.py 0,14 с
pydocstyle pydocstyle code_with_lint.py 0,21 с

Теперь давайте посмотрим на результаты.

Pylint

Pylint это один из самых старых линтеров (работает с 2006 года), но при этом он хорошо поддерживается. Можно сказать, что этот инструмент проверен временем. Контрибьюторы уже давно пофиксили все основные баги, а главный функционал хорошо отшлифовали.

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

Итак, вот результат запуска Pylint для приведенного выше кода:

No config file found, using default configuration * Module code_with_lint W: 23, 0: Unnecessary semicolon (unnecessary-semicolon) C: 27, 0: Unnecessary parens after 'return' keyword (superfluous-parens) C: 27, 0: No space allowed after bracket return( 'an unlucky number!') ^ (bad-whitespace) C: 29, 0: Unnecessary parens after 'return' keyword (superfluous-parens) C: 33, 0: Exactly one space required after comma def init(self, some_arg, some_other_arg, verbose = False): ^ (bad-whitespace) C: 33, 0: No space allowed around keyword argument assignment def init(self, some_arg, some_other_arg, verbose = False): ^ (bad-whitespace) C: 34, 0: Exactly one space required around assignment self.some_other_arg = some_other_arg ^ (bad-whitespace) C: 35, 0: Exactly one space required around assignment self.some_arg = some_arg ^ (bad-whitespace) C: 40, 0: Final newline missing (missing-final-newline) W: 6, 0: Redefining built-in 'pow' (redefined-builtin) W: 6, 0: Wildcard import math (wildcard-import) C: 11, 0: Constant name "some_global_var" doesn't conform to UPPER_CASE naming style (invalid-name) C: 13, 0: Argument name "x" doesn't conform to snake_case naming style (invalid-name) C: 13, 0: Argument name "y" doesn't conform to snake_case naming style (invalid-name) C: 13, 0: Missing function docstring (missing-docstring) W: 14, 4: Redefining name 'some_global_var' from outer scope (line 11) (redefined-outer-name) W: 17, 4: Unreachable code (unreachable) W: 14, 4: Unused variable 'some_global_var' (unused-variable) … R: 24,12: Unnecessary "else" after "return" (no-else-return) R: 20, 0: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements) C: 31, 0: Missing class docstring (missing-docstring) W: 37, 8: Redefining name 'time' from outer scope (line 9) (redefined-outer-name) E: 37,15: Using variable 'time' before assignment (used-before-assignment) W: 33,50: Unused argument 'verbose' (unused-argument) W: 36, 8: Unused variable 'list_comprehension' (unused-variable) W: 39, 8: Unused variable 'date_and_time' (unused-variable) R: 31, 0: Too few public methods (0/2) (too-few-public-methods) W: 5, 0: Unused import io (unused-import) W: 6, 0: Unused import acos from wildcard import (unused-wildcard-import) … W: 9, 0: Unused time imported from time (unused-import)

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

Обратите внимание, что Pylint добавляет к каждой проблемной области префикс R, C, W, E или F, что означает:

  • [R]efactor — нужен рефакторинг, поскольку показатель «good practice» не на должном уровне.
  • [C]onvention — нарушение соглашения о стандарте кода
  • [W]arning — предупреждение о стилистических проблемах или минорных программных проблемах
  • [E]rror — существенные проблемы в программе (скорее всего баг)
  • [F]atal — ошибки, мешающие дальнейшей работе.

PyFlakes

Pyflakes «торжественно клянется никогда не жаловаться на стиль и очень усердно стараться не допускать ложнопозитивных срабатываний». То есть Pyflakes не сообщит вам о пропущенных docstrings или о том, что имена аргументов не соответствуют стилю нейминга. Он фокусируется на логических проблемах в коде и потенциальных ошибках.

Преимущество этого инструмента в скорости. PyFlakes обработал файл лишь за небольшую долю времени, которое потребовалось Pylint.

Вывод после запуска Pyflakes для приведенного выше кода:

code_with_lint.py:5: 'io' imported but unused code_with_lint.py:6: 'from math import *' used; unable to detect undefined names code_with_lint.py:14: local variable 'some_global_var' is assigned to but never used code_with_lint.py:36: 'pi' may be undefined, or defined from star imports: math code_with_lint.py:36: local variable 'list_comprehension' is assigned to but never used code_with_lint.py:37: local variable 'time' (defined in enclosing scope on line 9) referenced before assignment code_with_lint.py:37: local variable 'time' is assigned to but never used code_with_lint.py:39: local variable 'date_and_time' is assigned to but never used

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

pycodestyle (прежде — pep8)

Этот инструмент проверяет код на соответствие некоторым соглашениям из PEP 8. Нейминг не проверяется, так же как и docstrings. Ошибки и предупреждения, выдаваемые этим инструментом, можно посмотреть в таблице.

Результат использования pycodestyle для приведенного выше кода:

code_with_lint.py:13:1: E302 expected 2 blank lines, found 1 code_with_lint.py:15:15: E225 missing whitespace around operator code_with_lint.py:20:1: E302 expected 2 blank lines, found 1 code_with_lint.py:21:10: E711 comparison to None should be 'if cond is not None:' code_with_lint.py:23:25: E703 statement ends with a semicolon code_with_lint.py:27:24: E201 whitespace after '(' code_with_lint.py:31:1: E302 expected 2 blank lines, found 1 code_with_lint.py:33:58: E251 unexpected spaces around keyword / parameter equals code_with_lint.py:33:60: E251 unexpected spaces around keyword / parameter equals code_with_lint.py:34:28: E221 multiple spaces before operator code_with_lint.py:34:31: E222 multiple spaces after operator code_with_lint.py:35:22: E221 multiple spaces before operator code_with_lint.py:35:31: E222 multiple spaces after operator code_with_lint.py:36:80: E501 line too long (83 > 79 characters) code_with_lint.py:40:15: W292 no newline at end of file

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

pydocstyle (прежде — pep257)

Этот инструмент очень похож на предыдущий, pycodestyle, за исключением того, что проверяет код не на соответствие PEP 8, а на соответствие PEP 257.

Результат запуска для приведенного выше кода:

code_with_lint.py:1 at module level: D200: One-line docstring should fit on one line with quotes (found 3) code_with_lint.py:1 at module level: D400: First line should end with a period (not '!') code_with_lint.py:13 in public function `multiply`: D103: Missing docstring in public function code_with_lint.py:20 in public function `is_sum_lucky`: D103: Missing docstring in public function code_with_lint.py:31 in public class `SomeClass`: D101: Missing docstring in public class code_with_lint.py:33 in public method `__init__`: D107: Missing docstring in __init__

Как и pycodestyle, pydocstyle помечает и разбивает по категориям найденные ошибки. Этот список не конфликтует ни с чем из pycodestyle, поскольку все ошибки имеют приставку D (означающую docstring). Список ошибок можно посмотреть здесь.

Код без ошибок

Если учесть предупреждения и исправить ошибки, найденные линтерами, вы получите примерно такой код:

"""Example Code with less lint.""" from math import pi from time import time from datetime import datetime SOME_GLOBAL_VAR = 'GLOBAL VAR NAMES SHOULD BE IN ALL_CAPS_WITH_UNDERSCOES' def multiply(first_value, second_value): """Return the result of a multiplation of the inputs.""" result = first_value * second_value if result == 777: print("jackpot!") return result def is_sum_lucky(first_value, second_value): """ Return a string describing whether or not the sum of input is lucky. This function first makes sure the inputs are valid and then calculates the sum. Then, it will determine a message to return based on whether or not that sum should be considered "lucky". """ if first_value is not None and second_value is not None: result = first_value + second_value if result == 7: message = 'a lucky number!' else: message = 'an unlucky number!' else: message = 'an unknown number! Could not calculate sum. ' return message class SomeClass: """Is a class docstring.""" def __init__(self, some_arg, some_other_arg): """Initialize an instance of SomeClass.""" self.some_other_arg = some_other_arg self.some_arg = some_arg list_comprehension = [ ((100/value)*pi) for value in some_arg if value != 0 ] current_time = time() date_and_time = datetime.now() print(f'created SomeClass instance at unix time: ') print(f'datetime: ') print(f'some calculated values: ') def some_public_method(self): """Is a method docstring.""" pass def some_other_public_method(self): """Is a method docstring.""" pass

Согласно «мнению» приведенных выше линтеров, этот код больше не имеет «ворсинок». И хотя логика сама по себе бессмысленная, вы можете заметить, что, как минимум, этот код отличается единообразием.

В рассмотренном случае мы запускали линтеры на уже написанном коде. Но это не единственный способ проверки качества кода.

Когда можно проверять качество кода?

Вы можете проверять качество своего кода:

  • по мере написания,
  • перед отправкой,
  • при запуске тестов.

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

Чтобы этого избежать, проверяйте качество кода почаще!

Проверка кода по мере его написания

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

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

Проверка кода перед его отправкой

Если вы используете Git, можно настроить Git hooks для запуска линтеров перед коммитом. Другие системы контроля версий имеют схожие методы для запуска скриптов в привязке к определенным действиям в системе. При помощи этих методов можно блокировать любой новый код, не соответствующий стандартам качества.

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

При запуске тестов

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

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

Заключение

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

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

Благодаря руководствам по стилю ваш код может стать единообразным. PEP8 — отличная отправная точка, если речь идет о Python. Линтеры помогут вам обнаружить проблемные места и стилевые несоответствия. Использовать эти инструменты можно на любой стадии процесса разработки; их можно даже автоматизировать, чтобы код с «пухом» не прошел слишком далеко.

Использование линтеров позволяет избежать ненужных дискуссий о стиле в ходе код-ревью. Некоторым людям морально легче получить объективный фидбэк от инструментов, а не от товарищей по команде. Кроме того, некоторые ревьюеры могут просто не хотеть «придираться» к стилю проверяемого кода. Линтеры не озабочены всеми этими политесами и экономией времени: они жалуются на любое несоответствие.

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *