2008年11月10日星期一

请不要忽视项目文档管理?

这么些年来,大大小小经历了一些信息系统的实施项目,有一部分是协助别人进行项目的实施工作,有一部分是自己负责管理项目的实施工作,另外还有一部分是通 过各种渠道了解的别人的项目实施工作,在这个过程中也在不断地思索一些管理方面的问题。信息系统的实施工作是纷繁复杂的,走过了这么些年,令自己欣慰的是 积累了一些成功的经验,另外一个感触就是一次次发现可改进之处的欣喜和感悟,现在就将项目管理过程中本人关于文档管理的一些潜质拙见通过本文进行一个分 享。

    在此前的关于信息系统的项目实施管理整个过程的文章中也提到过一些文档管理的内容,撰写此文的目的在于更加详实的表达我对项目管理过程中的文档管理的一些理解。

    文档管理的重要性
    一直以来,有很多的人都在说文档管理的重要性,最耳熟能详的莫过于软件项目中的人员变动以后后面的人无法识别之前的代码,或者识别的成本较高。事实情况 是,就算人员不变动,时间长了,当时的编写者也不会记得当时的思路是什么,因为时间会消磨一些东西,另外每个人的想法和思维方式也在发生变化。

    一个项目组织在进行一些想法的沟通时,由于个人知识结构的不同、所处的地位立场不同等原因也会导致对一个想法的最终实现方案的认知上存在差异,最终落实到 方案上也会有所不同,通过一个书面的文档可以将每个人的认知形成一个统一的认知,减少不必要的沟通障碍。同时明确的文档也可以给参与者更多思考的空间。

     为了达到一个好的沟通交流方式,我们需要各式各样的文档,在现实的项目管理过程中也确实存在了大量的文档,每个人都认为文档是重要的,但是当真正文档到来 的时候又开始困扰了,会不自觉的有些厌烦和无从下手。做文档的过程是辛苦的,更是麻烦的,原来几句话的事情在有了文档以后变得异常麻烦,需要对应好格式, 需要不同人员的审核和认可,难免就会觉得麻烦;而在自己做完文档以后发现可用性并不如想象中的那么高,真正想要的文档不知道到哪里去找,不同的文档说法不 统一,等等诸如此类的问题让使用者有点无从下手。

    或许是印证了一句话,叫做事情做的没有头绪的时候就想到了管理,对于那些各式各样的文档同样不会例外。

    文档管理的方式
    在文档管理的过程中,需要把握住一些重要的原则和方法,这样可以让我们的文档真正达到我们预期的目的。
文档模板的管理

    面对各类纷繁复杂的文档,如果各式各样,每个人一个风格,不论从美观性和可读性上都会有影响,所以在文档管理中的过程中需要建议一套文档的模板。

    在建立文档模板时,需要对一些格式进行要求,需要将一些基本的要素固化到文档模板中,确保文档需要的内容能够在文档中体现,例如文档的页眉页脚、文档变更历史、文档的目录方式、文档的字体等。

    在建立文档时,要注意做好文档的分类,各种分类有一个清晰的定义,使用者可以清楚地知道实际使用时要采用哪种模板。如果是使用共享目录方式管理文档,需要 在一个相对容易找到的文件夹目录中存放模板,如果是使用信息系统方式进行模板管理,最好能够在首页进行一个连接,或者是让使用者可以快速的搜索到。在建立 分类时,需要含有一个共同类,或者叫做公用类,因为在实际的使用过程中总是会有一些新的类别出现,也会有一些无法进行分类的文档,这个时候就可以通过共同 类进行管理。

    对于文档模板的变更需要能够做到及时告知使用者,并做好版本管理。
    文档目录的管理
    为了能够在纷繁复杂的文档中找到需要的文档,需要在进行文档管理时建立一套完整的文档目录体系,主要包括文档的索引管理和文档的分类管理。

    在进行文档管理前,需要对不同的文档建立一个分类,建议不同的分类可以便于文档的查找,也可以针对不同的分类制定不同的管理要求。如果是文件夹方式管理, 还需要分别不同的文件夹内容的安排以及权限的控制,因为文件夹管理的特殊性,需要注意其权限管理的简洁化;如果是采用信息系统管理,需要注意类型的编码体 系的建立。一个好的文档分类体系可以让使用者方便地进行文档的归类和查找,文档的分类在一些管理过程中还需要注意归档管理的需求。

    对于文档,需要建立一套索引机制,这里之所以特别提出索引机制,是因为在日常的文档使用中,会发现一些很多类似的文档,或者类似的内容说明,由于不同的撰 写者会对一些基本的概念或者原则的说明会存在一些差异,这个时候就需要能够有一个索引来明确什么是最准确的,这一点在项目管理过程中特别重要,因为一个项 目组往往会不时的发生一些变动,后来人面对多个说法时很难知道什么是对的,同时一些实际情况也会发生变动,一些说法也需要进行修正,而之前的一些文档也无 法进行更正,所以这个时候也需要有一个索引来明确正确的说法,这一点问题也可以通过文档版本的管理加以改善。另外对于那些纷繁复杂的文档,有些只是事务性 的,通过一个索引的方式,可以将一些重点的内容放在一个突出的位置,便于进行检索和阅览。

    不论是文件夹方式还是信息系统方式,都需要注意对文档的权限管理,特别是对一些归档的文档,避免归档的文档被不正确的修改或者删除。

    文档的命名规范
    各种文档,如果名称多样,或者名称含义模糊将会造成使用和交流上的不便,需要建立一套有效的命名规范体系。
    对于文档的名称,首先需要名称能够容易识别,有些使用者不是很注意文档的名称,经常直接用文件的默认名称,或者就是一个自己的姓名、项目的名称等,其他的使用者很难识别是什么类型的文档。

    在文档的使用过程中,可以规定在文档的某些部位必须放置文档的类型或者某些其他关键字,例如将要求将文档的类别放在文件名的头部,对于月度性的文档,规定头部必须放某年某月,等等。

    对于一些分不同版本的文档,可以要求分不同的版本进行管理,文件名称中注明版本号,对于终稿等一些标志性的内容可以加一些特殊的标识,这样可以明确其重要性和权威性。

    这些方式将有助于文档的文件名的整洁和清晰,使用者在查找时也较为方便。在进行文档的交互调整过程中,也可以增加一些日期或者修改者的标识来进行传递,主要的目的在于能够唯一识别一个文档,减少互相沟通的障碍。
