霍利瓦尼关于短毛绒的故事

我们都写代码。 很多代码。 当然有错误。 有时,这只是歪曲的代码,而有时,错误的代价就是爆炸的宇宙飞船 。 当然,没有人会故意制造障碍,每个人都试图尽其所能来监视质量,但是如果没有静态分析工具,几乎不可能确保所有事情都是完美的。

Linters帮助将代码带入单一样式并避免错误。 是的,只有当您准备好遭受苦难并且最终不要放弃“ pylint:disable”时,才让它落后。 什么是短绒棉 ,为什么Pylint不能做到,所以知道尼基塔· 索伯列文 (Nikita Sobolevn )如此了解并热爱短绒 ,以至于他甚至给自己的公司起名,以免打扰他们-wemake.services。

图片

以下是莫斯科Python Conf ++上有关短毛绒,如何正确执行以及如何不执行的报告的文本版本。 演示文稿具有很多互动性,在线性以及与观众的交流。 演讲者一路进行民意调查,并试图说服听众:他审视了这一趋势,并且在辩论中,他试图使比例平均化并改变舆论。 一部分民意测验被解密,但不是全部解密,因此附加了一段视频以完成图片。



为什么我们需要短绒呢?


短绒最重要的任务是使代码统一 。 有很多选择可以用Python编写相同的东西:在此处或此处加逗号,忘记用括号括起来,或者别忘了。 当人们长时间编写代码时,它就像是在不同时间缝制的零散拼凑而成的被子。 使用这样的毯子是不愉快的,不鼓励阅读代码,这是非常糟糕的。

短绒棉让生活更轻松 。 我来到代码审查并认为:“我不想这样做! 现在将有多余的空间和其他废话!” 我希望其他人准备好的代码,在那之后,我将欣赏伟大的概念性东西。

有时我看代码并认为一切都很好,然后在某些函数中看到太多变量或我没有注意的错误。 自动化会发现此错误,但我看了看。 为了不陷入这种情况-我用短绒棉 --他发现了所有隐藏且难以找到的东西。

什么是短绒?


最简单的仅检查样式 ,例如Flake8 。 在某种程度上,Black也是如此,但是它是一种自动成型机衬垫。 Linters测试语义更困难 ,而不仅仅是样式学:测试您做什么,为什么做,如果书写有错误,就会打败您。 一个很好的例子是Pylint ,我们都知道,使用和喜欢它。 我把这些短毛猫称为最佳实践 。 第三类是类型检查 ,这些短毛绒有点偏。 Python中的类型检查是新的,它现在由两个相互竞争的平台进行: MypyPyre

短绒如何使用?


我并不是说棉绒是万能药,而是万能的替代品。 事实并非如此。 Linter-金字塔的第一步,通过此步骤,代码得以投入生产。

金字塔包含三个步骤:

  • 发射短绒 。 它非常快,除了源代码外不需要任何东西-没有基础设施,没有设置。 检查:通过了第一次健康检查 -一切都很好,我们正在努力。
  • 测试阶段 。 由于非代码错误,此过程更加复杂且时间更长。 我们已经需要整个应用程序的正确和完整的设置。
  • 阶段审查



这些是代码投入生产的必要步骤 。 如果您不走一步,忘记了一些东西,或者审阅者说这行不通,则会看到题词:失败-错误代码无法投入生产。

您在工作中使用短绒吗?


如果您问一个苛刻的企业的开发人员(他们每周工作7天)是否使用短绒,结果发现至少三分之一的人非常严格地使用短绒: CI下降,检查很严格 。 其余的都大致相同地将linter 应用于测试样式从不作为报告系统使用 :他们启动了linter,生成了报告,并查看了一切情况。 使用了短绒,这很好。 在我们公司中,所有内容都构建得非常苛刻:硬链接,大量检查,双重代码审查。

代码审查


问题仅在此阶段出现。 这是金字塔中最困难的第一步:代码审查不能自动化,如果可能的话,它将导致代码编写自动化。 这样就不需要程序员了。

默认情况下,该过程如下所示:该代码用于审核,我发现了错误,并且我不想再犯了。 例如,我看到开发人员捕获到BaseException:“不要那样做。 请不要抓!” 10天后,同样的事情。 我再次提醒您:

- 我们没有捕获BaseException。
好,我明白了。”

