那些我在初级程序员阶段犯过的错

Samer Buna 开源社KAIYUANSHE




我要先说清楚一件事。如果你是一个初级程序员,这篇文章并不是要让你为自己可能犯的错误感到难过,而是要让你意识到这些错误,教你发现这些错误可能出现的迹象,并提醒你避免这些错误。


我过去曾犯过这些错误,并从中吸取教训。可喜的是我现在已经形成了良好的编码习惯,帮助了我避免这些问题。你也应该这样做。


以下这些错误出场顺序随机。



一般而言,高质量的写作内容无法轻易产出。它需要认真思考和研究。高质量的程序也不例外。


编写高质量的程序是一个分步骤的过程。

思考、调研、计划、编码、测试、修改


很遗憾,这个过程没有适用的缩略语。你需要养成习惯,总是进行 适量的 这些活动。


作为初级程序员,我犯下的最大错误之一是没有经过充分的思考和研究就开始写代码。虽然这对一个小型的独立应用程序来说是可行的,但对大型的应用程序却有严重的负面影响。


正如你在说任何你可能会后悔的话之前需要三思而行一样,你在编写任何你可能会后悔的代码之前也需要先思考。写代码也是交流思想的一种方式。



我来套用一下上述名言:



编程主要是指阅读既有代码,研究需求以及如何适应当前系统,并计划用少量可测试的增量代码来实现新功能。实际写代码的时间可能只占整个过程的 10%。


不要认为编程就是写几行代码。编程是一种需要培养的基于逻辑的创造力。



是的。在编写代码之前先行计划是好事,但是即便是好事,也可能物极必反。喝太多的水都会使你中毒呢。


不要寻找完美的计划。这在编程的世界里是不存在的。寻找一个足够好的计划,一个你可以用来开始的计划。因为计划永远赶不上变化。但计划的好处是推动你理清结构,使代码更加清晰。计划太多只是浪费时间。


我现在讲的只是对于小功能的一些规划。一次性规划所有的功能的做法根本就不可取! 我们通常称之为瀑布式方法,这是一个系统的线性的计划,有特定的步骤,依序完成。你可以想象,这种做法需要计划多少事情。这不是我要在这里谈论的那种计划类型。瀑布式方法对大多数软件项目都不适用。任何复杂的事情都只能根据实际情况灵活调整来实现。


编写程序必须是一项反应性的活动。你将添加你在瀑布计划中从未想过的功能。你会因为一些你在瀑布式计划中从未考虑过的原因而删除一些功能。你需要修复 bug 以适应变化。你需要保持敏捷。

然而,要始终计划好后续的少数新功能。要非常谨慎地去做,因为太少的计划和太多的计划都会损害你的代码质量,而代码质量是你不能冒的风险。



如果你只能关注你写的代码的一个方面,那么肯定是它的可读性。表意不清的代码就是垃圾。甚至是不可回收的垃圾。


永远不要低估代码质量的重要性。把编码看成是沟通实现的一种方式。作为程序员,最主要的工作就是要清楚地传达当前解决方案的实现情况。


我最喜欢的一句关于编程的名言是:



感谢 John!这真是个绝妙的建议!


即使是小事也很重要。例如,如果你在写代码的时候,缩进和大写字母不一致,你会直接被吊销编码执照。



还有一件小事是关于长行的使用。任何超过 80 个字符的代码行都很难阅读。你可能会想在同一行放置一些很长的条件判断,好让 if 语句更加清晰。不要这样做。永远不要让一行代码超过 80 个字符,永远不要。


许多像这样的简单问题可以通过 linting 和 formatting 工具轻松解决。在 JavaScript 中,我们有两个出色的工具可以完美地协同工作:ESLint 和Prettier。帮自己一个忙,把它们用起来吧。


下面还有几个与代码质量有关的错误。


- 在一个函数或文件中使用多行。 你应该始终将长代码拆分成较小的片段,可以分别进行测试和管理。我个人认为,任何超过 10 行的函数都太长了,但这只是经验之谈。


- 使用双重否定。 请千万千万不要这样做。

使用双重否定并不是不正确 (这是使用双重否定的例子)


- 使用简单的、宽泛的、基于类型的变量名。 给你的变量起一个描述性的、没有歧义的名字。



- 硬编码原始字符串和没有描述性的数字。 如果你想写一个依赖固定的原始字符串或数字值的逻辑,请使用常量保存该值并起一个好名字。



