Technical Debt (技术债)1 – 项目中的坑儿的形成

程序员,为了短期利益,而采用一些 “脏技术” 来实现功能,这种做法造成的后果,称之为“技术债”。

在我经历的项目中,对技术债还有一个形象的叫法–“坑儿”。没掉进去之前,一切都风平浪静,一旦掉下去则后果无法预料,也许一个鲤鱼打挺就起来了,拍拍土就可以继续前进,也有可能就粉身碎骨万劫不复。如果坑儿多了,那掉进去的几率就是100%。

借用无间道的一句话就是,出来挖坑,迟早都是要填的。

如果项目交付前掉进去了,那就导致项目交付延期。如果运气好,坑一直埋伏到项目交付后,一方面已经没有这笔填坑预算了,另一方面坑又不填不行。。。

说到这里,不禁要问了,能做程序员的也都算是精英人士了,为啥还会干这些自己挖坑自己跳的蠢事呢?

原因无非都是时间不够,1、项目总体时间就不够,设计上就欠了债,不可避免的程序员就会人人欠债;2、个人时间安排有问题,前松后紧,最后只有把坑挖在这,眼前这关过了,以后谁掉进去算谁的。

既然如此,是不是有了足够合理的时间安排就可以避免这个问题呢?当然不是这么简单了,实际项目实施中,不会有完美的计划,也不会有完美的程序员,总会有这样那样的赶工,挖下各种各样的坑儿。

发表在 软件开发 | 留下评论

小议项目文化

所谓的“项目文化”指的是,当项目组面临选择时,绝大多数人都会做出同一个选择,那这个选择所体现的价值观就是这个项目的文化。

本人所在的项目在任务重、工期紧的时候,作息时间便调整为“朝九晚九、周六不休”,并在没有加班费的情况,持续了三个月。这个阶段,兄弟们也有怨言,也会疲惫,但所有人都选择了先把事情做完、做好,再去争取个人利益。这就是一种文化,可以称之为“新时代劳模文化”。

与之相应的我也见识过诸如:

“趋吉避凶文化”——一旦遇到外部的压力,成员就会人往高处走,对个人来说,这种做法无可厚非,但团队中绝大多数人都这样选择的话,这个项目组也就不存在了。

“事不关己文化”——成员不了解或者懒得关心项目的整体目标,老大让干什么就干什么,不去关注为什么要做这个事情、做了之后对项目有什么用处、更不用说去思考有没有更好的做法了。

这些文化的形成,最初是取决于团队的领导,所谓上行下效,但是项目文化一旦形成,后续的事情便不由某个人控制了。所有成员已经有了相同或相似的价值观,新加入的成员如果价值观与之相异,那结果只有两种:被同化或选择离开。

项目文化将会一直伴随到整个团队消失,即便更换项目经理也没用,同样会是上面那两个结局。

立志做项目经理的人们,思考一下该用什么样的风格来建立团队吧。

发表在 项目管理, 软件开发, IT人生 | 标签为 | 留下评论

企业内部管理信息系统运维之我见

做企业信息管理系统的运维工作近一年了,把自己的一些感受在此进行记录。

项目实施是临时性的,一旦实施结束,系统投入使用,便进入了运维阶段,而且该阶段会一直持续下去。

对于实施方来说,系统上线意味着项目结束,但千万不要忘了你还有最后一项工作——交维。如果系统事先没有设计相应的维护工具,或者交维工作做得不好,导致系统无法监控其运行情况,或者无法对其进行配置调整,就会导致一下两种后果:1、对其不管不问,任由系统自生自灭,在使用过程中用户有误操作(这种情况是非常非常常见的,即便程序有非常非常严格的数据验证也无法避免)时,无法快速进行调整,则该系统会快速死亡。2、由于前期对该部分工作考虑不周,需要额外的花费大量人力物力开发维护工具及交维。

对企业来说,这个时候,您需要关注的事情才刚刚开始。

