找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9510|回复: 12

[教程源码] 设计良好的函数

[复制链接]

1

主题

0

回帖

2

积分

按键电脑&手机班学员

鲜花
0
猫粮
66
发表于 2018-4-29 10:57:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 tiybu 于 2018-4-29 11:04 编辑

好的代码和糟糕的代码非常明显,好的代码不需要过多思考就一下子能够看明白,而不是认为自己能看懂,大家都能看懂。好的代码是经过很多客观因素考虑后写出来的,而不是按自己的个人偏好去写的(但习惯了写好代码,就是自己的个人偏好);好的代码即使过了很长时间再去翻阅,也会一眼看懂。一眼就看得懂的代码不需要注释,代码才是最好的注释。

题主在写这篇帖子之前,考虑了 刚学函数却对此模糊不清 和 早就懂得使用函数却想学习些技巧 的朋友。

函数就像一个榨汁机,把一条萝卜放进去,按一下启动榨汁的按钮,就可以出来一杯新鲜的萝卜汁。如果放苹果,最后出来的就是苹果汁。组装榨汁机的人,不必考虑各组件是怎么实现的;使用榨汁机的人,不必考虑其机是怎么组装的。

函数的参数和返回值,都可以用外部(或全局)变量来代替。什么情况下给函数定义参数更合适? 什么情况下给函数返回值更合适?


参数的意义

现假设有一个自动登录的脚本。如果要我们去写,就可以把登录的功能划分出来,作为一个函数。先来考虑这种写法(为了方便演示,下面的一些注释假设为实现代码):

Dim userId,pwd

//.....

//设置登录账号
userId = "123456"

//设置登录密码
pwd = "qwe520"

//登录游戏
Login()

//....

Function Login()
           //.......
End Function

上面的写法并不直观,原因是每次调用Login函数之前都要设置userId和pwd变量,假如忘记了userId变量名的拼写怎么办呢? 那么又要去花时间找出来了,不仅如此,脚本写完后放在一边,时间长了再去翻阅原先写的这些代码,可能就忘记了这个变量名的作用,此时又要花时间去理解这个变量名。你们一定会想到,可以用参数指定账号和密码。登录论坛,登录企鹅,亦或登录其它的一些App、网站等等,都是先输入账号,后输入密码,这个操作顺序永远被我们记得。 所以,上述代码中的函数定义可以改写成:

Function Login(userId,userPwd)
           //.......
End Function

这样不仅一目了然,减少了记忆了负担,还大大降低了出错的风险。


参数定义原则

如果一个外部(或全局)变量作为某种状态以可改变两个以上的函数的执行,并且这种改变并不频繁,如果有这样的需求,就应该保持它为一个外部(或全局)变量。看下面的代码就会明白(为了方便演示,下面的注释假设为实现代码)。

Function IndexString(inputStr,findStr, ignoreCase)
        Dim retVal
        //......
        IndexString = retVal
End Function

Function ReplaceString(inputStr,oldStr,newStr, ignoreCase)
        Dim retVal
        //......
        IndexString = retVal
End Function

IndexString函数作用是得到字符串索引,类似VBS函数InStr;
ReplaceString函数作用是替换字符串,类似VBS函数Replace。

这两个函数的设计并不是最好的方案,原因是它们都有一个共同的参数ignoreCase,它指明是否需要区分大小写。字符串相关的操作函数,一般情况下很少需要指明是否区分大小写,如果在每次调用,都指明它,就显得比较麻烦。最好的办法是,把ignoreCase作为一个外部变量。上述代码更换成下叙代码为最优解:

dim  ignoreCase
ignoreCase = true //设置默认状态

Function IndexString(inputStr,findStr)
        Dim retVal
        //......
        IndexString = retVal
End Function

Function ReplaceString(inputStr,oldStr,newStr)
        Dim retVal
        //......
        IndexString = retVal
End Function

这样,如有需要,每次调用它们之前,就可以先设置ignoreCase。像VBS中的InStr函数就有可选参数,但按键精灵中的函数定义并不支持这样做。上述代码不仅做到了“可选参数”的功能,在某些情况下,也非常有用,比如像下面的情况:

dm.SetPath("c:\")

用过大漠插件的人都知道这个函数的作用。假设大漠的图色函数支持传入一个全局路径的字符串并支持可选,就不合适了——引起混乱。 这个SetPath函数的设置就相当于上述代码中的ignoreCase的设置。



返回值的意义

大部分情况下,把外部变量代替函数的返回值并不合适。一目了然、减少记忆负担、降低出错风险,给函数添加返回值更是增强了这三点。一个函数就像一个任务待命执行者,需要的时候可以命令它,任务完成了还可以给我们反馈完成的情况。厨师的职责是单一的,本职工作就是炒菜烹饪,不能干相关以外的事情。厨师做完菜会给顾客上菜吗? 函数的职责也应该是单一的,这是编写函数功能的一个原则。拿上面的Login函数作为例子,请先看下面的代码(为了方便演示,下面的注释假设为实现代码):

Login("123456","qwe123")

Function Login(userId,userName)
        //.....

        //检测是否登录成功
        If (isLogined) Then
                //选择角色....
                //......
        Else
                //.....        
        End If        

        //....
End Function

其实这样的写法并不明智,反而混乱了。函数的职责应该是单一的,只做一件事情,这里不仅给你做了检测是否登录成功,成功了还帮你选择角色。脚本写完后放在一边,时间长了再去翻阅原先写的这些代码,通过看函数的名字Login,知道了是登录的意思,但也不确定登录完了还干些什么事情,登录失败了又干些什么事情。

我们需要的是,通过一个名字,就能断定它主要是干什么的,而不用过多的怀疑。

所以必须树立一个原则——单一职责。

为了减少以后看代码的痛苦,树立这个原则是很明智的。

把上述代码更换成下述代码为最优解:

Dim flag
flag = Login("123456","qwe123")
If (flag) Then
        //选择角色
Else
        //........
End If

Function Login(userId,userName)
        //.....

        //检测是否登录成功
        If (isLogined) Then
                Login = true
        Else
                Login = false
        End If        

        //....
End Function

这样就更一目了然了,做了该做的事情。做完了还给我反馈信息——返回值。



函数的设计原则

1.单一职责原则

一个功能可能有很多个子功能,一个子功能可能又有很多个子子功能。应该把哪些功能写成函数是要斟酌的。但不可否定的是,函数始终保持单一的功能。一个函数通常来说只有20~30行代码,也会有意外。假如写的代码有足足50行以上,就要考虑是否要进行分解了。

2.金字塔原则

函数分为具体函数和抽象函数。具体依赖于抽象,抽象不能依赖于具体。它们是层级关系。大漠插件中有LeftClick、FindPic函数,它们是抽象的,Login函数可以依赖于它们。LeftClick依赖了Windows系统底层的API函数,那么API函数比LeftClick函数要抽象。

假设现在有一个需求:自动登录游戏过任务。

那么,我们划分成的函数可以是这样的:

Main()

//主程序
Function Pro()
        OpenGame("C:\XX游戏.exe")
        Login("123456","qwe123")
        PassTask()
End Function

//打开游戏
Function OpenGame(path)
        //.....
End Function

//登录游戏
Function Login(userId,userPwd)
        //...
End Function

//通过任务
Function PassTask()
        //....
End Function

他们的层级关系是这样的(图):

发论坛的图.jpg

虽然上面的图包含了大漠函数、API函数的层级关系,但平时我们自己写的函数以这种方式去关联是明智之举。






楼主热帖
  • 打卡等级:开宗立派

1087

主题

2141

回帖

4335

积分

院长

鲜花
9
猫粮
7413
QQ
发表于 2018-4-29 15:33:08 | 显示全部楼层
不错的文章

18

主题

48

回帖

104

积分

按键电脑&手机班学员

紫猫助手

鲜花
0
猫粮
494
QQ
发表于 2018-5-25 17:15:18 | 显示全部楼层
赞一下先....

1

主题

16

回帖

18

积分

按键电脑&手机班学员

鲜花
0
猫粮
189
发表于 2018-6-15 11:18:57 | 显示全部楼层
哇,超级好!  学习学习~
  • 打卡等级:初涉江湖

0

主题

23

回帖

23

积分

按键电脑&手机班学员

鲜花
0
猫粮
189
发表于 2018-6-17 19:52:00 | 显示全部楼层
虽然上面的图包含了大漠函数、API函数的层级关系,但平时我们自己写的函数以这种方式去关联是明智之举。 后面还有吗  希望楼主在更新呢!!!!!!!!!!(=^ ^=)

0

主题

1

回帖

1

积分

学前班

鲜花
0
猫粮
2
发表于 2018-7-16 14:33:33 | 显示全部楼层
o(∩_∩)oo(∩_∩)oo(∩_∩)oo(∩_∩)oo(∩_∩)oo(∩_∩)oo(∩_∩)o
  • 打卡等级:无名新人

0

主题

41

回帖

41

积分

按键电脑&手机班学员

鲜花
0
猫粮
114
QQ
发表于 2018-7-19 13:35:13 | 显示全部楼层
碉堡了Σ(゜゜)

0

主题

10

回帖

10

积分

学前班

鲜花
0
猫粮
13
发表于 2020-8-26 11:27:30 | 显示全部楼层
恩,很适合新手
  • 打卡等级:无名新人

6

主题

14

回帖

26

积分

按键电脑班学员

鲜花
0
猫粮
215
发表于 2021-10-24 12:18:02 | 显示全部楼层
感谢,已经学习到了,谢谢分享

1

主题

3

回帖

4

积分

学前班

鲜花
0
猫粮
10
发表于 2021-11-12 21:06:16 | 显示全部楼层
提供了很棒的程式邏輯設計思路(^-^)V
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|紫猫编程学园

GMT+8, 2024-11-21 21:50

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表