- 使用过得去的捷径和变通方法来避免在简单的问题上花费更多的时间。 不要回避问题。面对现实吧。


— 认为代码越长越好。 在大多数情况下,较短的代码会更好。只有以代码可读性更强为前提,才用长代码的写法。例如,不要为了让代码更短而使用巧妙的一行程序和嵌套的三元表达式,但也不要在不必要的时候故意让代码变长。删除不必要的代码在任何程序中都是你能做的最好的事情。



— 过度使用条件逻辑。你认为需要条件逻辑的地方大都可以在不使用条件逻辑的情况下完成。考虑所有的选项,并只根据可读性来选择。除非你能测量性能了,否则不要去优化性能。相关内容:避免 Yoda 条件 和根据条件的赋值。



记得刚开始编程时,每遇到一个问题,我找到一个解决方案后马上就会用它来运行。对于这个第一个找到的解决方案,我压根就不会去考虑它的复杂性和潜在的失败,匆匆忙忙的就想立马实施起来。


虽然第一个解决方案可能很诱人,但是好的方案往往是你在开始对所有找到的方案盘查后才发现的。如果你没有找到一个问题的多种解决方案,那可能是因为你没有完全理解这个问题。


作为一个专业的程序员,你的工作不是为问题找到 一个解决方案。而是找出解决问题的最简单方案。我所说的 "简单 "是指解决方案不仅必须正确地工作,充分地执行,还要简单到可以阅读、理解和维护。




另一个我不愿意承认的错误是,即使我发现第一个解决方案可能不是最简单,我还是坚持这个解决方案。


这可能是心理学上的 "不放弃 "的心态吧。在大多数活动中,这是一种良好的心态,但它不应该适用于编程。事实上,当说到到编程时,正确的心态是失败要趁早,失败是常事。


当你开始怀疑一个解决方案的时候,你应该考虑抛弃它,并且重新思考问题。不管你在这个解决方案上投入了多少精力。像 GIT 这样的版本控制系统可以帮助你分开管理和尝试多种不同的解决方案。把它利用起来吧。




我曾经不止一次浪费宝贵的时间去试图解决一个问题,而不去先研究一下这个问题。


除非你使用的是一种极其前沿的技术,否则当你遇到一个问题时,很可能别人早就遇到同样的问题了,并且找到了解决方案了。给自己省点时间,凡事先 Google 一下吧。


有时,Google 搜索之后你会发现,问题其实并不存在,你需要做的不是解决它,而是接受它。不要以为你知道了寻找解决方案必备的所有知识, Google 会让你吃惊。


然而,请注意你使用 Google 搜索的目的。新手的另一个标志是复制和使用别人的代码,却不了解这些代码。虽然这段代码可能正确地解决了你的问题,但你永远不应该使用你不完全理解的任何一行代码。


如果你想成为一个有创造力的程序员,永远不要自以为是,知道自己在做什么。




这一点不是关于使用面向对象的范式。封装概念的使用总是很有用。不使用封装往往会导致系统更难维护。


在一个应用程序中,一个功能应该只有一个可以处理它的地方。这通常是一个单一对象的职责。该对象只能向其他需要使用到它的对象暴露必须的接口。这跟保密无关,只是为了减少应用程序的不同部分之间的相互依赖。坚持这些规则可以让你安全地对你的类、对象和函数的内部进行修改,而不必担心更大范围内的破坏。


概念的逻辑单元和状态应该有它们自己的类。我所说的类,是指蓝图模板。这可以是一个实际的类对象或一个函数对象。您也可以把它认作是一个模块或一个软件包。


在一个逻辑类中,自包含的各种任务应该有自己的方法。一个方法应该是只做一件事,并且把这件事做好。类似的类应该使用相同的方法名称。


作为一个初级程序员,我以前通常没有能力为一个概念性的单元起一个新的类,也经常不能确定什么是可以自包含的。如果你看到一个 "Util" 类,像垃圾场一样,堆放了很多互不关联的东西,这就是新手写代码的特点。如果你做了一个简单的改动,然后发现这个改动有连锁效应,导致你需要改动其他很多地方,那就是新手代码的另一个特点。


在给类增加方法或给方法增加更多的职责之前,请思考并质疑自己的直觉。这里你需要花费点时间。不要跳过或认为以后会重构这个问题。第一次就要把它做对。