运维是通过定期检查系统运行情况(操作系统以及业务系统有无异常),实时监控系统资源使用情况(服务器的CPU、内存,存储设备、网络流量等等是否超过警戒值),来保证系统稳定运行;制订合适的数据备份方案,并严格执行;并对最终用户在系统使用过程中的问题进行及时解答或处理,来保障业务系统在企业中发挥最大的作用。

围绕上述目标,并根据ITIL中问题分级处理的思路,我们将整个维护团队分成三个组:Help Desk、系统维护、应用维护。

其中各组职责如下:

(1)  HelpDesk 直接面向企业用户,接收并处理用户投诉。投诉内容包括在线系统如何使用(主要针对新系统或者新员工)、在线数据修改(如已审批流程的错别字修改)、系统BUG投诉。

(2)  系统维护主要负责服务器硬件、网络、数据存储、操作系统、数据库等方面的日常监控及维护工作。

(3)应用维护主要负责配合Help Desk人员处理在线数据修改、在线系统Bug修改,以及制作自动化维护工具、响应客户小规模的需求变更。

按照以上配置,已经能够保障整个系统的正常运行,以及用户问题及时响应。

另外,将我们团队现在存在的一些问题也列举出来与大家共勉:

(1)维护手段原始,存在一些监控死角。目前对服务器各项硬件指标在进行实时监控。但在具体应用系统的运行情况监控中手段原始,主要依靠人工进行晨检、巡检来发现问题。这样做就导致:a、发现问题不够及时;b、对于一些没有运行界面的接口、后台服务、数据库作业等的无法进行有效的监控;

(2)知识积累不足。日常工作中使用工单系统记录了大量的用户投诉及问题处理过程,但是没有进行的提炼,导致后来的人员无法从中有效获取知识。在后来的工作中,采取了物质奖励来鼓励兄弟们提炼知识,同时推行规范化工作,即每日下班时对工单系统中记录的问题进行审核,其解决过程需要体现在知识库中。

(3)在我们这里,由于项目开发与系统运维是同一个团队在做,很多时候项目组为了开发新的功能,便对开发好的功能模块,匆匆上线,简单交维,甚至就不进行交维。而通常这种匆忙上线的系统,其开发及测试过程也是匆忙的,这就意味着,系统第一天上线,第二天便会出现bug,而开发团队已经投入到新模块的开发中去了,我们万能的应用维护团队此时便要出现了。长此以往,应用维护的工作量将会是个无底洞,另外也会导致开发团队的质量没有约束。

(4)由于分成了三个小组,各自有明确的分工,导致系统维护人员在日常工作中对业务了解的不够深入,导致在某些应用系统发生突发故障时无法快速定位并处理。

在接手维护工作之初,我曾经给团队以及客户提过一个“轻松维护、专业服务”的目标。

并且围绕此目标制定了几个工作思路:

(1)HelpDesk团队每周/每月对工单系统进行分析,找出用户投诉前三的系统,针对这些系统的问题重点分析,是否该系统BUG较多还是该系统用户体验不好不会使用,并制定相应方案或修改BUG或提升用户体验以减少投诉量。

(2)从工单中进行数据挖掘,建立知识库,并安排相应的专业技能培训,以缩短单个任务的处理时间。

(3)增加自动化维护工具,一方面寻找已有的产品,另一方面整理常见的维护操作,自行开发自动化工具。

在近一年的工作中也在按照这个思路逐步开展工作,当然在具体的执行中还存在不少的遗憾,希望今后其他人能继续做好,在减少自身工作量的同时,将系统保障做的更好。

当然在工作中也有一些问题,一直没有很好的解决思路,在此也将砖头抛出,希望引来美玉:

(1)如何进行绩效考核?

