随笔感悟——函数封装

-思绪来源:我在初学算法的时候,一直不愿意调用除了基础函数以外的其他功能函数,特别是有些函数里面就只有一句代码,这...有什么封装的必要?(例子:数据结构顺序表,其添加删除肯定属于基本逻辑操作,但这顺序表的长度到底有什么封装的必要,顺序表诶,四舍五入等于数组,求个长度还封装,哇,一句代码的事情诶,有没有搞错(我就举一个小例子

-我愿意改变,但有谁能告诉我原因是什么好吗,跪求啊

-真的是感悟,都快上升到哲学问题了....写的就是一种感觉

-意识流

 

 

 

 

 

我有认真思考过“应该如何去编程”,但是在学习的过程中我却只能读到一系列指示,像这样命名变量(大小驼峰法、匈牙利命名法、下划线法)、像这样组织代码(结构化,流程化)、像这样封装函数(高内聚低耦合)等等等等。

是,在大部分情形下,这些都是不错的做法。但这样做的理由是什么?

 

 

大家写代码都有自己的习惯偏好,对吧。虽然代码个人风格太严重不一定是好事,因为别人可能看不懂这份代码,由此可见这份代码的可读性和沟通性不见得好,这会带来很多麻烦事。但是,这不代表我会在一无所知毫无辨别能力的情况下,就随随便便随波逐流地采用大众方法。

我知道很多事情不能一概而论,不同的情况下有着不同的最优解法,但这些做法到底在什么样的上下代码中才能成为“通法”却没人指出。就像最基本的变量命名,如果要准确表达出变量的职责而使用很长的名字,这会使代码变得冗长从而加大阅读难度,可是如果为了图方便而使用缩写命名,那效果会更加不好,因为这太容易产生歧义了,其名字不具有描述唯一性。那面对这种情况我们该如何抉择呢?

其实,我在课本中从来没有看见过这些答案。

 

 

回想当初,我也有这方面的坏习惯,在最初学习编程的一段不算太长的时间里,我每天乐滋滋地跟着课本去使用一套abcdefjhijklmnopqrstuvwxyz的命名觉得自此打遍天下无敌手......

后来发现不对劲儿了,因为在复习的时候我有点看不懂我以前写的代码了,这感觉可不好受,看个代码跟做阅读理解题一样,说出去搞笑呢。于是我开始注意自己的代码书写规范,有去搜索过一些文章博客,也有浏览过一些公司发布的代码规范手册(如谷歌、BAT、华为),建议新手小白们有空也去看看学习学习。

但是看过以后呢,我觉得有些规范是说得很对,但为什么很对就不知道了。呵,学不到本质,这不是我想要的。不过不知道也没关系,毕竟你才刚开始学习能意识到代码书写规范的重要性和保有良好的注释习惯这就已经是个很好的开始了,但显然我还是想知道原因。

 

 

 

我有思索过、有考虑过、也有纠结过,但有时候我得出的结论明显并没有我想象期望中的那般好。可那又怎样,新手本来就很容易犯各种各样的错误,如果你现在不花时间去思考,等到以后还不是会花时间思考,一样的没差。

只是令我感到咋舌的是,如果每次学习自己喜欢的新事物新规则时就直接拿来用了,那你可能会与这些规则背后所蕴含的斟酌与考量其产生的奥妙感觉擦肩而过。如果每次写代码都按着模版照搬,管它三七二十一的反正现成的代码能用就是了,那这种复制粘贴简直和代码界的搬运工没有任何区别。其实,此处涉及到造轮子和用轮子之间的权衡问题,关于这个的讨论网上有很多,不妨搜搜看。

嗯,写这么些话是希望大家能抓住每一个有感而发的思考瞬间,不要错过与那些隐含知识碰面的机会。但值得注意的是,千万不要刻意地去思考一件事,那是挑刺儿。不过,若实在思考不出来也没关系,因为你与代码打交道的时间还很长,可以不急于一时。

毕竟在后续成长实践的过程中,你会逐渐有深刻认识的,就像学习软件开发一样,能慢慢地体会到软件不是简单的编写程序,其包含了很多技术问题间的协调,如分析方法、设计方法、形式说明方法、版本标准等等。到那时候你会自然而然地为了解决问题而去思考的,所以别担心,有机会的。

 

 

 

但对于我来说的话,思考这种情况还是很常见呢。我想着知其然还要知其所以然,就这样深究下去了,没错,这迟早会上升到哲学问题——“如何编程?”

可是这种哲学问题真的很容易泛泛而谈,即使大家给出了许多规则,但看到规则的大多数菜鸟们(包括我),其实根本不知道规则背后隐藏着什么样的思索,也不知道什么时候该打破规则、突破束缚。嘶,这就很扎心了。

我当然意识到了代码可读性的宝贵,也知道要写出高质量的代码势必要付出很多心血,融合自己与他人的代码风格总是有许多深思熟虑的。毕竟优秀的代码,绝非仅仅是功能的堆砌,它需要做到能有效的传达信息。因为有无数个事实表明,修改代码是编写代码成本的3倍、5倍之多,而且别人阅读理解你代码的时间可能要远远超出你编码所用的时间。

所以,要学会利用代码去精细准确的传达出你的想法,学会用代码与他人沟通,绝对不要写出垃圾代码。要知道,写出简洁明了结构良好且效率高的代码是一件重要、困难并且有趣的事,它值得每一个人下功夫去研究。

但这种代码不会自然产生,它是由数十个数百个看似琐碎,实则无比重要的决定堆砌出来的。我们在创造高质量代码的过程中,不断地在做出一些微小却重要的决定,虽然每一种编程方式都由许多决策组成,而且它们之间互相支持协同工作,但从中抽出一条却不一定能有效果。比如,片面地追求最精简最优化或最高效的代码就是个误区,代码并不是行数越少就越好,如果仔细注意或观察就会发现我们使用最多的代码是那些逻辑清晰结构良好的代码,而非那些标新立异看起来高深莫测的代码,当然炫耀的除外。

归根结底,这么说这么做的目的只有一个,就是希望你的代码可以清晰的表达出你的意图,并且自始至终保持一致性,让其他人可以理解并信任你的代码,能够信心十足的修改它,这样的代码才是好代码。

 

 

 

 

其实,代码要写得好,要点之一就是不能repeat东西。这是因为一旦要改需求,你会欲哭无泪的,程序员还是要减少修改代码的痛苦。那么重点就来了,在学习数据结构的时候关于函数封装的问题我可是吃过大亏,呵,接下来我将讲述我的惨痛经历。

 

不过在此之前,明确指出函数封装是有必要的,先不论你的代码风格或偏好习惯如何。

 

 

 

嗯...我印象最深的是当时用顺序表完成字符串操作的那个代码,虽然现在回看过去觉得很简单,但是那个时候的我确实不明白为什么要把一些很基本的语句包装起来。我一直觉得把求length的方法专门单独写进一个函数里面是如此的画蛇添足和多此一举,所以我理所当然的没听老师的方法也没按照课本上提供的案例那样来做,偏要自己写。

但是你知道的,关于数组下标的计算一直都是一个很容易出错的东西,所以我在后续手动修改顺序表长度时遇到了很多挫折。首先,肉眼查错和手动枚举修改代码是非常耗时耗力的傻行为,这不仅过程痛苦而且还容易出现纰漏,最重要的是这种修改过程会影响你理清思路,因为你的注意力全部集中在修改语句上,而非修改结构组织上。于是稍有不慎,改着改着就发现代码结构乱了,这太糟糕了。

我呢,在修改完代码后再次进行测试时,发现有个功能的结果出错了,但找了半天没觉得哪里出错。毕竟思路什么的都是对的,而且我还自以为挺谨慎的,不可能出错啊。直到我决定开始逐行排查代码的逻辑,最后发现有一个函数的length++初始值不对,多加了个1。我的天呐,我当时简直都要被气笑了,错在这个地方让我找了近一个小时。

那个时候我就明白了,像求长度这种使用率较高的底层代码,如果你把它封装成了函数,那么在你需要修改它的时候就只用在函数里面修改一次即可,不用像我之前那样手动一个个的去修改,找不全还很容易出错。所以这种封装的感觉啊,还挺像“一键修改”功能的,如同机器上的零部件一样制造一次后批量使用。

 

 

 

 

是的,函数封装相当于替换或减少了重复性的代码,可以使程序精简化。试想一下,当所有代码都写进一个main函数里面是不是很可怕,毕竟,一个函数实现多个功能会给开发、使用、维护带来很大的困难。若将没有关联或者关联很弱的语句放在同一函数中,会导致函数职责不明确,让人难以理解、难以测试、难以改动,所以将重复性的代码提炼成函数可以降低维护的成本。

但事情总有两面性,如果掌握不到封装的精髓,那很容易就犯下封装过度的错误。其具体表现就是,不断的分解再分解问题却对于精简逻辑结构毫无头绪,导致写了无数行代码封装了一堆函数但就是看不到一个完整的功能,分裂问题起来收不住,不知道何时适可而止。往往一个函数里面就三两行代码,互相之间调用这个调用那个的,引用得乱七八糟,最终把功能实现了但代码的维护性也让人不忍直视,自以为封装细致,其实是拿捏不好这个度。

所以你就知道了,我为什么当时会对那个length长度的封装抱有这么大的抵触,因为封装的函数里面就只有一行代码,这真的让人很难以接受啊。

 

 

 

 

那么,在编程时如何决定是否将代码封装成函数,或者如何辨别才不会做无所谓的封装呢。我觉得还是要以代码逻辑直观为最优先原则,然后讲究封装的高内聚低耦合,剩下的其他原则均低于此,即使是代码的时间和空间复杂度也不行。

 

当然,还有几个简单易懂的封装指标,可以借鉴一下:

1、函数要短小,不超过50行,从而避免使代码阅读者一次记住太多上下文。

2、函数只做一件事,一个方法只实现一个功能,要减少模块间的互相干扰,让各个模块的修改变化尽可能小的影响其他模块。

3、函数要使用“当且仅当一次”原则,不能重复,即使整合两个类似的函数是相当困难的。除此之外,若代码只被调用过一次且在逻辑上没有独立存在的必要,那这样的代码就不应该被封装成函数。

4、函数嵌套不能超过四层,尤其是if、for、while等语句之间互相包含的深度,嵌套多个语句或函数会影响执行效率,就像开车走弯路一样,而且每级嵌套都会增加阅读量,使你的脑力在不断消耗直至死机。这就像当你写了无数的if-else之后发现了switch-case时的心情,或者第一次听到函数指针这个事物时的心情。

5、函数内逻辑要最直观、最好理解,整体而言简洁可用即可。这不是代码高不高级的问题,高质量的代码是简单直白的,能用干净利落的抽象和直截了当的控制语句将函数有机组织起来,并且能清晰的传达出设计者的意图,这才是最重要的。

 

 

 

 

毕竟,封装的本质就是要减少重复信息的产生,归根结底无非就是为了能更好的重构代码,但这只是一部分原因,其实重复的本质并不仅仅局限于此。

重复,是由于你把同一个信息散播在了各个地方,这相当于复制了代码。可是就算你把重复的代码抽象成了函数,但函数调用了几次,这实际上也等于重复了几次。那么这时就应该从代码的结构框架上改,也就是利用数据结构和算法的知识来解决重复问题。

其实,“去掉重复的信息”这个问题也很值得深思,因为就算我们撇开代码的精简度不谈,就从你以后的工作来说,由于客户的需求总是在变,如果每次你遇到类似问题时,经常就直接写,而不是找曾经写过的函数来扩展,那这样解决问题的效率就高不起来。所以为什么不把你需要的各种信息提取出来,把专用函数扩展变成通用函数来减轻自己的负担。

所以记住吧,如果信息一旦被重复,那你的代码就会出现不同程度的腐烂和破窗,不要让你的这份代码散发出不好的气息。

 

 

 

 

啊,当然了,凡事不能一概而论,这并不是什么代码都能重构的,就算你想重构但有些代码就是不能重构也没法重构。

因为在一个具有一定规模的系统中,存在复杂模块是无法避免的事情,一次性把这些复杂模块都标注出来并不现实,而且我们对这些模块进行调整和优化也存在风险。所以,以合适的策略去优化真正需要优化的部分是很重要的,不要局限于重构。

况且还有些东西根本就没法重构,比如幻数。就像哈希表中的133,或者你自己在编写代码时写的0x2123、0.0211f等东西,当时你是明白这个数字的意思,但是别的程序员看这个代码可能很难理解,甚至过了一段时间之后,连你自己再看这段代码也忘记了这个数字代表的含义。

总之,你不记得也不知道这个数字的具体含义究竟是代表着什么,或者这个数字根本就没有什么含义,但是代码编译后程序依然可以正常运行,那这种东西就是幻数。举一反三,就算有一段很莫名其妙的代码摆在你面前,你不懂这段代码有什么意义,但它就是能很好的运行程序,而且没了它还不行。那这种东西怎么重构,它根本就不好重构,所以就别重构了。

对于这种现象呢,很多人觉得这种代码是定时炸弹,不改掉它心里不痛快。可是,只有当我们真的需要关注并修改它时,它才是问题所在,如果没有人需要阅读或修改这段代码,那么它的复杂度就算高成指数倍,那又有什么关系呢?但如果你实在犯了完美主义的强迫症,那就去改吧,毕竟心里痛快比较重要。

 

 

 

 

所以你会发现,大家都说封装好,但有些时候封装代码就不好,大家说重构很重要,但有时候重构根本就没有意义。在一些特定的情况下,规则并不适用,那么当你学着了解规则的同时,也得想一想规则背后的原因,不然这跟封建迷信有什么区别。

 

最后,祝大家都能写出好代码,嗯。

 

 

 

 

 

 

你可能感兴趣的:(岁时杂记,函数,封装,代码,重构)