一、流程的重要性:
编程的本质是四要素:流程、逻辑、数据、方法。
其中流程负责实现目标工作的调度,逻辑负责处理实现目标的过程中可能产生的各种条件分支,数据是编程的灵魂,计算机从诞生之日起就是为了处理数据而存在的,而数据的形态与处理方法甚至可以单独称为一门科学,这部分三言两语不好说明白,最后是处理数据的“方法”,一般我们称之为函数或命令,通过调用这些命令最终实现我们的目标。
通常我们在学习编程的过程中,最先接触的便是逻辑和命令,数据仅接触最简单的变量和传递,并在之后逐步学习的过程中,加深对命令和逻辑的理解,但流程与数据的处理能力却很容易被忽略。
- TracePrint "Hello World!"
- // 这段代码体现了命令 TracePrinnt
- // 体现了数据,立即值 "Hello World!"
复制代码
但事实上,复杂的流程处理能力和复杂的数据处理能力才是一个程序员的真实功力体现,其中又以数据处理最为复杂难懂。
这其中包含的道理很简单,指令仅需要记忆力和查阅文档的能力就可以掌握,逻辑的写法缺乏变化,因而这两种技术较为容易掌握。而流程大可到程序架构,小可到标准工作流程实现,虽五音,五音之合,不可胜听也;数据则是重剑无锋,大巧不工,分为不同的量级和形态(万级、百万级、千万级、亿级以上)(列、表、树、图……),不同的类型管理方式和优化方法都有所不同。
本期教程我们就主讲在脚本级别的小项目里,能用到哪些流程技巧,来简化我们的程序复杂度、提升可维护性、稳定性,从而增加我们对自己代码的掌控能力,也就是所谓的内功,我们需要学会使用大量的命令帮助我们实现各种各样的功能,但也需要学会流程控制能力来把各种功能融会贯通,使 1 + 1 大于 2。
我在学习编程和制作教学的过程中,看到过许多教学,但多数教学都只是教会你使用一些命令来完成特定的功能,当你实战项目的时候,往往会发现根本不知道怎么开始。
诚然,在目前较为浮躁的学习环境中,掌握一项看得到摸得着的技能更能给人安全感和获得能力提升的体验,但在绝大多数时候,架构能力才是最考验一个开发人员综合素质的。
基于这个原因,我制作了这期“架构师”系列图文教学,旨在教会大家怎样拆解流程,怎样分割复杂的工作流,怎样通过有效的流程结构降低代码整体的复杂度,怎样通过一些流程技巧,来提升代码的调试复杂度。
这一系列教学不会增加你的基础积累,但我相信仔细思考各种写法,会让你的工程交付能力,得到非常大的提升。
在正式学习开始之前,我们需要掌握一些前置技能:
1.具备合格的逻辑辨别能力:流程和逻辑密切相关,逻辑判断语句准确,才能确保流程的执行路线准确。
2.具备基本的数据推演能力:流程在执行的过程中,数据是如何变化的,必须未经调试就可以推演准确,这样才能确保编写的流程代码准确无误。
3.有工程实现能力:在缺乏实现能力之前,学习流程如空中楼阁,是舍本逐末,学习有先后,不能好高骛远。
当你看到这之后的内容时,我将默认你已经具备了上述要求的前置技能。
接下来给大家隆重推荐一种常用的结构化语句:do,这是实现各种标准化工作流常用的经典语句,在按键精灵里,do有5种写法:
- Do
- // 死循环写法,需要通过 Exit Do 语句退出循环,否则循环内的语句会不停的执行下去
- Loop
- Do While 条件
- // 前置判断循环,当条件满足时继续循环
- Loop
- Do Until 条件
- // 前置判断循环,当条件不成立时继续循环
- Loop
- Do
- // 后置判断循环,当条件成立时继续循环
- Loop While 条件
- Do
- // 后置判断循环,当条件不成立时继续循环
- Loop Until 条件
复制代码Do循环可以通过
Exit Do 语句跳出循环,在UiBot中,跳出循环使用Break语句,按键精灵X保持了对按键精灵2014的兼容,也使用Exit Do跳出循环。
按键精灵X和UiBot都支持
Continue 语句,这条语句可以跳过本次循环,同时适用于For语句和Do语句。
了解这些基础知识,是为了更好的使用它们,接下来,我将由浅入深的展示一些循环的用法,来帮助大家在编写代码时更加得心应手,在这之前,还需要和大家科普两个概念:流程和工作流。
二、流程:我们不妨先创造两个概念:平坦工作流(顺序执行结构)、复杂工作流(跳转执行结构)
这两者的区别在于,平坦工作流总是先执行完前面的一行,再去执行后面的一行,而复杂工作流则可能在一个区域内跳来跳去的执行。
在开发人员的功力不足矣成为人体调试器之前,遇到复杂工作流往往一时间难以理解,例如某程序员张三经常性的吐槽:我发现xxx的代码我看不懂、我发现我看不懂几个月前编写的代码了。
通常优秀的代码,都是能够让初级工程师也可以阅读和修改的代码,因此写的代码别人能不能看懂,往往也是能力的重要体现,但具不具备脑内调试代码的能力,则是更高段位的能力体现,否则可能会出现:我写了一个bug,但他恰好能正常运行的情况。
但这篇教学的目的,并不是要让每个人都具备这样的童子功,正相反,我努力的让不那么出色的工程师,也能够通过一些小技巧,来提升自己的能力,学会将复杂工作流拆分简化,从而让自己的代码更清晰,先入门,然后慢慢进步,这也是因为在数年前我发布过一篇双循环结构的教学视频大受欢迎的到的启示。
接下来展示两段工作流代码的区别:
- // 这是一段简单工作流,a 不可能先于 b 被输出,我们可以称之为线性执行结构
- TracePrint "a"
- TracePrint "b"
- // 这是一个复杂工作流,在一段流程代码中,包含多组循环,理解门槛很高
- Dim j = 1
- For i = 1 to 100
- Do
- If i mod 1 = 0 Then
- i = i + 1
- Else
- i = j + 1
- End If
- j = j + 1
- If j > 20 Then
- Exit Do
- End If
- Loop
- Next
- TracePrint i
- TracePrint j
- // 当然,我们也可以实现一个没那么复杂的工作流,这是一个循环,永无休止的执行任务
- Do
- TracePrint 1
- Loop
复制代码第一种结构很好理解,也符合大多数人对于一件业务的逻辑描述(这件事实现起来很简单,先做A,再做B,然后做C就可以了)
但很多需求方没有意识到他们忽略了很多种不那么正常的情况,这就导致程序编写出来后,各种BUG满天飞,完全无法维护,或者完全低估了实现一个需求的复杂度。
第二种结构则很像是为了实现一个比较复杂的功能,而临时编写的代码或算法,叠了很多层结构化语句的代码,看起来有一定复杂度,老手也需要经过大脑调试一遍,才大概能得出运算结果
第三种结构则是一种比较简单的结构化语句,这种语句只要多使用几次,明白
同一时间只有一行代码 在执行的基本原理(不严谨的描述,不要杠,这并不会显得你很厉害),而不是理解成循环是同一时间把 n 次流程跑完,那么他的执行逻辑就不拿理解了,这也是我们重点要讲的方法。
例如,我们可以实现这样一套非常标准的工作流程单元:
- Dim iFlowRet = 0
- Dim ST = Timer()
- Do
- If FindElement(xxx) = 1 Then
- // 找到某个界面特征
- Log.Info("记录日志,发现了一个特征,流程将会继续执行")
- iFlowRet = 0
- Exit Do
- End If
- If FindElement(xxx) = 1 Then
- // 找到第二个界面特征
- Log.Info("记录日志,这是出错的情况,需要单独处理错误")
- iFlowRet = 1
- Exit Do
- End If
- If Timer() - ST > 30 Then
- Log.Info("xxx 功能执行超时(30秒),请检查环境或逻辑是否正常")
- iFlowRet = -1
- Exit Do
- End If
- Loop
复制代码这套工作单元使用一个 Do 语句实现了在 30 秒内检测多个状态,并执行不同的代码,当多个这样的单元组合时,我们就可以实现一个比较“平坦”的工作流了。
在这之前,我们需要对抗的,仅仅是代码行数膨胀带来的厌恶情绪,但实际上,
100000行易于理解的代码,往往比1000行晦涩难懂的代码更好维护。
三、工作流:我们敲代码是为了实现一个目标,这个目标可能是对一些数字进行计算后得出结果,也可能是从游戏的任意位置走到固定点进行固定的操作,或者是从某个网银中获取流水数据上传到本公司的财务系统中。
我们要实现的目标总是明确的,在这个基础上,我们可以将这个目标拆分成多个小的步骤,每一个步骤又是一个明确的小目标,例如游戏走到固定位置做任务,我们可以拆分为以下几个小的工作流:
1. 识别当前位置,判断是否需要进行大地图寻路
2. 大地图寻路或传送功能的实现
3. 小地图寻路功能的实现
4. 对话NPC接取任务功能的实现
5. 自动完成任务功能的实现
现在我们创建一些新的名词,将这样细分的小工作流叫做
二级工作流或
工作模块,将整个目标叫做
一级工作流,或
整体目标。
然后进行相同特征统计分类(小学生物知识),这是一个架构师的基础技能之一,但尤为关键。
二级工作流往往带有非常强烈的模块性质,例如 大地图寻路、小地图寻路、NPC操作……这些
模块往往是可以独立运行调试的。
如果你在对一个任务进行拆分时,发现二级工作流不具备这些特性,那就需要仔细反思了,这是非常严重的架构力缺陷,要加强学习,重新规划,直到拆分工作流的颗粒度,满足这样的要求为止。
如果这个要求可以满足了,说明你已经开始理解模块化的基础门槛了,并不是只有OOP编程语言才能实现模块化,事实上一个水平过硬的程序员,不管是写C/C++还是写VBScript,甚至是ASM这样结构性极差的编程语言,都非常优雅易读,方便维护。
接下来我们可以着手为二级工作流进行
打标分类工作,可以选择的标记会随着经验的增加而变多,也就是应对不同复杂度模块的对策,最基本的标签有以下这些:
1. 平坦工作流程 或 复杂工作流程(二级工作流的逻辑流程复杂度)
2. 同步工作流程 或 异步工作流程(比如下载文件,同步流程需要等待和检测,异步流程可以直接退出)
3. 不稳定的流程 或 稳定到几乎不需要容错的流程(有的流程天天出问题,就需要强大的容错机制,有的则一条直线直接搞定)
4. 状态明确的流程 或 状态需要推算的流程(例如调用MessageBox API,你按哪个按钮,返回值非常明确,但写游戏脚本或者做RPA,对其他程序进行操作,一通模拟之后,状态往往需要识别确认,这也是为什么图色总是没那么稳定的缘故)
……
不同标签的工作流,我们当然需要区别对待,这样才能好钢用在刀背上,让写出来的程序更稳定 或者 更好维护,当然,我们还有一个目标,就是让代码更易阅读。
打标分类最主要的目的,是让我们
提前筛选出可能踩坑的模块,用非常高的容错标准去构建它,从而保障整个流程的稳定性!
4. 什么样的代码更易阅读:假设你是一个水平没那么差的工程师,至少不能说看见两三层嵌套循环就大脑发懵,逻辑理不清了。
那么,
每一层循环的工作意义是否明确?
每一个判断的语句是否足够简短(C语言开发者经常会写一段非常长的逻辑判断运算,这是极其蛋疼的做法),
变量的明明是否足够明确,
关键位置是否有注释(不必每一行都写注释,变量声明时行尾对齐注释解释一下这个变量是干啥的,结构化语句解释一下判断和循环的用途,函数前面注释一下这个函数的功能,参数和返回值的意义和类型,非常难的代码注释一下这个命令是干什么的,如有必要给出命令手册查询的网址或者文件位置)
基于以上我们对更易阅读的定义,那么我们自己的编写代码的时候,也可以按照这个标准来。
通用的东西,往往不需要太高的技术就能实现。
大道至简,想要追求能力的提升,先把童子功练扎实了,再去玩花的,你会更强,基础不牢,很容易翻车。
5. 下集预告:这一节课程比较的言之无物,但是我感觉有些难听的话还是说在前面比较好,不然之后要解释的东西太多了。
下一节课程这两天会开始写,这么一篇玩意断断续续的写了3天……蛋疼无比啊。
如果不出意外的话,下一节课程会讲一下各种类型的 do 语句基本实现