辛巴学院-Unity-剑英陪你零基础学c#系列(四)函数和封装
辛巴学院:正大光明的不务正业。
国庆长假结束了,我的心情是这样的:
你总是起不早,起不早
独自一个人沉睡到天亮
你无怨无悔的梦着那副本
我知道你根本就不想上班
你总是起不早,起不早
放假总是短暂,上班太难
请个病假,再睡上一天
今天我们聊c#的函数和封装
这个话题其实你有无数的资料,每个写过代码,没写过代码的人,都知道这些玩意。
我也没有太多新奇的角度,先记住这一点
这不是数学,这不是数学,这不是数学。
我接触最早的语言是basic,那个时候函数还没有那么深入人心,不是visual basic,dos时代接触的。有一些系统函数可以调用,自己写小程序却鲜少定义函数,或许是不能定义,反正我学那会完全不知道。
讲basic干什么?这不是关于c#的文章么?只是告诉你,曾经存在不那么依赖函数和封装的语言,函数不是必须的。但c#,或者说c系语言,都是以函数和封装作为基础。那么你可能会思考,一个不用函数和封装的代码该怎么写?过去的basic 有行号,可以goto 到一行接着执行。我们就依靠这个goto,将代码划分为几块,跳来跳去。
这实质上就是一个没有语法保障的封装,实质上就是函数。在c系语言,你也可以用goto,实际上编译成机器语言以后,本来就是goto来goto去。
把一块功能组织在一起,就叫做封装。专门将一块功能标记为一段子程序的语法,这就是函数。
每一行代码都是你的宝贵财产,比如衣服,两三件的时候,随便放在那里就行了。几十件的时候,不分类整理一下就有点麻烦了。几百件的时候,可能还要先分房间再分衣架。
这种需求就是封装的需求,它是随着系统复杂度的提升,自然而然会出现的需求。
如果你一时还完全体会不到这种需求,那先不要去考虑研究函数,研究封装,先去用一个函数写完整个程序,一直如此,一直如此。如果最终你也无法感受到封装的需求,那你也可以成为一个独特的程序员。
C#的函数语法是这样的 【修饰符】类型 函数名(【参数1】,【参数2】,…){…}
这玩意说不说也都差不太多,大家都知道。
怎么设计函数?
最关键是你能体会那种看到一个500行的函数,有一种好想好想把他重新整理成好几个函数的强烈冲动么?
这种冲动是学习函数设计方法,也就是学习封装的关键所在。
你也许已经发现很多优秀的程序员,他们却说不出什么,他们明明能写出优秀的代码,却不能解释为何要如此写,应用了何种方法或原则。
这一类程序员就是【本能型】,他们学会了基本的语法之后,跟着那种整理的冲动,一步步走向优秀。我认为本能型是最轻松的一种类型,爱和悟性,正是你最大的助力。
然而,世事总不如人意,总有些人需要一点帮助才能找到自己的切入点。
所以我们还是来探讨一下,函数怎么封装?
从命名开始,再写调用,至于实现,再说吧。
所有好的函数设计都是从命名开始的,然后编写调用代码,至于实现,呵呵,爱谁写谁写,如果你没有助手,那就是你写。现在你可能对这个讲法有着深深的怀疑,没关系。我们先谈命名。
你可能听过几种命名法,比如著名的匈牙利命名法。我们聊的是命名法的使命。命名法的使命是让一个名字清晰,准确,易识别。就.这.么.简.单。
- 用英文。我不抵制拼音,我说的是用英文字母,别用中文或火星文,输入起来太烦心,26个英文字母不借助任何输入法可以用键盘敲出来,这是天然的优势。无论你是怎么样的一个热爱自己民族文化的人,我相信你也不愿意抵抗这个优势,你可以造一个5000个按键的键盘,每个键对应一个常用中文字,我十分怀疑你要花多久才能熟练使用这样的一个键盘。至于用拼音,那只是一个low的问题,只是让你看起来像是一个土拨鼠,话说回来,随便查字典得来的英文要慎重,别随便乱用,要查就查个彻底,搞清楚语境用法,否则也还是一只土拨鼠。
- 无论哪种命名法,他都由单词和分隔符两个部分构成,有两种世界通用分隔符,下划线和驼峰法。举例m_Gold DoSth别发明你自己的特别分隔方法。单词没关系,用简写一定要用约定俗成的,简写取几个字符是不能乱来的,你自己定的规矩,别人怎么可能懂,比如inte 你知道是个what? 用Integer 或者int。cha 你知道是个what?用character 或者 char。
- 函数名字是动宾短语,变量名字、类型名是定语修饰从句。好吧,先给语文老师上柱香,我也不确定我说的对不对。
Void 打() 这就不是个好命名,因为没宾语,根据上下文也推断不出来打谁。Void 打豆豆()就好些了
Void 转圈() 这就问题不大,可以很容易推断出是自己转圈。此处用中文只是为了说明,别挑我理哦。
Int 金币 int 豆豆的金币 根据上下文,他们都是语文,你要从语文的角度考虑话说清楚了没。命名,本来就是文字的事儿。
命名说完了。
让我们回到先写调用还是先写实现的问题上。这个问题有个有趣的现象,大部分新手程序员都认为应该先写实现,当他们成为老手之后,就统一认为应该先写调用。
我们来思考这样一个问题?程序没跑起来之前,程序员是否知道程序的运行顺序。答案是知道。
我们再来思考这样一个问题?一个不知道其细节实现的函数,程序员是否能使用,答案是能。
我们再来思考这样一个问题?我们观察程序结构是看什么,看到我们欲观察的结构的末端时?我们需要看其函数实现么?答案是不需要。
程序设计就是程序没跑以前先规划好他怎么跑,规划程序怎么跑就要设计程序的结构,就是设计谁调用谁,不知道实现细节的函数不影响我们设计程序
所以
永远先写调用,再写实现,是程序设计的铁则
我想说这就聊完了,不过还是要照顾一下悟性不高的同学。
还是不免俗的来几条函数设计原则:
函数不要太长,100行以上就值得再思考一下了,空行不算。
所有的设计原则基本上都是经验原则,100 80 200 你也可能会听到各种版本,没关系,都一样,都是个经验数字。这个原则的出发点是别写太长,不容易看懂
函数命名就已经决定了函数做什么事,聚焦在这件事上,无论参数、返回值实现都要忠于这件事,否则,去另一个函数。
在阅读时好的函数是不需要看其实现就能理解其作用的,一个与命名不符的函数就是一个坑,虽然每个程序都是在坑人与被坑之中成长的,但是提前记得这个原则,会给你提供一定的帮助。
函数的一致性,就是函数的输入一致,输出就应该一致。
函数别增加和他无关的状态,这一条理解起来比较困难,所以我们分开讲
随机数函数表面看起来每次的输入都是空,返回都不同,其实并不是如此,随机数函数有个隐形输入,随机种子。随机种子一样,随机数函数返回就一样。
所有的函数都是这样的,计算机中不存在不确定的状态,所有的状态都确定,只有像随机种子、时间这类代码阅读者不易察觉的隐形状态。
这一条就是说要慎重设计函数的隐形状态,保持其从外部易于观察。
比如你写了一个函数 addrandhp(10),然后其行为是加 5 到 15点血,在10 加正负5范围。这就有个隐藏状态正负5之间的随机。
Addrandhp(5,15)用这样的设计方式就从外部容易理解其行为。
函数应尽早报告错误,比如先检查下输入是否合法。
在执行中函数出错,应该给予正常的异常反馈,用throw xxx,这也是有争论的,有不同的方法论。但是微软的类库里都是尽量早抛出异常的,我们跟随微软的风格。
不知不觉可能把零基础系列做的比较复杂了,可能我就适合写这种装逼效果的文字,反省反省。
再会,哈库拉玛塔塔