文档的变更管理

    文档在使用过程中发生变更是很常见的现象,对于发生变更的文档,需要通过手段加以约束,最常用的方法就是版本的管理,对于形成的文档及时进行归档保存。

    文档发生变更时,需要能够做到两点:第一,文档有清晰的变更记录,主要是针对变化的部分,不能让每个使用者在文档发生变化后都需要把文件通篇读一遍;第二,文档的最终版本要能方便的阅览,如果出现只能看变更历史才知道最终版本的话将大大提高使用成本。

    在文档发生变更时,需要能够通过必要的途径通知相关人,例如通过邮件通知或者公告通知的方式,避免新的文档产生后还有大量的使用者使用旧的文档,在实际操 作环境中,ISO9000管理使用的签字回收重要文档的方式很值得借鉴,当然对一些电子文档需要采用一些其他变通的方式来处理。

    文档的审核制度
    很多文档作为一种指导性文件,需要有一定的严肃性和权威性,因而对文档进行必要的审核是必须的。
    文档的审核时机一般为文档建立时和文档发生变更时,对于文档的适用范围的变更也应该进行必要的审核。通过文档的审核,可以检查是否存在错误的事项或者一些 不合理的事项,撰写者和审核者所处的岗位不同、知识结构不同,对于一个文档如何撰写的角度和看法也会不同,在后续的审核过程中能够很好的进行文档的校正。 同时,文档的审核机制也可以明确各自岗位的责任。

    后记
    信息系统的实施是一个负责的管理过程,需要方方面面的管理工作配合到位,文档作为一种日常交流的重要依据和工作成果的总结显得尤为重要,在文档管理的过程 中既要注意严肃性,又要能兼顾灵活性,要本着在达到正常的规范性的基础上尽可能地方便使用者的使用和交流,提高使用效率。
    文档管理工作在其他的一些管理工作中同样是一个重要的内容,很多大型企业都会有一个专门的档案管理部门进行文档的归档管理,这些都能充分体现文档管理的重要性之所在。





[广告] 金秋最关注楼盘-房不胜房

2008年11月6日星期四

人生致命的八个经典问题---你想过了吗?

人生致命的八个经典问题---你想过了吗?
问题一, 
如果你家附近有一家餐厅,东西又贵又难吃,桌上还爬着蟑螂,你会因为它很近很方便,就一而再、再而三地光临吗? 
回答:你一定会说,这是什么烂问题,谁那么笨,花钱买罪受? 可同样的情况换个场合,自己或许就做类似的蠢事。不少男女都曾经抱怨过他们的情人或配偶品性不端,三心二意,不负责任。明知在一起没什么好的结果,怨恨已经比爱还多,但却"不知道为什么"还是要和他搅和下去,分不了手。说穿了,只是为了不甘,为了习惯,这不也和光临餐厅一样?
——做人,为什么要过于执著?! 

问题二, 
如果你不小心丢掉100块钱,只知道它好像丢在某个你走过的地方,你会花200块钱的车费去把那100块找回来吗? 
回答:一个超级愚蠢的问题。 

可是,相似的事情却在人生中不断发生。做错了一件事,明知自己有问题,却*也不肯认 错,反而花加倍的时间来找藉口,让别人对自己的印象大打折扣。被人骂了一句话,却花了无数时间难过,道理相同。为一件事情发火,不惜损人不利已,不惜血本,不惜时间,只为报复,不也一样无聊? 失去一个人的感情,明知一切已无法挽回,却还是那么伤心,而且一伤心就是好几年,还要借酒浇愁,形销骨立。其实这样一点用也没有,只是损失更多。 
——做人,干吗为难自己?!

问题三, 
你会因为打开报纸发现每天都有车祸,就不敢出门吗? 
回答:这是个什么烂问题?当然不会,那叫因噎废食。 
然而,有不少人却曾说:现在的离婚率那么高,让我都不敢谈恋爱了。说得还挺理所当然 。也有不少女人看到有关的诸多报道,就对自己的另一半忧心忡忡,这不也是类似的反应?所谓乐观,就是得相信:虽然道路多艰险,我还是那个会平安过马路的人,只要我小心一点,不必害怕过马路。 
——做人,先要相信自己。

问题四, 
你相信每个人随便都可以成功立业吗? 
回答:当然不会相信。 
但据观察,有人总是在听完成功人士绞尽脑汁的建议,比如说,多读书,多练习之后,问了另一个问题?那不是很难? 
我们都想在3分钟内学好英文,在5分钟内解决所有难题,难道成功是那么容易的吗?改变当然是难的。成功只因不怕困难,所以才能出类拔萃。有一次坐在出租车上,听见司机看到自己前后都是高档车,兀自感叹:"唉,为什么别人那么有钱,我的钱这么难赚?"  我心血来潮,问他:"你认为世上有什么钱是好赚的?"他答不出来,过了半晌才说:好像都是别人的钱比较好赚。 
其实任何一个成功者都是艰辛取得。我们实在不该抱怨命运。 
——做人,依靠自己! 