基本的想法就是你想你的代码高内聚和低耦合,这只是一个花哨的术语,意识是说保持相关的代码在一起 (在一个类中),降低不同类之间的相互依赖。



跳出正在写的解决方案去思考问题往往很有诱惑力。在你写的每一行代码中,都会有各种各样的 "如果 "出现在你的脑海中。这对测试边界情况来说是件好事,但把它作为潜在需求的驱动力就是错误的。


你需要确定你的这些"如果 "属于这两大类中的哪一类。不要写那些你今天不需要的代码。不要为未知的未来做计划。


出于猜想“将来可能会需要”而去写一个功能是完全错误的,不要这么做。


尽可能编写目前正在实施的方案所需的最少量代码。当然,要处理边缘情况,但不要添加边缘功能。




在准备面试时,初级程序员通常会把注意力过多地放在算法上。识别好的算法并适时使用是很好的,但记住这些算法并不能给你的编程技能带来提升。


尽管如此,记住在语言中可以使用的各种数据结构的优劣,肯定会使你成为一个更好的开发者。


使用错误的数据结构就像一个巨大的明晃晃的广告牌,呼叫着新手快来这里写代码。


这篇文章并不是要教你数据结构的知识,但我想举几个简单的例子。


— 使用列表(数组) 而不是映射表 (对象) 来管理记录


最常见的数据结构错误可能是使用列表而不是映射表来管理一个记录列表。是的,为了管理一个记录列表你应该使用映射表。


请注意,我在这里说的是一个记录列表,每个记录都有一个标识符,用来查找该记录。使用列表来管理标量值是可以的,而且往往是更好的选择,特别是如果使用的重点是将值 "推入 "到列表中。


在 JavaScript 中,最常见的列表结构是一个数组,最常见的映射结构是一个对象 (在现代 JavaScript 中也有一个映射表的结构)。


使用列表而不是映射表来管理记录,往往是错误的。虽然这个说法确实是在管理大量记录的时候才成立,但我想说的是,请一直这么坚持做下去。这一点很重要,主要原因是当使用标识符查找记录时,映射表要比列表快很多。


- 不使用堆栈


当编写一些需要递归形式的代码时,总会倾向于使用简单的递归函数。然而,通常很难优化递归代码,特别是在单线程环境下。


优化递归代码取决于递归函数返回什么。例如,优化一个返回调用自身两次及以上的递归函数,比优化一个只返回调用自身一次的递归函数要难得多。


作为初学者,我们往往会忽略的是,除了使用递归函数之外,还有一种替代方法。你可以直接使用 堆栈 结构。自己推送函数调用到一个堆栈中,并在准备追溯调用时将它们弹出。



想象一下,给你一个像下图这样凌乱的房间。



然后要求你向该房间内添加一个物品。由于它已经是一团乱了,你可能会想把这个物品随手一放。几秒钟完事。


当你面对的是混乱的代码的时候,千万别这么做。不要让它变得更糟!总是让代码比你开始工作时更干净一些。


解决上面的房间问题,正确的做法是清理需要的位置,为放置新的东西腾出空间。举个例子,如果这个物品是一件需要放在衣柜中的衣服, 你需要清理出一条通往衣柜的道路。这也是正确完成任务的一部分。


这里有一些错误的做法,通常会使代码变得比原来更乱(不是一个完整的列表)。



关于不必要的 if 语句,看一下这段代码吧。



上面的 isOdd 函数有几个问题,但你能看到最明显的问题吗?


它使用了一个不必要的if语句。以下是一种等价的代码写法:




我已经学会尽可能地去避免撰写注释。大多数注释可以用更好的命名元素来代替。


例如,不要写下面这样的代码:



同样的代码也可以不加注释,像下面这样写:



只要给函数和参数更好的命名,就可以省去大部分的注释。在撰写任何注释之前请记住这一点。


然而,你有时会被迫陷入这样的情况,只有通过注释才能提高代码的"清晰度”。这时,你应该组织你的注释来回答 ,为什么用这段代码 ,而不是 ,这段代码是用来干嘛的。


如果你很想写一个 WHAT 注释来解释代码,请不要写那些显而易见的。以下是一些毫无用处的注释的例子,这些注释只会干扰代码:



不要成为这种程序员。不要接受这个代码。如果不得不处理,请删除这些注释。最重要的是,教育那些写这种注释的程序员,让他们知道自己写得有多差。如果你碰巧雇佣了写类似上述评论的程序员,让他们知道他们可能真的会因此而失去工作。


 Yep… 这真的非常严重。



