避免对着电脑大喊大叫的解决问题技巧

2025-05-24

避免对着电脑大喊大叫的解决问题技巧

最近工作中遇到了一些棘手的代码怪兽。我觉得自己状态不佳,剑也不够锋利,所以感觉自己非常挣扎。

正因如此,我决定提升一些属性(尤其是智力和智慧),以帮助自己应对这场困境。幸运的是,我找到了一本好书:《像程序员一样思考》(作者:V. Anton Spraul)。

像程序员一样思考

这本书解释了我们应该如何有条不紊地处理问题。我才刚开始读,但已经学到了一些解决问题的基础知识,想和大家分享。

通用问题解决技巧

1. 始终有一个计划

在着手解决问题时,如果一开始就敲代码而没有明确的方向,那可不是个好主意。有时我发现自己打字时没有经过深思熟虑,只是为了快速构建一些东西,如果“幸运”的话,最终还是会磕磕绊绊地找到解决方案。这种做法非常糟糕。

书中提到,我们应该始终有一个计划。一开始,这个计划通常包含如何找到问题的解决方案。

我们的计划很可能会在实施过程中需要调整,这很正常。没有计划,我们就会迷失方向,无法评估各种可能的情况和不同的替代方案,从而找到解决方案。

“我一直认为计划毫无用处,但规划却必不可少。”——德怀特·艾森豪威尔将军

我更喜欢将计划视为一张地图,它可以看到通往目的地的所有可能道路(甚至可以在旅途中绘制新的道路)。

本书强调,计划使我们能够设定并实现中期目标。这一点至关重要,因为通过实现这些中期目标,我们会感到自己正在朝着解决方案迈进。由于我们通常会努力奋斗直到找到解决方案,因此感到自己正在取得进步将有助于我们应对绝望。

2. 重述问题

一个乍看起来无法比较的问题,其实可以用其他术语或不同的方式表达。乍一看,我们可能认为这个问题是这样,但重新表述后,我们就会发现它完全不同。

视角的改变

用新的、更简单或更熟悉的术语重新表述问题,可以帮助我们重新定义它并提出不同的解决方案(希望比我们最初想象的更容易实现)。

重述问题可以被视为解决问题的规划步骤之一,如果我们重述问题,我们将在解决问题的道路上取得进展。

3. 划分问题

吃大象不是一口吃完,而是要分多次吃。其实,我从来没有吃过大象,也不会吃,因为它们是我最喜欢的动物之一!不过这个比喻仍然适用。

如果问题过于复杂,可以将其拆分成更小的模块。如果我们需要请求用户输入、处理数据并将其存储在数据库中,可以先构建一个打印在控制台上的简单输入框,然后再在此基础上进行构建。

如果我们需要构建一个复杂的循环,我们可以从一行或几行开始,手动执行循环应该执行的任务,直到我们清楚地知道需要什么。

这是我在编码时使用最多的技术,它可以帮助我在面对复杂问题时保持理智。

4. 从你所了解的开始

将问题分解成几个部分后,我们可以先解决已知问题,然后再集中精力解决其中比较棘手的部分。

我们的问题需要查找唯一值吗?如果我们知道如何Set在 JavaScript 中使用 a ,这或许能帮助我们解决这个问题。

从您所了解的开始,您可以建立信心和动力,实现目标。 - Spraul,V. Anton

5.减少问题

如果问题过于复杂或者无法细分为更小的部分,我们可以通过应用(或删除)约束来降低其复杂性。

用户是否应该能够在我们的待办事项应用中拖放任务?让我们逐步写出实现此功能所需的所有步骤,并(暂时)消除过于复杂的部分。稍后,我们可以回顾遗漏的部分,并寻求帮助,以解决问题的具体方面(而不是整个问题)。

本书作者在这里使用了一个很好的例子:

人们绝不会想被迫说:“这是我的程序,但它运行不起来。为什么呢?” 使用问题简化技术,人们可以精准地指出需要的帮助,例如说:“这是我编写的代码。如你所见,我知道如何计算两个三维坐标之间的距离,也知道如何检查一个距离是否小于另一个距离。但我似乎找不到一个通用的解决方案来找到距离最小的坐标对。” - Spraul, V. Anton

6.寻找类比

我们经常以不同的方式一遍又一遍地解决同一个问题。

我们应该注意我们面临的问题和我们过去解决的其他问题之间的相似之处。

如果问题是全新的,那就太好了,因为这是一个很好的机会,可以把它放进我们的“已解决问题的饼干罐”中以供将来使用。

7.实验

