We already mentioned that functions are critical in performing problem decomposition. Beyond problem decomposition, functions are valuable in software for a number of other reasons, including
Cognitive load—You may have heard of cognitive load [1] before. It’s the amount of information your brain can handle at any given time and still be effective. If you are given four random words and asked to repeat them back, you might be able to do that. If you are given the same task with 20 words, most of us would fail because it’s too much information to handle at once. Similarly, if you’ve ever been on a road trip with your family and are trying to optimize the travel time, combined with stops for the kids, lunch breaks, bathroom stops, gas station stops, good locations for hotels, and so on, you might have felt your head swimming to manage all those constraints at once. That point when you can’t handle it all at once is when you’ve exceeded your own brain’s processing power. Programmers have the same problem. If they are trying to do too much at once or solve too complex a problem in one piece of code, they struggle to do it correctly. Functions are designed to help programmers avoid doing too much work at once.
Avoid repetition—Programmers (and, we’d argue, humans in general) aren’t very excited about solving the same problem over and over. If I write a function that can correctly compute the area of a circle once, I don’t need to write that code ever again. That means if I have two sections of my code that need to compute the area of a circle, I’d write one function that computes the area of the circle, and then I’d have my code call that function in each of those two places.
Improve testing—It’s a lot harder to test a section of code that does multiple things compared to code that does one thing. Programmers use a variety of testing techniques, but a key technique is known as unit testing. Every function takes some input and produces some output. For a function that computes the area of a circle, for example, the input would be the circle’s radius, and the output would be its area. Unit tests give a function an input and then compare that input to the desired result. For the area-of-a-circle function, we might test it by giving it varying inputs (e.g., some small positive numbers, some large positive numbers, and 0) and compare the result of the function against the values we know to be correct. If the answers from the function match what we expect, we have a higher degree of confidence that the code is correct. If the code produces a mistake, we won’t have much code to check to find and fix the problem. But if a function does more than one task, it vastly complicates the testing process because you need to test each task and the interaction of those tasks.
Improve reliability—When we write code as experienced software engineers, we know we make mistakes. We also know Copilot makes mistakes. If you imagine you are an amazing programmer and each line of code you write is 95% likely to be correct, how many lines of code do you think you can write before at least one of those lines is likely to be incorrect? The answer is only 14. We think 95% correctness per line is probably a high bar for even experienced programmers and is likely a higher bar than what Copilot produces. By keeping the tasks small, tasks solvable in 12–20 lines of code, we reduce the likelihood that there’s an error in the code. If combined with good testing as noted previously, we can feel even more confident that the code is correct. Last, nothing is worse than code that has multiple mistakes that interact together, and the likelihood of multiple mistakes grows the more code you write. Both of us have been on multi-hour debugging expeditions because our code had more than one mistake and, for both of us, we became a lot better at frequent testing of short pieces of code as a result!
Improve code readability—In this book, we’re going to mostly use Copilot to write code from scratch, but that’s not the only way to use Copilot. If you have a larger piece of software that you or your coworkers are all editing and using, Copilot can jump in to help write code for that, too. It’s in everyone’s interest to understand the code, whether most of it is written by humans or by Copilot. That way, we can find bugs more easily, determine what code to start modifying when we want to add new features, and understand at a high level what would be easy or difficult to achieve with our overall program design. Having tasks broken down into functions helps us understand what each part of the code is doing so we can gain better insight into how it all works together. It also helps divide up the work and responsibility for ensuring the code is correct.
These benefits are huge for programmers. Programming languages haven’t always had functions. But even before they did, programmers did their best to use other features to mimic functions. They were ugly hacks (Google “goto statements” if you’re interested), and all programmers are happy that we have proper functions now.
You may be asking, “I see how these advantages matter to humans, but how do they affect Copilot?” In general, we believe all the principles that apply to humans apply to Copilot, albeit sometimes for different reasons. Copilot may not have cognitive load, but it’s going to do better when we ask it to solve problems similar to what’s been done by humans before. Since humans write functions to solve tasks, Copilot will mimic that and write functions as well. Once we’ve written and tested a function, whether by hand or by Copilot, we don’t want to write it again. Knowing how to test if your program is working properly is just as essential for code produced by humans as it is by Copilot. Copilot is as likely to make mistakes when it generates code, so we want to catch those mistakes quickly, just as we do with human-written code. Even if you only work on your own code and never have anyone else read it, as programmers who have had to go back to edit code we wrote years ago, let us tell you that it is important for your code to be readable, even if the only person reading it is you.
3.2 Benefits of functions
3.2 函数的益处
We already mentioned that functions are critical in performing problem decomposition. Beyond problem decomposition, functions are valuable in software for a number of other reasons, including
我们之前提到,函数在进行问题拆解方面发挥着关键作用。其实,函数在软件开发中之所以宝贵,还源于其他诸多方面的原因,包括:
Cognitive load—You may have heard of cognitive load [1] before. It’s the amount of information your brain can handle at any given time and still be effective. If you are given four random words and asked to repeat them back, you might be able to do that. If you are given the same task with 20 words, most of us would fail because it’s too much information to handle at once. Similarly, if you’ve ever been on a road trip with your family and are trying to optimize the travel time, combined with stops for the kids, lunch breaks, bathroom stops, gas station stops, good locations for hotels, and so on, you might have felt your head swimming to manage all those constraints at once. That point when you can’t handle it all at once is when you’ve exceeded your own brain’s processing power. Programmers have the same problem. If they are trying to do too much at once or solve too complex a problem in one piece of code, they struggle to do it correctly. Functions are designed to help programmers avoid doing too much work at once.
认知负荷。你可能听说过“认知负荷”这个概念 [1]。它指的是大脑在同一时间内所能有效处理的信息总量。例如,给你四个随机单词让你重复,你可能没问题;但如果是20个单词,大多数人会感到难以一次性记住。同样,在家庭自驾游中,当你试图同时考虑旅程总时间、孩子们的休息点、午餐、如厕、加油以及酒店位置等种种因素时,你多半会感到难以应对。这就是你的认知负荷达到极限的时刻。程序员在编程时也会遇到同样的问题。如果他们试图一次完成太多任务或解决过于复杂的问题,很容易出错。因此,函数的出现就是为了帮助程序员避免一次性承担过多工作。
Avoid repetition—Programmers (and, we’d argue, humans in general) aren’t very excited about solving the same problem over and over. If I write a function that can correctly compute the area of a circle once, I don’t need to write that code ever again. That means if I have two sections of my code that need to compute the area of a circle, I’d write one function that computes the area of the circle, and then I’d have my code call that function in each of those two places.
避免重复。程序员(我们认为,一般人也是如此)并不喜欢一遍又一遍地解决同一个问题。如果我编写了一个函数,它能正确计算圆的面积,那么我就再也不需要重复编写那段代码了。这意味着,如果我的代码中有两处需要计算圆的面积,我会编写一个计算圆面积的函数,然后在这两处分别调用这个函数。
Improve testing—It’s a lot harder to test a section of code that does multiple things compared to code that does one thing. Programmers use a variety of testing techniques, but a key technique is known as unit testing. Every function takes some input and produces some output. For a function that computes the area of a circle, for example, the input would be the circle’s radius, and the output would be its area. Unit tests give a function an input and then compare that input to the desired result. For the area-of-a-circle function, we might test it by giving it varying inputs (e.g., some small positive numbers, some large positive numbers, and 0) and compare the result of the function against the values we know to be correct. If the answers from the function match what we expect, we have a higher degree of confidence that the code is correct. If the code produces a mistake, we won’t have much code to check to find and fix the problem. But if a function does more than one task, it vastly complicates the testing process because you need to test each task and the interaction of those tasks.
提升测试效率。相较于测试单一功能的代码,测试一段同时执行多个任务的代码要复杂得多。程序员们会运用多种测试手段,但其中最关键的一种手段称为“单元测试”。一个函数通常接收一些输入并产生一些输出。例如,对于一个计算圆形面积的函数来说,其输入就是圆的半径,输出则是面积。单元测试向函数提供输入,然后将这些输入产生的输出与预期结果进行比较。对于计算圆面积的函数,我们可以给它多种输入(比如一些小的正数、一些大的正数和0),并将其结果与我们已知的正确值进行对比。如果函数的输出与我们的预期匹配,我们对代码正确性的信心就更高。如果代码出现错误,我们也不用检查太多代码来定位和修正。但如果一个函数执行多个任务,这将使测试过程变得复杂,因为你需要测遍每个任务以及它们之间的交互。
Improve reliability—When we write code as experienced software engineers, we know we make mistakes. We also know Copilot makes mistakes. If you imagine you are an amazing programmer and each line of code you write is 95% likely to be correct, how many lines of code do you think you can write before at least one of those lines is likely to be incorrect? The answer is only 14. We think 95% correctness per line is probably a high bar for even experienced programmers and is likely a higher bar than what Copilot produces. By keeping the tasks small, tasks solvable in 12–20 lines of code, we reduce the likelihood that there’s an error in the code. If combined with good testing as noted previously, we can feel even more confident that the code is correct. Last, nothing is worse than code that has multiple mistakes that interact together, and the likelihood of multiple mistakes grows the more code you write. Both of us have been on multi-hour debugging expeditions because our code had more than one mistake and, for both of us, we became a lot better at frequent testing of short pieces of code as a result!
提升可靠性。作为资深的软件工程师,我们在编写代码时难免会犯错。Copilot 同样不是完美无缺的。设想你是一名杰出的程序员,你写的每行代码都有 95% 的正确率,那么你觉得你能连续写多少行代码而不发生一个错误?答案是只有 14 行。即便是对经验丰富的程序员而言,每行代码 95% 的正确率也已经是极高的标准了;而对 Copilot 来说这个标准可能更难达到。通过限制任务的规模,控制在 12 至 20 行代码之内解决问题,我们就能减少出错的几率。结合上面提到的有效测试,我们对代码正确性的信心将更加充足。最后,最糟糕的情况莫过于代码中存在多个相互影响的错误,而且代码量越大,出错的概率也随之增加。我们俩都曾因为代码中存在多个错误而陷入长时间的调试困境;有了这样的教训之后,我们俩终于学会对小块代码进行频繁测试了!
Improve code readability—In this book, we’re going to mostly use Copilot to write code from scratch, but that’s not the only way to use Copilot. If you have a larger piece of software that you or your coworkers are all editing and using, Copilot can jump in to help write code for that, too. It’s in everyone’s interest to understand the code, whether most of it is written by humans or by Copilot. That way, we can find bugs more easily, determine what code to start modifying when we want to add new features, and understand at a high level what would be easy or difficult to achieve with our overall program design. Having tasks broken down into functions helps us understand what each part of the code is doing so we can gain better insight into how it all works together. It also helps divide up the work and responsibility for ensuring the code is correct.
增强代码可读性。本书运用 Copilot 的主要方式都是从零开始编写代码,但这并非 Copilot 唯一的使用场景。当你和同事们共同编辑和维护一个大型软件项目时,Copilot 也能助你一臂之力。理解代码对每个人都至关重要,无论是人类写的代码还是 Copilot 写的代码。有了这样的基础,我们就能更轻松地发现并修复 bug,确定在何处开始调整代码来添加新功能,并能在宏观层面上理解哪种方式更有把握实现程序的整体设计。将任务划分为函数,有助于我们理解代码的各个部分各自承担的职责,从而更好地理解它们是如何协同作用的。这也有助于我们分配工作和责任,确保代码的正确性。
These benefits are huge for programmers. Programming languages haven’t always had functions. But even before they did, programmers did their best to use other features to mimic functions. They were ugly hacks (Google “goto statements” if you’re interested), and all programmers are happy that we have proper functions now.
这些益处对于程序员来说极为重要。编程语言并不是从一开始就内置了函数功能。但在函数功能出现之前,程序员们想尽办法使用其他特性来模拟函数的效果。那些替代方案往往显得笨拙(如果你感兴趣,不妨搜索“goto语句”来了解一下),而现在我们有了真正的函数,所有程序员都为之庆幸。
You may be asking, “I see how these advantages matter to humans, but how do they affect Copilot?” In general, we believe all the principles that apply to humans apply to Copilot, albeit sometimes for different reasons. Copilot may not have cognitive load, but it’s going to do better when we ask it to solve problems similar to what’s been done by humans before. Since humans write functions to solve tasks, Copilot will mimic that and write functions as well. Once we’ve written and tested a function, whether by hand or by Copilot, we don’t want to write it again. Knowing how to test if your program is working properly is just as essential for code produced by humans as it is by Copilot. Copilot is as likely to make mistakes when it generates code, so we want to catch those mistakes quickly, just as we do with human-written code. Even if you only work on your own code and never have anyone else read it, as programmers who have had to go back to edit code we wrote years ago, let us tell you that it is important for your code to be readable, even if the only person reading it is you.
你可能会好奇,“我理解这些好处对人类极为重要,但它们对 Copilot 有何影响?”总体而言,我们相信,所有适用于人类的原则也同样适用于 Copilot,尽管背后的原理可能有所不同。Copilot 固然没有认知负荷,但当我们要求它处理人类解决过的类似问题时,它的表现往往会更加出色。既然人类通过编写函数来完成任务,那 Copilot 也会很自然地仿效这一做法。一旦我们编写并测试了一个函数,不论是手写还是让 Copilot 来写,我们就不希望重复再写一遍了。要学会验证程序是否运行正常,不论是人类编写的代码还是 Copilot 生成的代码,这一点都至关重要。Copilot 在生成代码时同样可能犯错,因此我们希望能够迅速发现并修正这些错误,就像我们对待人类编写的代码一样。如果你只是在独立编写和维护自己的代码,从未让他人阅读过,也有必要关心可读性吗?作为曾不得不回顾和修改多年前自己编写的代码的程序员,我们想告诉你,保持代码的可读性非常重要,哪怕唯一的读者只有你自己。