我将简单地阐述这一点。如果你觉得你是名程序员高手,并且很有自信写代码的时候不带测试,那在我这里你就是位新手。


如果你不在代码里写测试,那你很可能在用某些手动的方式来测试代码。如果你正在构建一个 web 应用程序,每写几行代码你就得刷新,然后做一些交互来进行测试。我也是这么做的。手动测试代码并没有错。但你应该通过手动测试代码来弄清楚如何对它进行自动化测试。如果你成功地测试了与应用程序的交互功能,那么在下次添加更多功能之前,你应该能够让代码自动运行,执行完全一致的交互功能。


你是一个人, 你就很难保证在每次修改完代码后,还能把之前验证成功的测试校验全部再做一遍。不如让计算机为你做这件事吧!


如果可以的话,甚至在写代码来实现需求之前,你就应该开始预算或设计需要测试校验的情况。测试驱动开发 (TDD) 可不只是一些花哨的炒作。它会对你思考产品特性,寻找更好的设计方案产生积极地影响。


测试驱动开发(TDD)并不对每个人都适用,它也不是对所有项目都行得通。但如果你可以把它利用起来(哪怕只是用起来一小部分),你都完全应该这样做。



看一下这个实现 sumOdValue 功能的函数。这还有什么问题吗?



断言测试通过。生活真是美好啊。是吧,这是对的吧?


上述代码的问题是它并不完整。它恰当地处理了一些情况(碰巧断言是其中一个例子),但除此之外还有很多问题。我们来列举其中的几种情况:


- 问题 #1: 没有处理空输入。如果这个函数被调用的时候没有传递任何参数呢?这种情况下就会产生一个错误,暴露出该函数的内部实现。




这通常是糟糕的代码的标志,主要有两个原因。


  • 函数的使用者不应当看到函数的实现细节。


  • 出错的信息对使用者没有任何帮助。函数对他们来说不起作用。然而,如果函数的出错信息能对函数的适用方法描述得更清晰具体一些,函数的使用者就会知道是他们使用不当。例如,你可以选择通过一个用户定义的异常来实现这个功能,像这样:




也许你不需要抛出一个错误,而是重新设计这个函数,忽略掉空的输入,然后返回 0 。不管怎么说,在这种情况下,必须要做一些事情。


- 问题 #2: 没有处理无效输入。如果用一个字符串,一个整数,或一个目标值而不是一个数组来调用该函数,会发生什么?


下面是这个函数现在会抛出的错误:



好吧,这很不幸,因为 array.reduce 绝对是一个函数!

由于我们给函数的参数命名为 array , 任何调用这个函数的参数 (在上面示例中的 42 ) ,在函数内部都会被打上 array 的标签。这个错误其实就是说 42.reduce 不是一个函数。


你看到这个错误有多让人费解了吧?也许一个更有帮助的错误会是这样的:



问题 #1 和 #2 有时被称为边缘情况。有一些常见的边缘情况需要考虑,但通常还有一些不太明显的边缘情况也需要进行思考。例如,如果我们使用负数作为参数会怎样?



-13 是一个奇数。这是你希望这个函数该有的行为吗?它应该抛出一个错误吗?复数是否也应该被求和加进来?还是说应该像现在的行为这样忽略掉负数?也许你会意识到,这个函数应该被命名为 sumPositiveOddNumbers


对这种情况做出决策很容易。但更重要的一点是,如果你不写测试用例来记录这次决策,那么未来的维护者就不知道你是故意忽略掉复数还是这里出现了 bug。



- 问题 #3: 并非所有有效案例都经过测试。抛开边缘情况不提,这个函数有一个合法的、非常简单的情况没有被正确处理。



上面的 2 本来不应该被加到求和中去。


解决方法很简单, reduce 接受第二个参数,作为 求和器的初始值 。如果没有提供这种参数(例如在上文的代码中), reduce 就会使用集合中的 第一个 值作为 求和器 的初始值。这就是为什么上述测试用例中的第一个偶数值被包括在总和中。


虽然你可能在一开始编写代码时就发现了这个问题,但暴露出这个问题的测试用例应该和其他许多测试用例一起首先被包含在测试中,比如全是偶数的数组,列表中含有 0 的数组 ,以及空数组。


如果你看到极少的测试不处理大多数情况或忽略边缘情况,那就是新手写代码的另一个标志。



