代码是如何生长的

2014年,我仍是一名大学生,在兴趣的驱使下走上了编程的道路。后在各类洪荒之力的推进下于2016年7月开始耗费半年多时间编写了一个叫作miniqueue的网站,项目虽然是失败的,不过经验是宝贵的。完成这个网站后,我写下了下面这些文字,它本已被我遗忘在脑海,今天整理电脑才又发现了它,读之,感受很有受益,在此分享,给学习编程的新手们,或是如我通常在个中摸爬滚打过的猿们。程序员

代码是如何生长的

这是一篇对miniqueue开发以来的总结性论文。数据库

设计是一个险恶的问题:

软件编程中,最重要的一环能够说就是软件设计,也叫软件架构,这是决定一个软件质量优劣的最根本一环。这一点从程序员的发展规划中就可见一斑(顶尖程序员每每冠以“软件架构师”一名)。经过这个软件,我有幸体验了一次糟糕设计下的编程,并逐步重构,转向良好设计。编程

设计是一个险恶的问题。根据Horst Rittel和Melvin Webber的定义,“险恶的问题,就是那种只有经过解决或部分解决才能被明确的问题。”这个看似矛盾的定义实际上是在说,你必须经过把某个问题解决一遍,才能明确的定义它,而后再次解决,以造成一个可行的方案。小程序

现实世界中一个极好的例子就是Tacoma Narrows大桥。这座位于美国华盛顿州塔科马的悬索桥,于1940年一个狂风大做的上午,因为在风力做用下摇晃剧烈而坍塌。直到这座大桥的坍塌,工程师们才知道应该充分考虑空气动力学因素。只有经过建造这座大桥(即解决这个问题),他们才能学会从这一问题中应该考虑的额外因素。安全

miniqueue的后台在早期阶段,沿用了制做小型程序的办法,即没有太多的考虑系统的层次性和模块性,一切以实现功能为主。在数据库和界面之间,只有一层粗糙的数据库操做层。这时的系统开发速度极快,由于它实际上忽视了不少关键问题。随着系统的不断扩展,这个数据库操做层变得愈来愈臃肿,并且难以维护。好比,每一个数据库的表在数据库操做层都映射了一个类,但表和表的关系有时并非独立的,有些表须要相互协做,以完成某些操做,此时联合的行为就被推到了界面层,这致使界面层原本用以调度页面,却附带要处理不少业务逻辑的问题。架构

因而我着手重构系统,在新设计的系统里,我在数据库操做层之上,界面层之下,添加了一个层,叫作系统层。系统层将会调用数据库操做层的接口,以实现数据库各个表的相互协做,由此它能够给界面层提供一套更加抽象的接口,这套抽象能够很好的隐藏数据库结构,客户看到的将是一个完整的系统。如,节点系统(TableSystem)设计了两个类——TableOperateSystem和TableFindSystem,其中TableOperateSystem就提供了一个建立新节点的接口createTable()。但节点系统并不映射那张叫作Table的数据库表,由于建立新节点这个动做实际上涉及不少张表的改动。如此一来,底层的数据库操做就和顶层的界面分离了,如今界面层仅和系统层打交道。编程语言

经过这一步重构,我还发现了一个额外的问题,系统的逻辑应该由系统层来接手。你很难想象,第一版的系统尽然是由界面来处理整个系统逻辑的。你能够干什么,不能够干什么,全由界面说了算。由于整个底层将数据库操做的接口都暴露了。若是我不进行此次重构,我甚至都不会发现这个问题的存在,正是因为系统层的出现,使得我对整个系统的理解更加深入,明了,才促成了这一意外发现。模块化

然而好景不长,在系统层,那个老问题再次显现——表与表的关系有时是相互关联的,这意味着,底层数据库操做层的某些类会在系统层纠缠在一块儿,这种纠缠带来的某些接口的联合调用会重复的出如今系统层的不一样位置,这显然不是咱们所想看到的。因而另外一个层显现了,此时思路变得很是清晰,明了。咱们缺一个数据模块层,用来将底层的数据库操做层模块化,提供某种比离散的数据库操做更加统一的接口,以便消除系统层里的接口纠缠!到目前为止,系统被设计成了四个层次,由顶向下分别是界面层,系统层,数据库模块层,数据库操做层。学习

在没有解决这个问题前,我认识不到系统分层的优点,也许从各类书籍中读到了相关内容,但没有实践经验的支撑,那些文字描述在个人脑海里始终是个空壳,而如今,什么是分层,什么是模块化,我已经有了全新的认识。测试