一年过去了-同样的错误。 一个新男人来了-同样的错误。 我认为-我们如何使一切自动化,以使情况不再发生,而只会浮现在脑海:“ 我们我们的棉绒加油吗? »我们创建一个开放的程序包,在其中放置我们在工作中使用的所有规则并自动执行规则检查,以免每次都不用手写。 我们马上就可以自动化一切!

自然,您可以说:“ 现成的短绒已经存在,它们可以工作,每个人都在使用它们-为什么要自己制造?”,您将绝对正确,因为确实有短绒。 让我们看看哪些和它们做什么。

皮林特


在标题“ 为什么不是Pylint?”上 “我已经多次听到这个问题。 我会更轻柔地回答他。 Pylint是Python代码的出色摇滚明星工具,但是它具有我不想在我的短毛绒上看到的功能。

它将所有内容混合在一起:样式检查,最佳做法和类型检查 。 由于没有类型信息,Pylint类型检查尚不完善:它试图以某种方式显示它,但效果不佳。 因此,通常当我在Django上编写model_name.some_property时,我会看到错误:“抱歉,没有这样的属性-您不能使用它!” 我记得有一个插件,我先安装了它,然后我使用Celery,它也带来了一些麻烦,我为Celery安装了插件,我使用了其他魔术库,结果,我随处写着:“ pylint:disable”……不是我想从林特那里得到什么。

用户隐藏的另一个功能是Pylint在Python中具有自己的Abstract语法树实现 。 这是解析代码并获取有关组成代码的节点树的信息时代码的外观。 我并不真正相信自己的实现,因为它们总是错误的。

除了皮林特(Pylint),还有其他的短毛猫也能发挥作用。

声纳古伯


一个很棒的工具,但是却是一个单独的工具,位于您的项目附近。

  • SonarQube将无法经常运行 :它需要部署在某个地方,监视,监视和配置。
  • 它是用Java编写的 。 如果要修复Python的linter,将使用Java编写代码。 我认为从概念上讲这是错误的-可以使用Python编写的开发人员应该能够编写代码以测试Python。

开发SonarQube的公司专门研究产品开发的概念。 这可能是一个问题。

SonarQube的优点是它具有非常酷的检查功能,可以显示复杂性,可能的隐藏错误和错误。 我喜欢支票,我会离开支票并更换平台。

片状8


出色的Linter非常简单,但是存在一个问题: 几乎没有规则可以检查代码的编写程度。 同时,Flake8有很多非常简单的插件:最小插件是需要实现的2种方法。 我认为-让我们以Flake8为基础并编写插件,但是我们已经了解了公司的收益。 因此,我们做到了。

世界上最严格的棉短绒


我们制作了一个工具,收集了我们认为适合Python的所有内容,并称为wemake-python-styleguide 。 该插件已公开发布,因为我认为默认情况下开放源代码是一种好习惯 。 我深信,如果将许多工具上传到开源,它们将受益。 对于我们的乐器,我们提出了一个口号: “世界上最严格的棉绒!”

我们的短毛绒中的关键词是严格的,这意味着痛苦和痛苦。

如果您使用短绒棉布,但它不会使您受苦,以至于只能抓着脑袋:“你为什么不喜欢,该死的你,”那么这就是一个糟糕的短绒棉布。 它会跳过错误,无法充分监控代码质量,因此我们不需要它。 我们需要世界上最严格的检查,这很多。 现在,我们在两种类别中都有大约250种不同的测试 :文体和最佳实践,但是没有类型检查。 Mypy参与其中,我们丝毫不关心他。

我们的短毛绒丝毫不妥协 。 我们没有类别“我不想这样做,但如果您真的想要,那么可以”的规则。 不,我们总是严厉地说-我们不这样做,因为那很糟糕。 然后人们来说:“有2.5个用例,原则上可以做到!”。 如果出现这种情况,请清楚地写下这条线,允许短绒棉袜忽略它,但要说明原因。 应该评论一下为什么您允许进行一些奇怪的练习以及为什么要这样做。 此方法对于记录代码也很有用。

最严格的棉绒不需要设置(WIP) 。 我们仍然有设置,但我们想摆脱它们:拥有自由,用户一定会进行配置,以使短绒棉绒无法正常工作。

一个好的工具不需要设置-它具有好的默认值。

通过这种方法,至少在理论上,代码将是一致的,并且对每个人都适用。 我们仍在处理此问题,尽管有设置,但您可以使用我们的工具并自行定制。