问题五, 
你认为完全没有打过篮球的人,可以当很好的篮球教练吗? 
回答:当然不可能,外行不可能领导内行。 

可是,有许多人,对某个行业完全不了解,只听到那个行业好**,就马上开起业来了。
我看过对穿着没有任何口味、或根本不在乎穿着的人,梦想却是开间服装店;不知道电脑怎么开机的人,却想在网上**,结果道听途说,却不反省自己是否专业能力不足,只抱怨时不我与。 
——做人,量力而行。 

问题六, 
相似但不相同的问题:你是否认为,篮球教练不上篮球场,闭着眼睛也可以主导一场完美的胜利? 
回答:有病啊,当然是不可能的。
可是却有不少朋友,自己没有时间打理,却拼命投资去开咖啡馆,开餐厅,开自己根本不懂的公司,火烧屁股一样急着把辛苦积攒的积蓄花掉,去当一个稀里糊涂的投资人。亏的总是比赚的多,却觉得自己是因为运气不好,而不是想法出了问题。 
——做人,记得反省自己。 

问题七,
你宁可永远后悔,也不愿意试一试自己能否转败为胜? 
解答:恐怕没有人会说:"对,我就是这样的孬种"吧。

然而,我们却常常在不该打退堂鼓时拼命打退堂鼓,为了恐惧失败而不敢尝试成功。以关颖珊赢得2000年世界花样滑冰冠军时的精彩表现为例:她一心想赢得第一名,然而在最后一场比赛前,她的总积分只排名第三位,在最后的自选曲项目上,她选择了突破,而不是少出错。在4分钟的长曲中,结合了最高难度的三周跳,并且还大胆地连跳了两次。她也可能会败得很难看,但是她毕竟成功了。她说:"因为我不想等到失败,才后悔自己还有潜力没发挥。"  一个中国伟人曾说;胜利的希望和有利情况的恢复,往往产生于再坚持一下的努力之中。 
——做人,何妨放手一搏。 

问题八, 
你的时间无限,长生不老,所以最想做的事,应该无限延期? 
回答:不,傻瓜才会这样认为。 
然而我们却常说,等我老了,要去环游世界;等我退休,就要去做想做的事情;等孩子长大了,我就可以……我们都以为自己有无限的时间与精力。其实我们可以一步一步实现理想,不必在等待中徒耗生命。如果现在就能一步一步努力接近,我们就不会活了半生,却出现自己最不想看到的结局。 

——做人,要活在当下。 


网易邮箱10周年,技术见证辉煌

2008年11月4日星期二

简析Java RMI 与 .NET Remoting

Java与.NET都提供了远程处理功能,但不完全相同.Java远程处理是通过一个"共享接口"实现的,而.NET可以通过一个"共享命令集"实现。下面就这两种方式来具体说明。

Java 远程处理

Java远程方法调用(RMI)提供了Java程序语言的远程通讯功能,这种特性使客户机上运行的程序可以调用远程服务器上的对象,使Java编程人员能够在网络环境中分布操作。
创建一个简单的Java分布式远程方法调用程序可以按以下几个步骤操作,

一、定义远程接口:
在 Java 中,远程对象是实现远程接口的类的实例, 远程接口声明每个要远程调用的方法。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,客户通过接口句柄发送消息即可。
远程接口具有如下特点:
1) 远程接口必须为public属性。如果不这样,除非客户端与远程接口在同一个包内,否则 当试图装入实现该远程接口的远程对象时,调用会得到错误结果。
2) 远程接口必须扩展接口java.rmi.Remote。
3) 除与应用程序本身特定的例外之外,远程接口中的每个方法都必须在自己的throws从句中 声明java.rmi.RemoteException。(或 RemoteException 的父类)。
4) 作为参数或返回值传递的一个远程对象(不管是直接,还是本地对象中嵌入)必须声明为远 程接口,而不应声明为实施类。
下面是远程接口的接口RmiSample的定义

 import java.rmi.*;
 public interface RmiSample extends Remote {
     public int sum(int a,int b) throws RemoteException;
 }

 二、实现远程接口:
远 程对象实现类必须扩展远程对象java.rmi.UnicastRemoteObject类,并实现所定义的远程接口。远程对象的实现类中包含实现每个远 程接口所指定的远程方法的代码。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。因为客户是指向接口的一个句柄,而不是它的哪个类。必须为 远程对象定义构造函数,即使只准备定义一个默认构造函数,用它调用基础类构造函数。因为基础类构造函数可能会抛出 java.rmi.RemoteException,所以即使别无它用必须抛出java.rmi.RemoteException例外。
以下是远程对象实现类的声明:

  import java.rmi.*;
  import java.rmi.server.*;
  public class RmiSampleImpl extends UnicastRemoteObject
                           implements RmiSample {
      RmiSampleImpl() throws RemoteException {
         super();
      }
      public int sum(int a,int b) throws RemoteException {
         return a + b;
      }
  }

三、编写服务器类:
包 含 main 方法的类可以是实现类自身,也可以完全是另一个类。下面通过RmiSampleServer 来创建一个远程对象的实例,并通过java.rmi.registry.LocateRegistry类的createRegistry 方法从指定端口号启动注册服务程序,也可以通过执行 rmiregistry 命令启动注册服务程序,注册服务程序的缺省运行端口为 1099。必须将远程对象名字绑定到对远程对象的引用上: Naming.rebind("//localhost:8808/SAMPLE-SERVER" , Server);
以下是服务器类的声明:

 import java.rmi.*;
 import java.rmi.registry.*;
 public class RmiSampleServer{
     public static void main(String args[]) {
         try {
         LocateRegistry.createRegistry(8808) ;
         SampleServerImpl Server = new SampleServerImpl();
         // 将该对象实例与名称"SAMPLE-SERVER"捆绑
         Naming.rebind("//localhost:8808/SAMPLE-SERVER" , Server);
         } catch (java.net.MalformedURLException me) {
            System.out.println("Malformed URL: " + me.toString());
         } catch (RemoteException re) {
            System.out.println("Remote exception: " + re.toString());
         }
     }
  }