看清类的合适粒度:

类应该有多大?在极端条件下,一个类能够吃下整个系统的所有代码,或者各个类有且仅有一个接口,这样每一个类就至关于一个方法。这两种情形就是类的两种极端粒度,它们都不是咱们想要看到的。那么问题出现了,类应该有多大?

软件设计原则中,有一条“单一职责原则”,简称SRP,它描述的是,类的职责要单一,不能将太多的职责放在一个类中。在没有经验支撑的状况下,它不过是一句空话,由于它什么也没告诉你,当你面对一个系统的时候,你仍是不知道一个类作到何种程度算是知足了单一职责原则。

这个问题在作miniqueue的时候始终困扰着我。在最初的设计中,数据操做层里的每一个类,对应一张数据库表,我认为这个类就知足单一职责了,由于它只对一个表负责。在开发初期,这些类确实可用,它们的粒度算是合适的,没有对我形成什么困扰。然而随着系统的发展,这个类开始“爆炸”了。因为它处理了数据库的增删改查四个功能,因此每当我须要它知足一个新的功能的时候,它都须要添加一项新的接口,因而我得再一次深刻类的内部去编程。这时你会发现你违反了另外一项原则——“开闭原则”简称OCP。“开闭原则”说的是,软件实体应该对扩展开放,对修改封闭。什么是扩展,什么是修改?这个问题我也是在不断的开发中才切实领会的!具体来讲,软件开发应该以类为最小单位,当一个类投入使用,它就不该该再被修改,注意这里的修改指的是这个类的接口,也就是它全部的public成员。

将类视为最小单位意味着,当你编写或者修改一个类的时候,该类是不可靠的!任何在构建中的类都是不可靠的,若是该类已经投入了使用,说明它已经过测试,系统认定它是可靠的,此时若是你从新在编译器中打开该类,试图对它进行修改,那么一个可靠的类将会回到不可靠阶段,这对整个系统的安全性和健壮性是极大的威胁。因此OCP说要对修改封闭,要让投入使用的类再也不发生变化。那么如何应对系统的功能扩展呢?以类为单位扩展!这意味着当你写1.0版时,整个系统的架子得容许新类的加入。有了这样的认识,类的合适粒度就有判断依据了。一个类不能作太多事,以致于让它宣称,它已经完整的处理了某件事,这样的类当需求变化时,它便不得不作出修改,从可靠回到不可靠。一个合适粒度的类应该作出这样的宣言:我已经完成了一个小功能,但我并无完成全部工做,若是你有其余须要,请查询和我相似的其余类。此时软件实体就能够作到OCP——能够扩展类,但不能够修改类。

让人沮丧的是,类的合适粒度永远没有正确答案,重构是编程中一件永远也干不完的事,但从合适的角度思考问题,能让你的系统工做的更舒服,让你的代码看起来更让人赏心悦目。

如何在独自编程的状况下看出层次:

软件的首要使命是:管理软件复杂度。Steve McConnell在那本著名的《代码大全》中使用了大量篇幅讲述这个问题。计算机先驱Edsger Dijkstra这样描述:“在语义的层次量上相比,通常的数学理论几乎是平坦的。因为提出了对很深的概念层次的须要,自动化的计算机使咱们面临一种本质上全新的智力挑战。”因为软件逻辑层次太深,现代计算机语言一路从打孔纸带发展到了面向对象编程语言,不一样解决方案的提出,本质上就是为了解决一个问题——管理软件复杂度。

新兴的面向对象编程语言其最大的优点就是有效的封装了复杂。然而在一个软件新手面前,面向对象和面向过程几乎没有区别,你能够想象不少刚刚接触编程的人都在使用面向对象语言编写面向过程的代码。这也难怪,学校里老师布置的做业是固定的,需求不会更改,学生只须要写出代码,让计算机得出他们预期的结果,就算完事了。而在真实的软件编程中,需求总会发生变化,软件老是会调整本身的功能,以适应客户的痛点,还有一点不容忽视,那就是代码量。小程序的代码量一个程序员可能不出三天就能完成,这样的程序显然没有必要花费好几个小时的时间去设计层次和模块,加之需求不变,这么作简直是牛刀杀鸡。在miniqueue初期,我也遇到了相似的麻烦,在个人经验内,这样庞大的系统我尚未接触过,曾写过的小程序,小到5个类就能解决问题,若是你不嫌难看彻底能够用一个类吞下全部代码。