我们依赖谁?


来自大量的工具。

  • 片状8
  • Eradicate是一个很酷的插件,它可以在代码中找到注释掉的片段并使您删除它们,因为在项目中存储无效代码是很糟糕的。 我们不允许这样做。
  • Isort是一种可强制您对导入进行正确排序的工具:顺序,缩进,优美的引号。
  • Bandit是一个很好的静态检查代码安全性的工具。 它找到有线密码,在代码中笨拙地使用assert ,调用sys.exitsys.exit并说所有这些都无法使用,但是如果需要,它会要求写下原因。
  • 以及超过20个用于检查方括号,引号和逗号的插件

我们在检查什么?


我们使用和执行4组规则。

复杂性是最大的问题。 我们不知道复杂性是什么,我们也没有在代码中看到它。 我们查看每天使用的代码,似乎并不复杂-接受,阅读并一切正常。 事实并非如此。 简单代码是一种熟悉的代码。 复杂度有我们测试的明确标准。 关于标准本身-稍后。 如果代码违反标准,那么我们说:“代码很复杂,请重写!”

变量名是一个尚未解决的编程问题。 谁会在何时何地阅读内容尚不清楚。 我们尝试使名称尽可能一致和易于理解,但是尽管我们尝试这样做,但问题尚未完全解决。

为了保持一致性 ,我们有一个简单的规则-到处都写相同的规则。 如果有任何认可的方法,请在各处使用。 不管您是否喜欢,一致性都更重要。

我们尝试仅使用最佳做法。 如果我们知道某些做法不是很好,那么我们将禁止使用它。 如果开发人员想要使用禁止的做法,那么我们期待他的论点:为什么以及为什么要申请。 在描述的过程中,也许可以理解为什么它不好。

什么是复杂性?


复杂度具有特定的指标,您可以查看并说出它是否困难。 有很多。

圈复杂度 -每个人最喜欢的圈复杂度。 它在代码中发现了大量嵌套的iffor ,其他结构,并指示代码分支过多和阅读困难。 使用嵌入式代码,一切都是不好的:您读,读,读-返回,读,读,读-跳起来,然后进入另一个循环。 从上到下安全地传递这样的代码是不可能的。

参数,语句和返回。 这些是定量指标:函数或方法中有多少个参数,此函数或语句和返回方法中有多少个参数。

内聚和耦合是流行的OOP指标。 内聚性显示了班级内部的联系。 例如,有一个类,您在其中使用所有方法和属性-声明的所有内容。 这是内部连接性高的好类。 耦合是指系统的不同部分(模块和类)之间的连接程度。 我们希望在类内实现最大的连通性,而在外部实现最小的连通性。 然后,该系统易于维护并且运行良好。

琼斯复杂度 -我借用了这一指标,但这仅仅是因为它是炸弹! 琼斯复杂度确定线的复杂度-线越复杂,就越难理解,因为短期的人类记忆不能一次处理5-9个以上的对象。 这就是所谓的米勒钱包

我们查看这些重要指标以及其他一些重要指标,并确定代码是否合适。 以我们的理解, 复杂性是瀑布

瀑布难度


困难始于我们编写了这一行,但这仍然很好。 但是随后业务来了,并说价格翻了一番,我们乘以2。在这一点上,Jones Complexity发疯了,并报告说现在这条线太复杂了-逻辑太多了。

好了,我们开始一个新变量,函数复杂度分析器说:

- 不,不是-现在函数中有太多变量。

我将创建一个新方法 ,并将参数传递给它。 现在检查函数参数的数量或内部的方法的数量,这也是不可能的-类太复杂了,必须分为两部分。 通过突出显示另一个类而失败。 现在有更多的类,并且一切都很好,但是检查模块的复杂性会报告该模块现在太复杂了,需要重构。 为什么呢?

这就是所谓的苦难。 这就是为什么我说短绒棉会让你受苦。 我们从一行中乘以2开始,最后重构了整个系统 。 添加一小段代码会导致整个模块的重构,因为复杂性像瀑布一样散布开来,并涵盖了所有可能的内容。

“需要重构” -这件事使您可以重构代码。 您不能只是坐下来:“我不触摸此代码,它似乎可以工作。” 不,有一天,您将在其他地方更改代码,复杂的瀑布将淹没您没有接触过的模块,因此您必须对其进行重构。 我相信重构是好的,而且重构越多,系统的稳定性和性能就越好。 而其他一切都是主观的!

