翻译:疯狂的技术宅
做者:Valinda Chan
英文标题:10 Steps to Solving a Programming Problem
英文连接:https://codeburst.io/10-steps...
本文首发微信公众号:充实的脑洞javascript
我老是听到刚入行不久的程序员这样说:知道本身要实现什么功能,同时处理逻辑和基本语法也都明白,可是就不知道该怎么写代码。若是把别人的的代码给你看,或者有人给你你一些指导,或许你能明白其中的思路。可是,在实际开发时仍然障碍重重。即便语法或逻辑都明白,也很难本身的想法转化为代码。在本文中我将会告诉你们我本身是怎么作的,还有一些解决典型问题的方法,但愿可以对你们有所帮助。java
若是不能理解给你的需求,也就没有办法实现它。 实际的需求和你认为的需求有很大的区别。假设有一个需求,当你阅读前几行时很是容易,可是接下来你就会假设其他部分与你曾经看到过的东西相似。好比你要作一个像“刽子手”同样的游戏,必定要通读它全部的规则,即使你曾经玩过这个游戏。我就曾经接受了一个开发任务,就是作一个像“刽子手”同样的游戏,可是当我看完需求中全部的规则时,才意识到要作的应该是“邪恶的刽子手”(这是一个深坑!)。程序员
有时我会试着向一个朋友解释某个需求,看她对我解释的理解是否和个人需求一致。若是你不想在开发了一半的时候才发现本身误解了这个需求,那么在开始的时候多花点时间是值得的。你对问题越了解,就越容易解决它。算法
假设咱们要建立一个简单的函数selectEvenNumbers
,这个函数的参数一个存放整数的数组,返回值evenNumbers
是一个只存在偶数的数组。若是没有偶数,那么久返回一个空数组。编程
function selectEvenNumbers() { // your code here }
如下是我思考的问题:数组
计算机怎样去判断是否是偶数? 检查该数是否能被2整除微信
我传给这个函数的参数是什么? 一个数组编辑器
数组中保存的内容是什么? 一个或多个整数ide
数组中元素的数据类型是什么? 整数函数
这个函数的目的是什么?之行结束后要返回什么? 目标是获得全部偶数,并把它们保存到数组中返回。若是没有偶数,就返回一个空数组。
找一张草稿纸,人工解决这个问题。至少考虑三组模拟数据,注意要考虑到极端状况和边界问题。
极端状况:在正常操做参数范围以外产生的问题或状况。或者是多个变量或条件都在其指定范围内,可是都同时处于极端的水平的状况。
边界问题:仅在极端(最大或最小值)参数的状况下发生的问题或情况。
举个例子,下面是一些要使用的样本数据集:
[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 ]
略有不一样。这就是为何我要尝试多种不一样的组合。在这些数据中,有的只存在一个元素;有些是浮点数,而不是整数;有些是一个元素中有多个数字,有些是负数。
寻找模式,找到归纳问题的方法,看看能不能减小无用或重复的步骤。
建立一个函数selectEvenNumbers
建立一个保存数据的空数组evenNumbers
检查数组[1, 2]
中的每一个元素
找到第一个元素
判断它是否能够被2
整除。若是是,就加到evennumbers
中
找到下一个元素
重复步骤4
重复步骤5和步骤4,一直到数组中没有任何其余元素
返回数组evenNumbers
,无论它是否是空数组
这个方法可能会让你想起数学概括法:
证实当 n = 1
, n = 2
, ...
的状况下成立
假设当 n = k
时成立
证实当 n = k + 1
时成立
伪代码
咱们已经有了处理步骤,接下来就要编写出伪代码了,伪代码能够转换成真实的代码,这有助于定义代码的结构,并使编码变得更加容易。您能够在纸上写伪代码,也能够在代码编辑器中用注释的形式来写。若是你在电脑上作会分心,我建议你用纸和笔来完成。
一般伪代码并无什么特定的规则,不过有的时候我可能会使用本身熟悉的某种语言的语法。因此不要被语法所纠缠。把精力放在逻辑和步骤上。
对于咱们所面对的问题,能够有不少不一样的方法。 例如,您可使用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
只要你能把它逐行地写出来,而且理解每一行的逻辑,用哪一种方式并不重要。
最后还要回顾一下,确保本身没有走偏。
当伪代码被准备好以后,就能够把每一行伪代码用本身正在使用的语言实现了。在这个例子中咱们将使用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 }
有时候,初级开发人员会被语法所困扰,致使难以继续前进。记住:语法会随着时间的推移而逐渐熟练起来。在编码的时候由于语法问题去翻参考材料并不丢人。
你可能已经注意到,简化和优化是常常性的话题。
“简单性是可靠性的先决条件。”
——荷兰计算机科学家Edsger W. Dijkstra,计算科学研究领域的先驱
在这个例子中,优化的方法之一就是经过使用filter
返回一个新数组来过滤原来数组中的项。这样咱们就不用再去定义另一个变量evenNumbers
,由于filter
将返回一个新的数组,其中包含与过滤器匹配的元素并复制一个新的数组。 这样就不会改变原来的数组。咱们也不用使用for
循环来进行遍历。过滤器将会遍历每一个项,若是在数组中的元素符合条件就返回true,不然就返回false
将其忽略。
function selectEvenNumbers(arrayofNumbers) { let evenNumbers = arrayofNumbers.filter(n => n % 2 === 0) return evenNumbers }
简化和优化代码可能须要迭代屡次,以肯定进一步简化和优化代码的方法。
这里有一些须要牢记的问题:
简化和优化的目标是什么?目标会被你的团队风格或我的喜爱所左右。是尽量地压缩代码仍是使代码更易阅读? 若是是后者,你可能会用单独的代码行来定义变量或计算某些变量,而不是试图在一行中作这些事。
怎样作才能使代码容易阅读?
还有没有多余的步骤能够去掉?
有没有变量或函数始终没有被用到过?
是否是存在重复的步骤?看能不能在另一个函数中定义它们。
有没有更好的处理边界问题的办法?
编写程序的本意是为了供人阅读,只是顺便让计算机可以执行它。
——“计算机程序的结构与解释”做者Gerald Jay Sussman和Hal Abelson
这一步应该贯穿始终。在调试的过程当中,您会很容易发现逻辑上的错误或漏洞。要充分利用集成开发环境(IDE)和调试器。当我遇到bug时,会逐行跟踪代码,来检查是否存在不符合预期地方。如下是我使用的一些技巧:
实用控制台能够查看错误信息,有时候它会告诉我须要检查哪一行,这就给了我一个大概的思路:从哪里开始。尽管有时候问题并不在提示给出的那一行。
注释掉某些代码块或者行,并输出调试信息,来检查剩余的代码是否能正常运行。能够根据实际状况对代码进行注释。
使用不一样的测试数据,看看代码是否仍然能够工做。以此来检查是否存在我没有想到的状况。
若是想要尝试另一种彻底不一样的方法,能够保存不一样版本的文件。我可不想在恢复原来代码的时候后悔莫及!
最有效的调试工具是仔细的思考,再加上输出清晰的调试信息。
——普林斯顿大学计算机科学教授Brian W. Kernighan
颇有可能在一个月以后你会忘记本身的代码都是什么意思,使用你代码的其余人可能也不知道。这就是为何要添加有效的注释的缘由:为了让你在回头看这些代码时节省时间。
不要这样去注释:
// 这是一个数组,而且遍历它
// 这是一个变量
我试着作一些简要、高级的注释,在出问题的时候能够帮我搞明白这段代码究竟是起到什么做用。尤为是在处理更复杂的问题时很是有用。它有助于理解某个特定功能在作什么以及为何这样作。经过使用清晰的变量名、函数名和注释,你(和其余人)应该可以理解:
这段代码是作什么用的?
它是怎样工做的?
从你的团队成员、教授和其余开发者那里获得反馈。检查堆栈是否会溢出。看别人如何解决这个问题并从中吸收教训。有时解决问题的方法有好几种。把它们都找出来,这样你进步会很快。
别在乎你写出良好风格的代码会花费多少时间,由于一旦你写出了糟糕的代码,那将会更慢。
——Bob Martin,软件工程师,敏捷宣言的合著者之一
哪怕是经验再丰富的开发人员也老是在不停的实践与学习。若是你获得了有用的建议,那么就要去照着作。重复作相同或相似的事情,不停的鞭策本身。随着一个又一个的问题的解决,最终你会成长起来。在每一次成功以后,必定要对问题进行回顾。记住,编程和任何事同样,会随时间的推移变得更加简单、更加天然而然。
欢迎扫描二维码关注微信公众号:充实的脑洞,第一时间推送我翻译的国外最新技术文章。