【写在前面的话:看看本文的时间,最后一次保存是去年的八月份,想想,还是先发表一下,然后待续,老规矩,在本文完成之前请不要转载。】
你是一个有控制欲的人么?你希望把控你的项目么?那么,切分他们吧。
1、为什么要切分你的项目
我们是邪恶的 -.-。我们希望把控我们的项目,所以,我们需要切分它。
想像一下,你有一个生日蛋糕,你想吃掉它,那么,按照惯例,作为寿星的你,应该先切分它,什么?你还要点蜡烛?OMG,你要在吹蜡烛时喷口水在蛋糕上吗?你的口味好重。
切分代码,大体上来说,在控制进度、应对变化、简化逻辑、代码复用、分工协作等方面,都有相当的好处。
1.1、控制进度
首先来说控制进度,当你开始开发一个大型站点时(当然,更多的时候是“你们”),作为项目经理的你来讲,你需要时不时的应对来自你的上级或者你的客户的询问–“小韩,做了多少了,还有多久能做完啊”。这个时候,你总不能说,“哦,我做了一些了,还有一段时间能做完。”
你的领导希望听到的是,系统一共有x个模块,我做完了x个,剩下x个模块,难度有些大,可能还需要x天的时间,等所有模块都完成了,再整体整合测试一下,就OK了。这样回答,我想你的表现会让你的领导觉得,你是一个思路清晰,能够把控的项目管理人员。这里再容我跑题一下,一般认为,管理职能分为“计划、组织、协调、控制”等。其中“计划”则首先依赖于对项目的切分,如果不能切分项目,则无从进行计划。
1.2、应对变化
我们都知道,项目,尤其是软件项目是多变的,这种变化可能来自于前期沟通的不完善,也可能来自于随着时间的发展,客户或软件使用者的需求随之发展而发生变化。如果项目中所有的部分都是互相缠绕的,那么则牵一发而动全身,你甚至无法对一个简单的变量名进行改变,而使得系统的其他部分不受到干扰。然而此时,如果对系统进行划分,将其分为若干层次、若干模块,层次与层次、模块与模块之间通过既定协议(如接口)进行通讯,则可以将原有的整体复杂系统切分为若干个子系统、子模块、子程序,各程序只要保持其输入、输出不变,则其内部的代码可以方便的更改而不怕会影响到外部。
我举个例子来讲,你可能用以下代码处理用户的登录以及其登录会话的持久化(Session)。
//登录代码
if($success_login) //假设$success_login为上一步判断用户名密码正确与否的标记
{
$_SESSION['user_name'] = $user_name;
$_SESSION['password'] = $password;
}
而使用如下代码判断是否登录
if(isset($_SESSION['user_name']))
{
//do something
}
那么假设有一天,你想使用username作为保存用户名的Session变量名称,则你需要找出所有形如$_SESSION['user_name']的代码,并修改,一旦有遗漏,你的程序将会出现难以查找的bug。
这就是因为在代码中,把用户登录会话的部分耦合在了全部的系统当中。实际上,你应该专门有一个管理用户登录会话的模块,比如叫做SessionManager,你的代码则可能变为这样:
/**
* 设定登录会话
*/
if($success_login) //假设$success_login为上一步判断用户名密码正确与否的标记
{
SessionManager::SetLoginUser($user);
}
/**
* 判断登录状态
*/
if(SessionManager::IsLogined())
{
//do something
}
这样,你在系统中,无需过度关注用户会话保存的具体实现,只要去调用SessionManager这个管理器的相关方法就可以了,而一旦发生前面提到的变化,你可以在SessionManager的内部完成此修改,而不会将这个问题渲染到全部代码。
1.3、简化逻辑
也许大部分普通的Web站点并没有复杂的逻辑,但是,随着网络世界越来越精彩,Web站点也会逐渐面临处理复杂逻辑的任务。一旦你面临复杂的系统,其中可能难倒你的,大部分不会是技术问题,而是逻辑问题,我们应当承认我们人类中的大部分,思维的稳定性和思维长度,都是有限的,我举个例子来说,在下棋的时候,在头脑中演算下一步应该如何走,对手针对你的下一步如何走这样的逻辑,没有经过一定锻炼的人,是不可能演算很多步的。对于系统逻辑来说也是一样的,同时考虑过多的问题,以及这些问题之间的关系,会让你无法承受,然而此时将一个大的、复杂的问题切分为若干小问题,在同一时刻只关注一个小问题,则可以让自己轻松快乐的去进行程序的编写。再举个例子,比如我们要写一个MSN的客户端,那么实际上是有很多问题要考虑的,你的程序如何与服务器通讯,如何构造和发送数据,如何接受和处理数据,如何将数据显示在界面上……so many,这个时候你可以将你的系统切分为若干的模块,有的模块去处理网络通讯,有的去处理数据包的解析和构造,有的去处理界面显示,有的去处理各种事件的调度,等等等等,在各个模块中,你还可以进一步划分,直到单个类遵循单一职责原则,而一个模块间各个类都是聚合的……等等等等,这里有很多方法和技巧,我们稍后会详细讨论。
1.4、代码复用
代码复用的一个重要基础是分割变化与不变的部分,并分别进行编程。还以我们上面的MSN客户端的例子来说,其处理网络通讯的模块,在其他的程序中是不是也可以使用呢?答案是肯定的,但是……这要求你能够良好设计这个模块,权衡其边界,以使其具有普适性。这种基础类的实现,不应当关联具体项目的问题,比如说最简单的,你的方法名,不应当叫做send_msn_request,而只是send_request,这只是一个很简单的例子,但是却说明了,你要考虑你系统中的某些聚合功能,是不是可以在其他项目中使用的,如果是,请将其切分出来,并撰写并非是与当前项目紧密耦合的代码,你可以使用一个适配器将其配接到当前项目中,而不是将其直接为当前项目撰写。
关于代码复用的内容,您还可以查看http://www.ibm.com/developerworks/cn/java/reuse/获得更多信息。
1.5、分工协作
分工协作首先要求在“工”上有的可分,现代生产是要求详细分工,流水线方式作业的,试想,哪怕一个简单的MP3,有做芯片的机器,有开发嵌入式系统的工程师,还有利用模具生产的合作工厂,最后将各个部分拼接起来,成为你手中那个会发声、可以控制的小玩意。程序开发也是一样的。每一个系统,无论简单复杂,总是由若干个子系统组成的,这些子系统通常是功能内聚的,并通过某些特定方式与其他子系统进行协作。简单说,你所看到的这个WordPress的系统,有文章的子模块、有分类/Tag的子模块,那么我们是否可以让两个人分别去开发文章的子模块和分类/Tag的子模块呢,答案是肯定的,合作者首先坐在一起,定义各自的系统边界,开发分类/Tag子模块的工程师无需了解文章是由标题、内容,甚至图片组成的,他只了解,需要给特定的文章(通过文章的id来作为关键标记),进行分类,或者标记Tag,同理,开发文章子模块的工程师也无需了解Tag和分类的区别,只需要做他的文章模块的CURD就可以了,彼此之间不需要了解太多,是增强系统稳定性,在变化时可以敏捷修改而不担心影响其他部分的重要方式。这可以降低系统的开发和修改成本,工程师之间不必担心进行了重复的工作,因为子系统边界已经确定,每个人需要做什么是非常清晰的,同时,他们不必担心他们所完成的功能如何被调用,因为这些调用规则在切分系统时就明确了。他们也不必考虑其他子系统是否会影响他的系统,包括其他子系统的bug或者修改,因为各子系统是高内聚、低耦合的,子系统的具体实现方式不会过多影响其他系统。
2、如何切分你的系统
切分系统是一种艺术而不是一种技术。从不同的角度看某个物体,你会得到不同的投影,一个正方形的投影,那么其原物体一定是正方体吗,当然未必。从不同的角度看待系统,就会有不同的切分方式。而我们要做的就是,试图从各个角度来看待系统,避免盲人摸象,最终选择一种权衡的、折衷的,对于系统的发展方向最契合的方式来切分。不同的程序员有不同的切分习惯,而我们也无法武断的去说一定要按照某种方式切分。毕竟,我们不是为了切分系统而切分,而是要达成某种目的,这些目的我在上面笼统的谈过,比如说,针对分工协作,如果团队中程序员的水平有一定差距,那么可以将相对内核的复杂部分切出来,交由有经验的程序员来完成,即时它可能在其他方面并不能达到你的目的。
切分系统有几种角度,让我们由浅入深的聊一聊。
2.1、横向切分
对于系统的切分,横向切分是最常见的,例如MVC模式:MVC(Model-View-Controller,模型—视图—控制器模式)用于表示一种软件架构模式。它把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller)。
这种切分方式主要考虑了系统是由几个层次来完成的,各个层次之间的功能是内聚的,比如在MVC中,Model层负责数据交互,它永远不必理解数据在界面上是如何展示的,同理,View层也不必理解数据是从MySQL数据库还是SQL Server甚至是文件中的哪个取出的。
这种层次的划分,可以将系统功能划分为几个大部分,这几个大部分相对独立,然而又通过行为/数据被粘合在一起,从而使得其中某个部分发生变化时,其他部分不至于那么敏感,比如,网站的界面是会频繁改版的,然而你会发现,几乎大部分情况都是,界面上的元素摆放发生了变化,但是数据本身并没有任何变化,那么我们只需要改变View层就可以了,你的C和M层的代码一点都不用改动。
另一个名词叫做“三层架构”,实际上和MVC类似,也有人将MVC直接成为三层架构。其代表是微软的示例程序PetShop(架构详细分析看这里)。其将系统大致分为DAL(Data Access Level,数据访问层)、BLL(Bussines Logic Level,业务逻辑层)以及界面表示层。
但是,我窃以为,仅有MVC是不够的。在MVC中,Model层的功能实际上是可以再分为数据逻辑和业务逻辑两个层次,即更倾向于三层架构。业务逻辑层可以复用多个数据逻辑层,这在仅有Model层的系统中是难以做到的,同时,业务逻辑是可能发生频繁变更的,此时简单的数据逻辑并没有发生任何变化,不需要花代价和承担风险进行修改。我举个例子来说,我们要设计一个用户系统,该系统有注册、登录、修改信息等功能,此时,我们可以根据需求制定出一个业务逻辑层,你可以将其叫做LogicOperation/UserOperation层(下文简称UserOper),在该层中切合需求进行功能的设计,即Login、Regist、ModifyInfo,那么,我们是否在这个层次中就开始撰写关于数据库操作的代码呢,答案是否定的,因为你会发现,在Login功能中,你需要去查询用户名是否存在这样的信息,在Regist中,你同样会查询该信息,此时,你应该继续抽象出一个功能相对原子化的数据操作层,即DataManager/UserManager,在UserManager中,实现标准的CURD(Create、Update、Read、Delete)功能,以及某些可能用到的其他功能,但是这些方法应该是原子化的,可以由UserOper组合调用,例如在Regist中,可能通过UserManager的IsAccountExist方法查询用户名是否存在,然后用CreateUser方法建立用户,而在Login中,首先通过IsAccountExist方法查询用户名是否存在,然后用ReadUserByAccount方法来获得用户信息,并继而比对密码,等等,你会发现,LogicOperation层是对DataManager层方法的组合调用,而其组合方式就是具体的业务逻辑。
2.2、纵向切分
2.3、混合切分
2.4、切分粒度
3、切分案例
3.1、一个CMS系统的切分
3.2、一个博客系统的切分
3.3、一个校内网APP程序的切分
4、切分作业
hanguofeng Web服务器端技术 PHP, 听小韩聊PHP项目开发