Pylint:代码分析器的详细测试

当Luke与Flake8合作并密切关注Pylint时,他给人的印象是Pylint产生的错误中有95%是错误的。其他开发人员在与这些分析器进行交互时有着不同的经验,因此Luke决定详细研究这种情况,并研究其代码中的11000行。此外,他称赞Pylint的好处,认为它是Flake8的补充。



Luke(卢克工厂)-英国开发人员之一,最近在他的文章中分析了流行的代码分析器。 Linters会研究代码,帮助您查找错误,并使其在样式上与团队中的开发人员编写的标准和代码一致。最常见的是Pylint和Flake8。我们在Leader-ID中我们也使用它们,因此我们很高兴翻译了他的文章。我们希望它将通过这些工具丰富您的经验。

初始设置和测试基础


在本实验中,我从一个项目中摘取了一部分代码,并使用基本设置启动了Pylint。然后他尝试分析结果:哪些警告有用,哪些警告错误。

有关从中获取代码的项目的一些帮助:

  • 用Django编写的常规应用程序(即在同一Python中)。Django有其自身的特点,并且作为框架,它有其局限性,但允许您编写普通的Python代码。使用模板的其他库(回调函数或模板方法设计模板)也有一些缺点,作为框架。
  • 包含22,000行代码。大约有11,000条线通过Pylint(如果省略间隙,则为9,000条线)。该项目的这一部分主要由视图和测试代码组成。
  • 为了分析该项目的代码,我已经使用Flake8,处理了收到的所有错误。该实验的重点是评估Pylint作为Flake8的附加产品的好处。
  • 该项目对代码进行了很好的测试,但是由于我是其唯一作者,因此我没有机会进行同行评审。

我希望这种分析对其他开发人员决定在堆栈中使用Pylint来检查代码质量很有用。我假设如果您已经在使用诸如Pylint之类的东西,则可以系统地将其用于必要的代码质量控制,以最大程度地减少问题。
因此,Pylint已向我的代码发出1650条索赔。

在下面,我按类型将所有分析器注释分组,然后将它们提供给他们。如果需要这些消息的详细说明,请查看Pylint函数列表相应部分

虫子


Pylint在我的代码中发现了一个错误。我将错误视为程序运行期间发生的或可能潜在的错误。在这种情况下,我使用了例外- broad-except.except Exception,不仅仅是Flake8捕获的例外。这将导致运行时行为出现某些异常。如果此错误曾在运行时弹出(不是弹出的事实),则错误的代码行为不会有严重的后果,尽管...

合计:1

有用


除了该错误外,Pylint还发现了一些我认为有用的东西。这些代码并非源于它们,但是在重构过程中可能会出现问题,并且原则上在扩展功能列表和支持代码时会出现将来的错误。

他们七人too-many-locals/ too-many-branches/ too-many-local-variables它们属于我的代码中结构不良的三个部分。考虑一下结构会很好,而且我敢肯定我会做得更好。

其他错误:

  • unused-argument×3-其中一个确实是门框,并且代码随机正确地执行。如果我使用其他两个不必要的和未使用的参数,将来会导致问题。
  • redefined-builtin ×2
  • dangerous-default-value ×2-不是bug,因为我从未使用过默认值,但是以防万一,将其修复会很好。
  • stop-iteration-return ×1-在这里,我为自己学到了一些新东西,我自己永远找不到。
  • no-self-argument ×1

合计:16

外观编辑


我会较少注意这些事情。它们微不足道或不太可能。另一方面,它们的校正不会是多余的。其中一些是有争议的风格。我在其他部分中谈到了一些类似的门框,但此处列出的门框也适用于这种情况。定期使用Pylint,我会纠正这些“缺陷”,但是在大多数情况下,我不会担心它们。

invalid-name×192

这些基本上是单字母变量名称。在不感到恐惧的情况下使用,例如:


要么


许多都在测试代码中:

  • len-as-condition ×20
  • useless-object-inheritance ×16(Python 2旧版)
  • no-else-return ×11
  • no-else-raise ×1
  • bad-continuation ×6
  • redefined-builtin ×4
  • inconsistent-return-statements ×1
  • consider-using-set-comprehension ×1
  • chained-comparison ×1

总计:252

无用


这是我有理由以编写代码的方式编写代码的时候,即使在某些地方这似乎很不正常。而且,我认为最好保留这种形式,尽管有些人不同意或倾向于使用另一种可能避免问题的代码编写风格。

  • too-many-ancestors ×76

在测试代​​码中,我使用了许多类别的杂质来放置实用程序或存根。

  • unused-variable ×43