简单的列举一些考核的扣分项是很容易的,但没有切实做过维护工作的人是很难了解到兄弟们在这个工作中的枯燥及辛苦的,但是你把所有工作做得尽善尽美又是维护人员的天职,无法据此进行奖励,在这种情况下,日常的绩效考核就变成全是扣分/罚款了。。。。

(2)如何对接口、后台服务、数据库作业进行自动化监控?

对于接口程序可以在设计时增加一个专门返回自身当前运行状态的接口,之后采用专用程序定期进行探测;对于后台服务也可以设计一个对外的Remoting接口,返回当前运行状态,供程序定期探测。而这些接口的内容不仅仅要返回,当前是否正在运行,还需要进行简单的运行逻辑验证,如判断所需的数据库是否能正常打开(不需要写入数据)或者所需的网络资源能否使用等等。

对于Sql Server的作业来说,执行失败时有几种处理方式:发送邮件、记录到事件查看器,这些还有迹可循,但如何自动监控,本来该执行的作业压根就没按时执行呢?

发表在 项目管理, IT人生, 企业信息化 | 标签为 | 留下评论

请勿贸然开工-翻译

原文地址:http://www.jrothman.com/blog/mpd/2011/01/dont-start-a-project-with-scarcity.html

前几天与一个项目经理聊天时,他提到:“我的开发人员不足,测试人员不足,UI人员也不足,项目该怎么做呢?”

我说,“如果你能增加工期,那人手问题也就解决了。你能增加工期吗?”。

他转动着眼睛说到,“你觉得呢?”

“这样的话,就别做这个项目了,老大不给你提供足够的支持。你能咋整嘛?”

现在你可能觉得没法说不,但如果你的领导不从公司其他的项目人员名单中给你协调一些的全职人员,你为啥还要做呢?

如果你觉得在左右为难,老大不给支持,但又没法拒绝这个事情,那请注意以下几点:

  1. 告诉老大,项目启动时你所需要的人员名单。
  2. 告诉老大,你什么时候可以交付项目。但是要注意:交付的时间会比包括你、你老板以及所有人所能想到的都长,而且是很长很长…
  3. 去说服影响你的老大、跟其他同事谈判、求爷爷告奶奶的去借,或者私下找人帮忙去把项目中所需要的人找来一至两个星期,看一下这样一来项目进度如何。然后,在这个基础上来制定剩余的项目计划。

切记,任何情况下,如果你觉得即便人手不足(无论哪种岗位)也必须要启动这个项目,那么千万不要承诺项目交付日期。切记!

做为一个项目经理,你也得学习一下如何保守的管理项目集中的耗时费力但又无足轻重的任务。然后再去引导你的老大如何管理项目集,使事情想有利于你的方面发展。一旦你理解了项目之间的依赖关系,你会发现,你的项目作为组织整体战略的一部分,谁会影响到你的项目管理工作,并且使它变得更加简单。

原文后还有一段关于项目管理访谈的邀请,没翻了,如下:

And, listen to my Spot On Projects” interview with Gil Broza on January 24. Sign up using this link, http://www.3pvantage.com/jrothman/opt-in.php?ver=RBL. To be honest, an agile approach, combined with influencing your managers to manage the project portfolio is the only way out of this mess. Do join us.

发表在 项目管理 | 标签为 | 留下评论

今天是爷爷的生日

打电话回去,爷爷又哭了。

年年给爷爷过生的时候爷爷奶奶姑姑叔叔老爹老娘哥姐弟妹聚齐独缺我一人。

争取今年春节在家多呆一段时间。

发表在 个人随笔 | 标签为 | 一条评论

将数据库记录转化成insert割接语句的脚本1.1

系统发布之前,需要将已经在开发环境建好的数据库以及数据一次性导出为SQL 脚本。

其中,表结构、存储过程、视图、函数等内容,SQL Server已经提供了导出向导。

除了这些之外,有些系统的初始化数据也避免不了的需要生成insert脚本,便于部署时一次性导入,本文中的SQL就是为了将数据库中的数据转化为insert脚本而写。