然而miniqueue不是一个课程设计式的小做业。到目前为止,它的代码量就已经超出了一我的的理解范围——即,你必须依靠类的封装性帮你安全的忽视系统的其余部分。有一个问题浮现了,在团队协做的状况下,人们自然须要设计接口,以实现系统不一样部分的衔接,即系统各个部分能够分别同时开发,只要你们事先约定接口。但我是独自编程,整个系统是一部分一部分完成的,不存在同时开发的问题,对于各个类的做用我很清楚,不一样的类如何协做我也明白,此时层次就显得异常难以察觉。如同完成一幅巨型壁画,若是是多人配合,你发现不一样的人能够被分配以完成不一样的层次,若有人画背景,有人画人物。但若是整幅画都由你一人完成,你极可能会从这幅画的某个角落开始画起,而不是先背景后人物这样分层实现。miniqueue的构建过程当中我就遇到了这样的状况,它蒙蔽了个人双眼,使得我花了很长一段时间才看出整个系统的层次性。

过后我作了总结,如何在独自编程的状况下,看出系统的层次(固然这个问题只对新人有用,由于你一旦经历过一次,这样的问题就不会再出现,由于你的思考角度已经发生了转变)?

首先,在编程以前,想象两我的物,一是你(类的编写者),一是客户(类的使用者),固然在独自编程的状况下,客户极可能就是3天后的你,但你要认为客户对该类的内部浑然不知,他只能看到这个类的接口,仅此而已。这样一来你就得认真编写类的使用说明,认真编写接口。而当三天后你开始使用这个类的时候,不要去想类的内部是如何实现的,仅仅盯着类的接口,若是你发现类的接口让你摸不着头脑,说明这个类的抽象是存在问题的,这时候不要使用你对它内部的知识来强行使用该类!你应该放下手头的工做,回到那个类的内部,看看是什么缘由致使了接口的模糊性!这是看出层次性的第一步,即,你要将类的编写和类的使用彻底分离,避免穿过类的接口去使用类。

其次,我要告诉你们一个浅显但却很是容易被忽视的事实——从接口的角度看,系统由底层到顶层是逐渐收紧的!这是一个摆在你面前你立刻就会想明白的道理,但实际构建系统的过程当中,特别是新手,很容易对它视而不见。表如今,既然这个类,顶层须要使用,为何不让顶层直接去调用它呢?若是你让顶层直接调用一个很是灵活的类(如同个人界面层直接调用数据库操做层),最后你会发现,实际上你不过是把不少事情压缩到了顶层去作,而顶层最后会被这些不该该属于这个层次处理的事情填的异常臃肿。而一个健康的系统,其底层应该提供数量众多,且灵活的接口,从这里开始,慢慢向上,每一个层处理一些事情,并告诉其上层,不用再关注它所处理过的事情,这样一层层向上,很复杂的一个事物将会被层次,分别一点点隐藏起来。当你看到一个灵活的类时,你就应该知道它在层级结构中应该是处于下层的,而当你看到一个接口比较狭窄的类时,你就应该知道它应该处在上层。

最后,实现分层的重要一点——观察接口的抽象!当你在设计一个类的时候,不要写任何代码,相反,你应该敲下一些文字,用以指导你的抽象。一个类是什么,不是由它的名字,或者你对它写的注释决定的,而是它的接口所表现出来的抽象。直立行走,杂食,不能飞翔,体力极好能够连续奔跑40千米,体长175厘米,体重70千克,智商100,对于这个对象,就算我给它命名animal,你也能看出来他是一个human。当你在编写程序的时候,优先考虑的是类的抽象,而不是内部的具体实现,你就可以看出分层的端倪了。由于“层次”说的就是某些抽象一致的类的集合。有了类的抽象,再思考层的抽象,就会清晰许多。无论是类仍是层,它们都是为了隐藏某些复杂性而存在的。

代码是如何生长的:

我是个编程新手,又恰巧在这样的状况下,试图去编写一个不简单的程序,致使我把,一段代码从“萌芽”到“破土而出”走了一遍。我想不是任何人都有这个机会,毕竟参加工做后你们面对的是软件架构师已经搭建好架子的程序,多数人并不须要本身重头去架构一个程序,况且是在经验匮乏的状况下去架构一个程序。这一路走来,我推翻了不少设计,重写了不少代码,它们成为了回收站里的牺牲品,但在个人脑海里,它们为我构建了一个全新的编程世界。总结来讲,永远不要有一劳永逸的想法,代码历来不是固定的,它一直都在生长——何为生长?——在迭代中精进与断舍离。

相关文章
相关标签/搜索