首页 > 代码库 > 从编程到软件工程
从编程到软件工程
前言:
本文是我自己在三年多的软件编程学习上的亲身体验,我将自己学到的体会到的进行记录下来,很明显,对于我是最适用的,不喜勿喷。至于为什么要记录下来,一是对我自己目前阶段的想法的梳理总结,二是希望给以后的新人一些经验参考。
虽然我自身是C#语言的忠实践行者,但我也不排斥其他的语言,诸如java,python,还有网页三大件语言等等,至于C和C++,语言确实很强大,只是应用方向多半为底层开发,或是对性能要求到变态的程序开发,在一般的中小型系统开发,工具开发中,开发效率实在比不上C#或是java,如果只是开发个windows的一个软件小工具,无疑C#的开发效率是最高的,几秒钟就可以生成一个窗口界面。有时候会有人问我,学习什么编程语言好?这真的是一个太难回答的问题,需要从自身的喜好,能力,希望从事的行业多方面考虑的,至于说薪资,无论哪个语言学的厉害了,薪资都是没有上限的,前提是真的学精通了。话说回来,无论学习哪种语言,这都一条孤独而又寂寞的道路,就是选择了相当长的时候不说话,只是和电脑在打交道,选择了不停的折磨自己,就为了体验在完成功能或解决BUG时的快感,所以,一般不建议妹纸学习编程。
我要说的东西和具体的语言关系不大,编程语言都是有共通的,刚开始学习的时候,基本都是从语言的变量类型来学习的,什么bool,byte,int,float,double,string(对python来说,虽然对象没有类型,但是实例仍然是有类型的)等等,然后再学习流程控制语言,大多数的编程语言都会提供if-else,do-while,while,for循环等等,然后介绍输入输出,读写文件,方法和类,类的概念实在是太大了,通常都会花很多章节来说明,模块和组件,有的还有怎么做界面。
几乎所有的编程语言的书都会有《从入门到精通》的系列,之所以都是这么命名,因为它真的是入门而已,这些说也基本都只是介绍我上述所说的知识,之所以不深入也是有原因的,对于初学者,一次灌入的信息量太大,会导致难以接受。而言更关键的问题是,很多编程大神,经验丰富的程序员,也仅仅是自身比较厉害,并不代表传授知识厉害,会编程和怎么把编程技巧传授给新人,这实在是两个完全不同的领域。而且大神体会的都是一些抽象后的精髓,对于新人来说,都是一些虚无飘渺的东西。就好比,我们要学习兵法,老师来一句“兵者诡道也”,鬼知道应该什么搞,但是对于一些经验丰富的人来说,可以一点就通。
一般反复多读几次《从入门到精通》的书,基本上算是真的入门了,可以称之为程序员了,这时候需要找一些小的项目练练手,比如写写一些小工具,文件统计处理的,或是写个小游戏,俄罗斯方块之类的。猜数字游戏,迷宫游戏之类的,一般这样几个项目下来就一门语言的编程就大抵清楚了,但是这和软件工程还差得远呢,虽然软件工程也是由一些简单嗯的语句组成的,但是这个区别就像,你肯定懂中文,但是写不出史记,甚至很难写出一篇优美的文章。
说了这么说废话,接下来才是真正的主题,从编程到软件工程,在我的经验上得出的结论是需要走三个大阶段才能完成,接下来分别论述下这三个大阶段。在此处不将算法的研究放进来,以来因为算法的研究又差不多算另一个领域了,二来一般人实现功能,基本都是从网上直接搜索来的,一般网上公开的算法都是相对比较成熟的,至少比我想的算法要好。
1.代码提炼
在掌握了一门语言的基本类型和语法后,基本上都是自己开发一些小的软件了,个人的比如小型聊天软件,俄罗斯方块,五子棋,随机数游戏。至于企业软件,如果没有相应的环境,确实难以想象可以做什么系统,所以在此处我推荐开发一个账户登录,密码修改等等软件,这是所有企业软件必备的,等这种小软件做多了,自然就遇到瓶颈了。我这时候就在想,简单的我都会,虽然代码丑了点(但是自己看不出来啊,不然肯定改掉了),难的我也做不了,陷入了一种尴尬的境地。
要想突破自己的水平,就要学习提炼代码,这是一个漫长的技术,很难一下子掌握,即使有了这个思想,也是一步步慢慢来的。在刚开始写代码的时候,一个方法里总会写大量的代码段,而且当时的想法是“这么写挺好的,顺序执行,通俗易懂”,现在来想想,确实也没错啊。但是写了很多个方法,软件后,会出现这种情况。一小部分的代码出现在了你的软件中的多个地方,写的时候复制一下没什么,问题是这个逻辑以后改起来就麻烦了。所有出现这个复制的代码段全都得改,一开始学习的时候我就经常苦恼于这种问题,也是因为入门的书上不会讲解这种软件开发思路。举个例子,你在你的程序中多个地方都使用了数据库连接字符串,当你服务器的IP地址变化的时候,就需要对所有的字符串进行变更,如果一开始就只定一个变量时,以后更改起来会非常的块。
有了这个思路的时候,后面写代码的时候不知不觉就会按照功能对代码进行方法提炼,比如代码块中有一段是现实数据加密解密的,就很适合提炼出单独的加密解密方法,这样就可以在程序的多个地方应用同一套逻辑体系,方便以后的变更。如果提炼了许多功能类型差不多的方法,这时候可以考虑将这些方法放到专门的一个类中,起一个合适的名称,方便自己以及别人查找。在你提炼了很多方法和类的时候,做的多了,会发现一些类或方法比较通用,需要在你自己开发的多个程序之间共用,以前的做法是在每个程序项目中进行拷贝,但是在优化或者BUG修复的时候就需要对每个应用程序进行维护升级了,而且很容易出错,留下安全隐患。
这时候可以考虑将通用的功能类提炼成一个单独的组件,方便在所有的程序中共用,你需要明白,提炼组件不是一件容易的事情,我们需要循序渐进的来做,刚开始可以提炼一些简单的类,方法,然后学习方法指针(C#中是委托),这个是进阶编程必须掌握的技能,一定要多花时间研究,它可以实现方法中的部分逻辑分离实现,从而达到更高的代码重用,在C#中还有一个技术是泛型,也是代码重用的关键技能(也可以成为算法重用)。这些技术可以提炼一些你以前代码中会碰到一个代码块因为中间的一点点代码不同而又很难提炼(刚开始学习时通常是参数提炼的方式)出来的情况。特别说明,程序的注释非常重要,之前我听到一个总监曾对开发人员说:“程序开发完成后,注释不要太多,影响项目加载速度,能删就删”,对此我不敢苟同,我的习惯是除了每个功能块方法必须的注释以外,在类库的顶端也会着重说明类的功能,已经使用的示例,在方法的内部也会分步骤进行说明方法到底实现了什么功能。对于程序员来说,写出来的程序绝大多数是被人来看的,包括我自己,良好的注释绝对大大提升别人(甚至是长时间以后的自己)理解代码的速度。
如果你一直按照这种思维方式学习和写代码,久而久之你就代码自然就会有很强的维护性和可读性(通常对于自己来说,我刚开始学习时,看到这种代码就觉得反人类啊,方法这里跳那里,跳来跳去哪里都不知道了),也可以形成自己的组件库来加速应用程序的开发。但是有一点还是不得不明白,代码的提炼并没有任何的提升应用程序的性能,相反的还有可能损失了一点点性能,而且更要命的是对于异常的控制更加的困难,将在下一节说明。
2.异常处理
我相信所有写过代码的人都碰到过开发的应用程序突然奔溃的场景,我们在开发测试功能的时候发生了一些异常导致程序奔溃无所谓,没什么损失,只要发现异常并修复就好了,比如说经常会碰到空对象的操作,数组的索引值小于0或超出最大值,更复杂的异常在多线程的程序中发生了资源竞争或是逻辑错误导致的,这种异常往往更难重现,非常难以排查,即使在今天,开发多线程的程序仍然是一件非常困难的事情。
上述所说的异常在学习和开发中肯定会经常碰到,但是本节所说的异常要比我们平时所说的异常要上升一个层次,上述所说的代码异常在调试阶段基本都能测试出来,并能完美的解决这些异常。本节所说的是业务逻辑的异常,通常在我们的程序中都会需要读写文件的代码,网络请求访问的代码,恰恰是这两个功能是最容易出现异常且无法避免(比如说读取文件,谁知道用户会不会手贱把它删除了呢,至少网络功能,会不会拔网线)。
如果你的应用程序业务比较复杂,比如服务器程序(S)向设备A请求数据,S再进行分析存储到文件或数据库,将结果发送给设备B进行响应操作,这是一个典型的业务处理模型。我们要清楚,如果每个环节顺利完成,代码也会非常简单,但是问题就是中间的每个环节都是可能发生异常的,而且是不可避免的,尤其是建立在局域网的通信基础上,谁知道什么时候会不会坏,会不会不稳定。我们需要做不仅仅是捕获异常不让应用程序奔溃这个事情,还要结合业务逻辑来实现,假设S-A失败了怎么办,S分析保存失败了怎么办,前面都成功了,S-B失败了怎么办,是否需要状态的额外提醒,是否需要回滚状态,这里的例子只有三个步序,如果长达十步的操作,任一环节的出错都要仔细考虑应对结果。
我们在调用第三方组件的类库时(包括微软自身的类库),也是没有办法控制方法不发生异常,因为这些代码本就不是我们自己写的,好在微软类库提供的方法明确表明了调用该方法时可能会发生的异常,好让调用者心里有数,好根据自身的需求进行处理异常,这就要求我们自己在开发类库时也要遵循相应的准则,例如我们在类库时开发了一个类,包含了一个事件,在实现事件调用的时候,就应该把调用的异常重新抛给调用者的应用程序,假如类库吞噬掉了一个异常,但是对于调用者却一无所知,应用程序却还在运行,会留下非常大的安全隐患。
并不是所有的代码都要增加异常捕获,虽然程序健壮了,但是开发效率和性能都下降了,至于中间的取舍平衡,只能靠经验来自己摸索。
3.状态恢复
我们的应用程序(尤其是服务端的程序)总要处理一些数据,而且经常是24小时不间断的在处理数据,缓存数据。这部分的内容总结起来其实很短,只有一句话,我希望我的软件系统即使现在关闭,再打开,对生产环境的影响微乎其微。怎么解释呢,还得使用例子再说明。
假设我们的服务器端应用程序需要访问现场10台设备的数据(此处简单处理,只限于读取),每台设备都是一致的,包含设备状态(是不是在运行中),实时数据(比如说温度压力),条码等等。我们的软件系统除了实时读取数据外,还需要进行缓存数据,比如,每台设备缓存的温度压力曲线,比如上一次设备停机时间,启动时间(用来计算设备的总运行时间),设备状态等等。
想象一下,如果我们因为需要更新软件程序需要关闭程序几秒钟,然后再运行,这时候所有的状态数据全部丢失,系统重新进行计数统计,如果你的系统本就不在意这些缓存信息,当然就不需要这些考虑了,但是一个完善的,友好的软件系统就需要考虑这些不为人所注意的方面,因为在我的经验看来,运行在现场的软件系统经常会因为各种各样的原因导致程序需要重启一下,对此,我们在设计程序的时候,就要充分的考虑到这种情况,对运行到当前的状态进行存储,下次运行时可以直接恢复,并且可以检测上次关闭的时间,根据程序关闭时间长度来选择性的恢复状态。
如果一个系统只有一个服务器,状态恢复相对也会简单一点,如果是分布式的多服务器端程序交互,整个系统状态的恢复将会是极其困难的,即时开完完成后,也需要花大量的时间来测试系统的稳定性。
参考资料------《重构改善既有代码的设计》
------《CLR Via C#》
一个C#版本的C-S项目模板地址:https://github.com/dathlin/C-S-
从编程到软件工程