使用时,需要修改脚本中的三个参数:@tablename-表名称,需要导出数据的表;@selectcondition-数据过滤条件,值为空则表示导出所有数据,否则在此加上查询条件,如只需要导出id<100的数据,则该值设为“id<100”;@isneedidentity-是否需要将自增字段一起导出,1表示需要,0表示不需要。

–再上一版本的基础上可以设置是否输出identity字段

–修正了将null字段的值输出为”的bug

declare @tablename varchar(100)
declare @selectcondition nvarchar(1000)
declare @isneedidentity int
–设置需要获取数据的表名
set @tablename=’permissionitem’
–设置条件
set @selectcondition=”
–设置是否需要生成自增字段
set @isneedidentity=1

if object_id(@tablename) is null
print ‘this table is not exists. please change the datebase and try again.’
else
begin
declare @colname varchar(80)
declare @coldatetype int
declare @needquotation int
declare @isidentity int

declare @sql_text nvarchar(4000)
declare @sql_colstext nvarchar(4000)
declare @sql_valuestext nvarchar(4000)

declare @datetypetable table (datetypename varchar(40),datetype int, needquotation int)

insert into @datetypetable (datetypename,datetype,needquotation) values (‘bigint’,127,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘binary’,173,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘bit’,104,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘char’,175,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘datetime’,61,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘decimal’,106,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘float’,62,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘image’,34,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘int’,56,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘money’,60,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘nchar’,239,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘ntext’,99,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘numeric’,108,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘nvarchar’,231,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘real’,59,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smalldatetime’,58,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smallint’,52,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smallmoney’,122,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘sql_variant’,98,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘sysname’,256,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘text’,35,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘timestamp’,189,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘tinyint’,48,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘uniqueidentifier’,36,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘varbinary’,165,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘varchar’,167,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘xml’,241,1)

declare cur_cols cursor for
select a.[name],a.user_type_id,b.needquotation,a.is_identity from sys.all_columns a inner join @datetypetable b on a.user_type_id=b.datetype where a.[object_id]=object_id(@tablename) order by a.column_id

set @sql_colstext = ”
set @sql_valuestext = ”
open cur_cols
fetch next from cur_cols into @colname,@coldatetype,@needquotation,@isidentity
while @@fetch_status=0
begin
if @isidentity = 0 or @isneedidentity = 1
begin
if @sql_colstext=”
set @sql_colstext = @sql_colstext + @colname
else
set @sql_colstext = @sql_colstext + ‘,’ + @colname
if @needquotation=1
if @sql_valuestext = ”
set @sql_valuestext = @sql_valuestext + ”’ + case when ‘ + @colname + ‘ is null then ”null” else ”””” + ‘ + @colname + ‘ + ”””” end + ”’
else
set @sql_valuestext = @sql_valuestext + ‘,” + case when ‘ + @colname + ‘ is null then ”null” else ”””” + ‘ + @colname + ‘ + ”””” end + ”’
else
if @sql_valuestext = ”
set @sql_valuestext = @sql_valuestext + ”’ + isnull(convert(varchar(50),’ +  @colname + ‘),”null”) +”’
else
set @sql_valuestext = @sql_valuestext + ‘,” + isnull(convert(varchar(50),’ +  @colname + ‘),”null”) +”’
end
fetch next from cur_cols into @colname,@coldatetype, @needquotation,@isidentity
end
CLOSE cur_cols
DEALLOCATE cur_cols

set @sql_text=’select ”insert into ‘ + @tablename + ‘(‘ + @sql_colstext + ‘) values (‘ + @sql_valuestext + ‘)” from ‘ + @tablename + case when @selectcondition=” then ” else ‘ where ‘ + @selectcondition end
–print @sql_text
exec (@sql_text)
end

发表在 Sql Server | 标签为 | 留下评论

将数据库记录转化成insert割接语句的脚本