现在让我们谈谈口味。 这是一个整体和互动的部分!

霍利瓦尔


让我们支持,评论是开放的。 首先,让我提醒您,名称是一个复杂且尚未解决的问题。 您可以为如何命名变量而斗争,但是我们有一些方法至少可以帮助您避免犯明显的错误。

名字


您如何看待: var,value,item,obj,data,result ? 什么是数据 ? 一些数据。 结果是什么? 某种结果。 我经常在一个难以理解的类中看到结果变量和对某些推理方法的调用-我想:“这是什么结果? 他为什么在这里?”

许多开发人员不同意我的观点,并说value是一个完全正常的变量名:

- 我总是使用键和值!
- 为什么不使用键和值,而是说键是名称,值是姓? 为什么无法命名first_name和last_name-现在有了上下文。

人们通常会同意,但无论如何他们都会争论。 这是一件非常全面的事情:至少有3个人花了我一个小时的时间与我争论。

用一个字母命名变量可以吗?


例如, q ? 我们都知道经典的情况: for i in some_iterable: 我是什么? 在C语言中,这是标准做法,一切都源自于此。 但在Python中,集合和迭代器。 集合包含具有名称的元素-让我们以不同的方式称呼它们。

一半的开发人员认为调用变量i,x,y,z是正常的。

我相信您不能用一个字母来命名姓名。 我想要更多的背景信息,而下半部分开发人员也同意我的观点,这是很好的。 如果由于历史原因,在C语言中还是可以允许的,那么在Python中这是一个很大的问题,您无需这样做。

一致性


让我们从众多方法中选择一种,然后说:“让我们去做。” 不管是好是坏-不再重要-都是一致的。

我们只是在谈论Python 3,根本不考虑传统。

我有一个论点:当我们继承某些东西时,我们应该从中知道-看到父母的名字会很高兴。 有趣的是,通常我们看到父母的名字,除非这是一个对象 。 因此,我为自己制定了一条规则:当我写一个类时,我继承自某些东西-我总是写父母的名字。 不管是什么-模型,对象或其他东西。

如果可以选择编写Class Some(object)class Some ,那么我将选择第一个。 一方面,它表明我们清楚地总是写我们继承的东西。 另一方面,它没有特别的冗长 :我们不会因几次额外的击键而失去任何帮助。

三分之二的开发人员更熟悉第二种选择,我什至知道为什么。 我的假设:都是因为我们很早就从第二版Python迁移到第三版,现在我们表明我们正在使用第三版Python编写。 我不知道这个假设是正确的,但在我看来确实如此。

F线可怕吗?


答案选项:

  • 是的:他们失去了背景,将逻辑放在模板中,并且不掉毛-(38%)。
  • 不行 他们是奇迹! -(62%)。

有一个假设是f线很糟糕。 他们把任何东西塞进他们! f线与.format不同,差异很大。 当我们声明一个模板,然后对其进行格式化时,我们将分别执行两个操作:首先定义模板,然后对其进行格式化。 声明f线时,我们同时执行两个操作:我们立即声明模板并同时设置其格式。

f线存在两个问题。 我们声明了f线的模板,一切正常。 然后,我们决定将模板向上移动2行或将其移动到另一个函数-一切都中断了。 现在没有允许我们格式化字符串的上下文 ,并且我们无法正确处理它们。 f线的第二个大问题是:它们使您可以做糟糕的事情- 将逻辑粘贴到模板中 。 假设有一行,我们只需在其中插入用户名和单词“ Hello”,这很正常。 没有什么特别可怕的,但是然后我们看到用户名用大写字母表示,我们决定将其转换为Title大小写并直接写在username.title()模板中。 然后,条件,周期,导入出现在模板中。 以及php的所有其他部分。

所有这些问题使我说f线是一个坏话题 ,我们不使用它们。 有趣的是,我们没有f线适合我们的情况。 通常,任何格式都适用,但是我们选择.format其他所有方法都是不可能的- %或f线都不可行。 .format的工作也受到限制,因为您可以在其中放置双引号并写入变量名称或其顺序。

在报告中,f线对手的数量从33%增加到38%-这虽然很小,但却是胜利。

数字


