如何去解决一个问题?

566 阅读7分钟

在工作中遇到一个疑难问题时,要怎么去解决这个问题呢,这是一个经常出现的场景。在实际工作的摸索中,自己也逐渐总结出来一套方法论了。

发现问题

在解决一个问题之前,首先要发现这个问题。这里听起来可能是一句废话,但这里想表达的是,你在解决某个事情之前,有没有思考过,这个事情它到底是不是一个问题呢?这里就要引出问题的定义了。

在 《拆解一切问题》中作者提出:

问题其实有两个层次的含义,一个是疑问,一个是难题。

在疑问层级的问题,很容易解决,可能只需要你去百度一下或者咨询下别人就完成的。就像某某人的生辰、天干地支等信息,都是一些有明确答案的。

我们这里说的 “发现问题”,就是指 “难题”层级的问题。当遇到一件事情后,首先需要思考,这是一个“疑问”,还是一个“难题”。如果是“疑问”的话就很简单了,我们直接通过搜索引擎或者相关人员咨询一下即可。不需要花费太多精力去深究根源。如果是 “难题”的话,那就说明我们需要通过一系列方法,来解决它了。

拆解问题

当我们明确了一个问题后,首先要做的是先梳理出这个问题的大致脉络,对这个问题有一个整体的认知。这样子我就可以基于问题脉络来对问题进行一个拆分了。

为什么要将问题做拆分呢,目的就是降低解决的难度。当面对一个难题时,第一感觉就是无从下手,就像是愚公移山故事中的太行和王屋两座大山一样。为了搬走两座“大山”,就需要将每座山的山石一点一点挖一下,一部分一部分的移走,解决问题也是相同思路,将一个大问题拆解成一个个“小块”,然后将每个“小块”解决掉,这样子“大山”就被“搬走”了。

拆解问题也是需要一些技巧的,就像是“庖丁解牛”一般,只有沿着“筋骨”去拆,才能将问题拆解得更合理,这样子解决每个节点时才会更轻易。如果拆分不合理的话,反而会提升解决的难度。

在拆解问题时要秉持一个原则,我把它叫做“合并同类项”。就是相同/相似内容作为同一个节点来看待,将问题拆解成内容不同的多个节点。然后在同个节点下,再尝试拆分,层层递归,把问题拆解成一个个更容易解决的节点,逐个攻破。

解决节点

当把整个难题拆解成一个个“小块”的节点后,这样子解决起来就简单多了。我们只需要针对这个节点的问题,去攻坚击破就行了,这样子就达到了一个化繁为简的目的。

拆分成节点后,每个节点也可以看做是一个个 “问题”,那这样子就又回到了我们开始 “发现问题”这一步骤了。如果是“疑问”,那就直接找“答案”即可;如果是“难题”,那就再继续“拆解问题”。这样子就形成了一个递归循环,当我们把“难题”最终拆解成了每个具体的“疑问”时,那每个“疑问”的答案都是可以直接找到的。这样子,我们就解决了所有的“节点”了。

总结问题

解决了所有的节点后,我们还要回来整理总结下整个问题的“答案”。需要像串珍珠一样,把所有的节点再串起来,站在全局的角度来总结整个问题的解决方案。在每个节点之间,也都是有一条条“细线”连接着的,通过这些“细线”的串联,搞清楚每个节点在起承转合,理解整个流程的管线。这样子才算是把一整个难题解决掉。

完成了串联,理解并解决了整个问题,接下来要做的就是进行文档整理和总结了。很多人可能会觉得,问题既然都已经解决了,为什么还要做整理和总结呢?目的是为了补充和巩固解决问题流程中遗漏的知识点。在解决问题的流程中更多的是在关注解决方案,可能会忽略解决方案的原理和细节。正是通过整理和总结的方式,让自己重新梳理一下整体流程,这样可以加深自己对解决问题过程的印象,并思考过解决过程中遗漏的地方,方便自己再进行相关知识的查缺不漏。

实例场景

偏文字性质的描述性文章,总是让人难以理解,图文并茂或实例演示可以降低理解难度。就像是在数学问题教学中,数形结合会更易于学生们理解。

这里把就把最近在做的 Flutter 的 opencv_dart 库改造来作为实例进行讲解吧。

发现问题

首先咱们来“发现”一下“问题”,毫无疑问, “opencv_dart 库改造” 这件事它一定一个“难题”了。既然需要“改造”,那就不仅仅只要一个“答案”就能完成的了。这一步明确后,咱们来继续下一步“拆解问题”。

拆解问题

在拆解之前,需要先大致了解这个问题的脉络。从 opencv_dart 库的项目结构来说,主要就分为了 opencv_dart、dartcv、dartcv_full 三个仓库了。按照这个仓库划分,咱们就可以先把问题拆解成三个“节点了”。然后我们继续把“节点”进行拆解。

首先来看 dartcv_full 仓库,这个部分比较简单,只包含两部分内容:

  1. CMakeList 编译文件和 GitHub 的 workflow CI/CD 管线
  2. 存放编译后生成的 libopencv 库文件

这样子咱们就能把仓库拆分成两个“节点”了。一个节点是执行编译脚本生成库文件,另一个节点是存在生成的库文件。这样子就把“节点”拆解成最简单的粒度,也就是“疑问”层级了。后面只需要进行“执行脚本”、“保存文件”两个动作就行了。那如果“执行脚本”、“保存文件”对于你来说,仍然是个问题的话,那就可以再继续拆分了。

其次来看看 dartcv 仓库,这个仓库主要是包含了 C Wrapper 文件、iOS 的 podspec 文件、CMake 文件。

  1. Podspec 文件比较简单,主要包含 “源码下载”、“库文件下载”、“功能配置”三部分;
  2. CMake 文件也类似,主要包含 “库文件下载”、“功能配置”两部分;
  3. C Wrapper 文件则主要是对 C++ 函数的二次包装;

这样子我们就能 dartcv 仓库也拆分成三部分了,针对每个部分里面的功能,再进行拆分。

最后来看看 opencv_dart 仓库。这个仓库包含了 packages/dartcv、packages/opencv_core、packages/opencv_dart 三个文件夹。按照文件夹结构,可以继续拆解成三个“节点”。然后再分别看看每个“节点”。

  1. opencv_dart 依赖 opencv_core,以及 iOS、Android 的原生依赖
  2. opencv_core 依赖 dartcv,导出 opencv 相关功能 API,以及 iOS、Android 的原生依赖
  3. dartcv 中主要是 ffigen 文件和 dart 头文件

这样子就把整个 opencv_dart 库完成了拆解了。

下面来整理下整个问题的所有节点:

├── opencv_dart
    ├── opencv_dart
        ├── 依赖 dartcv
        ├── 依赖 iOS
        └── 依赖 Android
    ├── opencv_core
        ├── 依赖 dartcv
        ├── 依赖 iOS
        └── 依赖 Android
    └── dartcv
        ├── FFIGEN
        ├── Header
        └── Export
├── dartcv
    ├── podspec
        ├── 源码下载
        ├── 库文件下载
        └── 功能配置
    ├── cmake
        ├── 源码下载
        ├── 库文件下载
        └── 功能配置
    └── C Wrapper
        ├── core
        ├── imgcodecs
        └── contrib
└── opencv_full
    ├── 执行脚本
    └── 保存文件

从这个树结构中,就可以按照节点来逐个击破了。在解决了所有节点后,完成整个问题流程的梳理,就可以进行最后一步的“总结问题”了。这里把总结文档放出来作为参考:opencv_dart 编译改造方案