在测试代​​码中几乎所有时间都发现了它,我打破了记录:


...并且未使用任何元素。有多种方法可以防止Pylint报告错误(例如,给出名称unused)。但是,如果您以我写的形式保留它,它将可以阅读,并且人们(包括我自己)将能够理解和支持它。

  • invalid-name ×26

在这些情况下,我在上下文中分配了适当的名称,但不符合Pylint命名标准的情况。例如,db(这是数据库的常见缩写)和其他一些非标准名称,在我看来,这些名称更易于理解。但是你可能不同意我的看法。

  • redefined-outer-name ×16

有时,对于内部和外部上下文,变量名都拼写正确。而且您将永远不必在内部上下文中使用外部名称。

  • too-few-public-methods ×14

示例包括具有使用attrs创建的数据的类,其中没有公共方法,以及实现字典接口的类,但是需要确保该方法正常工作的类。__getitem__

  • no-self-use ×12

它们出现在测试代码中,在这里我有意向没有参数的基类中添加了方法self,因为这样可以更方便地导入它们并使它们可用于测试用例。其中一些甚至包含在单独的函数中。

  • attribute-defined-outside-init ×10

在这些情况下,有充分的理由按原样编写代码。基本上,这些错误发生在测试代码中。

  • too-many-locals×6,too-many-return-statements×6,too-many-branches×2,too-many-statements×2

是的,这些功能太长了。但是看着它们,我看不到任何清洁和改进它们的好方法。功能之一很简单,尽管很长。它具有非常清晰的结构,并且没有歪曲的编写方式,我可以想到的任何减少它的方法都将包括无用的不必要或不便的功能层。

  • arguments-differ ×6

这主要是由于在重写的方法中使用了* args和** kwargs,它使您可以保护自己免受第三方库方法签名的更改(但在某些情况下,此消息可能表示真实的错误)。

  • ungrouped-imports ×4

我已经使用isort导入

  • fixme ×4

是的,有几件事需要修复,但是现在我不想修复它们。

  • duplicate-code ×3

有时,您使用少量的样板代码,这只是必需的,而当功能体中没有太多实际逻辑时,此警告就会消失。

  • broad-except ×2
  • abstract-method ×2
  • redefined-builtin ×2
  • too-many-lines ×1

我试图找出以什么自然的方式来打破这个模块,但是没有。这是一个示例,您可以看到短绒棉绒是错误的工具。如果我有一个包含980行代码的模块,而我又添加了30行代码,那么我越过了1000行的限制,来自linter的通知在这里无济于事。如果980线很好,那么1010为什么不好?我不想重构该模块,但是我希望lint不会产生错误。我现在看到的唯一解决方案是以某种方式使棉绒保持沉默,这与最终目标相矛盾。

  • pointless-statement ×1
  • expression-not-assigned ×1
  • cyclic-import ×1

我们通过将零件的一部分移至其中一个功能来确定该循环。我找不到基于限制来构造代码的更好方法。

  • unused-import ×1

我已经# NOQA在使用Flake8时添加了该错误,所以不会弹出此错误。

  • too-many-public-methods ×1

如果在我的测试班中有35个测试而不是20个受监管的测试,这真的有问题吗?

  • too-many-arguments ×1

合计:243

无法修复


此类别反映了由于外部限制(例如需要将类返回或传递给必须满足某些要求的第三方库或框架)而无法解决的错误。

  • unused-argument ×21
  • invalid-name ×13
  • protected-access ×3

包括对“文档化的内部对象”的访问,例如sys._getframestdlib和Django中的访问Model._meta

  • too-few-public-methods ×3
  • too-many-arguments ×2
  • wrong-import-position ×2
  • attribute-defined-outside-init ×1
  • too-many-ancestors ×1

合计:46

错误消息


Pylint显然是错误的事情。在这种情况下,这些不是Pylint错误:事实是Python是动态的,Pylint试图发现无法完美或可靠地完成的事情。

  • no-member ×395

与几个基类相关联:来自Django和我自己创建的那些基类。由于动态/元编程,Pylint无法检测到变量。