四、编写使用远程服务的客户机类:
客户机类的主要功能有两个,一是通过Naming.lookup方法来构造注册服务程序 stub 程序实例,二是调用服务器远程对象上的远程方法。
以下是服务器类的声明:

 import java.rmi.*;
 import java.rmi.server.*;
 public class RmiSampleClient {
     public static void main(String[] args)
     {
         try {
            String url = "//localhost:8808/SAMPLE-SERVER";
            RmiSample RmiObject = (RmiSample)Naming.lookup(url);
            System.out.println(" 1 + 2 = " + RmiObject.sum(1,2) );
         } catch (RemoteException exc) {
             System.out.println("Error in lookup: " + exc.toString());
         } catch (java.net.MalformedURLException exc) {
             System.out.println("Malformed URL: " + exc.toString());
         } catch (java.rmi.NotBoundException exc) {
             System.out.println("NotBound: " + exc.toString());
         }
      }
   }

五、编译代码:
要编译 Java 源文件,请运行 javac 命令:
 javac RmiSample.java RmiSampleImpl.java RmiSampleServer.java RmiSampleClient.java

六、为远程对象实现创建根和干:
要创建存根程序和骨架文件,应以包含远程对象实现的已编译类包全名运行 rmic 编译器。
存根(Stub)是远程对象在客户端的代理,它将RMI调用传递给服务器端的骨架(Skeleton),后者负责将该调用传递给实际的远程方法输入如下:
D:RMI>rmic -d D:RMI RmiSampleImpl 执行这个命令, 若rmic成功运行,RMI目录下就会多出两个新类: RmiSampleImpl_Stub.class RmiSampleImpl_Skel.class 它们分别对应的是存根(stub)和骨架(skeleton).

七、运行代码:
运行服务端程序:在Windows下,输入下列命令,在后台启动RmiSampleServer程序:
D:RMI>java RmiSampleServer
运行客户端程序:
D:RMI>java RmiSampleClient
客户端输出: 1 + 2 = 3

.NET 远程处理。

微软的.NET Remoting提供了一种允许对象通过应用程序域与另一对象进行交互的框架,从结构上而言,.NET Remoting对象非常适合通过网络访问资源,而又无需处理由基于SOAP的WebServices所带来的难题。下面介绍创建一个简单的.NET Remoting分布式远程方法调用程序一般所需要的几个步骤。

第一步:创建共享远程对象

创建一个C# Library,并将其命名为RemoteObject。这将创建一个我们的.NET Remote客户端和服务器端用来通讯的"共享命令集"。
 public class RemoteObject : System.MarshalByRefObject
 {
     public RemoteObject()
     {
        System.Console.WriteLine("New Referance Added!");
     }

     public int sum(int a, int b)
     {
        return a + b;
     }
 }
名字空间是对象所需要的。请记住,如果得到System.Runtime.Remoting.Channels.Tcp名字空间不存在的信息,请检查是否象上面的代码那样添加了对System.Runtime.Remoting.dll的引用。
 
using System;
using System.Runtime;

我们为对象使用的名字空间是RemoteSample,下面的对象是MarshalByRefObject,在其中我们创建了一个引用和包括服务器端操作全部完成所需要的所有工作。

namespace RemoteSample
{
 public class RemoteObject : System.MarshalByRefObject
 {
    public RemoteObject()
    {
       System.Console.WriteLine("New Referance Added!");
    }

    public int sum(int a, int b)
    {
       return a + b;
    }
 }
}//RemoteSample名字空间结束

保存文件,命名为RemoteObject.cs
用命令行csc /t:library RemoteObject.cs 编译文件,就会得到一个RemoteObject.DLL文件,并可以在编译其他的C#文件中使用它。


第二步:创建服务器对象

创建服务器对象,并将它命名为RemoteServer。在创建服务器对象时必须实现以下操作:

1)构造服务器信道。
TcpServerChannel是.NET remoting支持的二种信道类型中的一种,可以将它设置为对象对来自哪一个端口的请求进行回应,ChannelServices.RegisterChannel将把该端口号与操作系统中的TCP/IP栈绑定。

TcpServerChannel channel = new TcpServerChannel(8808);
ChannelServices.RegisterChannel(channel);

也可以设置为另一种的信道类型HTTP, 只要简单地用System.Runtime.Remoting.Channels.Http名字空间中的HttpServerChannel对象替换即 可。使用HTTP和TCP信道之间的区别在于:如果应用程序是在局域网上运行,则最好使用TCP信道,因为它的性能要好于HTTP信道;如果应用程序是在 互联网上运行,则有时候根据防火墙的配置,HTTP是唯一的选择。需要记住的是,如果使用了防火墙软件,则防火墙应该配置成允许TCP数据流量通过你为对 象选择的端口。

2)将服务端上的对象 Type 注册为已知类型。
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject),
"RemoteObject", WellKnownObjectMode.SingleCall);
这 行代码设置了服务中的一些参数和把欲使用的对象名字与远程对象进行绑定,第一个参数是绑定的对象,第二个参数是TCP或HTTP信道中远程对象名字的字符 串,第三个参数让容器知道,当有对对象的请求传来时,应该如何处理对象。尽管WellKnownObjectMode.SingleCall对所有的调用 者使用一个对象的实例,但它为每个客户生成这个对象的一个实例。如果用WellKnownObjectMode.SingleCall则每个传入的消息由 同一个对象实例提供服务。

