几点感悟

工作时偶有所思,留下点感悟。
 
契约的重要性
 
  软件部件之间的交互方式就是契约。契约包括传递给软件部件的参数,以及软件部件何时何种情况抛出错误。契约和现实生活中零件的规格极其类似,软件契约的无法达成,就和零件规格不一致一样,无法正常协同工作。因此,在契约的情况下,只需要考虑软件部件和其他部件之间的契约(接口),软件部件的内部实现变得不太重要。每个软件部件都有其自身严格定义的自责,系统应当能够定位这些功能和职责,从而当部件发生错误时,只需要修复该部件的错误,即可使得整个系统的错误得到修复。
 
      因此,在设计时需要考虑契约因素。每个部件只要符合契约即可,不应该考虑其他不需要关心的因素。例如,数据库访问类正常工作的条件是数据库正常工作,在数据库访问类无法正常工作的情况下,如果数据库无法正常工作,则应当去尝试修复数据库,而不应当去要求在数据库访问类中处理这种错误。如果数据库无法工作,数据库访问类理当无法工作。这就是契约。
 
      使用契约手段设计软件,需要有良好的错误检测手段,以便当系统发生错误时快速方便定位出具体错误位置。
 
      契约设计的最难点在于对契约的定义。通常,如果部件与部件之间的交互仅仅是简单的几个数据类型传递则相对来说好办一些。如果部件和部件之间交互的对象是一个非常复杂的对象,而该对象内部某些方法是不稳定的,则这种情况下作为参数的对象内部发生异常错误就很难界定这种错误是那个部件导致的。因为对于复杂对象,在使用该对象之前描述该对象所期望的内部状态是困难的。例如:工作流对象内部包含太多内容,就很难一次性描述需要那些东西来避免空指针的情况。此外还有对于软件构造过程中,不同的对象,其契约要求是不一样。例如:方法的契约要求通常是参数是否设置正确,组件的契约要求则通常是其它组件内部设置符合要求。
 
      软件必定会朝简单化、组件化、契约化方向发展。从目前的阶段来看,在软件的各种层面上都有契约的概念。早起Windows平台下开发插件、到 Java 中一系列的开发规范、到面向服务的开发结构 SOA 都是某种契约。
 
先设计后实现
 
      动手做软件之前需要对软件需求进行深入了解,然后进行概要以及详细设计。切忌不作设计就立即动手搭建系统。任何随意的行为都会导致随意的系统,随意的系统往往错漏百出。因此,设计时使用契约原则,进行概要设计,详细设计。
 
测试驱动开发
 
      使用测试来驱动设计是比较困难的。最难的点在于:如何设计所有的测试,测试的细致程度如何?最简单的测试是软件的大目标,即:软件能够正常运行。但对于一个巨大的软件项目,单元测试显然是一个巨大的工作量。如果考虑减少工作量,那么单元测试又很难覆盖所有的测试点。显然,这里需要寻找一个哲学的平衡点,找到那些关键的测试点进行测试,设计较好的测试。
65 次阅读

Delphi 中的字符串的使用

心血来潮,仔细分析比较了一下 Delphi 字符串的各种陷阱。
 
1. 字符串的声明:
 
    可以用以下三种方式来声明字符串:
 
    1)  定义方式:
           S: string;
         初始化:
            S := ‘Hello World’;
         说明:
         这种情况是编译器默认处理的情况,编译器初始化了字符
         串 S,分配内存调整引用计数,其中 S[0] 为字符串长度
         ,但是不能直接访问,必须使用 Length 或者 SetLength
         函数来访问。
 
   2)  定义方式:
           S: PChar;
         初始化:
            S := ‘Hello World’;
         说明:
         这种情况下,实际上编辑器先做了字符串的初始化,再将字符
         串赋值给字符指针。Delphi 编译器自动处理 PChar 和 String 的
         转换过程。
 
    3) 定义方式:
           S: array [1..256] of Char;
        初始化:
            S := ‘Hello World’;
        说明:
       
        这种情况下,对字符串的初始化长度必须小于给定的数组长度,
        否则默认情况下编译出错。
       
--
另外,不能使用的是 s: array of Char; 这种方式的定义,这种方式定义了一个动态数组必须使用 SetLength 来初始化长度,但仍然不能直接对其进行字符串赋值,只能是当作一个数组来使用了。
 
2. 使用 FillChar 字符串和数组的区别:
 
对于字符串的 FillChar 初始化:
 
        const Len = 10;
       var S: string;
       begin
           SetLength(S, Len);
           FillChar(S[1], Len, 0);
       end;
 
