哈哈哈然后我要开始讲一个经常在发生的事实了。程序员们可能会感到一些不适99.999999999%做技术的都会被问到或者被吐槽到“你的程序怎么又出bug了”▲图片来源于网络版权归原作者所有反正我作为程序员的内心世界是如同一万只草泥马飞奔而过难以压抑内心的激动每次都差点忍不住想说“你写篇几百字的作文还有错别字呢我码个几万行的代码还不允许出错了“可能同样是做技术的你此时在不断点头哈哈。但是这么讲毕竟也缓解不了矛盾我们还是得摆事实讲道理不是啥都不怕就怕程序员有文化所以Z哥想来带你好好分析一下这个事情当你再遇到这个情况的时候可以拿这些观点来反驳不是做技术的也可以了解下程序员的难处谁没个难处呢多多包容什么是Bug任何一个「问题」的产生本身是没有好坏之分的但是为什么会有的就不被care甚至还会很喜欢而有的会被吐槽呢根本原因是因为产生了利益损失。比如年前拼多多出问题送了很多无门槛券。作为一个用户自然很喜欢夸你夸到飞起怎么会吐槽你呢。但是作为利益损失方必然破口大骂害我倾家荡产所以如果没有产生利益损失我想其他人也不会来找你吐槽。但是「问题」就等于「bug」吗我认为并不是「问题」不等于「 bug」。因为程序员的职责是什么拿造房子来比喻的话我认为最核心的工作真的和“搬砖”非贬义词无异就是根据设计师产品经理设计好的设计图砌砖编码建成和设计图纸上一模一样的建筑。所以如果一个东西造出来与设计不符那么它可以说是bug或者缺陷缺斤少两不完整。否则并不是bug但可以被称之为「漏洞」完全没考虑到的表示不在预料之内的情况。之前看到过一个形象的比喻你家里的窗可以从外面打开那叫漏洞。你家里的窗打不开那叫bug。但是要承认bug是必然存在的。为什么它是如何出现的呢Bug是如何出现的正如前面所说程序员做的是“造房子”的事情。这件事完整的步骤分为3步。与产品经理讨论并确定功能确定一个可以实现的设计图纸将每个单独的元件抽象出来确定施工方案将相关的元件实现并进行组合完成建设带上材料开始施工第一步“与产品经理讨论并确定功能”主要是沟通靠“看”和“理解”。但是沟通本身是一个有损耗的过程特别是在职责非常明确的组织中产品经理啪啦啪啦讲了很多到实际做的时候你必然还是会去翻阅需求原型、需求文档之类的重新理解一下。这个时候就是一个非常危险的时期。比如像下面这个的答案是什么▲图片来源于网络版权归原作者所有答案是17不对。我猜你可能没注意到这些地方。▲图片来源于网络版权归原作者所有为了让你有深刻的印象这个举例可能比较刻意和夸张一些但是我想在你的身边由于没注意到或者理解有误的现象肯定很常见。沟通是相互的这锅只让程序员背的话的确太委屈了点。第二步“将每个单独的元件抽象出来”这主要是一个人抽象能力的体现。但是抽象是啥抽象是“透过现象看到本质”的能力这个能力理论上是可以无限增长的。随着你对相关信息的掌握越多这个能力会越强会无限趋近于100%但永远不会真正达到100%因为没人知道怎么才算100%。所以当你具备的信息没那么多的时候是不是就抽象的不是那么合理不合理会导致什么虽然不会直接产生bug但是会更容易产生bug。但是人不都是需要经历这么一个成长的过程么可以说精通一项能力的背后都是踩着无数的bug过来的。要么在来这个组织之前已经踩过了要么在这个组织里踩。因此前者的薪资也比后者高。所以如果过分苛求没有bug等于是在扼杀每个人成长的机会并且在透支未来的可能性。人会变得非常保守、不敢尝试新事物。但是外部环境在不断变化新事物总会被动的需要去接纳技术的更新越来越快趋势不可逆然而对新事物的接受能力又得不到锻炼一旦遇到这种情况在接触新事物的时候会产生更多的问题欠下的债总要还的。第三步“将相关的元件实现并进行组合完成建设”这就是实际的coding过程而coding是一个主观的完全由人主观掌控的事情。人毕竟不是机器不可能不犯错就如前面提到的写文章的时候出现错别字一样。可能你会说有测试人员啊测试的工作不就是通过逆向思维来给程序员查缺补漏吗的确是的但测试的介入只是降低错误率只是让不出现bug的概率小数点后多几位指望发现100%的问题还是不太现实的。至少在当下的条件下是这样为什么呢因为代码的本质是各种逻辑的组合。比如一个完整的业务流程有10个环节每个环节有3种可能性这是一个什么复杂度的系统3 ^ 10 59049个分支理论上存在的可能性数量想要100%覆盖这些场景付出的成本几乎是不可接受的。然而我们实际的系统中遇到的个别场景甚至还要复杂的多。其实每个正在运行的系统都有bug包括我们每天在使用一些热门系统玩游戏的小伙伴们肯定熟悉“卡bug”这个词。只是这些bug有没有被执行到有没有被发现被多少人发现而已。那么我们只能举手投降吗那倒不至于办法还是有的。减少bug的惯性想法首先最容易想到的一点是增加测试人员。这也是最容易看得到的“成本”一种方式毕竟招一个人就得支出一份工资啊。所以增加测试人员这个方案是最不容易被老板们采纳的方案。除非你可以说服这个人力成本的投入小于获得的价值。另外这个方案其实还增加了沟通成本沟通的「隐性成本」其实非常大但是往往容易被忽略。关于沟通成本感兴趣的可以看我之前写的《就简单聊聊沟通效率问题》其次会想到的就是程序员代码写的严谨一点仔细一点啊。这也是一种缺啥补啥的惯性思维。先撇开到底能不能达到严谨一点仔细一点的目的。那怕达到了他会产生什么结果呢可能是下面3种。更多的条件验证更多的单元测试更多的抽象提炼可以确定的是这些工作会增加2样硬性的东西投入的时间和整体的复杂度。时间很好理解我们就来聊聊复杂度。一个常识是越简单的东西越不容易产生bug。比如112出现bug的可能性无非就是加号写成来减号1写成了4之类。但是112并且1*11并且1/11。。。等等这些验证条件越多那么由于验证条件自身的错误而产生问题的可能性反而更多。所以代码的复杂度和产生bug的概率是成正比的并且具有「边际效用递减」的效果。这就意味着做更多的验证带来的收益会越来越小。因此这个方案哪怕真能执行到位也不是一个特别好的方案。那有没有相对靠谱一些的办法呢有但需要我们换一个角度来看待这个问题。换一个角度看待bug既然无法100%避免bug那我们可以换个角度考虑一下如何让解决bug的过程更快甚至快到你都没有察觉。解决bug主要就是做2件事找到bug的产生点然后修复它。每天都在解决bug的程序员们应该知道这事最费时间的是“找bug”的过程。因为“修复bug”是一个技术性问题这个对不同人的差异其实是很小的因为程序员们每天在写的代码都是差不多的非常同质化的况且还有标准答案“文档”可以参考。比如都知道string.concat()是拼接string.split()是分割。该用分割的地方不小心用了拼接那改掉就好。但是“找bug”就不是这样了。比如你刚刚改完一行代码后发布出现的问题你不用找就知道问题出现在哪。但是让你排查一个刚接手没多久的系统肯定是一脸懵逼。根本原因在于这个过程不像技术性问题具有确定性它是充满不确定性的处在一个“混沌”的环境中。所以对待bug的重点就变成了如何更快的发现和找到bug。关于这点Z哥的建议是打好日志学会利用工具每次的迭代规模尽可能的小首先打好日志。日志其实就是我们在编码的时候安插在程序中“记录员”它替我们记录着我们认为容易出现问题的地方所产生的信息。但是系统无时无刻都在运行着必然会产生大量的日志信息如何从这些信息中快速的找到关键信息就是需要考虑的问题。另外如果每个人都随意的用自己喜欢的记录日志的方式那么从风格迥异的日志中找你需要的信息就变得很头疼时间不一致格式不一致等等。所以要做好打日志这个事情就需要定义一个标准比如必须要有时间包含当前上下文的参数等等。我们还可以给日志做一下归类定义不同的日志级别在记录的时候带上前缀。比如【info】、【warning】、【error】之类。如此一来平时更着重关注的就是error级别的信息而且由于将其他级别的信息剥离了出去使得这里的数据量大大减少更便于查看。不过日志记录毕竟是一个在做“预判”如果日志中没有记录到怎么办呢这里提醒大家不要先想着怎么调试。如果你面对的系统是一个单体应用倒还好。如果你面对的是一个大型的分布式系统调试的效率低不说这事你一个人可能还完不成。而且如果你直接调试生产环境的话说不准还会产生什么副作用摊上新的问题。找bug本质上是一个排除法的过程设断点调试也是如此。但是从起点开始一步一步的做排除法效率太低了。应该先通过自己的经验、拥有的部分信息先逻辑推理一下缩小排查的范围。哪怕你最终还是需要调试的话
为啥程序会有bug?
哈哈哈然后我要开始讲一个经常在发生的事实了。程序员们可能会感到一些不适99.999999999%做技术的都会被问到或者被吐槽到“你的程序怎么又出bug了”▲图片来源于网络版权归原作者所有反正我作为程序员的内心世界是如同一万只草泥马飞奔而过难以压抑内心的激动每次都差点忍不住想说“你写篇几百字的作文还有错别字呢我码个几万行的代码还不允许出错了“可能同样是做技术的你此时在不断点头哈哈。但是这么讲毕竟也缓解不了矛盾我们还是得摆事实讲道理不是啥都不怕就怕程序员有文化所以Z哥想来带你好好分析一下这个事情当你再遇到这个情况的时候可以拿这些观点来反驳不是做技术的也可以了解下程序员的难处谁没个难处呢多多包容什么是Bug任何一个「问题」的产生本身是没有好坏之分的但是为什么会有的就不被care甚至还会很喜欢而有的会被吐槽呢根本原因是因为产生了利益损失。比如年前拼多多出问题送了很多无门槛券。作为一个用户自然很喜欢夸你夸到飞起怎么会吐槽你呢。但是作为利益损失方必然破口大骂害我倾家荡产所以如果没有产生利益损失我想其他人也不会来找你吐槽。但是「问题」就等于「bug」吗我认为并不是「问题」不等于「 bug」。因为程序员的职责是什么拿造房子来比喻的话我认为最核心的工作真的和“搬砖”非贬义词无异就是根据设计师产品经理设计好的设计图砌砖编码建成和设计图纸上一模一样的建筑。所以如果一个东西造出来与设计不符那么它可以说是bug或者缺陷缺斤少两不完整。否则并不是bug但可以被称之为「漏洞」完全没考虑到的表示不在预料之内的情况。之前看到过一个形象的比喻你家里的窗可以从外面打开那叫漏洞。你家里的窗打不开那叫bug。但是要承认bug是必然存在的。为什么它是如何出现的呢Bug是如何出现的正如前面所说程序员做的是“造房子”的事情。这件事完整的步骤分为3步。与产品经理讨论并确定功能确定一个可以实现的设计图纸将每个单独的元件抽象出来确定施工方案将相关的元件实现并进行组合完成建设带上材料开始施工第一步“与产品经理讨论并确定功能”主要是沟通靠“看”和“理解”。但是沟通本身是一个有损耗的过程特别是在职责非常明确的组织中产品经理啪啦啪啦讲了很多到实际做的时候你必然还是会去翻阅需求原型、需求文档之类的重新理解一下。这个时候就是一个非常危险的时期。比如像下面这个的答案是什么▲图片来源于网络版权归原作者所有答案是17不对。我猜你可能没注意到这些地方。▲图片来源于网络版权归原作者所有为了让你有深刻的印象这个举例可能比较刻意和夸张一些但是我想在你的身边由于没注意到或者理解有误的现象肯定很常见。沟通是相互的这锅只让程序员背的话的确太委屈了点。第二步“将每个单独的元件抽象出来”这主要是一个人抽象能力的体现。但是抽象是啥抽象是“透过现象看到本质”的能力这个能力理论上是可以无限增长的。随着你对相关信息的掌握越多这个能力会越强会无限趋近于100%但永远不会真正达到100%因为没人知道怎么才算100%。所以当你具备的信息没那么多的时候是不是就抽象的不是那么合理不合理会导致什么虽然不会直接产生bug但是会更容易产生bug。但是人不都是需要经历这么一个成长的过程么可以说精通一项能力的背后都是踩着无数的bug过来的。要么在来这个组织之前已经踩过了要么在这个组织里踩。因此前者的薪资也比后者高。所以如果过分苛求没有bug等于是在扼杀每个人成长的机会并且在透支未来的可能性。人会变得非常保守、不敢尝试新事物。但是外部环境在不断变化新事物总会被动的需要去接纳技术的更新越来越快趋势不可逆然而对新事物的接受能力又得不到锻炼一旦遇到这种情况在接触新事物的时候会产生更多的问题欠下的债总要还的。第三步“将相关的元件实现并进行组合完成建设”这就是实际的coding过程而coding是一个主观的完全由人主观掌控的事情。人毕竟不是机器不可能不犯错就如前面提到的写文章的时候出现错别字一样。可能你会说有测试人员啊测试的工作不就是通过逆向思维来给程序员查缺补漏吗的确是的但测试的介入只是降低错误率只是让不出现bug的概率小数点后多几位指望发现100%的问题还是不太现实的。至少在当下的条件下是这样为什么呢因为代码的本质是各种逻辑的组合。比如一个完整的业务流程有10个环节每个环节有3种可能性这是一个什么复杂度的系统3 ^ 10 59049个分支理论上存在的可能性数量想要100%覆盖这些场景付出的成本几乎是不可接受的。然而我们实际的系统中遇到的个别场景甚至还要复杂的多。其实每个正在运行的系统都有bug包括我们每天在使用一些热门系统玩游戏的小伙伴们肯定熟悉“卡bug”这个词。只是这些bug有没有被执行到有没有被发现被多少人发现而已。那么我们只能举手投降吗那倒不至于办法还是有的。减少bug的惯性想法首先最容易想到的一点是增加测试人员。这也是最容易看得到的“成本”一种方式毕竟招一个人就得支出一份工资啊。所以增加测试人员这个方案是最不容易被老板们采纳的方案。除非你可以说服这个人力成本的投入小于获得的价值。另外这个方案其实还增加了沟通成本沟通的「隐性成本」其实非常大但是往往容易被忽略。关于沟通成本感兴趣的可以看我之前写的《就简单聊聊沟通效率问题》其次会想到的就是程序员代码写的严谨一点仔细一点啊。这也是一种缺啥补啥的惯性思维。先撇开到底能不能达到严谨一点仔细一点的目的。那怕达到了他会产生什么结果呢可能是下面3种。更多的条件验证更多的单元测试更多的抽象提炼可以确定的是这些工作会增加2样硬性的东西投入的时间和整体的复杂度。时间很好理解我们就来聊聊复杂度。一个常识是越简单的东西越不容易产生bug。比如112出现bug的可能性无非就是加号写成来减号1写成了4之类。但是112并且1*11并且1/11。。。等等这些验证条件越多那么由于验证条件自身的错误而产生问题的可能性反而更多。所以代码的复杂度和产生bug的概率是成正比的并且具有「边际效用递减」的效果。这就意味着做更多的验证带来的收益会越来越小。因此这个方案哪怕真能执行到位也不是一个特别好的方案。那有没有相对靠谱一些的办法呢有但需要我们换一个角度来看待这个问题。换一个角度看待bug既然无法100%避免bug那我们可以换个角度考虑一下如何让解决bug的过程更快甚至快到你都没有察觉。解决bug主要就是做2件事找到bug的产生点然后修复它。每天都在解决bug的程序员们应该知道这事最费时间的是“找bug”的过程。因为“修复bug”是一个技术性问题这个对不同人的差异其实是很小的因为程序员们每天在写的代码都是差不多的非常同质化的况且还有标准答案“文档”可以参考。比如都知道string.concat()是拼接string.split()是分割。该用分割的地方不小心用了拼接那改掉就好。但是“找bug”就不是这样了。比如你刚刚改完一行代码后发布出现的问题你不用找就知道问题出现在哪。但是让你排查一个刚接手没多久的系统肯定是一脸懵逼。根本原因在于这个过程不像技术性问题具有确定性它是充满不确定性的处在一个“混沌”的环境中。所以对待bug的重点就变成了如何更快的发现和找到bug。关于这点Z哥的建议是打好日志学会利用工具每次的迭代规模尽可能的小首先打好日志。日志其实就是我们在编码的时候安插在程序中“记录员”它替我们记录着我们认为容易出现问题的地方所产生的信息。但是系统无时无刻都在运行着必然会产生大量的日志信息如何从这些信息中快速的找到关键信息就是需要考虑的问题。另外如果每个人都随意的用自己喜欢的记录日志的方式那么从风格迥异的日志中找你需要的信息就变得很头疼时间不一致格式不一致等等。所以要做好打日志这个事情就需要定义一个标准比如必须要有时间包含当前上下文的参数等等。我们还可以给日志做一下归类定义不同的日志级别在记录的时候带上前缀。比如【info】、【warning】、【error】之类。如此一来平时更着重关注的就是error级别的信息而且由于将其他级别的信息剥离了出去使得这里的数据量大大减少更便于查看。不过日志记录毕竟是一个在做“预判”如果日志中没有记录到怎么办呢这里提醒大家不要先想着怎么调试。如果你面对的系统是一个单体应用倒还好。如果你面对的是一个大型的分布式系统调试的效率低不说这事你一个人可能还完不成。而且如果你直接调试生产环境的话说不准还会产生什么副作用摊上新的问题。找bug本质上是一个排除法的过程设断点调试也是如此。但是从起点开始一步一步的做排除法效率太低了。应该先通过自己的经验、拥有的部分信息先逻辑推理一下缩小排查的范围。哪怕你最终还是需要调试的话