完整的对象代码如下所示:
 using System; using System.Runtime;
 using System.Runtime.Remoting;
 using System.Runtime.Remoting.Channels;
 using System.Runtime.Remoting.Channels.Tcp;
 using RemoteSample;
 namespace RemoteSampleServer
 {
  public class RemoteServer
  {
      public static void Main(String[] args)
      {
         TcpServerChannel channel = new TcpServerChannel(8808);
         ChannelServices.RegisterChannel(channel);
         RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject),
                                  "RemoteObject", WellKnownObjectMode.SingleCall);
         System.Console.WriteLine("Press Any Key");
         System.Console.ReadLine();
       }
   }
 }
保存文件,命名为RemoteServer.cs
用命令行csc /r:System.Runtime.Remoting.dll /r:RemoteObject.dll RemoteServer.cs 编译这一程序生成的RemoteServer.EXE文件。


第三步:创建Remote客户端程序
创 建服务器对象,并将它命名为RemoteClient。在创建服务器对象时首先创建了一个TCP客户端信道,该信道并不是绑定在一个端口上的;其次获取一 个对远程的RemoteObject对象的引用。 Activator.GetObject方法返回一个对象类型的值,我们随后会将它返回的值赋予RemoteObject。我们传给它的参数与在服务器对 象中传递给RemotingConfiguration的参数非常地相似,第一个参数是对象类型的,第二个参数是远程对象的URI。

 ChannelServices.RegisterChannel(new TcpClientChannel());
 RemoteObject remoteobj = (RemoteObject)Activator.GetObject( typeof(RemoteObject),
                       "tcp://localhost:8808/RemoteObject");

 RemoteClient的全部代码如下所示:

 using System;
 using System.Runtime.Remoting;
 using System.Runtime.Remoting.Channels;
 using System.Runtime.Remoting.Channels.Tcp;
 using RemoteSample;
 namespace RemoteSampleClient
 {
  public class RemoteClient
  {
     public static void Main(string[] args)
     {
        ChannelServices.RegisterChannel(new TcpClientChannel());
        RemoteObject remoteobj = (RemoteObject)Activator.GetObject( typeof(RemoteObject),
                           "tcp://localhost:8808/RemoteObject");
        Console.WriteLine("1 + 2 = " + remoteobj.sum(1,2).ToString());
        Console.ReadLine();//在能够看到结果前不让窗口关闭
     }

   }
 }

保存文件,命名为RemoteClient.cs
用命令行csc /r:System.Runtime.Remoting.dll /r:RemoteObject.dll RemoteClient.cs 编译这一程序生成的RemoteClient.EXE文件。


第四步:测试
在windows中运行Server.exe,然后打开另一个窗体运行Client.exe。
如果一切正常的话,客户端输出: 1 + 2 = 3。



[广告] 金秋最关注楼盘-房不胜房

2008年11月3日星期一

为什么员工要加班

效率 vs 稳定 vs 后期维护 vs 成长

(为什么员工要加班)

----------------------------------------------------------------------------------------------------------------------

如有转载,必需保证本篇文章的完整性,包括本段内容。如果引用,必需注明文章标题、作者和时间。

黄延冬 于 深圳

2008.11.1

----------------------------------------------------------------------------------------------------------------------

目录:

1. 为什么写这篇文章

2. 背景

3. 问题

4. 标准化的漫长之路

5. 这就是我想说的

======================================================================

1.为什么写这篇文章

    为了我自己减少工作,减少加班,特写此文。

 

2. 背景

    经过几年的工作,终于发现,一个公司的成长,终究是经历着千辛万苦的。

    从毕业之后的第一个公司,到之前的一个公司,三家,要么已经死掉了,要么缩小了,要么折腾来折腾去地没钱赚,看上去,各有各的原因,但结果却只有一个:公司的成长总是处于挣扎之中,好像没有成熟的时候,永远是"创业"阶段。

    刚 毕业的时候,在公司里能够写一个软件上能用到的代码就满足了,不管是一个窗口还是一个过程,不需要做任何考虑,写出来只要能正常运行就算是测试通过。后来 换了公司,我志向于VC++的开发,不再使用delphi、vb这样的开发工具了,而VC++的上手,却经历了自我学习的3到4年时间,这个过程太过漫 长,幸运的是,结果还算是用上了VC++,那时很高兴,觉得学习了一些东西,终于迈入了VC++的大门。

    事 实上,真正拿VC开始写较为广泛的程序,是从本公司开始的,以前,只是用VC做数据库开发,因为是公司内部使用的工具,模式都很固定,界面也不需要复杂和 花哨,唯一要做的就是数据库的查询、写入、更新和删除这样的操作,所以,除了数据库的查询之外,对我并没有太大的帮助,每天也只是做着模式一致的几乎重复 的工作。而现在却不一样了,有很多工作要做,有很多程序要写,有很多设备要支持,有很多应用要实现,有很多版本要兼容,有很多的程序架构要考虑。自从腾工 离开之后,我开始思考这些问题,这些不是主动开始思考的,而是被动地开始思考。因为没有人指导,全是凭着经验,碰到了多次相同的问题之后,才开始变"聪明 "的,才开始思考这些问题,我想,这已经够被动了,我们还要继续被动下去吗?

 

