摘要:但是,在实际开发时仍然障碍重重。我就曾经接受了一个开发任务,就是做一个像刽子手一样的游戏,但是当我看完需求中所有的规则时,才意识到要做的应该是邪恶的刽子手这是一个深坑。边界问题仅在极端最大或最小值参数的情况下发生的问题或状况。
翻译:疯狂的技术宅
作者:Valinda Chan
英文标题:10 Steps to Solving a Programming Problem
英文链接:https://codeburst.io/10-steps...
本文首发微信公众号:充实的脑洞
我总是听到刚入行不久的程序员这样说:知道自己要实现什么功能,同时处理逻辑和基本语法也都明白,但是就不知道该怎么写代码。如果把别人的的代码给你看,或者有人给你你一些指导,或许你能明白其中的思路。但是,在实际开发时仍然障碍重重。即使语法或逻辑都明白,也很难自己的想法转化为代码。在本文中我将会告诉大家我自己是怎么做的,还有一些解决典型问题的方法,希望能够对大家有所帮助。
1. 把给你的需求反复阅读三遍以上(或者直到看吐了为止)如果不能理解给你的需求,也就没有办法实现它。 实际的需求和你认为的需求有很大的区别。假设有一个需求,当你阅读前几行时非常容易,但是接下来你就会假设其余部分与你曾经看到过的东西类似。比如你要做一个像“刽子手”一样的游戏,一定要通读它所有的规则,即便你曾经玩过这个游戏。我就曾经接受了一个开发任务,就是做一个像“刽子手”一样的游戏,但是当我看完需求中所有的规则时,才意识到要做的应该是“邪恶的刽子手”(这是一个深坑!)。
有时我会试着向一个朋友解释某个需求,看她对我解释的理解是否和我的需求一致。如果你不想在开发了一半的时候才发现自己误解了这个需求,那么在开始的时候多花点时间是值得的。你对问题越了解,就越容易解决它。
假设我们要创建一个简单的函数selectEvenNumbers,这个函数的参数一个存放整数的数组,返回值evenNumbers 是一个只存在偶数的数组。如果没有偶数,那么久返回一个空数组。
function selectEvenNumbers() { // your code here }
以下是我思考的问题:
计算机怎样去判断是不是偶数? 检查该数是否能被2整除
我传给这个函数的参数是什么? 一个数组
数组中保存的内容是什么? 一个或多个整数
数组中元素的数据类型是什么? 整数
这个函数的目的是什么?之行结束后要返回什么? 目标是得到所有偶数,并把它们保存到数组中返回。如果没有偶数,就返回一个空数组。
2.至少使用三组模拟数据进行手动模拟找一张草稿纸,人工解决这个问题。至少考虑三组模拟数据,注意要考虑到极端情况和边界问题。
极端情况:在正常操作参数范围之外产生的问题或情况。或者是多个变量或条件都在其指定范围内,但是都同时处于极端的水平的情况。
边界问题:仅在极端(最大或最小值)参数的情况下发生的问题或状况。
举个例子,下面是一些要使用的样本数据集:
[1] [1, 2] [1, 2, 3, 4, 5, 6] [-200.25] [-800.1, 2000, 3.1, -1000.25, 42, 600]
在刚开始的时候,很容易忽略这些步骤。
因为你的大脑对于偶数的概念十分清楚,所以只要看到一组数据,就可以从中找到2,4,6这样的数字,几乎意识不到自己的大脑是怎么思考的。可以尝试更多的数据,它会改变你大脑通过观察来解决问题的习惯。这有利于帮你实现真正有效的算法。
我们来看第一个数组:[1]
查看数组 [1] 中唯一的元素
判断是否为偶数:嗯,并不是
确定这个数组中没有其他的元素了
确定在这个数组中没有偶数
返回一个空数组
接下来看第二个数组:[1, 2]
先看数组[1, 2]中的第一个元素
数字是1
判断是否为偶数:不是
看数组中的下一个元素
数字是2
判断是否为偶数:是的
创建一个数组evenNumbers ,并把数字2添加到其中
确定数组中没有其他元素了
返回的数组evennumbers 是 [ 2 ]
再多看几遍。请注意处理[1]的步骤和[ 1, 2 ]略有不同。这就是为什么我要尝试多种不同的组合。在这些数据中,有的只存在一个元素;有些是浮点数,而不是整数;有些是一个元素中有多个数字,有些是负数。
3.简化并优化你的步骤寻找模式,找到概括问题的方法,看看能不能减少无用或重复的步骤。
创建一个函数selectEvenNumbers
创建一个保存数据的空数组evenNumbers
检查数组[1, 2]中的每个元素
找到第一个元素
判断它是否可以被2整除。如果是,就加到evennumbers中
找到下一个元素
重复步骤4
重复步骤5和步骤4,一直到数组中没有任何其他元素
返回数组evenNumbers ,不管它是不是空数组
这个方法可能会让你想起数学归纳法:
证明当 n = 1, n = 2, ... 的情况下成立
假设当 n = k 时成立
证明当 n = k + 1 时成立
4. 写出伪代码
伪代码
我们已经有了处理步骤,接下来就要编写出伪代码了,伪代码可以转换成真实的代码,这有助于定义代码的结构,并使编码变得更加容易。您可以在纸上写伪代码,也可以在代码编辑器中用注释的形式来写。如果你在电脑上做会分心,我建议你用纸和笔来完成。
通常伪代码并没有什么特定的规则,不过有的时候我可能会使用自己熟悉的某种语言的语法。所以不要被语法所纠缠。把精力放在逻辑和步骤上。
对于我们所面对的问题,可以有很多不同的方法。 例如,您可以使用filter,但是为了尽可能简单地说明前面的例子,我们现在将使用一个基本的for循环(但是当我们重构代码时,将会使用filter )。
下面是一个伪代码的例子,它有比较多的语言描述:
function selectEvenNumbers create an array evenNumbers and set that equal to an empty array for each element in that array see if that element is even if element is even (if there is a remainder when divided by 2) add to that to the array evenNumbers return evenNumbers
下面这段伪代码比较简洁:
function selectEvenNumbers evenNumbers = [] for i = 0 to i = length of evenNumbers if (element % 2 === 0) add to that to the array evenNumbers return evenNumbers
只要你能把它逐行地写出来,并且理解每一行的逻辑,用哪种方式并不重要。
最后还要回顾一下,确保自己没有走偏。
5. 把伪代码翻译成真正的代码并进行调试当伪代码被准备好之后,就可以把每一行伪代码用自己正在使用的语言实现了。在这个例子中我们将使用JavaScript。
如果你把伪代码写在了纸上,那么就把它作为注释输入到自己的代码编辑器中,之后再替换为代码中的每一行。
然后我调用这个函数,并给它一些我们之前使用过的样本数据集。可以用它们来检查代码执行的结果是否和预期一致。还可以编写测试用例来检查实际的输出是否符合预期。
selectEvenNumbers([1]) selectEvenNumbers([1, 2]) selectEvenNumbers([1, 2, 3, 4, 5, 6]) selectEvenNumbers([-200.25]) selectEvenNumbers([-800.1, 2000, 3.1, -1000.25, 42, 600])
我通常在每个变量或者每一行后面都使用console.log()。这将会帮助我检查变量值和代码是否符合预期。通过这种方法,可以很容易的发现代码中的问题。下面的例子是我在运行时会检查哪东西。在我所有的代码中都会这样做。
function selectEvenNumbers(arrayofNumbers) { let evenNumbers = [] console.log(evenNumbers) // I remove this after checking output console.log(arrayofNumbers) // I remove this after checking output }
最后使每一行伪代码都有对应的真实代码。//后面是伪代码,其它部分是用JavaScript实现的真实代码。
// function selectEvenNumbers function selectEvenNumbers(arrayofNumbers) { // evenNumbers = [] let evenNumbers = [] // for i = 0 to i = length of evenNumbers for (var i = 0; i < arrayofNumbers.length; i++) { // if (element % 2 === 0) if (arrayofNumbers[i] % 2 === 0) { // add to that to the array evenNumbers evenNumbers.push(arrayofNumbers[i]) } } // return evenNumbers return evenNumbers }
为了避免混淆,我去掉了伪代码。
function selectEvenNumbers(arrayofNumbers) { let evenNumbers = [] for (var i = 0; i < arrayofNumbers.length; i++) { if (arrayofNumbers[i] % 2 === 0) { evenNumbers.push(arrayofNumbers[i]) } } return evenNumbers }
有时候,初级开发人员会被语法所困扰,导致难以继续前进。记住:语法会随着时间的推移而逐渐熟练起来。在编码的时候因为语法问题去翻参考材料并不丢人。
6. 简化并优化你的代码你可能已经注意到,简化和优化是经常性的话题。
“简单性是可靠性的先决条件。”
——荷兰计算机科学家Edsger W. Dijkstra,计算科学研究领域的先驱
在这个例子中,优化的方法之一就是通过使用filter 返回一个新数组来过滤原来数组中的项。这样我们就不用再去定义另外一个变量evenNumbers,因为filter 将返回一个新的数组,其中包含与过滤器匹配的元素并复制一个新的数组。 这样就不会改变原来的数组。我们也不用使用for循环来进行遍历。过滤器将会遍历每个项,如果在数组中的元素符合条件就返回true,否则就返回false将其忽略。
function selectEvenNumbers(arrayofNumbers) { let evenNumbers = arrayofNumbers.filter(n => n % 2 === 0) return evenNumbers }
简化和优化代码可能需要迭代多次,以确定进一步简化和优化代码的方法。
这里有一些需要牢记的问题:
简化和优化的目标是什么?目标会被你的团队风格或个人喜好所左右。是尽可能地压缩代码还是使代码更易阅读? 如果是后者,你可能会用多带带的代码行来定义变量或计算某些变量,而不是试图在一行中做这些事。
怎样做才能使代码容易阅读?
还有没有多余的步骤可以去掉?
有没有变量或函数始终没有被用到过?
是不是存在重复的步骤?看能不能在另外一个函数中定义它们。
有没有更好的处理边界问题的办法?
7.调试编写程序的本意是为了供人阅读,只是顺便让计算机能够执行它。
——“计算机程序的结构与解释”作者Gerald Jay Sussman和Hal Abelson
这一步应该贯穿始终。在调试的过程中,您会很容易发现逻辑上的错误或漏洞。要充分利用集成开发环境(IDE)和调试器。当我遇到bug时,会逐行跟踪代码,来检查是否存在不符合预期地方。以下是我使用的一些技巧:
实用控制台可以查看错误信息,有时候它会告诉我需要检查哪一行,这就给了我一个大概的思路:从哪里开始。尽管有时候问题并不在提示给出的那一行。
注释掉某些代码块或者行,并输出调试信息,来检查剩余的代码是否能正常运行。可以根据实际情况对代码进行注释。
使用不同的测试数据,看看代码是否仍然可以工作。以此来检查是否存在我没有想到的情况。
如果想要尝试另外一种完全不同的方法,可以保存不同版本的文件。我可不想在恢复原来代码的时候后悔莫及!
8.添加有效的注释最有效的调试工具是仔细的思考,再加上输出清晰的调试信息。
——普林斯顿大学计算机科学教授Brian W. Kernighan
很有可能在一个月之后你会忘记自己的代码都是什么意思,使用你代码的其他人可能也不知道。这就是为什么要添加有效的注释的原因:为了让你在回头看这些代码时节省时间。
不要这样去注释:
// 这是一个数组,并且遍历它
// 这是一个变量
我试着做一些简要、高级的注释,在出问题的时候可以帮我搞明白这段代码到底是起到什么作用。尤其是在处理更复杂的问题时非常有用。它有助于理解某个特定功能在做什么以及为什么这样做。通过使用清晰的变量名、函数名和注释,你(和其他人)应该能够理解:
这段代码是做什么用的?
它是怎样工作的?
9.通过代码评审获得反馈从你的团队成员、教授和其他开发者那里得到反馈。检查堆栈是否会溢出。看别人如何解决这个问题并从中吸取教训。有时解决问题的方法有好几种。把它们都找出来,这样你进步会很快。
10.实践,不停的实践别在意你写出良好风格的代码会花费多少时间,因为一旦你写出了糟糕的代码,那将会更慢。
——Bob Martin,软件工程师,敏捷宣言的合著者之一
哪怕是经验再丰富的开发人员也总是在不停的实践与学习。如果你得到了有用的建议,那么就要去照着做。重复做相同或类似的事情,不停的鞭策自己。随着一个又一个的问题的解决,最终你会成长起来。在每一次成功之后,一定要对问题进行回顾。记住,编程和任何事一样,会随时间的推移变得更加简单、更加自然而然。
欢迎扫描二维码关注微信公众号:充实的脑洞,第一时间推送我翻译的国外最新技术文章。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87282.html
摘要:前端日报精选变量声明与赋值值传递浅拷贝与深拷贝详解浅谈自适应学习比你想象的要简单常见排序算法之实现世界万物诞生记中文深入理解笔记与异步编程译不可变和中的知乎专栏译怎样避免开发时的深坑疯狂的技术宅在翻译网格布局掘金详解改变模糊度亮 2017-08-15 前端日报 精选 ES6 变量声明与赋值:值传递、浅拷贝与深拷贝详解浅谈web自适应学习 React.js 比你想象的要简单常见排序算法之...
摘要:可能部分限制已经不再适用。当移动端的浏览器加载了到的图片数据后,就会停止加载其他图片,甚至浏览器还会崩溃。大多数网站都不会受到这条限制的影响,因为保持页面合理的大小通常是一种很聪明的做法。替换掉属性后,旧的图片数据最终得到了释放。 原文作者:Thijs van der Vossen 本文翻译自《How to work around the Mobile Safari image res...
摘要:所有派生状态导致的问题无异于两种无条件的根据来更新无论和是否匹配来更新。派生状态最常见的错误就是将这两者混和在一起。因此通常被用于性能优化而不是来判断派生状态的正确性。我们可以使用派生状态来存储过滤列表这种方式避免了重新计算。 原文链接:https://reactjs.org/blog/2018... 翻译这篇文章的起因是因为在一次需求迭代中错误的使用了getDerivedState...
摘要:关键字会实例化一个新的对象实例,并在执行构造函数时将指向该实例。原文链接译是什么对象的内部工作原理 原文链接:What is this? The Inner Workings of JavaScript Objects (需要梯子) 原文作者:Eric Elliott 译文永久链接:【译】什么是 this?JavaScript 对象的内部工作原理 译者:士心 翻译目的:函数动...
阅读 505·2021-08-31 09:45
阅读 1607·2021-08-11 11:19
阅读 849·2019-08-30 15:55
阅读 792·2019-08-30 10:52
阅读 2814·2019-08-29 13:11
阅读 2877·2019-08-23 17:08
阅读 2800·2019-08-23 15:11
阅读 3030·2019-08-23 14:33