除非你是一个总是独自工作的超级码农,否则毫无疑问,你在生活中总会遇到一些很愚蠢的代码。初学者不会认识到这一点,他们通常认为这是很好的代码,因为它似乎在正常运行,而且已经成为代码库的一部分很长时间了。


更糟糕的是,如果糟糕的代码使用了糟糕的实践,初学者可能会被诱惑在代码库的其他地方重复这种糟糕的实践,因为他们把这些当作好代码进行学习了。


有些代码看起来很糟糕,但可能有一些特殊的情况迫使开发者把它写成这样。这就是应该编写详细注释的地方了,可以教初学者了解这些特殊情况以及为什么代码要这样写。


作为一个初学者,你应该认为任何你不理解的无文档的代码都是糟糕的代码。质疑它。询问它。使用 git blame 找出原因!


如果该代码的作者已经离开很久,或者他自己也记不住为什这样写,那么就好好研究这些代码,尝试了解关于它的一切。只有当你完全理解了这份代码,你才能形成关于它是坏还是好的认知。在这之前不要做任何假设。



我认为 "最佳实践 "这个词实际上是有害的。它暗示你不需要进一步研究。这才是有史以来最好的实践。不容置喙!


没有最佳实践这回事。也许有 目前来说 针对这种编程语言的  好的实践


我们以前确定为编程的最佳实践的一些东西,如今却被贴上了不良实践的标签。


如果你投入足够的时间,你总是可以找到更好的实践。不要再担心什么最佳实践,专注于你能做的最好的事情。


不要因为你在某处读过的一段引文说可以这么做你就去做,也不要因为你看到别人这样做,或者因为有人说这是最好的实践你就去做。这包括我在这篇文章中提出的所有建议! 质疑一切,挑战所有的理论,了解所有可能的选择,做出明智的决定。




虽然自 Donald Knuth 写下上述言论以来,编程已经发生了翻天覆地的变化,但我认为这句话在今天仍然是一条宝贵的建议。


有一条法则可以帮助记住这条建议:如果你不能测量代码中疑似存在的性能问题,就不要试图优化它。


如果你在执行代码之前就进行优化,那么你很有可能是在过早地进行优化。还有一个极大的可能性是,你投入时间做的优化是完全没必要的。


当然,有一些很明显的优化,是你在引入新的代码之前就应该考虑的。例如,在 Node.js 中,不淹没事件循环或阻塞调用栈是极其重要的。

这是你应该始终牢记的早期优化的例子。扪心自问:我所考虑的代码是否会阻塞调用栈?


任何未经测量就在任何现有代码上进行的非明显优化都被认为是有害的,应该避免。你认为可以提高性能的事情,如果做了,可能会变成一个新的、意想不到的 bug 的源头。


不要浪费时间去优化未经测量的性能问题。



向应用程序添加功能的最简单方法是什么?从你自己的角度来看,或者从它在当前用户界面中的适应性来看。对吧?如果该功能是为了从用户那里获取一些输入,那就从你已有的表单上添加。如果该功能是要添加一个链接到页面,那就在你已有的菜单列表里面添加。


不要成为那样的开发者。要做专业的开发者,始终站在终端用户的角度上来考虑问题。专业的开发者要设想这个特定功能的用户需要什么,怎样使用。要想方设法让用户容易找到和使用这个功能,而不是想方设法让这个功能便捷添加在应用程序中,却丝毫不考虑该功能的可发现性和可用性。



每个人都有一个最喜欢的工具清单,以协助他们进行与编程有关的活动。有些工具很强,有些工具较差,但大多数工具都擅长处理某一特定任务,而对其他许多任务就不那么友好了。


锤子是把钉子钉进墙里的伟大工具,但它用它来拧螺丝钉就很差劲。不要因为你 "喜欢 "那把锤子,就用锤子来拧螺丝。不要因为这是亚马逊上最受欢迎的锤子,用户评分5.0,就用它来拧螺丝。


依靠一个工具的知名度而不是它的适用性,是真正的新手的标志。


关于这一点的一个问题是,你可能不知道适用某个特定任务的“更好的”工具。在你目前的知识范围内,这个工具可能是你所知道的最好的工具。然而,与其他可选工具相比,它不会进入榜单前列。你需要熟悉你可以使用的工具,并对你可以开始使用的新工具保持开放的态度。