字符串的 FillChar 调用的第一个参数必须是符串的第一个字符,下标是从 1 开始的。 注意,S[0] 是不允许访问的。也可以使用 PChar 来做这件事情:
 
    var
           S: String;
           P: PChar;
   begin
           SetLength(S, 5);
           P := PChar(S);
           FillChar(P[0], 5, 0);
   end;
   
    此时,对 P 进行 FillChar 操作时,实际上时按照数组方式来进行
   ,在进行 PChar 转换后, P[0] = S[1] ,因此 P 的下标为 0 。
 
        S := ‘Hello’;
       P := PChar(S);
  
   则: S[1] = P[0] = ‘H’
3. PChar 和 String 的通用:
 
 procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
  P: PChar;
begin
  S := ‘Hello World’;
  P := PChar(S);
   ShowMessage(P);
end; 
 
 即:PChar 可以直接当作字符串来使用。连 S := S + P 这样的式子都没有错。
 
4. P:PChar 的使用
 
    S := ‘Hello’;
   P := PChar(S);
 
    实际上 P 此时理论上(使用字符串常数初始化时实际上不是这样)
    指向 S[1],需要特别注意 的是,PChar 是指向字符的指针,因此又存在
    如下关系:
 
    P[0] = S[1] = P^ = ‘H’
 
    P^ 指的是指针指向的内容。同样也存在这样的关系:
 
    Integer(P) = Integer(@p[0])
 
    当使用 SetLength(S, 10) 来初始化 S 时,下列关系成立:
 
    Integer(P) = Integer(@p[0]) = Integer(@S[1])
 
    但是使用字符串常数初始化时实际上不是这样:
 
    Integer(P) = Integer(@p[0]) != Integer(@S[1])
 
    但是 S[1] 的地址却和 P 的地址不同,是否 S := ‘Hello’ 时,S 被复制了?
   (这儿又是一个编译器魔法)
 
5. PChar 和 PShortString
 
    1) PChar 的误区:
 
    由于 PChar 往往能够和字符串通用,例如:
 
        P: PChar;
       P := PChar(S);
       ShowMessage(P)
  
   因此,往往会误理解为 PChar 就是字符串,所以在获取字符时
    会使用错误用法:
 
        P^[0] (错误用法)
 
    正确的用法是:
 
        P[0]
 
    P 实际上是指向了第一个字符的指针。
 
    2) 看看 PShortString 吧:
 
    对于 PShortString ,它是一个指向字符串的指针。因此,如果
  
       PStr: PShortString;
 
    则 PStr^ 指的是一个字符串,因此获取字符串的内部字符时使用
 
        PStr^[0] (合法形式,但需要注意此时下标从 0 开始)
 
    这里使用 PShortString 又有一个很奇怪的场景:
 
        PStr: PShortString;
       S: string;
        PStr := PShortString(S); // (正确用法)
       // PStr := @S; // (错误用法)
 
    显然,这个用法相当让人费解,不禁让人怀疑其实 String 内部就是一个指针(编译器魔法)错误用法 PStr := @S 合乎对指针的理解: PStr 是指向字 符串的指针,那么现在就取字符串的地址并赋值给它。但是事实上却不管用。正确用法: PStr := PShortString(S); 包括 P := PChar(S) 都显然不 符合指针的定义的。
  
   此处可见 Delphi 本身语法的不统一性。
后记
——
 
    本人接触 Delphi 已经不下 5 年,但平时若不仔细推敲 Delphi 语法,仍然会深陷其编译器陷阱中,其实很惭愧,本人以前太浮躁。
    从某种意义上讲,Delphi 在快速开发的同时隐藏了太多的细节,但这些细节又隐藏的不太好,时常又会暴露给程序员。
    此处涉及到的字符串相关的类型还比较少,除了上述类型外,Delphi 中的字符串还包括 Ansi 和 Wide 系列。
74 次阅读

什么是专业?

Oracle的工程师过来给我们培训。中间休息的时候我特地问了一句:“你们给我们提供的文档一般是谁写的?是开发人员吗?”

对方笑了笑说:“他们有专门的一批人写文档。通常的情况下是先是设计人员做出详细设计,这些设计详细到只要普通的编程人员照着设计一步步写最终就能完成整个产品。因此,有专门的一批人根据这些设计文档,就已经能完成很好的培训文档了。这些工作往往是外包到印度去做的。”

听完有些豁然开朗,终于亲耳听到美国的软件工程是怎样的状况。国内的IT环境多么混乱和浮躁。大家津津乐道于设计模式、开发框架,殊不知在这种作坊式的开发环境中,也许只能永远面对一个一个的项目,永远coding不完的需求。至少我们公司是这样的:一个开发人员,同时进行两个项目的开发。一个项目中2-3个开发人员,负责完成所有的工作:设计、编码、文档、部署。oh, My God!

人类现代社会因为高度分工而使得生产力进一步发展,为什么在中国的软件,就做不到良好的分工,良好的运作呢?

111 次阅读