由于测试代码的结构而发生了许多错误(我使用了django-functest的模板,在某些情况下,可以通过使用调用的“抽象”方法添加其他基类来纠正该错误NotImplementedError),或者可能是重命名许多测试类(我我没有这样做,因为在某些情况下会造成混淆。

  • invalid-name ×52

出现此问题主要是因为Pylint应用了PEP8常量规则,考虑到用定义的每个顶级名称=都是“常量”。确切地定义常量的含义比看起来要困难,但这不适用于某些固有常量的事物,例如函数。另外,该规则不应应用于不太熟悉的创建函数的方式,例如:


由于缺乏对常量的定义,因此有些例子值得商de。例如,是否应该将在模块级别定义的,可能具有或不具有可变状态的类的实例视为常量?例如,在此表达式中:


  • no-self-use ×23

Pylint错误地指出,在许多情况下,我分别使用继承执行各种实现时,Method可能是一个函数,但我无法将它们转换为函数。

  • protected-access ×5

Pylint错误地评估了谁是“所有者”(当前代码片段创建了对象的受保护属性,并在本地使用该属性,但是Pylint看不到这一点)。

  • no-name-in-module ×1
  • import-error ×1
  • pointless-statement ×1

该语句实际上产生了结果:


我用它故意造成一个异常错误,该错误不太可能被测试发现。我不怪Pylint没认出来……

总数:477

小计


我们还没有达到终点,但是现在该将结果分组了:

  1. “好”-“错误”和“实用程序”块-在这里,皮林特绝对有帮助:17。
  2. “中性”-“化妆品变化”-普林特(Pylint)的次要利益,错误不会造成损害:252。
  3. Pylint想要在代码中进行更改的“错误”-“无用”,“无法修复”,“不准确”-不需要的地方。包括由于外部依赖性而无法进行编辑的地方,或者Pylint仅仅错误地分析了代码的地方:766。

好与坏的比例很小。如果Pylint是我的帮助代码审查的同事,我恳求他离开。

要删除错误通知,您可以淹没传出的错误类(这将增加使用Pylint的无用性)或在代码中添加特殊注释我不想做后者,因为:

  1. 这需要时间!
  2. 我不喜欢堆积使沉默者沉默的评论。

当lint肯定有一个加号时,我将很高兴添加这些伪注释。另外,我对注释感到焦虑,因此我的语法突出显示将它们生动地显示出来:如本文所推荐但是,在某些地方,我已经NOQA在Muffle Flake8中添加了#条注释,但是对于一个部分,您只能添加五个错误代码。

字串


Pylint发现的其余问题缺少文档行。我将它们放在单独的类别中是因为:

  1. 它们非常有争议,您可能会对这些事情有完全不同的政策。
  2. 我没有时间分析所有这些。

Pylint总共发现了620个缺少的船坞线(在模块,函数,类方法中)。但是在很多情况下,我是对的。例如:

  • 从名称中清除所有内容时。例如:
  • 当已经定义了文档字符串时-例如,如果我实现Django的数据库路由器接口在这种情况下添加自己的行可能很危险。

在其他情况下,这些描述行不会损害我的代码。在Pylint发现的大约15%至30%的案例中,我会想到:“是的,我需要在此处添加一个文档字符串,谢谢Pylint的提醒。”

总的来说,我不喜欢在任何地方和任何地方添加船坞字符串的工具,因为我认为在这种情况下它们会变坏。最好不要完全写它们。类似的情况,并带有不良评论:

  • 阅读它们会浪费时间:它们没有其他信息,或者包含不正确的信息,
  • 它们有助于下意识地突出显示文本中的文档字符串,因为它们包含有用的信息(如果您在这种情况下编写它们)。

关于缺少文档行的警告很烦人:要删除它们,您需要单独添加注释,这与添加停靠站本身所花费的时间差不多。加上所有这些都会产生视觉噪音。结果,您将获得不必要或无用的文档行。

结论


我相信,我最初对Pylint的无用假设是正确的(在使用Flake8的代码库中)。为了使我能够使用它,反映误报数量的指标应该较低。

除了产生视觉噪音外,还需要更多时间来整理或过滤掉虚假通知,因此我不希望在项目中添加任何其他内容。初级开发人员将更谦虚地进行编辑以删除Pylint的评论。但是,这将导致他们破坏有效的代码,没有意识到Pylint是错误的,或者结果是您将进行大量重构,以便Pylint可以正确理解您的代码。

如果您从一开始就在项目中使用Pylint,或者在没有外部依赖项的项目中使用Pylint,那么我认为您会有不同的见解,并且错误通知的数量会更少。但是,这可能导致额外的时间成本。

另一种方法是将Pylint用于有限数量的错误。但是,只有少数几个问题,其回答被证明是正确的,或者极少是错误的(相对和绝对)。其中有:dangerous-default-valuestop-iteration-returnbroad-exceptionuseless-object-inheritance

无论如何,我希望本文在考虑使用Pylint还是与同事发生纠纷时对您有所帮助。

All Articles