前言
来到上海以后,从业这么多年,碰到和见到了很多人和事,一直想写个关于软件项目管理的长篇,不过人微言轻的缘故,估计写了也没人看,索性就一直忙于学习和工作。
现在经济状况不好了,似乎项目也没有之前那么多,正好趁这个机会,慢慢的写点儿东西出来,为了自己将来能够回忆一下当年的拼搏,也为了当年一起奋斗的兄弟和同事留个念想。
就从公交ERP这个项目开始吧。
项目之初的争论
这个项目是多年前就完成的,是涉及大型公交企业的ERP系统,当时.net平台刚刚推出,项目组就决定使用C#作为开发语言。
回想起来,这个风险是很高的,在那个foxpro流行的年代,直接用新的开发语言,开发一个默认领域的项目,如果现在让我选择是否做项目经理,大概是没有这个胆量的。
但在当时来说,几乎所有人都很乐观,尤其是年轻的组员,他们认为:
1、可以通过这个项目提升自己的从业经历;
2、又能够用到最新最流行的开发语言;
只这两点,就已经够大家兴奋了。后面他们的跳槽成功,也说明他们的兴奋是有道理的,当然我也从中得到了很多好处。
但当时,由于自己是项目负责人(当时分工没有那么精细,是现在产品经理+项目经理的复合体),只有我是比较担心的,虽然也看好上面的两点,但是更加重视下面两个问题:
1、新语言不熟悉,会拖慢项目进度;
2、缺少领域专家,对公交业务要从零开始学习,难度很高;
由于存在上面的顾虑,所以在项目之初,项目组内部进行了不少争论和吵架。包括组员也进行了不少变动,有组员被调到其他项目的,也有离职的。当然也有新招聘进来的,回头看,这些都是一个软件开发团队正常的变动,只是当时作为项目经理的我,感觉有点儿焦头烂额。
项目误区
再小的公司,也有办公室政治,在当时,虽然得到了直属领导和老板的信任,但由于项目成立之初的人员,并不是我挑选的,里面参杂了各个部门的精英。作为负责项目的我,一开始是很满意的,毕竟这么多牛叉的程序员都归我管。
只要大家一门心思的去开发,困难总是能解决的。不过事实发现,我想的太简单了。以下先说两个误区:
1、关于团结:比如团队里有两个比较要好的员工,平时他们除了睡觉,几乎形影不离,开始我认为是好事,但后来发现,一旦有什么要求不符合其中一人的口味,他们两个会团结起来反对我,造成我只能让步,严重影响了项目任务的分配和进度,也严重影响了我的权威。
2、关于工作气氛:项目之初,老板送给我3本书,名字不太记得了,但都是微软团队管理方面的书籍,我利用晚上的时间全部阅读了一遍。里面讲了很多调动团队气氛的方法,也就是“团建”。后来发现,国内团队应用最多的是“搓一顿”。因此,也经常请团队成员吃饭,甚至是自掏腰包。但并没有收到很好的效果,对项目的进展帮助不大。
造轮子
说了这么多非代码的事情,下面是关于开发上面的重点。
现在可能稍微有经验的程序员都知道,接触到一门新语言,开发一个新项目,最重要最紧急的事情就是赶紧造轮子。不过我这里说的造轮子可能还有一层含义,是项目管理的造轮子。
不过还是先说代码领域的造轮子吧。
记得当时又要学习新语言,还要跟着甲方学习业务,回到团队还要把任务分解安排好,并协调项目进度,有些心力憔悴,好在当时精力充沛,熬夜也扛得住。
前后团队共有2个程序员参与并编写了造轮子的工作,其中第一个员工还用他的名字拼音作为类库名称放在整个项目中,因为表现突出,我默许了。
前面忘记说了,当时BS结构还没有流行,浏览器也没有现在这么强大,考虑到业务的复杂性,最终采用了CS结构,所以轮子也集中在控件上,下面罗列一些古董级别的文本框代码欣赏一下吧:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace Traffic.ControlLib
{
/// <summary>
/// CTextBox 的摘要说明。
/// </summary>
[ToolboxBitmap(typeof(TextBox))]
public class CTextBox : System.Windows.Forms.TextBox, Traffic.ControlLib.IDataControl
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
const int DEFAULT_COMPLETE_LENGTH = 0;
private int m_CompleteLength = DEFAULT_COMPLETE_LENGTH;
private Queue retQueue = null;
public bool ValidateResult = true;
public CTextBox()
{
// 该调用是 Windows.Forms 窗体设计器所必需的。
InitializeComponent();
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器
/// 修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
//
// CTextBox
//
this.Validated += new System.EventHandler(this.CTextBox_Validated);
this.Enter += new System.EventHandler(this.CTextBox_Enter);
}
#endregion
private String[] GetCustomList(Queue q)
{
if(q.Count!=0)
{
String[] retArray = new String[q.Count];
q.CopyTo(retArray,0);
return retArray;
}
return null;
}
private void CTextBox_Enter(object sender, System.EventArgs e)
{
if(retQueue == null||(retQueue.Count==0))return;
// create an AutoComplete object
ShellLib.ShellAutoComplete ac = new ShellLib.ShellAutoComplete();
// set edit handle
ac.EditHandle = this.Handle;
// set options
ac.ACOptions = ShellLib.ShellAutoComplete.AutoCompleteOptions.None;
ac.ACOptions |= ShellLib.ShellAutoComplete.AutoCompleteOptions.AutoSuggest;
ac.ACOptions |= ShellLib.ShellAutoComplete.AutoCompleteOptions.AutoAppend;
ac.ACOptions |= ShellLib.ShellAutoComplete.AutoCompleteOptions.UpDownKeyDropsList;
// set source
ShellLib.SourceCustomList custom = new ShellLib.SourceCustomList();
custom.StringList = GetCustomList(retQueue);
ac.ListSource = custom;
// activate AutoComplete
ac.SetAutoComplete(true);
}
private void CTextBox_Validated(object sender, System.EventArgs e)
{
if(this.retQueue == null)return;
if(this.retQueue.Count>=this.CompleteLength)this.retQueue.Dequeue();
this.retQueue.TrimToSize();
this.retQueue.Enqueue(this.Text.Trim());
this.ValidateResult = true;
}
private void OnKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
//MessageBox.Show("keydown");
}
[
Category("Custom"),
Description("To set or get the ListDown length for auto complete."),
DefaultValue(DEFAULT_COMPLETE_LENGTH)
]
public int CompleteLength
{
get
{
return m_CompleteLength;
}
set
{
m_CompleteLength = value;
if(m_CompleteLength != DEFAULT_COMPLETE_LENGTH)
{
retQueue = new Queue(m_CompleteLength);
}
}
}
[Category("Custom"),Description("To get or set the value of this control.")]
public string DataValue
{
get
{
return this.Text.Trim();
}
}
public bool PutEntityToValue()
{
try
{
this.Text = this.Tag.ToString().Trim();
return true;
}
catch
{
this.Text = string.Empty;
return false;
}
}
public bool PutValueToEntity()
{
try
{
this.Tag = this.DataValue;
return true;
}
catch
{
try
{
this.Tag = string.Empty;
return false;
}
catch
{
this.Tag = 0;
return false;
}
}
}
}
}
陆陆续续的,一共造成几十个各类轮子,给开发统一和快捷带来了极大的便利。这两位造轮子的同事,一直到现在都很欣赏他们,可惜后来各自都离开了公司,奔向了更大的前途空间。