The Mistakes I Made As a Beginner Programmer | by Samer Buna | EdgeCoders | Medium
这篇文章不是为了让初级程序员为自己犯的错误难过,而是让他们意识到这些错误。学习它们出现的原因,并提醒自己避免。
错误的排序并非错误的优先级。
1) Writing Code Without Planning
一般来说,高质量的文字作品是不容易创造的,它需要仔细的思考和研究。高质量的程序也不例外。
高质量程序的完成流程:Think, Research, Plan, Write, Validate, Modify.
不幸的是,做到这些是没有捷径的。需要培养一个习惯,让自己每创建一个程序,就过一遍上述流程。
作为一名初级程序员,我犯过的最大的错误之一就是没有经过深思熟虑就开始编写代码。虽然这可能适用于小型独立应用,但是对大型应用有很大的负面影响。
就像在说话之前需要思考一样,在编写代码以前也需要思考。编码也是一种交流思想的方式。
When reviewing code, count to 10 before you refactor a line. If the code does not have tests, a hundred.
– Samer Buna
编程主要是指阅读以前的代码,研究需要什么以及它如何适应当前的系统,并计划用小的、可测试的增量来编写功能。实际编写代码的过程可能只占整个过程的10%。
不要把编程想成是写几行代码。编程是一种基于逻辑的创造力,它需要培养。
2) Planning Too Much Before Writing Code
写代码前有个计划是一件好事,但是如果做得太过,就会适得其反。
不要寻找完美计划。这在编程世界是不存在的。找到一个足够好的计划——一个足够开始的东西。事实是,计划会变,但它的好处是强迫你遵循某种结构规范,从而使你的代码更加清晰。太多的计划只是在浪费时间。
我只是在讨论规划小功能。一次性规划所有功能并不现实。这就是名为瀑布流(waterfall approach)的工作方法,它是一个系统的线性计划,有明显的步骤,要逐一完成。你可以想象这种方法需要怎样的计划。这不是我在这里讨论的那种计划方式。瀑布式方法对大多数软件项目来说是行不通的。任何复杂的事情都只能通过敏捷地适应现实的方式来实现。
编写程序必须是一种响应性的活动。你将增加一些在瀑布计划中从未考虑过的特性。你将因为一些在瀑布计划中从未考虑的原因而删除一些特性。你需要修复错误并适应变化。你需要保持敏捷。
然而,总是要计划你接下来的几个功能。要非常小心地做,因为太少的计划和太多的计划都会伤害你的代码质量,而你的代码质量是你不能冒险的。
3) Underestimating the Importance of Code Quality
如果你只能关注自己代码的一个方面,那应该是它的可读性。不清晰的代码就是垃圾,它甚至都无法回收利用。
永远不要低估代码质量的重要性。将编码看成一种沟通实现的方式。作为一名程序员,你的主要工作是清楚地传达你正在做的任何解决方案的实现。
作者喜欢的关于编程的一句话:
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
– John Woods
小事也很重要。如果你连缩进和大小写都没有把握,那么你就不应该继续写代码。
另一件事是使用长行。如果一行内容超过80个字符,它就会变得难以阅读。你可能会想把一些长的条件放在同一行,以保持if语句块的突出。不要这样做。 永远不要超出80个字符的限制 。
许多这样的简单问题都可以通过工具解决。在JS中,有两个工具:ESLint和Prettier。总是使用它们。
以下是一些与代码质量有关的错误:
- 在一个函数或文件中使用过多行。你应该总是把长代码分成较小的块,这样可以分别进行测试和管理。我个人认为,任何超过10行的函数都是长代码,但这只是经验而已。
- 使用双重否定。请不要这样做。
- 使用简短、通用或基于类型的变量名称。给你的变量起一个描述性的、不含糊的名字。
There are only two hard things in Computer Science: cache invalidation and naming things.
– Phil Karlton
- 在没有描述的情况下硬编码基本字符串和数字。如果你想写一个依赖于固定的基本字符串或数字值的逻辑,请将该值放在一个常量中,并给它一个好的名字。
const answerToLifeTheUniverseAndEverything = 42;
- 使用省力的快捷方式和变通方法来避免花费更多时间解决简单的问题。不要绕过问题。面对现实。
- 认为长代码更好。在大多数情况下,较短的代码是更好的。只有在使代码更具可读性的情况下才写长一点的版本。例如,不要为了使代码更短而使用巧妙的单线和嵌套的三元表达式,但也不要在不需要的时候故意使代码变长。删除不必要的代码是你在任何程序中能做的最好的事情。
- 过度使用条件逻辑。你认为需要条件逻辑的大部分内容都可以在没有它的情况下完成。考虑所有的替代方案,并仅基于可读性选择。除非你能够进行度量,否则不要为性能进行优化。相关:避免使用Yoda条件和条件语句内的赋值。
4) Picking the First Solution
当我开始编程时,我记得当我遇到问题时,我会找到一个解决方案,然后立刻开始实现。我会在考虑第一个解决方案的复杂性和潜在失败之前立即急于实现它。
虽然第一个解决方案可能很诱人,但好的解决方案通常是在你开始质疑所有发现的解决方案时发现的。 如果你无法想出多种解决问题的方法,那可能是你没有完全理解问题的标志。
作为一个专业的程序员,你的工作不是找到问题的解决方案。而是为问题找到最简单的解决方案。我所说的“简单”是指解决方案必须正确地工作,充分地执行,但仍然简单到可以阅读、理解和维护。
There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.
– C.A.R. Hoare
5) Not Quitting
我经常犯的另一个错误是坚持第一个解决方案,即使我发现它可能不是最简单的方法。这可能与“不放弃”的心理有关。在大多数活动中,这是一种良好的心态,但它不应该适用于编程。事实上,当涉及到编写程序时,正确的心态是 尽早失败,经常失败 。
当你开始怀疑一个解决方案的时候,你就应该考虑把它扔掉,重新思考这个问题。无论你在这个解决方案上投入了多少资金,都是如此。像Git这样的源码控制工具可以帮助你进行分支,试验许多不同的解决方案。充分利用这一点。
Do not be attached to code because of how much effort you put into it. Bad code needs to be discarded.
6) Not Googling
有很多情况下,我浪费了宝贵的时间试图解决一个问题,而我本应该先进行研究。
除非你使用的是最新的技术,否则当你遇到问题时,很有可能别人也遇到了同样的问题,并找到了解决方案。节省一些时间,先谷歌一下。
有时,通过谷歌搜索会发现,你认为的问题其实并不存在,你需要做的不是解决它,而是拥抱它。不要以为你知道挑选问题解决方案所需的一切。谷歌会给你带来惊喜。
然而,要注意你在谷歌上搜索的内容。新手的另一个标志是在不理解的情况下照搬和使用别人的代码。虽然那段代码可能正确地解决了你的问题,但你不应该使用你不完全理解的任何一行代码。
如果你想成为一个有创造力的编码员,永远不要认为你知道自己在做什么。
The most dangerous thought that you can have as a creative person is to think that you know what you’re doing.
– Bret Victor
7) Not Using Encapsulation
这与面向对象范式并不相关。使用封装的概念总是有用的。不使用封装往往会导致更难维护的系统。
在一个应用程序中,一个功能应该只有一个地方可以处理它。这通常是一个单一对象的责任。该对象应该只透露应用程序的其他对象使用它时绝对必要的信息。这不是关于保密,而是关于减少应用程序不同部分之间的依赖性的概念。坚持这些规则可以让你安全地对你的类、对象和函数的内部进行修改,而不必担心在更大范围内搞坏事情。
逻辑和状态的概念单元应该有自己的类。我所说的类,是指蓝图模板。这可以是一个实际的类对象或一个函数对象。你也可以把它识别为一个模块或一个包。
在一个逻辑类中,自包含的任务片段应该有自己的方法。方法应该只做一件事,而且要做好这件事。类似的类应该使用相同的方法名。
作为一名初学者程序员,我并不总是有意识地为每个概念单位开始一个新的类,而且我经常难以确定哪些内容可以自成一体。如果你看到一个“Util”类,它被用作许多不相关的内容的堆积场所,那就是新手代码的一个标志。如果你进行了一个简单的更改,然后发现这个更改会产生连锁反应,需要在其他地方进行许多更改,那就是新手代码的另一个标志。
在向类添加方法或向方法添加更多职责之前,请思考并质疑您的直觉。你在这里需要时间。不要跳过或认为您稍后会重构它。第一次就做对。
这里的主要思想是希望代码具有高内聚性和低耦合性,这只是一个花哨的术语,意思是将相关代码放在一起(在类中),并减少不同类之间的依赖性。
8) Planning for the Unknown
在编写解决方案时,往往很容易陷入超出自己的思考。每写一行代码,你的脑海中就会冒出各种假设。这对于测试边缘情况是很有用的,但是将其作为潜在需求的驱动器(driver)是错误的。
你需要识别自己的假设属于那两个主要分类中的哪一个。不要写你今天不需要的代码。不要为未知的未来制定计划。
在编写代码时,因为你认为将来可能需要某个功能而去实现它是错误的,不要这样做。
在实现解决方案时,始终只编写你今天所需的最少量的代码。当然要处理边缘情况,但不要添加边缘功能。
9) Not Using the Right Data Structures
准备面试时,初级程序员通常会过分关注算法。识别好的算法并在需要时使用它们是有好处的,但是死记硬背它们可能永远不会为你的编程天赋做出贡献(attribute to your programming genius)。
然而,熟记你所用语言中各种数据结构的优缺点肯定会让你成为一个更好的开发者。
如果你用错了数据结构,别人就会知道你是一个初级程序员。
一些例子:
– 使用列表(数组)而不是映射(对象)来管理记录
最常见的数据结构错误可能是使用列表而不是映射来管理记录列表。是的,要管理记录列表,应该使用映射。
请注意,我在这里谈论的是一个记录列表,每个记录都有一个标识符,用于查找该记录。使用列表存储标量值是可以的,通常是更好的选择,特别是如果使用的重点是向列表“推送”值。
在 JavaScript 中,最常见的列表数据结构是数组,最常见的映射数据结构是对象(现代 JavaScript 中还有一个 Map 数据结构)。
对于管理记录,使用列表而不是映射通常是错误的做法。虽然这一点只有在处理大型集合时才真正正确,但我认为应该一直坚持使用映射。这样做的主要原因是,当使用标识符查找记录时,映射比列表更快。
– 不使用堆栈
在编写任何需要递归形式的代码时,我们往往会倾向于使用简单的递归函数。然而,在单线程环境下,递归代码通常很难进行优化。
优化递归代码取决于递归函数的返回值。例如,优化一个返回两个或更多递归调用的递归函数要比优化一个只返回一个递归调用的递归函数更困难。
作为初学者,我们往往忽视了一种替代使用递归函数的方法。你可以使用一个栈结构,将函数调用推入栈中,并在准备好遍历函数调用时开始弹出栈。
10) Making Existing Code Worse
总是让代码比刚开始工作时,更干净一些。
以下是几种通常会使代码变得比原来更乱的错误做法(不完整的列表):
- 复制代码。如果你只是在复制/粘贴一段代码后只修改其中一行,你就是在弄乱代码。始终把抽象的概念放在心上,并在可能的时候使用它。
- 不使用配置文件。如果你需要使用一个有可能在不同环境或不同时间不同的值,这个值就属于一个配置文件。如果你需要在你的代码中的多个地方使用一个值,这个值就属于一个配置文件。当你在代码中引入一个新的值时,只要一直问自己这个问题:这个值是否属于配置文件?答案很可能是肯定的。
- 使用不必要的条件语句和临时变量。每个if语句都是一个逻辑分支,需要至少进行两次测试。当你可以在不牺牲可读性的情况下避免条件语句时,你应该这样做。这方面的主要问题是用分支逻辑来扩展一个函数,而不是引入另一个函数。每次你认为你需要一个if语句或一个新的函数变量时,你都应该问自己:我是在正确的层面上改变代码,还是应该去思考更高层次的问题?
关于不必要的if语句的话题,想想这段代码吧:
function isOdd(number) {
if (number % 2 === 1) {
return true;
} else {
return false;
}
}
上面的 isOdd 函数有几个问题,但你能看出最明显的问题吗?
它使用了一个不必要的if语句。下面是一个同等的代码:
function isOdd(number) {
return (number % 2 === 1);
}
11) Writing Comments About the Obvious Things
我通过吃了亏才学会避免在可以的情况下写注释。大多数注释可以用代码中更好命名的元素来替代。
例如,不写成这样:
// This function sums only odd numbers in an array
const sum = (val) => {
return val.reduce((a, b) => {
if (b % 2 === 1) { // If the current number is odd
a+=b; // Add current number to accumulator
} return a; // The accumulator
}, 0);
};
而是写成这样:
const sumOddValues = (array) => {
return array.reduce((accumulator, currentNumber) => {
if (isOdd(currentNumber)) {
return accumulator + currentNumber;
} return accumulator;
}, 0);
};
为函数和参数使用更好的名称通常可以使大多数注释变得不必要 。在编写任何注释之前请记住这一点。
然而,有时你会被迫处于这样的情况:唯一能够增加代码清晰度的方式是通过注释。这时你应该构建注释来回答“为什么要写这段代码”而不是“这段代码在做什么”的问题。
如果你非常想要写一个“这段代码在做什么”的注释来澄清代码,请不要指出显而易见的事情。以下是一些无用的注释示例,它们只会给代码增加噪音:
// create a variable and initialize it to 0
let sum = 0;// Loop over array
array.forEach(
// For each number in the array
(number) => {
// Add the current number to the sum variable
sum += number;
}
);
不要成为那种程序员,也不要接受那种代码。如果你不得不处理这些注释,请删除它们。更重要的是,教育那些写出这种注释的程序员它们有多么糟糕。如果你雇佣了那些写出以上注释的程序员,请让他们知道这可能会导致他们失去工作。是的,这种注释就是这么糟糕。
12) Not Writing Tests
我来简单地说一下这个观点。如果你认为自己是一位专业的程序员,并且这种自信让你可以不写测试就写代码,那在我的眼里你还是个新手。
如果你没有在代码中编写测试,那么你很可能会以其他方式手动测试你的程序。如果你正在构建一个 Web 应用程序,你会在写几行代码后刷新并与应用程序交互。我也是这样做的。手动测试你的代码没有什么问题。然而,你应该手动测试你的代码,以找出如何自动测试它。如果你成功地测试了与应用程序的交互,那么你应该回到你的代码编辑器,并编写代码以在下次添加更多代码到项目时自动执行完全相同的交互。
你是一个人类。你会忘记在每次代码更改后测试所有以前成功的验证。让计算机为你做这件事吧!
如果可以的话,在编写满足验证的代码之前,先尝试猜测或设计验证。测试驱动开发(TDD)不仅是一些花哨的炒作。它对你思考特性的方式以及如何为它们设计更好的方案产生了积极的影响。
TDD并不适用于所有人,也不适用于所有项目,但如果你能够利用它(即使只是部分利用),你应该完全这样做。
13) Assuming That If Things are Working then Things are Right
看一下这个实现 sumOddValues 功能的函数。它有什么问题吗?
const sumOddValues = (array) => {
return array.reduce((accumulator, currentNumber) => {
if (currentNumber % 2 === 1) {
return accumulator + currentNumber;
}
return accumulator;
});
};
console.assert(
sumOddValues([1, 2, 3, 4, 5]) === 9
);
以上代码的问题在于它不完整。它正确处理了一些情况(并且使用的断言是这些情况之一),但除此之外还有很多问题。让我列举一些:
– 问题一:没有对空输入的处理。当函数在没有任何参数的情况下被调用时应该发生什么?现在,当这种情况发生时,你会得到一个错误,揭示了该函数的实现:
// TypeError: can't access property "reduce", array is undefined
这通常是坏代码的标志,主要有两个原因。
- 你的函数的用户不应该遇到关于它的执行细节。
- 这个错误对用户没有帮助。你的函数只是对他们不起作用。然而,如果错误更清楚地说明了使用问题,他们就会知道他们不正确地使用了这个函数。例如,你可以选择让函数抛出一个用户定义的异常,像这样:
// TypeError: Cannot execute function for empty list.
也许你需要设计你的函数来忽略空输入,并返回一个 0 的总和,而不是抛出一个错误。不管怎么说,对于这种情况,必须要做一些事情。
– 问题二:没有对无效输入的处理。如果用一个字符串、一个整数或一个对象值而不是一个数组来调用该函数,会发生什么?
以下是该函数现在会抛出的内容:
sumOddValues(42);
// TypeError: array.reduce is not a function
错误不该是这样,因为 array.reduce 绝对是一个函数!
因为我们给函数的参数命名为 array ,所以你用函数调用的任何东西(上面例子中的 42 )在函数中都被标为 array 。这个错误基本上是说, 42.reduce 不是一个函数。
你看到这个错误是多么令人困惑,对吗?也许一个更有帮助的错误会是:
// TypeError: 42 is not an array, dude.
问题一和二有时被称为边缘案例。这些是需要计划的一些常见的边缘情况,但通常还有一些不太明显的边缘情况,你也需要考虑。例如,如果我们使用负数会怎样?
sumOddValues([1, 2, 3, 4, 5, -13]) // => still 9
-13 是一个奇数。这是你希望这个函数具有的行为吗?它应该抛出一个错误吗?它应该把负数包括在总和中吗?还是应该像现在这样简单地忽略负数?也许你会意识到,这个函数应该被命名为 sumPositiveOddNumbers 。
在这种情况下做出决定很容易。更重要的一点是,如果你不写一个测试用例来记录你的决定,那么你的函数的未来维护者将不知道你对负数的忽略是故意的还是有问题的。
It’s not a bug. It’s a feature.
– Someone who forgot a test case
– 问题三:不是所有的有效案例都被测试。抛开边缘案例不谈,这个函数有一个合法的、非常简单的案例,它没有正确处理:
sumOddValues([2, 1, 3, 4, 5]) // => 11
解决方案很简单, reduce 接受第二个参数,作为 accumulator 的初始值。如果没有提供该参数(如上面的代码), reduce 将只使用集合中的第一个值作为 accumulator 的初始值。这就是为什么上面的测试案例中的第一个偶数值被包含在总和中。
虽然你可能马上或在编写代码时就发现了这个问题,但这个揭示了这个问题的测试用例应该和其他许多测试用例一起,首先包括在测试中,比如全偶数,一个有 0 的列表,以及一个空列表。
如果你看到最小的测试没有处理很多情况或忽略了边缘情况,那是新手代码的另一个迹象。
14) Not Questioning Existing Code
除非你是一个总是独自工作的超级程序员,否则毫无疑问,你在生活中会遇到一些愚蠢的代码。初学者不会认识到这一点,他们通常认为这是很好的代码,因为它看起来是有效的,而且它已经成为代码库的一部分很长时间了。
更糟糕的是,如果糟糕的代码使用了糟糕的实践,那么初学者可能会被引诱在代码库的其他地方重复这种糟糕的实践,因为他们认为这是好的代码。
有些代码看起来很糟糕,但它周围可能有一个特殊的条件,迫使开发者把它写成这样。这是一个很好的地方,可以写一个详细的评论,告诉初学者这个条件以及为什么代码要这样写。
作为一个初学者,你应该认为任何你不理解的无文档的代码都是坏的候选。质疑它。询问它。 git blame 它!
如果该代码的作者早就不在了,或者不记得了,那就研究该代码,并尝试了解关于它的一切。只有当你完全理解了这段代码,你才能形成一个意见,无论它是坏还是好。在这之前不要做任何假设。
15) Obsessing About Best Pracrices
我认为“最佳实践”这个词实际上是有害的。它意味着不需要进一步的研究。这里是有史以来最好的做法。不要质疑它!
其实并没有所谓的最佳实践。只有当前时间下和这种编程语言下的好实践。
我们以前认定的一些编程中的最佳实践,今天被贴上了不良实践的标签。
如果你投入足够的时间,你总是可以找到更好的做法。不要再担心最佳做法了,把注意力放在你能做得最好的地方。
不要因为在某处读到的一句话,或因为看到别人这么做,或因为有人说这是最佳做法而去做某件事。这包括我在这篇文章中给出的所有建议!质疑一切,挑战所有的理论,了解你所有的选择,只做受过教育的决定。
16) Obsessing About Performance
Premature optimization is the root of all evil (or at least most of it) in programming.
– Donald Knuth(1974)
虽然自Donald Knuth写下上述声明以来,编程已经发生了很大的变化,但我认为它在今天仍然具有借鉴意义。
关于这个问题,需要记住的好规则是:如果你无法通过代码来测量怀疑的性能问题,请不要尝试优化它。
如果你在执行代码之前就开始优化,很可能你的优化过早了。而且你花费时间优化的那一部分代码很可能是完全不必要的。
当然,在引入新代码之前,还有一些明显的优化应该始终考虑。例如,在Node.js中,非常重要的是不要淹没事件循环或阻塞调用栈。这是一个你应该时刻记住的早期优化的例子。问问自己:我正在思考的代码是否会阻塞调用栈?
对任何现有代码进行没有评估的非明显优化都是有害的,应该避免。如果你认为某个优化可以提高性能,但没有进行评估,那么可能会导致新的、意想不到的 Bug。
不要浪费你的时间去优化未评估的性能问题。
17) Not Targeting the End-user Experience
如何最简单地为应用程序添加一个功能?从自己的角度看,或者从它如何适应当前的用户界面来看待它。如果该功能是捕捉用户的某种输入,则将其附加到您已有的表单中。如果该功能是添加链接到页面,则将其添加到您已有的嵌套链接菜单中。
不要做那种开发者。要成为那些设身处地为最终用户着想的专业人员之一。他们想象这个特定功能的用户需要什么,以及他们可能的行为方式。他们考虑的是如何让用户容易找到和使用这个功能,而不是让这个功能以某种方式存在于应用程序中,而不考虑这个功能的可发现性和可用性。
18) Not Picking the Right Tool for the Job
每个人都有自己喜欢的工具清单,以协助他们进行与编程有关的活动。有些工具很好,有些不好,但大多数工具对一件特定的事情很好,而对许多其他的事情却不那么好。
锤子是把钉子钉进墙里的好工具,但它是使用螺丝钉的最差工具。不要因为你“喜欢”这把锤子,就用它来打螺丝。不要因为那是亚马逊上最受欢迎的锤子,有5.0条用户评论,就用锤子敲打螺丝。
依靠一个工具的知名度而不是它在多大程度上适合这个问题,是一个真正的新手的标志。
这个观点的一个问题是,你可能不知道某个工作的“更好”的工具。根据你目前的知识,某个工具可能是你所知道的最好的工具。然而,当与其他选项相比较时,它可能不是最佳选择。你需要熟悉可用的工具,并对你可以开始使用的新工具保持开放的心态。
一些程序员拒绝使用新工具。他们对自己现有的工具感到舒适,可能不想学习任何新工具。我理解并能够理解这一点,但这是错误的。
你可以用原始的工具建造一座房子并花费很长时间,或者你可以投入一些时间和金钱购买好的工具,更快地建造一座更好的房子。工具不断改进,你需要熟悉学习和使用它们。
19) Not Understanding that Code Problems Will Cause Data Problems
一个程序的一个重要方面往往是对某种形式的数据进行管理。程序将成为增加新记录、删除旧记录和修改其他记录的界面。
即使是程序代码中最小的错误,也会导致它所管理的数据出现不可预测的状态。如果对数据的所有验证都是完全通过同一个有缺陷的程序完成的,那就更是如此了。
初学者可能无法立即理解代码与数据之间的关系。他们可能会觉得使用一些有缺陷的代码在生产中是可以的,因为某些不起作用的功能 X 不是特别重要。然而,这会导致代码不断引入数据完整性问题,这些问题一开始并不明显。因此,我们需要注意避免使用有缺陷的代码来保证数据的正确性。
更糟糕的是,只是修复了由这些 bug 引起的明显问题,而没有修复由这些 bug 引起的微妙的数据问题,会导致累积更多的数据问题,将情况推入“无法恢复的级别”标签。
如何保护自己免受这些问题的困扰?你可以简单地使用多层数据完整性验证。不要仅依赖单个用户界面。在前端、后端、网络通信和数据库中创建验证。如果不行,那么至少需要使用数据库级别的约束。
熟悉数据库约束条件,当你向数据库添加列和表时,使用所有的约束条件:
- 对于某一列的 NOT NULL 约束意味着该列的空值将被拒绝。如果你的应用程序假定该字段存在值,则应该在数据库中将该字段的源定义为 not null。
- CHECK 约束是一个自定义表达式,必须评估为 true 才能接受数据。例如,如果你有一个正常的百分比列,其值必须在 0 和 100 之间,你可以使用 CHECK 约束来强制执行这种限制。
- PRIMARY KEY 约束意味着该列的值既不为 null,也是唯一的。你可能在使用这个约束。数据库中的每个表都应该有一个主键来标识其记录。
- FOREIGN KEY 约束意味着该列的值必须与另一个表列中的值相匹配,通常该列是一个主键。
与数据完整性相关的另一个初学者问题是缺乏事务(transaction)思维。如果多个操作需要更改相同的数据源并且它们彼此依赖,则必须将它们包装在一个事务中,以便在其中一个操作失败时进行回滚。
transaction meaning in programming
In programming, a transaction refers to a sequence of operations (such as read, write or update) that are performed as a single unit of work. The objective of using transactions is to ensure consistency and accuracy of data, even in the presence of failures, concurrency, and other unexpected events.
In other words, transactions are used to group database operations together so that they can either all succeed or all fail, ensuring data integrity.
20) Reinventing the Wheel
这是一个棘手的问题。在编程中,有些轮子是值得重新发明的。编程不是一个定义明确的领域。许多事情变化得如此之快,新的需求被引入的速度超过了任何团队所能处理的。
例如,如果你需要一个根据一天中的时间以不同速度旋转的轮子,而不是定制我们都知道和喜爱的轮子,也许我们需要重新思考一下。然而,除非你真的需要一个不用于其典型设计的车轮,否则不要重新发明它。只要使用该死的轮子就可以了。
有时在众多可用的选项中选择所需的“轮子”品牌是具有挑战性的。在购买前进行一些调查和尝试!软件“轮子”的好处是它们中的大多数都是免费且开放的,你可以查看它们的内部设计。你可以轻松地通过其内部设计质量来判断编码“轮子”。如果可以的话,请使用开源“轮子”。开源软件包易于调试和修复,并且可以轻松替换。此外,在公司内部提供支持也更容易。
然而,如果你需要一个“轮子”,不要购买一辆全新的汽车,然后把你正在维护的车放在新车上面。不要为了使用其中的一个或两个函数而包含整个库。最好的例子是 JavaScript 中的 lodash 库。如果你只需要打乱一个数组,只需导入 shuffle 方法,而不是导入整个 lodash 库。
21) Having the Wrong Attitude Towards Code Reviews
编码新手的一个标志是,他们经常把代码审查看成是批评。他们不喜欢它们。他们不欣赏它们。他们甚至害怕它们。
这就是错误的。如果你有这种感觉,你需要马上改变这种态度。把每一次代码审查看成是一个学习机会。欢迎他们,欣赏他们。向他们学习。最重要的是,当你的评审员教会你一些东西时,要感谢他们。
你是一个永远的代码学习者。你需要接受这一点。大多数代码审查会教你一些你不知道的东西。把它们归类为一种学习资源。
有时候,审核者会犯错误,这时你就需要教他们一些东西了。然而,如果这些东西从你的代码中无法明显地看出来,那么也许你的代码需要进行修改。无论如何,如果你需要教你的审核者一些东西,知道教学是作为程序员最有回报的活动之一。
22) Not Using Source Control
新手们有时会低估一个好的源代码/修订控制系统的力量,我指的是Git。
源代码控制不仅仅是将你的更改推送给其他人进行构建和开发。它比那要重要得多。源代码控制是关于清晰的历史记录。代码将会被质疑,代码的进展历史记录将有助于回答一些棘手的问题。这就是我们关心提交信息的原因。它们是另一种通信渠道,使用小的提交帮助未来维护你代码的人了解代码如何达到现在的状态。
尽早尽快地提交,并且使用现在时动词在提交主题行中保持一致。在提交信息中详细说明,但要记住它们应该是摘要。如果你需要在信息中使用多于几行的内容,那可能意味着你的提交太长了。合并!
在你的提交信息中不要包含任何不必要的内容。例如,在提交摘要中不要列出添加、修改或删除的文件。这些列表存在于提交对象本身中,并且可以使用一些 Git 命令参数轻松地显示出来。在摘要信息中只会造成噪音。有些团队喜欢对每个文件更改使用不同的摘要,我认为这是提交过大的另一个迹象。
源代码控制也与可发现性有关。如果你遇到一个函数,开始质疑它的必要性或设计,你可以找到引入该函数的提交,并查看该函数的上下文。提交甚至可以帮助你确定哪些代码引入了程序中的错误。Git甚至提供了一个在提交之间进行二分查找的命令(bisect命令),以定位引入错误的单个有罪提交。
在更改成为正式提交之前,源代码控制也可以以非常好的方式进行利用。使用像暂存更改、选择性修补、重置、隐藏、修改、应用、差异、反转等功能,可以为你的编码流程添加一些丰富的工具。了解它们,学习它们,使用它们,并且欣赏它们。
在我看来,你知道的Git特性越少,你就越是个新手。
23) Over-Using Shared State
这只是关于共享状态是问题的根源,应尽可能避免使用。如果无法避免,使用共享状态的次数应该被绝对最小化。
作为一个初级程序员,我没有意识到的是,我们定义的每一个变量都代表着一种共享状态。它持有的数据可以被与该变量相同范围内的所有元素所改变。范围越全,这种共享状态的跨度就越大。尽量让新的状态包含在小范围内,并确保它们不会向上泄漏。
当多个资源需要在事件循环的同一个tick中一起改变该状态时(在基于事件循环的环境中),共享状态的大问题开始发生。竞争条件会发生。
问题在于:新手可能会尝试使用定时器作为解决共享状态竞争条件问题的变通方法,特别是当他们需要处理数据锁定问题时。这是一个很大的警告信号。不要这样做。要注意这一点,在代码审查中指出这一点,并且绝不接受这种做法。
24) Having the Wrong Attitude About Errors
错误是一件好事。它们意味着你正在取得进展。它们意味着你有一个容易的后续变化,以取得更大的进展。
专家级程序员喜欢错误。新手则讨厌它们。
如果看到这些奇妙的红色小错误信息困扰着你,你需要改变这种态度。你需要把它们看成是帮助者。你需要与它们打交道。你需要利用它们来取得进展。
有些错误需要升级为异常。异常是用户定义的错误,你需要对其进行规划。有些错误需要被忽略。它们需要使应用程序崩溃并使其退出。
25) Not Taking Breaks
你是一个人,你的大脑需要休息。你的身体需要休息。你会经常陷入困境而忘记休息。我把这看成是新手的另一个标志。这不是你可以妥协的事情。在你的工作流程中整合一些东西,迫使你休息。进行大量的短暂休息。离开你的椅子,走一小段路,用它来思考你接下来需要做什么。带着新的目光回到代码中来。