如果一切看起来都很黑暗,而且我们不知道为什么程序的行为就像它有生命一样,那么进行实验可以帮助我们。

我通常用console.log“探索”的方法来了解发生了什么。我们可以做一些小调整,看看输出结果会如何变化。然后提出假设并进行测试。

区分“实验”“猜测”很重要。实验是一个可控的过程:我们收集能够帮助我们解决问题的信息。猜测则是漫无目的地敲代码,然后祈祷得到最好的结果。

8. 不要沮丧

最后一项技术与其说是一项技术,不如说是一种建议:不要感到沮丧。

我必须承认,我曾多次感到沮丧,而且我可以向你保证,这对我解决问题没有一点帮助。

你内心的愤怒如何帮助你解决问题?你为什么生气?或者更好的问题是:你为什么选择生气?

斯多葛哲学的两条格言可以为我们提供帮助:

“我们在想象中遭受的痛苦比在现实中遭受的痛苦更多。”——塞涅卡

和:

人生的首要任务很简单:辨别和区分事物,这样我才能清楚地告诉自己,哪些是我无法掌控的外在因素,哪些与我实际掌控的选择有关。那么,我该去哪里寻找善恶呢?不是去寻找那些无法掌控的外在因素,而是去寻找我内心深处那些属于我自己的选择…… ”——爱比克泰德,《论语》,2.5.4-5

我们必须学会控制自己如何应对挫折。作为开发人员,我们每天都很可能会遇到代码中的错误或需要实现的复杂功能,其中很大一部分问题很难解决。因此,最好不要因此而感到沮丧,而是将其视为我们工作的一个“功能”。

我经常会想起这个问题

你想吃什么屎三明治?因为最终我们都会吃到。——马克·曼森

然后我意识到:“我已经选择了这场斗争,所以我不应该为此而生气。”

可以帮助我们摆脱沮丧的时刻的方法是再次重复这些步骤:制定新计划,以不同的方式重述问题,创建不同的问题划分和变更等。

练习时间

现在,让我们把这些概念付诸实践。为了方便起见,我把书中提出的一个问题从 C++ 改编成了 JavaScript。

顺便说一句,您可以从我的存储库中获取源代码(我计划在阅读本书的过程中不断添加新问题):think-like-a-programmer-book-problems

Write a program that uses a single console.log statement to produce a pattern of hash symbols shaped like half of a perfect 5 x 5 square (or a right triangle):

#####
####
###
##
#

Spraul, V. Anton. Think Like a Programmer (p. 26). Adapted from C++ to JavaScript by Damian Demasi.
Enter fullscreen mode Exit fullscreen mode

计划

我们要把问题分解成更小的部分,然后逐一解决。我们会在这个过程中寻找类比。最终,我们会把所有问题整合在一起。

重述问题

深入分析问题后,我们发现需要打印一个模式,并且该模式在每一行都会改变。我不知道您怎么想,但这对我来说意味着“迭代”,所以我们将使用某种循环来解决这个问题。

我们的重述可能是这样的:“编写一个循环,打印一行井号,其中每一行都比前一行少一个井号,第一行有 5 个符号。”

划分问题

我们可以这样划分这个问题:

  1. 打印一行 5 个井号
  2. 打印 5 行,每行 5 个井号
  3. 找到一种方法,随着计数的增加而减少数字
  4. 将第 3 点应用于第 2 点

从你所了解的开始

让我们从简单的部分开始:打印 5 个哈希值:

    let halfSquare = '';
    for (let i = 1; i <= 5; i++) {
        halfSquare += '#';
    }
    console.log(halfSquare);
Enter fullscreen mode Exit fullscreen mode

输出:

#####
Enter fullscreen mode Exit fullscreen mode

太棒了!虽然不多,但这是真功夫😅。

减少问题

现在,我们不再打印哈希值数量递减的 5 行代码,而是打印哈希值数量相同的 5 行代码。这样做可以简化问题,降低其复杂性和约束条件。

let halfSquare = '';
    for (let i = 1; i <= 5; i++) {
        for (let j = 1; j <= 5; j++) {
            halfSquare += '#';
        }
        halfSquare += '\n';
    }
    console.log(halfSquare);
Enter fullscreen mode Exit fullscreen mode

输出:

#####
#####
#####
#####
#####
Enter fullscreen mode Exit fullscreen mode

寻找类比

在上一段代码中,我们使用了已知的方法(循环)来重复执行哈希值 5 次,从而形成了一个嵌套for循环。这就是我们的类比。

实验