您是否喜欢这样的数字: final_score = 69 * previous result / 3.14 。 这似乎是标准的代码行,但是69是什么? 当我查看我前段时间编写的代码时,经常会出现这样的问题,而当时的经理说:

- 请乘以147。
- 为什么是147?
- 我们有这样的收费。

我乘以并忘记了,或者很长一段时间我都选择了某个系数的值,以便一切正常进行,然后我忘记了如何选择它以及为什么。 事实证明,重要的研究工作仍然隐藏在无标题的数字后面。 我什至不知道这个数字是什么,但是我只能通过稍后提交以某种方式找到,回忆和恢复它。

为什么不做不同的处理-将所有复数与名称和文档一起放入您自己的变量中? 例如,对于数字69,请写下这是市场中的平均指标,现在该常数具有名称和上下文。 我会写一篇评论,说我在这样的研​​究现场一直坚持下去。 如果未来的研究发生变化,那么我将来更新数据。

因此,我们保证不会有魔术数字通过我们的代码并从内部使其复杂化。 他们检查了每一行的复杂性,然后说:“这里是4766。这是什么,我不知道,自己弄清楚!” 对我来说,这是一个伟大的发现。

结果,我们意识到我们需要遵循这一点,并且我们不会在代码中遗漏任何魔术数字。 很好的是,几乎100%的同事都同意我们的意见,而且他们也没有使用这些数字。

但是也有例外-它们是从-10到10的数字,数字100、1000之类的数字,仅仅是因为它们经常被发现并且没有它们很困难。 我们是强硬的,但不是虐待狂 ,请三思。

您是否使用“ @staticmethod”?


让我们考虑一下什么是staticmethod 。 您是否想过为什么在Python中使用它? 我不知道 我有一个美丽的皮林特说:

- 看,您不是在使用 self cls 做一个静态方法!
- 好的,Pylint,我要做一个静态方法。

然后我教了Python给初学者,然后他们问什么是静态方法以及为什么需要它。 我不知道答案,我只是想是否可以用一个函数编写相同的函数,或者不可以在常规函数中使用self ,仅仅是因为这是一个此类并且正在发生某些事情。 为什么我们需要一个静态方法构造?

我用谷歌搜索了这个问题,结果竟然像兔子洞一样深。 在许多其他编程语言中,staticmethod也不受欢迎。 合理的-静态方法破坏了对象模型。 结果,我意识到-staticmethod不是这里的地方 ,我们看到了。 现在,如果我们使用staticmethod装饰器,则linter会说:“不,对不起,重构!”

大多数开发人员不同意我的观点,但仍有大约一半的人认为编写常规方法或常规函数比使用staticmethod更好。

__init __。Ru中的逻辑-好还是坏?


这是我最喜欢的话题。 当然,当您创建一个新程序包并以某种方式调用它时,它会创建__init __。Ru,您想知道将其放入什么内容? 在__init __。Ru中放什么,以及在文件中并排放置什么? 对我来说,这是一个不平凡的问题,我总是迷路:也许最重要的事情? 然后我想-不,相反,我会将最重要的内容放在最容易理解的环境中。 如果将某些内容放在__init __。Ru中,然后全部导入,那么循环导入也很糟糕。

我看了看各种流行的库,爬上它们的__init __.Ru,并发现基本上存在垃圾或向后兼容性。 对我来说,当我开始创建带有许多子包的大型包时,这个问题就急剧地出现了,您会迷失方向。 , . Python, , , __init__. , 90% .

— , API, , - , , ? , , . API . , __init__. - : , , .

, I_CONTROL_CODE — . , . , , __init__. — . , , , - , .

hasattr ?


hasattr ? , , Python — . hasattr , ().

, , hasattr , , . , hasattr, , . - , Python -, hasattr . , . getattr « , ». hasattr — getattr, exception .

50 50 — , , .



, layer-linter . ? : , , , . , - . - . .

cohesion . , . Cohesion , . False Positive , — , , .

vulture Python . - , Python , . ohesion.

Radon , : Halstead , Maintainability Index , . , — .

Final type


Final- Python. Typing Extensions, , . , - , — , . , - - , ? 不用了 . - — , , , .

Gratis


, .



, .

, . , . , Python- , .

, Moscow Python Conf++ , . , , Python-.

. . , , , .








Source: https://habr.com/ru/post/zh-CN433480/


All Articles