系统发布之前,需要将已经在开发环境建好的数据库以及数据一次性导出为SQL 脚本。

其中,表结构、存储过程、视图、函数等内容,SQL Server已经提供了导出向导。

除了这些之外,有些系统的初始化数据也避免不了的需要生成insert脚本,便于部署时一次性导入,本文中的SQL就是为了将数据库中的数据转化为insert脚本而写。

使用时,需要修改脚本中的两个参数:@tablename-表名称,需要导出数据的表;@selectcondition-数据过滤条件,值为空则表示导出所有数据,否则在此加上查询条件,如只需要导出id<100的数据,则该值设为“id<100”;

declare @tablename varchar(100)
set @tablename=’permissionitem’
declare @selectcondition nvarchar(1000)
set @selectcondition=”

if object_id(@tablename) is null
print ‘this table is not exists.’

declare @colname varchar(80)
declare @coldatetype int
declare @needquotation int

declare @sql_text nvarchar(4000)
declare @sql_colstext nvarchar(4000)
declare @sql_valuestext nvarchar(4000)

declare @datetypetable table (datetypename varchar(40),datetype int, needquotation int)

insert into @datetypetable (datetypename,datetype,needquotation) values (‘bigint’,127,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘binary’,173,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘bit’,104,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘char’,175,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘datetime’,61,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘decimal’,106,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘float’,62,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘image’,34,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘int’,56,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘money’,60,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘nchar’,239,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘ntext’,99,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘numeric’,108,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘nvarchar’,231,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘real’,59,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smalldatetime’,58,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smallint’,52,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘smallmoney’,122,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘sql_variant’,98,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘sysname’,256,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘text’,35,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘timestamp’,189,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘tinyint’,48,0)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘uniqueidentifier’,36,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘varbinary’,165,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘varchar’,167,1)
insert into @datetypetable (datetypename,datetype,needquotation) values (‘xml’,241,1)

declare cur_cols cursor for
select a.[name],a.user_type_id,b.needquotation from sys.all_columns a inner join @datetypetable b on a.user_type_id=b.datetype where a.[object_id]=object_id(@tablename) order by a.column_id

set @sql_colstext = ”
set @sql_valuestext = ”
open cur_cols
fetch next from cur_cols into @colname,@coldatetype,@needquotation
while @@fetch_status=0
begin
if @sql_colstext=”
set @sql_colstext = @sql_colstext + @colname
else
set @sql_colstext = @sql_colstext + ‘,’ + @colname
if @needquotation=1
if @sql_valuestext = ”
set @sql_valuestext = @sql_valuestext + ”””’ + isnull(‘ + @colname + ‘,””) + ”””’
else
set @sql_valuestext = @sql_valuestext + ‘,””” + isnull(‘ + @colname + ‘,””) + ”””’
else
if @sql_valuestext = ”
set @sql_valuestext = @sql_valuestext + ”’ + isnull(convert(varchar(50),’ +  @colname + ‘),”null”) +”’
else
set @sql_valuestext = @sql_valuestext + ‘,” + isnull(convert(varchar(50),’ +  @colname + ‘),”null”) +”’
fetch next from cur_cols into @colname,@coldatetype, @needquotation
end
CLOSE cur_cols
DEALLOCATE cur_cols

set @sql_text=’select ”insert into ‘ + @tablename + ‘(‘ + @sql_colstext + ‘) values (‘ + @sql_valuestext + ‘)” from ‘ + @tablename + case when @selectcondition=” then ” else ‘ where ‘ + @selectcondition end
–print @sql_text
exec (@sql_text)

发表在 Sql Server | 标签为 | 留下评论

兰州中川

子夜抵兰,车行中川。
凉月当空,星散其边。
景色微明,延绵黄山。
偶现篝火,人在山间。

翻一首旧诗出来撑门面。写于 2006-11-06

发表在 个人随笔 | 标签为 | 留下评论