一些码农拒绝使用新工具。他们对现有的工具很满意,不想学习任何新的工具。我理解这一点,我也能体会到这一点,但这种做法明显是错误的。


你可以用最原始的工具建造房子,慢慢来,或者你可以花费一些时间和金钱购买现金的工具,更快地建造一个更好的房子。工具在不断地改进,你要乐意去学习和使用它们。



程序的一个重要方面往往是管理某种形式的数据。程序就是一个添加新数据、删除旧数据和修改数据的接口。


即使程序代码中最小的 bug 也会导致其管理的数据处于不可预测的状态。如果对数据的所有验证都完全通过这个有 bug 的程序来完成,那就更是如此。


当涉及到代码与数据的关系时,初学者可能不会马上理解其中的联系。他们可能会觉得继续在生产中使用这段有 bug 的代码没什么关系,因为失效的功能 X 并不特别重要。问题是,有 bug 的代码可能会不断引入数据完整性问题,而这些问题在一开始并不明显。


更糟糕的是,如果发布的代码修复了 bug,却没有修复由这些 bug 导致的细微数据问题,那只会积累更多的数据问题,最终贴上 "不可恢复级别 "的标签。


你如何保护自己免受类似问题的影响?你可以简单地使用多层数据完整性验证。不要依赖单一的用户界面。在前端、后端、网络通信和数据库上都进行数据验证。如果做不到这一点,那么你至少要使用数据库级别的约束。


要熟悉数据库约束,当你向数据库添加列和表时,尽可能适用这些约束条件。


NOT NULL 非空约束意味着该列的空值将被拒绝。如果你的应用程序假定该字段存在一个值,它的来源应该在你的数据库中定义为非空。


UNIQUE 唯一约束意味着该列上的所有值不能在整个表中出现重复。例如,这对用户表中的用户名或电子邮件字段来说是很好的应用场景。


CHECK 约束是一个自定义的表达式,它必须被评估为真,数据才会被接受。例如,如果有一个正常的百分比列,其值必须在 0 到 100 之间,你可以使用一个 Check 约束来强制执行。


PRIMARY KEY 主键约束意味着列的值必须同时是非空和唯一的。你可能正在使用这个约束了。数据库中的每个表都应该有一个主键来识别其记录。


FOREIGN KEY 外键约束意味着此列的值必须与另一表中的列(通常是主键)显示的值相匹配。


另一个与数据完整性有关的新手问题是缺乏对数据传输的思考。如果多个操作需要改变同一个数据源并且相互依赖,它们就必须被包裹在一个数据传输当中,以便在其中一个操作失败时能够回滚。



这是一个棘手的问题。在编程领域,有些轮子确实值得重新造一遍。编程不是一个定义明确的领域。如此多的事情,变化来得如此之快,新的需求被引入的速度超过了任何团队能够处理的速度。


例如,如果你需要一个轮子,根据一天的不同时间以不同的速度旋转,而不是定制我们都知道和喜爱的轮子,也许我们就需要考虑重新造一个。但是,除非你确实需要一个非常规设计的轮子,否则都不要重新再造一个新轮子。就用原来那个讨人厌的轮子吧。


有时候在众多可选的牌子里挑选需要的轮子是一种挑战。在购买之前先做一些研究和尝试! 软件 "轮子 "最酷的地方是,它们中的大多数都是免费的,开放的,你可以看到它们的内部设计。你可以很容易地通过其内部设计的质量来判断软件轮子的优劣。如果可以的话,使用开源的轮子。开放源码软件包可以轻松地调试和修复。它们也可以很容易地被替换掉。此外,在内部支持它们也不费力。


然而,如果你只需要一个轮子,千万不要去买一整辆新车,然后把你正在维修的车放在那辆新车车顶。不要仅仅为了使用其中的一两个函数而引入一整个代码库。这方面最好的例子是 JavaScript 中的 lodash 代码库。如果你需要随机打乱一个数组,只要引入 shuffle 方法即可。可别导入整个 lodash 库。



编码新手的一个标志是,他们经常把代码审查看成是批评。他们不喜欢、 不感激、 甚至害怕代码审查。


这是错误的。如果你有这种感觉,你需要马上改变这种态度。把每一次代码审查看成是一个学习的机会。学会欢迎和欣赏, 并从中学习。最重要的是,当你从代码审查人员那里学到东西时,要感谢他们。


在写代码这条路上,你永远都是一个学习者。你需要接受这一点。大多数代码审查都会教你一些你之前不知道的东西。请将它们归类为学习资源。