现在我们需要找到一种方法,让一个值随着循环的增加而减少。在书中,这被称为“通过递增计数来减少计数”。

让我们从较简单的部分开始尝试找到这种行为:向上计数。

for (let i = 1; i <= 5; i++) {
    console.log(i);
}
Enter fullscreen mode Exit fullscreen mode

输出:

1
2
3
4
5
Enter fullscreen mode Exit fullscreen mode

在查看预期的最终结果(哈希三角形)并将其与我们的实验进行比较后,我们可以将这些数字视为每行需要减去的哈希数加 1:

线 结果
第一行: 5 个哈希值 - 1 + 1 = 5 个哈希值
第二行: 5 个哈希值 - 2 + 1 = 4 个哈希值
第三行: 5 个哈希值 - 3 + 1 = 3 个哈希值
第四行: 5 个哈希值 - 4 + 1 = 2 个哈希值
第五行: 5 个哈希值 - 5 + 1 = 1 个哈希值

如果我们使用 6 个哈希值,我们就可以去掉“+ 1”部分:

线 结果
第一行: 6 个哈希值 - 1 = 5 个哈希值
第二行: 6 个哈希值 - 2 = 4 个哈希值
第三行: 6 个哈希值 - 3 = 3 个哈希值
第四行: 6 个哈希值 - 4 = 2 个哈希值
第五行: 6 个哈希值 - 5 = 1 个哈希值

因此,由于 6 对于我们的目的来说是一个重要的数字,并且减去的数字看起来像是 for 循环迭代并打印出新行,让我们看看当我们从 6 中减去当前迭代次数时会发生什么:

for (let i = 1; i <= 5; i++) {
    console.log(6 - i);
}
Enter fullscreen mode Exit fullscreen mode

输出:

5
4
3
2
1
Enter fullscreen mode Exit fullscreen mode

这看起来不错!

因此,由于第一行需要有 5 个哈希值,迭代应该从 1 到 6 - 1 = 5。

let hashLine = '';
for (let i = 1; i <= 6 - 1; i++) {
    hashLine += '#';
}
console.log(hashLine);
Enter fullscreen mode Exit fullscreen mode

输出:

#####
Enter fullscreen mode Exit fullscreen mode

下一行,为了构建循环,我们需要将条件改为i <= 6 - 1条件i <= 6 - 2。这个模式指向一个在每次迭代时递增值的循环,而我们已经有了这样的循环:它是外层for循环。

将这些发现与我们已有的知识结合起来,我们就可以得出问题的结论:

let halfSquare = '';
for (let i = 1; i <= 5; i++) {
    for (let j = 1; j <= 6 - i; j++) {
        halfSquare += '#';
    }
    halfSquare += '\n';
}
console.log(halfSquare);
Enter fullscreen mode Exit fullscreen mode

输出:

#####
####
###
##
#
Enter fullscreen mode Exit fullscreen mode

这就是奋斗、思考和实验的成果。

总结

对我来说,一次性运用所有这些技巧并不容易。我发现我解决问题的方法虽然“我的方式”,但这往往并非最合适的方法。与大多数新习惯一样,实施特定的流程会遇到一定的阻力。这就是为什么我选择一步一步地实施这些解决问题的技巧(而且我注意到,我在这里完全采用了“初始”的方法,将问题分解成更小的部分,作为实施这些解决问题技巧的技巧🤯)。


Notion 模板、追踪器和路线图

🙋‍♂️ 嘘!在您离开之前,我有一些东西要与您分享。如果您订阅了我的新闻通讯,那么您已经知道这一点;如果您还没有订阅,我想与您分享以下 3 个免费资源:

👍 我的Notion 模板包含超过 440 页的 Web 开发内容,因此您可以使用 Notion 来组织编程主题

👍 我的HTML 学习进度跟踪器和路线图概念模板,因此您可以跟踪学习 HTML 概念的进度(并复习它们)。

👍 我的CSS 学习进度跟踪器和路线图概念模板与以前相同,但使用 CSS。

它们完全免费下载和使用,但我总是愿意接受捐赠(我需要买咖啡来将其转换成代码😅)。


🗞️新闻通讯 - 如果您想了解我的最新文章和有趣的软件开发内容,请订阅我的新闻通讯

🐦TWITTER——在Twitter上关注

给我买杯咖啡

文章来源:https://dev.to/colocodes/problem-solving-techniques-to-avoid-yelling-at-your-computer-41e9
PREV
React:类组件与函数组件
NEXT
衡量你在 Web 开发中的进度:为什么它很重要以及如何做到