3. 问题

    有两个问题:一个是当前的工作,一个是未来的工作。

    实 际上,这两个问题,已经被广泛地讨论过了,只是每个公司并不一定认真地考虑和讨论过这个问题罢了,它们也就是程序的功能和性能问题,而这两个问题又是其它 问题的基础问题,所以它们非常重要,我们需要认真思考和对待。显然,从经验,或者是说从其它任何事情上看,为了我们的目标,我们都不应该只喊口号而不付出 真实的行动,或者只是缓慢地行动,那不是我们想要的,至少它不是我所想要的(真对当前状况而言,我可能只会更关心我所做的工作内容),因为它严重地影响着 一个公司的发展,影响着我的工作,这是一种思想,我想,从公司的发展看,一个企业的负责团队有责任去思考这个问题。

    最早发现的问题是录像的文件名问题。采用扩展名来判别录像文件是由哪种设备产生的,现在看来显然是可笑的,然而,这段代码,在解码库中还是依然存在的,虽然它在当初是发挥了巨大的作用,应该记住它的功劳,但当我们看到它时,也应该记起曾经的狭隘眼光。

    和 录像文件括展名相关的就是解码信息的存储。所有的文件都没有一个统一的正式的文件头,由此造成的后果是,不允许用户修改录像文件名。这对用户而言,显然是 一个过份的要求。所以,我们改了,增加了录像文件头,虽然这个文件头的结构不太好,眼睛睁得不够大,但还是开始行动了,它是一个好的开端,是第一个比较正 式的通用的也考虑了版本兼容问题的具有强制性遵守要求的格式文档。它很重要,堪称是一个里程碑。它预示着我们过去陈旧的思想开始发生转变,并且开始付诸行 动了。我们可以为之高兴一把,但也应该看到,未来的路还很漫长很艰辛很残酷。实践证明了这个漫长、艰辛、残酷它确实存在,而且决不是夸张!

    接 着,就是网络协议的版本问题。此问题体现在设备的升级过程中,在深圳公交中,也很明显地体现出来没有定义好协议所带来的一系列问题。当有新功能提出并在实 际中发现过去的协议有弊端时,旧的协议已完全不能满足新的要求,所以我必需修改握手协议,还有其中的部分通信协议。这虽然在SDK中做到了前后版本的兼 容,但付出的代价却不小,同时,给用户造成的不便,却无法得到修正。在其它的新项目中,也对原来的旧协议进行了稍许修改,因为全部重新定义,将会有大量的 工作出现,这主要包括代码的重写、调试、兼容处理三个方面,它会严重影响项目的进度,所以,即使是原来的协议并不"完美",我们还是得继续使用,不是吗? 现在,我们有两种协议:文本协议和二进制协议。虽然当初我认为应该有两套协议和现在的协议数量是一样的,但我所考虑的协议功能和现有的却是不同的,现在的 文本协议只是对原来深圳公交协议的简单升级,它只是对原协议的弊端进行了补充,并对协议内容做了适当的修改,但我认为,这和我们公司的现实情况并不相符, 我们不仅仅是需要一个更完善的协议,而更需要的是我们的"标准"协议,它应该承载我们未来协议的重担,而不仅仅是一个简单的升级,因为不定义好,我们以后 还要改动,还要痛苦地"升级",而不能简单高效地升级,现在的这种局面,问题已初露端倪,往后的情形,就让我们试目已待吧。

    后 来,我们又定义了帧头打包格式的标准,此标准可以说是一波三折,直到目前,还疑点重重。当初的定义,由于本人太过武断,未进行广泛地调查,就把结构给定义 了下来。结果,结构里面的数据格式却远远不能满足实际的需要,因为各种设备的数据格式不尽相同,为了之间的互相兼容,不得不进行频繁地升级,结果却发现, 升级任何一个小的结构体,都会导致组合种类的数量级飙升,小的结构体越多,其飙升的速度就会成倍地增加,这会为以后的兼容处理带来不应有的困难。在这种情 况下,不得不把这个标准完全推翻,并重新定义,虽然新的定义和老的在大的结构上一致,但其思想的改变,导致以后的升级会轻松很多,而且升级的频率会大大地 降低,然而,这里面还有问题,这问题的成因和过去大家的工作风格是密不可分的,后面会详述。

    以上问题,是我所了解稍许的。其问题的形成,除了有历史遗留原因之外,根本上是我们的思想问题,思想没有得到纠正,依然停留在过去那种走一步是一步的原始状态。

    在 "创业"阶段,这些都不是问题,反而可以提高工作效率,尽快拿出产品,没错,这是起步时的做法。因为我们可以少做考虑,把更多的时间放到产品的推出上,需 要有饭吃,才谈得起发展。在往后的时间里,当有了基本保障之后,这种不进行长远考虑的思想,将会把企业禁锢在一个小小的范围之内,其水平只能停留在一定的 高度,不可能壮大,甚至会把企业扼杀掉!这时候,在开发新产品的时候,会发现,想做到新产品和旧产品完全不扯上关系那是不可能的事情,比如我们的SDK, 不可能为新的产品单独重头写一个SDK,这是显而易见地。问题也随之而来,原有的产品会有缺陷,我们需要修补,如果做旧的产品的时候从来不考虑以后的情 况,那么现在的问题很严重,如果原来做了部分考虑,则现在的问题可能会比不考虑的情况少一点点。如果只是升级一次两次,即使麻烦,也不至于做不下去,但如 果是要一直升级下去,则终有一天会做不下去的。你会发现,过去的高效率,导致了现在的低效率,也导致了稳定性的日趋下降,日子越久,效率和稳定性都会成倍 地降低,由此也导致了后期维护成本的成倍飙升,如果不早做计划,这种情况的发生是不可能避免的。我们现在的情况如何?我认为不容乐观,最起码这上面做得还 远远不够。

    我 们在讨论用户的需求问题,在清楚需求之后,再讨论如何来实现这些功能,再讨论清楚之后,开始付诸行动,一切很顺利。是吗?不!只是现在的情况比早期好了很 多,至少上面的步骤基本上都有进行,那么,我就只说现在的问题。现在的问题,有点像黎明前的黑暗,一切都已准备就序,只等太阳升起把大地照亮。这个太阳, 就是我们的行动,这里面的东西,就是我们的思想,就是我们要做的产品,这里面的问题,就是太阳还未升起,一切都还未有序。我们思想和产品中的问题,需要我 们用行动来解决和消除,这其中首当其冲的就是我们思想的问题解决。我们有两种协议,不说解析不一样,重要的是,里面的常用数据格式不一样,录像文件的常用 数据格式和协议里的不一样,协议里的常用数据格式和设备里的不一样,设备里的常用数据格式和设备的外围设备里的又有版本兼容问题,有了文档约定,大家并没 有去遵守,设备软件本身的前后版本兼容问题。很久以来,研发部做过联调的同事们应该会有一些感触才对,除了功能的调试之外,有很大一部分的时间花在了这些 常用数据的格式上,因为各个地方的格式并不统一,彼此之间为了弄清楚对方的要求,都做了大量的工作,加班到半夜,这个加班,值得吗?这不是造成不稳定的一 个因素?这不是效率降低的一种表现?这不是维护成本提高的一种表现?所以,我们需要标准,我们自己的标准,为的是统一,大家都按照这个标准来,谁不符合这 个标准,就是谁的责任,就谁来加班,不是更好吗?深圳公交为了升级,文档进行了修改,增加了协议,一切都已就序,设备和PC端的代码都已经完成,本是在下 班之前就能完成的,结果联调到半夜12点多才完成,原因是设备的代码和文档不符,结果还要求修改文档,这是一种怎样的思想?我想,做工作说清楚了,大家都 会做,只是时间长短不同罢了,认真去做,都做得出来,但思想就不一样了,如果没有雄霸世界的野心,怎会在外国建立军事基地?如果要让美国撤掉外国的所有军 事基地,美国的总统肯定不干,因为它的思想就是要雄霸世界,你想让他转变这个思想,太难了。同样,我们过去那随便定义数据格式,或者是有了文档约定却不去 遵守的思想要想彻底改掉,去建立大家都"易于"接受的标准,也不是那么容易,但如果没有标准,永远都是处于产品的完善之中,公司不会成熟,不会长大,没有 希望,怎么可能留住人才?

 

