分类:
2008-03-10 09:29:18
要开发某个项目里某个模块,通常都遵循一个指导,并有一个一般化的流程,在做任何事情之前,有个规划和原则是明智的,无论是核心代码基础设计,还是界面基础设计,以及遍布整个开发生命周期各个阶段的各种约定,规则,这些都是基础。
建立核心代码基础架构是一些细小而相关的任务的集合,它提供一些整个模块都需要基础服务,全局变量配置服务,安全基础服务,数据访问服务,调试跟踪服务,日志记录服务,错误处理服务,以及其它一些通用的服务,之所以把这些功能称为服务是因为你在写以后的业务编码的时候可以以松散耦合的方式来使用这些服务,你在设计基础架构的时候要尽量做到这一点,这使你的架构变的灵活,在以后修改架构的时候不至于过多的牵连到其它层次的代码,并且这些写出的架构有很好的可扩展性,在你的架构不支持某项新业务的时候可以在基础架构里添加一个服务来支持它。架构不易写的过于细致,造成过渡设计,也不应该写的过于简单,没有什么实际作用,不要走这两个极端,关于这方面的争论足可以写一篇长篇大论的文章。在ASP里主要就是写一个通用的函数,放在某个固定的文件夹里,在使用某项架构服务的时候把它包含进去,然后调用里面的函数。在ASP.NET里就是写一个或几个独立的类库,供使用的时候调用。关于建立架构大家可以参考一些其它的书籍,推荐看一下《asp.net电子商务高级编程》里使用的那个基础架构和《.net企业应用高级编程》里的那个架构,它们会给你相当多的指导,这两个架构一个适用于小型的电子商务平台,一个适用于中等规模企业的应用平台,后者使用了很多企业应用技术(比如说remoting,性能计数器等)来支持企业级应用的开发。
界面基础设计,提供一些界面显示的重复元素(导航,脚注等),以及提供软件界面一致性的功能。Web开发里常用css来保持整个站点风格的一致性,然后写一个HTML模板或者输出HTML代码的函数以供多个页的重复调用,在ASP.NET里可以有更多的选择,用户控件,服务器控件等,总之,这些都是一些客户端的技术,需要对xml,xslt,css,javascript,html等技术有个了解。关于如何提高软件的用户体验可以参考其它一些文章,推荐微软出的《windows用户体验》(《windows user experience》)一书。如果你开发的是WinForm程序,建议看看office 2003系列软件的界面和易用性设计,如果你是开发WebForm程序建议看看微软的官方网站,并思考一些东西。
各种约定和规则包含的方面有很多,有编码约定,命名规则,文件夹设置,命名空间约定,以及各种文档约定等。在设计开发之前尽量确定它们,几乎各种语言都有自己的编码和命名约定,比如说在表示常量的时候都用大写字母,私有变量用下划线开头(类c的语言,如果使vb的话,私有变量可以用下划线结束)。也许你公司内部的各种约定和厂商和标准组织提供的约定有所冲突,但是建议还是使用你的开发小组已经习惯了的命名规则,因为强制学习另外一种规则需要时间。尽量保持这些规则在同一个项目的各个模块里遵循一种原则,保持一致性。文件夹设置也有一套规则,比如说Control目录里放置用户控件,Style目录里放置样式表文件,一个项目分配一个子目录等。类库命名空间用 “公司名.产品名.项目名开头”,为每个项目分配有意义的名字空间可以增强程序的可维护性。
开发流程也非常重要,它来保证你在开发中有条不紊并提高开发效率,避免走弯路。在做完需求后,我们按一下流程来完成开发。首先使目标陈述和问题描述,这包括罗列要实现的功能,权限描述,画用例图,写用例描述等。这个过程的结果为后面的工作提供指导,在开发过程中要经常看看你在这个过程写的东西,并在必要的时候修改更新,因为客户的需求常常也会更新增加的。因为我做的一般都是小工程,我们的项目也不可能遵循大型项目开发流程,不必画完整的UML图,也不遵循RUP,XP,FDD里面的过多规则,一切出自于一种经验。
描述完功能后开始设计数据库,设计完数据库后设计界面,设计完界面后根据界面知道来确定需要的编码,接下来编码,最后测试运行程序。我认为一个能让客户满意的程序最少要满足以下几点:完成主要的功能;安全;界面友好,易用性强;这也是衡量一个软件是否成功的最低标准。
这一切是一个简化的过程,我无法把他写的过于细致,也没有必要,每个人都有自己的开发习惯,而且这个流程和指导也会随着个人的经验而改动更新。
下面是我写的一个OA里的一个模块的开发过程,我试图使用我写的以上这些经验。
工作计划搜索模块
问题陈述
1. 按部门查看工作计划,指定部门名称;
2. 按人员查看工作计划,指定人员名称;
3. 查看日工作计划,指定起始日和终止日;
4. 查看周工作计划,指定起始周和终止周;
5. 查看月工作计划,指定起始月和终止月;
6. 查看年工作计划,指定起始年和终止年;
权限描述:
1. 管理员,总经理,董事长可以查看所有部门员工已提交的工作计划;
2. 各部门经理,主管可以查看本部门所有员工已提交的工作计划;
3. 每个员工可以查看自己的工作计划,包括已提交和未提交的;
角色设置:
1. 管理员
2. 董事长
3. 总经理
4. 部门经理
5. 员工
部门设置:
1. 市场部
2. 技术部
工作计划表【log】
【ID】 编号
【UserName】 添加用户
【Title】 标题
【Content】 内容
【AddDay】 填写时间
【Dept】 添加用户所在部门
【Class】工作计划类型,day,week,moth,year分表表示日计划,周计划,月计划,年计划
【State】计划状态,0为默认值,表示计划未提交,1表示计划已提交
工作日志搜索流程
搜索工作计划 判断角色 管理员⑶ 总经理⑶ 董事长⑶ 技术部经理⑵ 市场部经理⑵ 普通员工⑴ ⑴只能查看自己的工作计划 ⑶可以查看公司所有员工及自己的工作计划 ⑵可以查看本部门员工和自己的工作计划
图1 权限判断流程
搜索工作计划 选择日志类型 日计划 月计划 周计划 年计划 选择起始日期和终止日期 选择起始周和终止周 选择起始月和终止月 选择起始年和终止年
图2 选择日志类型
设计界面
在设计好数据库,并确定了一定的业务流程后就可以着手设计用户界面了。用户界面不仅提供一个用户入口,还为你设计后台编码提供了知道,界面设计出来之后基本上就可以确定后台要编写哪些代码来相应这些界面了。为了提供一致的用户体验,我们可以用html+css+javascript来没让windows程序的效果,选择了不同的日志类型后时间范围会有所变化,这样习惯使用windows的用户在使用软件的时候不至于困惑。
按日查看
按周查看
按月查看
按年查看
下面是生成界面的主要代码
<%
dim i,sql
ShowSearch()
%>
<%Sub ShowSearch%>
提交"> border="0" align="center" class="InputFrameMain">
重置">
<%End Sub%>
开始编码
正式编码前要确定编码约定,命名规则,以增强编码的可读性,方便别人查看代码,也方便日后维护,如果用结构化编码,需要考虑各个函数的粒度,函数要尽量写的小巧而独立,尽量避免写功能强大,代码繁杂的函数,在面向对象编码里的方法也要遵循这个原则。首先要根据上面画的流程图来分解任务,确定每个独立的用例或者功能,每个独立的功能写个小函数,最后用一个总函数来组织调用这些小函数以完成特定的任务。我没有画类图,只是简单罗列一下,这通常比较简单也很有效。
SearchLogByDay() |
按日搜索 |
SearchLogByWeek() |
按周搜索 |
SearchLogByMonth() |
按月搜索 |
SearchLogByYear() |
按月搜索 |
GetViewLogFlag(Role) |
'获取某个角色查看日志计划的权限, '返回值1表示只能查看自己的日志计划 '返回值2表示只能查看本部门员工的日志计划 '返回值3表示能查看任何部门及任何人的日志计划 |
FillSelectDept() |
填充部门下拉列表 |
FillSelectUser() |
填充用户下拉列表 |
SearchLog() |
日志搜索函数,主函数入口 |
GetDept(UserName) |
返回某个用户的部门 |
chks(Flag,PageFlag) |
返回一个数字是否在一个用逗号分隔开的字符串里,因为一个许日志查看许可对应着若干个角色,所以要判断某个角色是否有某种的日志查看授权 |
GetDeptName(DeptID) |
根据部门ID返回部门名称 |
|
|
设计一个优秀出色的函数并不容易,你必须小心翼翼的处理函数的不便量,前置条件,后置条件,比如说你要的函数需要一个大于0的参数,但是传递进来的参数是负数,或者你的函数需要返回一个不会空的值,但是最后经过计算得到的值为空,遇到这样的情况你都得考虑去如何处理,你应该尽力的考虑周全,因为你处理不好的话,你的函数很可能给调用者返回一个错误的值,这样会影响别人的工作。你还得处理好你的函数执行过程中可能出现的错误和异常,常规错误的话可以用返回码的形式通知调用者出了什么情况,而出现异常的话,你要尽量保护现场,并尽量不要改变整个系统的状态,尽量报告多的信息给调用者,以便他们决定下一步该如何做。尽量避免你已经修改了某个表的数据后函数出现异常并且无法回滚,你应该用事务确保你的函数里的实际操作(数据库操作或者文件操作等)要么全部执行完毕,要么什么操作都没执行,不能执行了半截由于出错停到了那里。
设计一个好的函数还有好多其它的指导和技巧,不过暂时只能介绍到这里,这些基础的东西要常看常练习才能体会。判断一个函数质量也还有好多其它的标准,比如说代码是否紧凑,注释是否合理,可修改性,强壮性等。
经过推敲我还是决定把SearchLogByDay(),SearchLogByWeek(),SearchLogByMonth(),SearchLogByYear()这几个函数去掉,而把他们集成到SearchLog函数里,这样看起来很紧凑,不至于在你查看代码的时候看到主函数调用某个函数后又把注意力转移到另一个函数的定义那里,这样做会增加你使用滚动条定位代码的机会,还有一个原因就是这几个函数只用一次。当然有些功能用粒度更小的函数来实现更好,比如GetDept(UserName)函数,在主函数里有好几次要调用它,所以把它封装成一个小函数。具体写代码的时候尽量避免各个层次上的代码重复,它会给你带来维护恶梦,让你的程序在重构时增加难度。关于重构这项技术可以利用搜索引擎查看相关资料,在新版.NET里实现代码重构已经非常容易了。另一个就是避免你的函数互相调用的嵌套太深,函数互相调用的层次太深的话有时候自己也会被各个函数之间的调用关系而迷惑的。
下面是经过考虑后写出的代码,经过测试,它可以用。的确,这不是最好的代码,实现上面的需求可以有很多可替代的方案,但这几个函数来执行的任务已经相当复杂了,并且函数结构本身也相当复杂了,但不是最复杂的情况,我已经尽量保持它结构清晰,并提供最少的必要的功能,因为函数要保证安全,所以里面加了权限判断的代码,所以才看起来很大。
<%
'执行搜索,实际应用中应当把UserName,MyDept,MyRole保存在Session里
SearchLog "jack",3,8
Dim rs '全局记录集
Function SearchLog(MyUserName,MyDept,MyRole)
'定义私有变量
Dim sql 'sql字符串
Dim LogType '查看日志的类型,值可以是day,week,month,year和空
Dim Day_StartDay,Day_EndDay '按日查询所需要的参数
Dim Week_StartYear,Week_EndYear,Week_StartWeek,Week_EndWeek '按周查询所需要的参数
Dim Month_StartYear,Month_EndYear,Month_StartMonth,Month_EndMonth '按月查询所需要的参数
Dim Year_StartYear,Year_EndYear '按年查询所需要的参数
Dim Dept '按部门查看所需要部门名称
Dim UserName '按用户查看所需要的用户名称
Dim ViewType '用户日志从查看类型
Dim ShowStr '显示字符串,用来告诉用户正在查询日志的范围
'通过Request给私有变量赋值,在asp里Request就是全局的对象,省了写那么多函数的参数了,直接接就行了
LogType = Request("LogType")
Day_StartDay = Request("Day_StartDay")
Day_EndDay = Request("Day_EndDay")
Week_StartYear = Request("Week_StartYear")
Week_EndYear = Request("Week_EndYear")
Week_StartWeek = Request("Week_StartWeek")
Week_EndWeek = Request("Week_EndWeek")
Month_StartYear = Request("Month_StartYear")
Month_EndYear = Request("Month_EndYear")
Month_StartMonth = Request("Month_StartMonth")
Month_EndMonth = Request("Month_EndMonth")
Year_StartYear = Request("Year_StartYear")
Year_EndYear = Request("Year_EndYear")
Dept = Request("Dept")
UserName = Request("UserName")
ViewType = Request("ViewType")
'对接受到的Request的值做进一步的处理来保证生成sql语句能查询出想要的结果
If Day_EndDay = formatdate(now(),"YYYYMMDD","-") Then Day_EndDay = formatdate(DateAdd("d", 1,now()),"YYYYMMDD","-") '如果结束天是今天,要加一天,否则不能显示今天的日志计划
If Len(Month_StartMonth) = 1 Then Month_StartMonth = "0" & Month_StartMonth '如果是小于10一下的月,要在前面加0,否则SQL语句的结果不对
If Len(Month_EndMonth) = 1 Then Month_EndMonth = "0" & Month_EndMonth
'初始化sql
sql = "select * from [Log] where [ID] is not null "
'判断日志查看类型
Select Case LogType
Case "day"
sql = sql & " and [Class] = 'day' and [AddDay] between '"& Day_StartDay&"' and '"&Day_EndDay&"'"
ShowStr = "您正查看的是"&Day_StartDay&"到"&Day_EndDay&"的日计划"
Case "week"
sql = sql & " and [Class] = 'week' and DATEName(year, [AddDay]) + DATEname(week, [AddDay]) between '"&Week_StartYear&Week_StartWeek&"' and '"&Week_EndYear&Week_EndWeek&"' "
ShowStr = "您正查看的是"&Week_StartYear&"年"&Week_StartWeek&"周到"&Week_EndYear&"年"&Week_EndWeek&"周的周计划"
Case "month"
sql = sql & " and [Class] = 'month' and DATEName(year, [AddDay]) + DATEname(month, [AddDay]) between '"&Month_StartYear&Month_StartMonth&"' and '"&Month_EndYear&Month_EndMonth&"' "
ShowStr = "您正查看的是"&Month_StartYear&"年"&Month_StartMonth&"月到"&Month_EndYear&"年"&Month_EndMonth&"月的月计划"
Case "year"
sql = sql & " and [Class] = 'year' and DATEPART(year, [AddDay]) between "& Year_StartYear&" and "&Year_EndYear&""
ShowStr = "您正查看的是"&Year_StartYear&"年到"&Year_EndYear&"年的年计划"
Case else
ShowStr = "您正查看的所有类型的计划"
End Select
'Response.Write("ViewType:" & ViewType & "
")
'Response.Write("ViewLogFlag:" & GetViewLogFlag(MyRole) & "
")
'Response.Write("Dept:"& Dept & "
")
'Response.Write("MyDept:"& MyDept & "
")
'判断用户查看类型
Select Case ViewType
Case "dept"
Select Case GetViewLogFlag(MyRole)
Case "1"
Response.Write("你没有查看部门员工工作计划的权限"):Response.End()
Case "2"
If Cstr(MyDept) = Cstr(Dept) Then '这里一定要加上Cstr,不加不行,
sql= sql & " and state=1 and dept='"&MyDept&"'"
ShowStr = ShowStr & "
当前查看部门是"& GetDeptName(MyDept)
Else
Response.Write("你没有查看本部门员工工作计划的权限"):Response.End()
End If
Case "3"
sql= sql & " and state=1 and dept='"&Dept&"'"
ShowStr = ShowStr & "
当前查看部门是"& GetDeptName(Dept)
End Select
Case "username"
Select Case GetViewLogFlag(MyRole)
Case "1"
Response.Write("你没有查看其它员工工作计划的权限"):Response.End()
Case "2"
If Cstr(GetDept(UserName)) = Cstr(MyDept) Then '判断是否是本部门员工
sql= sql & " and state=1 and username='"&UserName&"'"
ShowStr = ShowStr & "
当前查看用户是"& UserName
Else
Response.Write("你没有查看本部门员工工作计划的权限"):Response.End()
End If
Case "3"
sql= sql & " and state=1 and username='"&UserName&"'"
ShowStr = ShowStr & "
当前查看用户是"& UserName
End Select
Case Else
sql= sql & " and username='"&MyUserName&"'"
ShowStr = ShowStr & "
当前查看的是你自己的工作日志"
End Select
Set rs=server.CreateObject("adodb.recordset")
sql = sql & " order by AddDay desc,id desc" '加上排序语句
Response.Write(sql) '测试
Response.Write("
"&ShowStr)
rs.Open sql,conn,1,1
if not(rs.eof) then
Deptname = GetDeptName(rs("dept"))
Dim mType
Select Case rs("class")
Case "day"
mtype="日计划"
Case "week"
mtype="周计划"
Case "month"
mType="月计划"
Case "year"
mType="年计划"
End Select
end if
End Function
Function GetDeptName(DeptID)
Dim rsDept,deptname
Set rsDept=conn.Execute("select deptname from dept where deptid="&DeptID)
GetDeptName=rsdept(0)
End Function
'返回某个用户的部门
Function GetDept(UserName)
Dim rs
Set rs = Conn.Execute("SELECT DeptNo FROM [users] WHERE username = '"&UserName&"'")
If Not Rs.Eof Then GetDept = rs(0)
rs.Close:Set rs = Nothing
End Function
'设置角色ID,下面的常量可以单独放在一个Const.asp里,便于配置,或者可以在Golbal.asp里让程序启动的时候从数据库里动态加载,以便确定哪些角色有哪些许可,如果角色的授权许可经常变,只能让它每次使用的时候都实时从数据库调了,这里设置成常量是因为我的系统里角色对应的许可授权不经常变,并且这样减少数据库操作提高了性能,并增强了灵活性(至少比在每个函数里硬编码灵活)
Const ROLE_ADMIN = ",4,5,6,7" '可以查看所有人日志的角色ID
Const ROLE_DEPT_ADMIN = ",8,9,10,11,12,13,14" '可以查看部门日志的角色ID
Const ROLE_USERS = ",15,16,17" '之可以查看自己的日志的角色ID
'获取某个角色查看日志计划的权限,
'返回值1表示只能查看自己的日志计划
'返回值2表示只能查看本部门员工的日志计划
'返回值3表示能查看任何部门及任何人的日志计划
Function GetViewLogFlag(Role)
If chks(ROLE_ADMIN,Role) Then
GetViewLogFlag = 3
End If
If chks(ROLE_DEPT_ADMIN,Role) Then
GetViewLogFlag = 2
End If
If chks(ROLE_USERS,Role) Then
GetViewLogFlag = 1
End If
End Function
Function chks(Flag,PageFlag)
Flag = "," & Flag & ","
PageFlag = "," & PageFlag & ","
Flag = Replace(Flag,",,",",") '替换相邻的逗号
Flag = Replace(Flag,",",",") '替换中文逗号
PageFlag = Replace(PageFlag,",,",",")
PageFlag = Replace(PageFlag,",",",")
If InStr(Flag,PageFlag)>0 Then
chks = True
Else
chks =False
End If
End Function
%>