丢弃代码

2020-09-26 03:10:09

关于什么使Python比Rust更好的原型语言的讨论正在进行中(Python可能只是一些被描述的弱类型语言的原型)。问题是,我更喜欢用Rust做我的原型,而不是Python。显然,我不是唯一一个。所以我想分享几件事,关于是什么让铁锈适用于这些类型的一次性编码短跑,至少对我来说是这样。

有时候,我们的目标并不是编写性能良好、正确、理智地处理各种错误、拥有出色的用户体验和可维护性的完美代码,当然,这些项目才是我们引以为豪的。我们将它们固定在GitHub的个人资料上,并撰写关于它们的博客文章。我们编写完整的最佳实践手册,介绍如何做到这一点。

但有时我们只需要把一些东西快速地组合在一起,而不是太在意质量。那种硬纸板和大量的鸭子胶带之类的东西。这些措施包括:

单机版调试工具(“我需要在服务器上抛出10k个这样奇怪的请求,看看它是否会触发bug。没有吗?好的,让我们试试别的…。“)。

搜索一篇科学论文中声称的反例(“一旦我有了它,我就可以证明它是反例,所以我就不再需要代码了”)。

只需处理一次数据(“我想知道这些.txt文件中有多少破坏了Unicode”)。

在提交之前,先计算一下是否有机会实现(“我可以将更改以压缩的二进制差异的形式分发吗,或者这样的差异会很大吗?”),然后再提交给它(“我可以将更改作为压缩的二进制差异分发吗?”)。

演示目的(“我们希望按照这个思路构建一些东西,但是,您知道,实际上是有效的”)

当然,还有更多。我甚至不确定是否有更多的“正确”编码或这种“丢弃”编码。除了我们不会真的吹嘘我们的丢弃代码(“看看我在午休时缝合在一起的多可怕的怪物”),我们不会写教程如何编写它们,等等。所以这正是我们不写😈的那种博客文章。

这一次我们不是写一些合适的东西,而是讨论如何写出糟糕的代码,但是速度要快。我们已经决定把这件事做得非常草率,并承认以后不会为此感到羞耻:。

我们想在这上面花尽可能少的时间。就这么做吧,在使用完密码后扔掉它,然后继续前进。这一次午饭前就要结束了。

我们不太关心性能(只要它也在午餐前跑完)。

我们不关心处理所有的角落案例,只关心我们在数据中实际遇到的案例。

我们不在乎考试,只要我们有足够的信心答案是足够准确的。

注意:请确保不要让任何人将其放入生产😇。如果你不删除它,确保在显眼的地方有注释,警告人们不要使用它。

问题是,铁锈让我们在乎。这是“铁锈”的一个特点。它会抱怨我们的代码不是产品质量,我们需要做得更好,以节省生产线上的痛苦。它的类型系统在坚持一些小细节上可能真的很麻烦,比如int和字符串实际上不是一回事,拥有的东西和借来的东西以及…之间有区别。嗯,你知道的,所有这些东西。铁锈想让你做一个好的、合适的、可维护的代码。

另一方面,Python实际上并不坚持任何东西。因此,在Python中不关心会更容易。

我知道很多编程语言,并且会选择我希望在给定时间内最适合我的那一种。所以,对于一些非常简单的东西,我只是简单地拼凑了几行外壳(还有一些稍微不那么简单的-我不好意思承认,大约1000行长的外壳怪物在实际生产中持续运行了多年-但它确实运行了)。如果能用两三条难看的管道就能做到,那就好了。

