尊敬的 KV:
我被分配到一个新项目,并且一直在查看团队放在内部 wiki 上的、公认简略的文档。我花了一天左右的时间盯着看似一长串的开源项目列表,他们打算将这些项目集成到他们一直在构建的系统中,但我找不到哪里描述了他们的原创工作。我问了一位项目团队成员,我可以在哪里找到该文档,被告知实际上没有什么需要文档化的,因为他们需要的所有功能都可以在 github 上的各种项目中找到。
我真的不明白为什么人们不理解外包工作也意味着外包责任,而在一个软件项目中,责任和问责制至关重要。
责任感
尊敬的 Responsible:
虽然“在 github 上 fork 我”这种风格的系统设计似乎是新鲜事物,但不幸的是,我不得不向您保证,事实并非如此。自从软件库发明以来,那是在我出生之前,可能也在您出生之前,通过简单地获取所需的位来构建系统的想法一直是软件构建的方式。我们都依赖于我们没有编写的代码片段,并且经常依赖于我们甚至无法读取的代码,因为它以二进制形式到达。即使我们可以读取它,我们会吗? OpenSSL 的代码是开源的,任何关心或敢于阅读的人都可以阅读,但 Heartbleed 漏洞却潜伏了两年才被发现。问题不仅仅是能够看到代码;它更多地与您可能拖入以完成工作的复杂性有关。
您质疑其他团队成员为什么没有关于他们打算如何将下载的各种位拼接在一起的文档是正确的。即使许多部分是由预先存在的软件组成的,也必须有一个关于它们如何集成的架构。在没有架构的情况下,一切都是混乱,以这种有机模式构建的系统可以工作一段时间,但最终会腐烂,它们散发出的恶臭是迫在眉睫的厄运的恶臭。
一个软件系统总是由其他组件构建而成,您需要问的问题是
让我为您分解一下。
软件的可靠性不仅仅在于知道是否有人为了窃取信息而编写它,尽管如果您从某个三字母机构获取椭圆曲线代码的因子,您可能需要认真考虑一下。说软件是值得信赖的,就是知道它有良好的记录——希望以年为单位衡量——在面对滥用时经过良好测试和稳定。人们一直在软件中发现错误,但我们都知道一个值得信赖的软件是什么样的,因为它在运行中很少失败。
API 的稳定性是我在其他回复中暗示过的,但它值得,或者我应该说似乎需要,经常重复。如果您在开车,而给您指路的人每隔一个街区就修改一次路线,您会认为这个人不知道他或她要去哪里,而且您可能是对的。同样,API 具有果冻稳定性的软件表明,构建这些 API 的人在项目开始时并不真正知道他们在做什么,而且现在软件有了用户群,他们可能仍然不知道。我经常遇到一些系统,似乎是为了快速解决问题而编写的——并且以一种让 Google 或 Facebook 为使用它创建的任何可疑服务支付大量现金的方式。API 不需要写在石头上,但它应该足够稳定,您可以依赖它超过一个点版本。
理解组件的使用是 github 世代似乎最常栽跟头的地方。一些程序员会根据他们试图解决的问题进行搜索;在 Stack Overflow 中找到一个指向他们问题解决方案的网页或条目;然后,在没有任何尽职调查的情况下,将该组件拉入他们的系统,而不管组件的大小、复杂性或最初的预期用途。举一个简单的例子,我在 github 的搜索框中输入了“红黑树”。然后它吐出,“我们找到了 259 个仓库结果。” 这意味着存在 259 种不同的红黑树实现。当然,它们跨越了各种语言。
仓库 | 语言 |
---|---|
56 | C |
43 | Java |
41 | C++ |
17 | JavaScript |
13 | Python |
9 | Ruby |
8 | Go |
8 | C# |
4 | Haskell |
3 | Common Lisp |
我们如何评估所有(任何?)这些实现?我们可以按用户评分(又名“星级”)以及 fork 对它们进行排序,fork 是指有多少人试图扩展代码。这些衡量标准都不是客观的。我们仍然不知道代码大小、API 稳定性、性能或代码的预期用途,这还是针对一个相对简单的数据结构,而不是像 Web 服务器这样的大块代码。
要了解一段代码是否适合您的用途,您必须阅读作者如何使用它的相关信息。如果作者编写了文档(是的,我会等到您停止嘲笑),那么这可能会指示他或她的目标,然后您可以查看该目标是否与您的目标相符。所有这些都是驾驭每天由小小的敲击手指产生的软件海洋所需的尽职调查。
最后,您在一件事上完全正确:您可以外包工作,但最终更难外包责任。
KV
尊敬的 KV:
当你的调试器失效时,你会怎么做?您过去谈到过您用来查找错误而无需诉诸打印语句的工具,例如printf()在 C 语言中,以及它们在其他语言中的同类,但总有工具失效的时候,我发现我必须使用某种形式的蛮力来找到问题并解决它。
我正在处理一个程序,当我们转储系统状态以进行本应没有副作用的操作时,状态显然会发生变化;但是,当然,当调试器附加到程序时,状态保持不变。在我们求助于打印语句之前,也许您可以提出另一个建议。
蛮力调试
尊敬的 Brute:
工具,就像编写它们的人一样,不是完美的,我不得不求助于各种形式的蛮力调试,放弃我的调试器而使用简陋的打印语句。
但是,从您所写的来看,另一种形式的蛮力可能更合适:二分查找。如果您有一个长时间运行的操作导致副作用,那么找到导致您麻烦的操作部分的最简单方法是将操作分解为几个部分。您是否可以用一半的输出来触发错误?如果是,是哪一半?一旦您确定了有错误的一半,请再次将该部分分成两半。继续二分过程,直到您缩小了问题的位置,好吧,虽然不是完全“瞧!”,但您肯定比诅咒您的调试器取得更大的进展——并且如果系统您正在调试的段确实很大,那么它将比添加大量打印语句花费的时间更少。
通常,打印语句会掩盖时序错误,因此如果错误与时序相关,则添加打印语句可能会误导您认为错误已消失。我见过太多程序员在启用调试和打印语句的情况下交付软件,尽管消息进入 /dev/null,仅仅是因为“启用调试后它可以工作”。不,它不是“启用调试后可以工作”;调试正在掩盖错误,而您只是很幸运。当合适的时刻来临时,软件的用户将是不幸的,并且无论打印语句如何,都会发生时序错误。我希望您不是在从事制动系统或航空电子设备的工作,因为,嗯,轰隆。
如果您的目标是找到错误并修复它,那么当您的更精细的工具失效时,我可以推荐分而治之作为一种调试方法。
KV
喜欢它,讨厌它?请告诉我们
Kode Vicious,凡人称之为 George V. Neville-Neil,为了乐趣和利润而从事网络和操作系统代码的工作。他还教授各种与编程相关的课程。他感兴趣的领域是代码探险、操作系统和重写你的糟糕代码(好吧,也许不是最后一个)。他获得了马萨诸塞州波士顿东北大学计算机科学学士学位,并且是 、Usenix 协会和 IEEE 的成员。他是一位狂热的自行车爱好者和旅行者,目前居住在纽约市。
© 2014 1542-7730/14/0600 $10.00
最初发表于 Queue vol. 12, no. 6—
在 数字图书馆 中评论本文
Charisma Chan, Beth Cooper - 调试 Google 分布式系统中的事件
本文介绍了 2019 年在 Google 工程师如何调试生产问题方面进行的研究成果,包括工程师以不同组合方式有效调试时使用的工具类型、高级策略和低级任务。它考察了用于捕获数据的研究方法,总结了生产调查的常见工程历程,并分享了专家如何调试复杂分布式系统的示例。最后,本文将这项研究的 Google 特性扩展到提供一些您可以在组织中应用的实用策略。
Devon H. O'Dell - 调试心态
软件开发人员花费 35-50% 的时间来验证和调试软件。调试、测试和验证的成本估计占软件开发项目总预算的 50-75%,每年超过 1000 亿美元。虽然工具、语言和环境减少了花在单个调试任务上的时间,但它们并没有显着减少花在调试上的总时间,也没有减少调试的成本。因此,过度关注开发过程中消除错误是适得其反的;程序员应该拥抱调试作为一种解决问题的练习。
Peter Phillips - 使用跟踪增强调试
创建一个模拟器来运行旧程序是一项艰巨的任务。您需要彻底了解目标硬件以及模拟器要执行的原始程序的正确功能。除了功能正确之外,模拟器还必须达到以原始实时速度运行程序的性能目标。实现这些目标不可避免地需要大量的调试。错误通常是模拟器本身中的细微错误,但也可能是对目标硬件的误解或原始程序中的实际已知错误。(原始程序的二进制数据也可能变得微妙地损坏或不是预期的版本。)
Queue Readers - 又一天,又一个错误
作为本期程序员工具主题的一部分,我们在 Queue 决定就调试主题进行一项非正式的 Web 调查。我们要求您告诉我们您使用的工具以及您如何使用它们。我们还收集了关于那些难以追踪的错误的故事,这些错误有时会让我们想到从事另一种职业。