有时,代码审查人员也会犯错,这时就轮到你来教他们了。然而,如果仅从你的代码中看不出明显的问题,那么在这种情况下,也许你的代码需要修改了。如果你一定要给代码审查人员上一课的话,那要知道,教会别人是你作为一个程序员可以做的最有价值的活动之一。



新手们有时会低估一个好的版本控制系统的威力,我其实指的是 Git。


版本控制不仅仅是要将你的修改推送给别人,让他们在此基础上继续开发。除此之外还有更重要的。版本控制是关于清晰的历史记录。代码会受到质疑,而代码的历史记录将有助于解决一些棘手的问题。这就是为什么我们要关心信息的提交。它们是沟通实现的另一个渠道,通过使用信息提交有助于未来维护者弄清楚代码是如何发展到现在的状态的。


经常提交,尽早提交,为了保持一致性,在提交的主题行中使用动词的一般现在时。提交的信息要详细,但要记住,应该尽可能提交总结性的信息。如果你需要多放几行,那可能说明你的提交太长了。优化一下吧!


不要在提交信息中包含任何不必要的内容。比如,不要列出被添加、修改或删除的文件。这些在提交对象本身就包含了,并且通过一些 Git 命令参数就可以轻松地显示出来。在总结性的信息中放上这些东西简直就是噪音。有些团队还喜欢对每个文件进行不同的总结,我认为这也是提过量提交另一个标志。


版本控制也跟可发现性相关。如果你遇到一个函数,并开始质疑它的需求或设计,那么你可以找到引入这个函数的提交记录,查看该函数的上下文。提交甚至可以帮助你识别哪些代码在程序中引入了 bug。Git 甚至可以允许你在提交中之间进行二进制搜索( bisect 命令),来定位引入 bug 的提交。


即使在更改成为正式提交之前,版本控制也可以以奇妙的方式加以利用。诸如分段修改、选择性打补丁、重设、存储、修正、应用、比较、撤回等功能的使用,为你的工作流提供了丰富的工具。理解,学习,使用,并欣赏它们。


在我看来,你知道的 Git 特性越少,你就越像是个新手。



再次说明,这不是一个关于函数式编程 Vs 其他范式的观点。那是另一篇文章的主题。


实际上,共享状态是问题的源头,如果可能的话,应该避免。如果避免不了,则应将共享状态的使用保持在绝对最低限度。


作为一个初级程序员,我曾经没有意识到,我们定义的每个变量都代表着一个共享状态。它持有的数据可以被与该变量相同范围内的所有元素所改变。范围越全,这种共享状态的跨度就越大。尽量将新的状态控制在小范围内,并确保它们不会向上泄漏。


当多个资源需要在同一个事件循环中同时修改共享状态的时侯(在基于事件循环的环境中),共享状态就会发生问题。竞态条件将会出现。


事实是:一个新手可能会尝试使用定时器,作为解决这个共享状态的竞态条件问题的方法,特别是当他们必须处理数据锁的问题时。这是相当危险的信号。不要这么做。注意它,在代码审查中指出它,并且永远不要接受它。



错误(Erros) 不是坏事。这意味着你正在取得进展。意味着你们可以通过简单的后续修改,以取得更多的进步。


专家程序员喜欢 errors。新手们则痛恨它们。


如果少量的这些鲜艳的红色 error 信息让你感到困扰,那你真的需要改变这种态度了。你需要把它们看作帮手。你需要处理它们。你需要利用它们来取得进步。


有些 errors 需要升级为异常 (exceptions)。异常是用户定义的错误,你需要对其进行规划。有些 errors 不需要管。它们的作用就是让应用程序崩溃并逼其退出。



你是一个人,你的大脑需要休息。您的身体需要休息。你会经常因为进入状态而忘记休息。我把这看成是新手的另一个标志。这不是你可以妥协的事情。请在你的工作流程中整合一些东西,强迫自己休息。可以短暂多次地休息。离开椅子,走一段路,思考你接下来需要做什么。随后神清气爽地继续回来写代码。


这篇文章可真长, 你该去休息了。


作者丨Samer Buna

译者丨梅嘉宜,李思颖、审校:庄表伟

编辑丨王萱


相关阅读 | Related Reading


李建盛老师看来,参与开源百利而无一害。开源布道前途漫漫,初心不改。


社区参加指南来了!诚挚邀请您参加开源之夏!