这些年来,我经常使用perl(那个人不在乎它是整数还是字符串…。不,更正一下,在Perl中,所有东西都是字符串,int根本不存在。这可能是为丢弃代码而设计的语言,我也做过一些Python(类似于Perl,但是里面有适当的对象,而且所有东西都是一本字典)。

但最近我注意到,如果我试着做类似的事情,我会在Rust中做得更快。不是因为它运行得更快(嗯,通常也是这样,但这不是重点),而是我更快地完成了手头的任务,并且咒骂的次数稍微少了一点。

这当然部分是因为我对Rust比Python更精通,也是因为Rust的心理模型比Python的更接近我大脑的运作方式,这也是因为我比Python更精通Rust,也是因为Rust的心理模型比Python更接近我的大脑。您的里程会有所不同-如果您是一名Python斗牛士,已经在其中编码了几十年,并且刚刚学习Rust,那么您肯定会在Python中做得更快。

但是,在Rustfast中可能会使用一些技巧来完成这些操作(即,比现在更快,但不一定比$OTHER_LANGUAGE快)。

铁锈以其缓慢的编译时间而闻名。Python没有编译时间。如果您每次都要等待编译时才会有一堆错误出现在您的面前,那么它会减慢您的速度。特别是因为每次您试图编译它时,Rust都喜欢向您抛出一堆错误。Rusis以其巨大的错误消息而闻名,所以它想通过大量使用它们来吹嘘自己有多好。

但是,您可以注意到,您并不是真的需要每次都构建和运行。你通常只想检查一切是否都在正确的道路上。对于Python,您确实需要实际运行它(因为Python实际上没有太多的编译时间,所以它喜欢在运行时向您抛出一堆错误),但是Rust是“只要编译就是正确的”语言。我所说的遵守,实际上是指主要是类型检查。

锈蚀分析仪语言服务器。您将在编辑器中看到红色的曲折,而不必编译。它并不完美(有时错误列表是不同的,有时只是放弃了特定的项目),但它正在变得更好,并且在没有任何编译的情况下指出了大多数错误。

货物检查只执行编译的第一阶段,并将在代码生成之前停止。这意味着它不会产生任何可以运行的东西,但是它的速度要快得多,并且提供了我们非常想要的一系列错误。

您可以让Cargo Watch在另一个终端中异步重新编译代码。我只是瞥了一眼,看看周围是否有任何错误,但我不会等待它-在最坏的情况下,错误列表是过时的一个迭代。它可以用来做其他事情,比如保持当前机箱的文档是最新的,或者在编译可执行文件时抢先一步,甚至在每次保存时重新运行所有测试(我在这里离开了主题;我们在这里故意马虎,那么我们在谈论什么测试呢?)。

这些不会缩短编译时间,但可以消除热编码路径中等待它们的时间。编译仍然需要一些时间(特别是如果您有很多依赖项并进行干净的发布构建),但这并不意味着它必须减慢您的速度。

在与铁锈一起工作了一段时间后,人们学会了依靠它们,而不是攻击它们。

当我想知道我的代码是否正常工作时,我实际上必须运行Pythonthing,并向其提供数据,这是我自己提高速度的主要原因,也是我怀念Python的原因所在,当我想知道我的代码是否正常工作时,我实际上必须运行Pythonthing并向其提供数据。这意味着我要么需要设置一个更小的输入器,等待整个程序被处理,要么在运行5分钟后,它就会在某个特定的参数上爆炸,或者切换参数的顺序,这就意味着我需要设置一个更小的输入器,等待整个东西被处理完,然后让它在运行5分钟后爆炸。在运行Python代码10次迭代之后(每次都会在代码中越来越晚地崩溃),它终于结束了。到那个时候,我不再相信它应该做什么,在所有这些重试之后,所以我回去不得不想办法重新检查它。

在Rust中,我不仅在几乎完成之前不必运行代码,甚至当我向它提供整个输入时(我通常会这样做),它通常会更快,并且它会在第一次运行到完成。我也可以更快地浏览代码。使用Python时,我会停下来查看文档,想一想是什么类型放在哪里,等等,因为只有在运行时才能找出来实在是太痛苦了。我在写代码的时候需要小心。对于Rust,我只需要输入代码,得到红色的曲折,修复它,然后继续前进。它将检查这些内容是否以任何对编译器有意义方式点击在一起的工作外包出去。

这在某种程度上是在“慢慢来”的主题下进行的。通过确保每件事都有正确的类型并很好地对齐,它会使每次迭代变得更慢,但它也使得在整个工作足够好之前有更少的迭代是可能的。

此外,不要轻信这样的印象,即在那里抛出不安全的东西来绕过一些检查就可以节省你的时间。不会的。这是个陷阱。如果你不确定你需要它,那么你很可能不知道,做不安全的权利是一项很大的工作。做错事很容易,也许比安全地做更容易,但你以后会为此付出代价的,当你试图弄清楚为什么这件事会变得非常奇怪的时候。如果您在代码中添加任何重要的不安全因素,您将冒着日夜待在调试器前的风险。支票在那里是有原因的。

我不是说要克隆一切。即使在原型代码中,如果只是“查看”,我也经常使用&;stras参数。但我会在显而易见、琐碎的情况下这么做。那些我真的不需要再去想的事情。

但是,如果您发现自己正在考虑编写任何类型的-&>Impl Iterator<;Item=&;Impl Display>;,只需停下来并在其中添加一个Vec<;字符串&>即可。当你关心在真正的产品代码中限制分配或转换的数量,或者设计借用东西的正确方案时,厚颜无耻地让计算机多做一点工作,以节省时间和脑力。不要因为没有把生命放在任何地方,或者没有放置弧形或者RefCells(如果它能让你移动得更快的话)而感到难受。这可能意味着设计不是最优的,但如果它起作用,…。

在进行了一段时间的Rust编码之后,您会感到“如果没有这个特定的堆分配,这真的应该是可能的”。培养这种直觉,这是很有用的。但是当你在速跑时,一定要把它放在一边。

但也要听听拉斯特告诉你的设计太烂了。如果代码中充斥着RefCells或其他气味指示器,以至于不可能看穿它,这是一个信号,它没有经过深思熟虑,你可能不知道你想要做什么。当然,你可以强行完成所有这些,但无论如何它可能会产生错误的结果。这里的目标不是什么抽象的优雅,工作需要快速完成,但确保一个人首先理解交易往往比不理解就写出来要快。铁锈似乎特别擅长在缺少设计的地方给出暗示。

当然,这里的平衡在生产和丢弃代码之间是不同的。让你自己费力地阅读一些RefCells和RCS,特别是当你看到为什么设计不是最优的,但编写的工作量仍然很少的时候。对于这一点和应该在6个月或6年后出现的代码来说,权衡是不同的。

轻松处理错误。我个人避免在这类代码中对用户错误7进行解包,但无论如何都会毫不犹豫地将::ErrorAnywhere放在任何地方,并让它从Main中冒泡出来(我想一目了然地看到我认为是代码中的bug,以及我只是给它指定了不存在的文件名)。有时,我会给它附加一个上下文(比如它正在处理的文件名),因为我预计会在那里搞砸一些东西,知道文件名会为我在整个会话中节省一些时间,但不会太多。我希望在使用它的时候知道代码,所以我不需要太多的细节-毕竟,它很短,从现在开始只用了5分钟。错误消息不一定要完美,我只是不打算设计我自己的错误类型。

不要在性能调优(CPU和内存)方面做得过多。是否可以通过std::fs::read_to_string将整个文件加载到字符串中,而不是逐行读取?当然可以,去做吧。如果它不适合RAM,那么仍然更喜欢.line(),而不是手动循环.read_line.read_line并重用分配这一更快的选项。

允许自己不关心警告。CLIPPY是否抱怨解开或者比解开或解开慢?让它抱怨吧。我还在开着警告牌。部分原因是我懒得把它们关掉,但如果这玩意儿最终不能工作,警告是第一件检查是否有可疑之处的事情。只要在心理上把它们打勾成无害的,然后继续前进,不要花时间重构和重写代码来让它们安静下来。

记录或打印到处都是的东西。它不一定是完美的、可调的,或者其他任何东西。但是,有一种方法可以检查它是否运行良好,这是一件很好的事情。并由这些DBG进行调试!宏(如果它们已经存在)通常比在调试器中再次运行整个过程要快。您也可以将它们用于眼球级别的基准测试(该部分感觉是否很慢,或者它的打印速度是否足够快),您不需要比这些更好的基准测试了。

不用担心异步/等待。在该原型中,您不需要处理数百万个并发连接。如果您不能以顺序的方式处理一个接一个的连接,那么启动线程(如果它们出错,就忘掉它们/记录它们)。

使用更简单的算法。你需要计算中位数吗?对数组进行排序,然后选择中间的东西。这是最理想的吗?当度量的指标是开发人员时间时,肯定是这样。

毫不犹豫地生成代码-使用现有的派生板条箱,编写简单的宏(不过,简单的宏不必担心它们在所有可能的情况下都能工作,您的复制粘贴狂欢才是最重要的),或者编写一个sed命令,它可以输出足够接近Rust代码的东西,可以通过在这里和那里更改5个字符来进行编辑和编译。

如果只有很短的时间预算,重要的是要记住不要太长时间地不断地敲打着一个问题的头。如果你在同样的问题上停留了超过5分钟,决定你应该放弃那条特定的道路。你不需要赢得这场特殊的战斗。API不需要完美,或者其他什么。就给它加RC或类似的东西。或者干脆断定部分代码根本不是必需的,😇。

标准图书馆和外面的板条箱都有很多可以让你走得更快的好东西。让他们读一读,探索一下。这可以在生产率较低的时间内完成。人们可以浏览Option、Iterator和Result的方法,而不是在火车乘车或午餐后浏览Facebook。您知道您可以使用Box::Leak来获取静态引用吗?同样,还有一些有用的板条箱值得一查,可以让生活变得更轻松(当然,清单是不完整的):

人造丝经常让人几乎免费地加快速度。这取决于您需要的速度,但是如果您可以让它运行1分钟而不是5分钟,那么它可以加快您的迭代过程,如果您唯一需要做的更改是将代码中正确位置的_PAR_ITER放入_PAR_ITER中,那么这是值得的。

邦帕罗是一个凹凸分配器。虽然主要动机是性能,但它也可能使您在复杂数据结构中的内存处理和生命周期变得容易得多。

CROWBEAM_CHANNEL和CROWBEAM_UTILS::thread使编写生产者-消费者模式之类的东西变得更容易。不过,一定要检查一下你是否需要它们。通常情况下,只要提前把所有的东西都做成VEC<;_>;就足够好了。

这个想法是要有一种“我认为周围有什么东西正好做了我现在需要的事情”的感觉,并且知道在哪里寻找它,不一定要记住关于它们的每一个细节。

如果您还需要别的东西,一定要检查一下有没有板条箱。这5分钟的搜索可以节省数小时来修复手摇CSV解析器。生态系统包含很多内容,在这些原型期间,使用过时的、未维护的、半未完成的依赖项通常是可以接受的(当然,人们会非常不愿意将其放入生产代码中)。选择具有良好文档和方便的API的,而不是具有最令人印象深刻的基准测试的,引入依赖项,而不是再次重写轮子。

根据人们对这两种语言的精通程度,在编写一次性代码时,Rust可以是一个可行的选择。然而,这确实需要思想上的转变。编写这些丢弃代码的方式与用Python或Perl编写代码的方式不同。这里的目标是从战略上走捷径,知道哪些事情根本不重要。一定要简化一般的工作经验。但最终,最佳结果将是第一次尝试就正确地编写它(尽管第一次尝试花费的时间会更长),而不是多次迭代,就像对脚本语言所做的那样。

此外,它还需要改变通常的产品质量锈编码-所有教程和书籍中都描述了这一点。请记住,优化指标是开发人员时间,并在此基础上做出战略决策。一定要记住,可维护性并不重要,跑慢4倍并不重要,只要它们没有时间在加速过程中咬你,你就可以走捷径。

当然,这些技术中的一些并不完全是Rust所特有的,但是需要在Rust的上下文中提醒它们,而Rust并不完全推荐它们。

顺便说一句,不要忘记询问您是否真的需要编写代码,或者是否有一个完整的现有工具正在做您需要的事情,或者是否两次grep和一次sed就可以完成这项工作。