4. 标准化的漫长之路

    PC端 为什么要做SDK?是为了方便其他用户使用我们的产品。其他用户不需要关心我们内部的数据是怎么传递的,也不需要知道内部的数据是怎样存放的,他们只需要 关心他们想要的数据,关心这些数据的格式存放。SDK有一个问题,就是版本的升级问题。如果只是功能的增加,也许不成问题,增加接口即可,但如果是复杂的 升级,结果就不一样了。比如原来有的数据是一个整数,没有小数位,但由于设备的升级,有了小数位,这时候,接口的数据格式就要改变,用户在拿到新的SDK 后,需要修改程序以处理带小数点的数据。更复杂的升级会造成SDK的兼容问题和对用户的使用带来更大的麻烦。比如原有的SDK所处理的某一个参数数据都是 3个标识项的,现在有一个新的设备,这个参数却有4个标识项,其中的3个和其它的设备是一样的,那么,SDK如何做到设备之间的兼容?增加一个接口?显示 不好,因为都是设置的同一个参数,如果增加了接口,则用户必需真对设备来调用不同的接口,会造成一定的麻烦。这种情况下,SDK会对原来的接口增加一个参 数,但这个参数有一个缺省值,由SDK内部确定这个参数有没有用,这样用户在拿到新的SDK后,如果还是在原来的设备上使用,则不必理会这个新增加的参 数,只需要重新编译一次工程即可。所以,SDK的功能除了和设备交互之外,很重要的一个功能是对设备的参数进行了加工后传给了上层程序,上层程序不管是对 哪一种设备,它们的处理方式都是一样的,即SDK把N种设备都处理成为第N+1种标准的虚拟设备,对用户而言,都是一样的设备。那么,我们定义标准,除了 升级之外,也是要让所有的设备都做到统一,尽量做到统一,以便减少SDK的处理繁琐程度,以及增加设备之间的互通性,版本升级的兼容性。实际上,设备也同 样应该有一套SDK,这套SDK应该是处理所有外部设备传回的数据,把它们处理成统一的格式,应该具有扩展性,版本兼容性。否则,外围设备发生改变时,设 备所收到的数据格式不一样,将会增加工作量。最明显的例子就是GPS原来的2个小数位变为4个小数位所产生的影响,不仅影响到了设备,也影响到了PC的 SDK,还影响到了上层程序,影响巨大,你会认为这个改动,花费4个人每人半小时的时间就能搞定吗?如果你这么认为,那太天真了。但如果原来是定义好了, 考虑到小数点后10位小数,一开始PC就和设备定义好这样的协议,则这样的变化,PC端就不需要做任何改动,不用花一秒钟的时间,要知道,有那讨论的时 间,还不如早点下班休息一会儿也好。但我们确实没有早点下班,反而是为此花费了大量的时间去更正,去讨论,去修改对外的文档,去重新理解这个GPS,还要 做到和以前版本的兼容而新定义了一个结构,还要记住新旧GPS之间的差别,当有新人问的时候,还要花费时间去讲述这段历史,以便他能够理解为什么会有两个 差不多的结构存在而没有把它们合并成为一个。所以,我们加班,加班来做这些无聊的事情,做这些本来就不应该占用时间的事情。做这些本来就不应该有的工作。 今天,你是否将会做很多无聊的工作?是否做了很多无聊的工作?你是否还要加班?

    虽 然都是程序,但不同的程序是有着很大区别的。这不仅仅是哪种语言的问题,而是一种感性认识的存在,在实际中,这些问题确实存在。如果把我们的SDK限制 住,用户只能使用VC来开发,虽然对大部分客户来说都不是问题,但绝对不是所有的客户都没有问题,成都的客户,就是一个活生生的例子。这只是其中的一个表 面问题,另外还有一个问题,对于没有使用过c、c++、或者是VC的研发人员来说,如果没有进行过底层的开发,我相信,十之八九都不会关心long型变量 占了多少字节,char和TCHAR有什么区别,结构还有字节对齐这样的问题,所以,我们的SDK要做到"通用",让这一类的开发人员也易于理解,而不是 故弄玄虚地再故意弄出几个以位来表示的结构成员,那样只会增加客户的负担,只会增加我们自己的工作量。同样,我们要定义的标准,是为了产品的长远发展,是 为了减少工作量,是为了尽快地推出新产品,是为了研发部各小组之间交流的方便。除了也有维护方便性之外,我们还应该看到,我们定义的数据结构,人工来看是 次要的,而计算机可以方便地读取和处理是首要的,所以,除了某些特殊的情况之外,我们应该按照计算机的意思来定义数据结构,这样,才会使程序的运行效率提 高。基于这些思想,设备就不应该让外围设备的数据格式在设备中到处乱窜,SDK不应该让设备的数据格式在SDK中乱窜,上层的应用程序不应该让SDK的数 据格式在SDK的接口上乱窜,能使用数字表示的,就不要使用字符串来表示。只有每一层都严格地把关,最后到了上层的应用程序,才会做到最大限度地统一,上 层应用程序才不会因为SDK的改动而大修。设备应该做到数据格式的统一,无法统一的,放到SDK上来统一,如果SDK也无法统一的,只能麻烦上层的应用程 序来适应了。同样,设备不应该把难懂的数据发给SDK来解析,SDK应该使用通常语义就能理解的定义来定义数据,这种难以理解的语法和数据,应该在设备内 部处理,为什么要把这种难以理解的语义,从头带到尾,让每个环节的研发人员,都难以理解呢?所以我建议,设备端也需要有一套自己的SDK,专门来处理外围 设备发来的数据,把它们处理成设备使用的标准数据格式,而且这个SDK对所有的设备都通用,完成一个之后,其它设备都直接使用,岂不美哉?

    研 发部写软件的同事们,特别是做PC软件的同事们,如果稍有注意,很容易发现,VC的include目录中的文件里,还有msdn的sample文件里和文 档的示例中,也包括msdn文档,有很多2000年之前的代码或者文章,1998年的有很多,最早的可能到了1995年,请恕我孤陋寡闻和不好的记性,不 知道那里是否有1990年之前的代码和文章,即使现在vc的开发环境已经修改了很多,IDE也大为改观,但1998年的代码还是占到了相当的比例。而听到 我们的同事说,不只一个人说过,我们的代码能用上个8年就非常了不起了。8年,是我听到的最长的时间,最短的有说不超过3年的。这大部分是在我定义标准格 式的文档时听到的。是啊,我们一开始就没有打算过让我们的代码和产品能稳定地用上50年、100年,计划就是短短几年内就要重写,就要大改,就要把旧的产 品踢掉卖全新的产品,有多少人可以做到三年一大改,5年一重写?100个人吗?还是1000个人?而我们现在才几十个人,PC也才不超过10个人,能做到 经常大改和重写吗?买我们产品的客户,注定就是要用上3、4年后再买我们的产品?笑话啊,所以,我常为此担心,也因此而无法使定义的标准畅通无阻地执行下 去,更可笑的是,先写文档做为标准,再写代码,然后再根据写好的代码来修改标准的文档,难道这还不够荒唐吗?如果是刚毕业的学生,或者是经验不多的研发人 员提出这样的问题,我觉得不大奇怪,我也喜欢只用int i, a, b;这样的写法,把a用做超过100行代码的函数中进行时长统计的变量,我也喜欢直 接使用int counter这样的全局变量。但是,这应该只是在"新手"的时候才用,在有了"经验"之后,就不应该再有这样的代码出现,最起码不应该让 这样的代码留在成形的代码中过夜。我们的研发人员如何?我没有检查过别人的代码,我不敢妄下结论,但有"经验"的都仔细地捉摸一下自己在当前水平的基础 上,技术水平是否还有提高的余地。

 

5. 这就是我想说的

    我们现在是有饭吃了,那么,就应该思考一下以后如何才能吃我们想吃的,而不是想着明天可以多赚10块钱而今天要拼命地不加思考地工作,那效率太低了,而且还会越来越低,每天多赚10块钱的计划会越来越受到挑战。

    公 司的高层,应该多考虑一下如何去制定标准,如何让这些标准能够准确无误地推行下去。这已经严重地影响了现在的效率、稳定和维护,这些问题已经迫在眉睫了, 岂能再这样拖延下去?也许一个公司的成长必然要受到这些问题的困扰,但当我们发现的时候,应该及时地去解决,使公司尽快成长起来,而不是面对问题犹豫不 决,无所适从,让员工为这些问题而彻夜地加班。在一个体面的公司里上班,特别是在一个高科技的公司里做着体面的工作,走在大街上应该是骄傲和自豪才对,可 是我走在大街上如此地匆忙,生活的压力是如此之巨,我未感受到科技带来的轻松,反而被这飞速发展的科技压得气吁吁。

    还有,我不想加班。因为加班并不能解决所有的问题,就像有钱并不能解决所有的问题一样简单。

(全文完)


[广告] 金秋最关注楼盘-房不胜房