优秀软件开发人员的 5 种解决问题的技能
五种解决问题的技巧
1——学会将大型复杂目标分解为小型、简单的目标。
2——学会平行思考。
3——学会抽象,但不要过度抽象。
4——学会考虑重新使用现有的解决方案
5——学会以数据流的方式思考。
这篇文章最初发表在CoderHood上,题为《优秀软件开发人员的五项解决问题的技能》。CoderHood 是一个致力于软件工程人性化维度的博客。
为了高效工作,软件工程师必须磨练解决问题的能力,掌握一门需要多年学习和实践的复杂技艺。尽管新手可能会觉得难,但理解编程语言、框架甚至算法并非构建软件的难点。
例如,语言很简单,尤其是受C语言启发的命令式语言。C语言只有32个关键字,而且它们的含义很容易掌握:
汽车 | 双倍的 | 整数 |
休息 | 别的 | 长的 |
案件 | 枚举 | 登记 |
字符 | 外部 | 返回 |
常量 | 漂浮 | 短的 |
继续 | 为了 | 签署 |
默认 | 转到 | sizeof |
做 | 如果 | 静止的 |
C 还有 14 条预处理器指令,这些指令也不难理解:
#定义 | #错误 | #进口 |
#elif | #如果 | #包括 |
#别的 | #ifdef | #线 |
#如果定义 | #pragma |
将许多指令串联起来以完成有用的任务要复杂得多。这个概念类似于用英语等人类语言书写。英语中的每个单词都很容易理解,但将许多单词组合在一起以组成结构良好、清晰的句子和段落却并非易事。这需要学习他人的经验并进行大量的练习。
软件开发更多的是解决问题,而不是编写代码或理解技术。要想擅长解决问题,需要大量的实践和经验。软件工程师首先是一个问题解决者,其次才是程序员。计算机语言、框架和算法都是可以通过学习来掌握的工具。然而,解决问题的过程非常复杂,除非通过长期的实践和实际指导,否则很难掌握。
如何像软件工程师一样思考
通过实践,软件工程师能够学会以能够有效解决问题的方式思考。学习是自然而然的,但需要时间。您可以通过识别和磨练解决问题的技能来加速学习进度,从而像经验丰富的软件工程师一样思考。
我将用一个非技术性的问题来解释你必须学习的五项技能。这个问题是:如何为四个咖啡偏好不同的人煮咖啡?
煮咖啡
四个人 --- A、B、C 和 D --- 都想喝咖啡,而且他们有特定的要求。
- A.黑色。
- B. 仅限奶油。
- C. 糖和奶油。
- D. 仅含糖。
你需要找到一种方法或算法,快速且按要求制作一杯咖啡。目标是找到一种方法,让你能够同时为四个人提供尽可能热的咖啡。
五种解决问题的技巧
1——学会将大型复杂目标分解为小型、简单的目标。
在本例中,按要求为四人煮咖啡是总体目标。然而,你不能仅仅把“煮咖啡”当成一个单一的动作。任何会煮咖啡的人都会告诉你,你需要把工作分解成几个小任务。每个任务都要足够简单,以便一次性轻松完成。
以下是根据主要目标制作咖啡所需完成的任务列表:
将工作分解成任务的能力是人类与生俱来的,也是完成大多数任务的必备技能。除了学会如何煮咖啡之外,完成上述清单并不需要任何特殊的学习或练习。换句话说,几乎任何人都可以做到。
然而,像煮咖啡这样的日常实际问题与像软件构建这样更复杂的挑战之间的区别在于,构建软件的步骤很少经过大量排练。要列出构建特定软件所需的任务,需要事先进行过多次练习。这在软件构建中并不常见。这就是为什么经验丰富的软件工程师不会立即编写任务清单。相反,他们会将整个问题分解成更简单的任务。
工程师将需要解决的问题称为“什么”,将需要完成的任务称为“如何”。在咖啡制作的例子中,子目标列表(即“什么”)可以如下制定。
子目标:
- 我们已经准备好杯子等待装满。
- 我们有足够的研磨咖啡。
- 咖啡机里装满了水。
- 我们有一个装满咖啡粉的滤纸。这个子目标需要子目标2的完成。
- 装满咖啡的滤纸在咖啡机里。此子目标需要完成#4。
- 咖啡机正在煮咖啡;需要完成#3 和#5。
- 咖啡机已煮好;需要完成第 6 步,并且经过足够的时间。
- 我们的杯子里装满了咖啡;需要完成#7 和#1。
- 将奶油添加到两个杯子中;需要完成#1。
- 在一个有奶油的杯子中加入了糖,在另一个没有奶油的杯子中加入了糖;需要完成#1 和#9。
- 将每个杯子中的内容物搅拌均匀;需要#8、#9 和#10 才能完成。
请注意,这些不是任务或操作的描述。它们是对需要获得的结果及其依赖关系的描述。
我们可以制作一个图形来直观地展示上面的列表。方框代表子目标,每个方框内的箭头代表实现子目标所需满足的要求:
就冲咖啡来说,直接列出任务清单很诱人。这只适用于那些不需要太多计划就能解决的简单问题。然而,在软件开发中,这种情况很少见。
规划子目标使我们能够从行动中抽象出结果,这是一个关键的区别。例如,子目标4“我们有一个装满研磨咖啡的滤纸”,可能需要去商店购买滤纸。子目标中重要的是需要实现的结果。“什么”是重要的抽象,而“如何”则是一个取决于具体情况的策略选择(例如,您是否拥有滤纸,或者您是否需要购买滤纸?)。
2——学会平行思考。
上面列出的子目标可以按列出的顺序依次完成。然而,这并不是最佳方案。依赖关系可以提示我们哪些任务需要按特定顺序完成,哪些任务不需要。
例如,你可以先开始研磨咖啡豆,然后在电动研磨机工作的同时,往咖啡机里加水。这是因为往咖啡机里加水不需要准备好咖啡粉。而且,研磨咖啡豆只需要启动研磨机,其余时间我们都要等待研磨机完成。如果研磨机是手动的,让你忙个不停,那就另当别论了。
此外,在等待咖啡机冲泡的时候,你可以把杯子从橱柜里拿出来。事实上,直到我们需要往杯子里倒咖啡的时候,杯子才算准备好。
你也可以在咖啡煮好之前就把奶油和糖倒入空杯中。这样做可以省去搅拌的麻烦;如果你在已经加了少量奶油和糖的杯子里倒入大量热咖啡,咖啡成分会自然混合,无需搅拌。另一方面,如果你在装满咖啡的杯子里倒入少量糖或奶油,则需要搅拌才能混合均匀。
重新排序任务
为了最大化并行执行,重新排序任务可以节省时间并省去一个步骤。最终的任务列表(按最佳顺序排列)如下:
- 将咖啡豆放入电动研磨机,然后启动。
- 当研磨机工作时,将水注入咖啡机。
- 研磨完成后,将研磨好的咖啡放入过滤器中。
- 将过滤器放入咖啡机。
- 启动咖啡机。
- 冲泡咖啡时,从柜子里拿出四个杯子。
- 将奶油放入两个咖啡杯中。
- 将糖放入一个不加奶油的咖啡杯中,另一个加奶油的咖啡杯中。
- 等待咖啡冲泡完毕。
- 将咖啡倒入每个咖啡杯中。
或者,表示为序列图(单击可展开):
请注意,任务的排序不会影响子目标列表。它依赖于子目标列表,并受其引导,但不会改变子目标列表。不依赖子目标的任务可以以任何方式重新排序;这可以最大化执行的并行性。
此外,可以先思考子目标,而不必决定如何实现其中任何一个。这进一步将整体问题分解成可以单独解决的小问题。
3——学会抽象,但不要过度抽象。
按照规定制作咖啡的目标过于狭隘和规定性。如果问题发生变化,现在有五个人而不是四个人,有人想在咖啡里加香草糖浆,而另一个人想喝无咖啡因的咖啡,那么子目标和任务列表就必须重新设计。
程序员学会设计解决方案,这样就不必每次参数发生变化时都重新设计。他们学会以某种方式抽象问题,使解决方案能够解决任何与原始问题类似的问题。
抽象咖啡制作
在咖啡的例子中,你可能需要将子目标抽象化,使其能够容纳任意数量的 N 人,而不是固定的 4 人。你或许还想引入一个抽象的概念,用于描述奶油和糖之类的东西。你可以称之为“额外添加物”。这个列表可能会随着时间推移而变化,包括糖浆、肉豆蔻、泡沫、蛋酒、伏特加,或者任何人们喜欢在咖啡中添加的东西。这样的抽象化可以允许在每杯咖啡中添加任意数量的任意数量的额外添加物。
另一个抽象概念可能是咖啡的种类。含咖啡因和无咖啡因是两种常见的咖啡类型,但还可以有更多,比如浓烘焙、中烘焙、轻烘焙、哥伦比亚咖啡、早餐拼配等等。
由于咖啡机每次只能冲泡一种咖啡,你可以看出这会让事情变得多么复杂。如果原始问题中的四个人中,一些人想要含咖啡因的咖啡,而其他人想要脱咖啡因的咖啡,那么同时为他们提供尽可能热的咖啡的要求就会变得很复杂。这时,你就需要抽象出咖啡机数量的概念——如果你有 Y 种咖啡,那么最多有 Y 台咖啡机。此外,如果有 Y 台咖啡机独立运行,操作的并行性也会变得很棘手。这时,开发人员可能需要考虑抽象咖啡师的数量。如果有 K 位咖啡师一起工作,整个过程会发生什么变化?
更多抽象
到那时,你可能会意识到咖啡只是饮料的另一种成分,那么为什么要让它变得独特呢?你可以把咖啡归为额外成分之一,从而消除咖啡这个专门的概念。然而,咖啡需要特殊的处理和准备,所以也许所有额外成分都应该允许通用的特殊处理和准备。例如,如果蛋酒是额外成分之一,那么可以有一整套流程来描述蛋酒的制作过程。但是,到那时,你可以将任何准备工作抽象化,蛋酒就仅仅是你可以创造的另一种产品了。
过度设计
头疼了吗?欢迎来到过度工程的扭曲世界。你有没有发现,如果抽象化太过深入,画面会迅速变得复杂?我们从一种咖啡、四个顾客、固定的附加服务配置和一位咖啡师开始。这很简单也很自然。但如果你试图抽象制作咖啡的方方面面,并考虑到需要服务 N 个人、X 种附加服务、Y 种咖啡、K 位咖啡师……那就变得复杂了,尤其是在你想要优化成本和速度的情况下。你很快就会从制作咖啡过渡到使用任何可能的流程制作任何产品。哎哟。
理想主义的软件工程师会尝试将所有东西抽象化,最终会设计出一台复杂的机器,而这台机器很可能只打算用来为一个四口之家煮咖啡。然而,经验丰富的软件工程师会将事物抽象化,以便解决必要的用例,并可能考虑其他可能的抽象,但这样做只是为了确保当前的决策不会阻碍未来的需求。
平衡
何时停止抽象?这是一门需要经验精炼的艺术。经验法则是,你应该只抽象那些你认为很快就会用到的用例,并尽量不要用狭隘的决策阻碍未来的抽象。
4——学会考虑重新使用现有的解决方案
无需重新发明轮子。
并非所有东西都需要从头开始重新发明。经验丰富的开发人员在开始设计解决方案之前,总是会考虑使用现有的工具。
比如,与其自己煮咖啡,不如去星巴克买一杯,然后带给你的四个朋友?如果你没有杯子、咖啡和咖啡机,去星巴克会是一个更便宜、更快捷的解决方案,尤其是如果你不打算长期每天都煮咖啡的话。
寻找并重新使用现成的解决方案是开发人员需要掌握的解决问题的技能的一部分。
5——学会以数据流的方式思考。
经过多年的实践,经验丰富的开发人员开始思考软件和解决问题的方式,即数据在系统中的流动。在咖啡制作问题中,我们可以将其想象成水、咖啡、杯子和其他物品从源头流向目的地的过程。
水来自水龙头;咖啡来自食品储藏室里的袋子(之前是杂货店),杯子来自橱柜,而其他饮料则来自不同的来源,具体取决于它们是什么。
这些材料(数据)经过一系列处理、转换和混合的步骤。初始状态是存储在任何地方的原始资源,最终状态是一连串盛满咖啡和额外配料的杯子。最终的目的地是某人的嘴里。
从数据流的角度思考,可以将主要目标及其子目标可视化为一系列方框和箭头。方框代表影响系统内物料流动的每个动作,箭头则如同物料流经的管道。
如果您喜欢这篇文章,请保持联系!
- 在 CoderHood 上查看我的所有帖子。别忘了订阅(免费),以便通过电子邮件接收新帖子的通知。
- 加入我在 LinkedIn 上的专业网络。
- 在 Twitter 上关注我。
- 加入我的 Facebook 页面。
- 最后,请在 dev.to 上关注我!