Zade's Weblog

程序人生

Monthly Archives: 2月 2006

翻译的艺术-public/private

翻译的艺术-public/private

 

计算机技术词汇的翻译并不太难,但是要翻译的很好就不是很容易了。前人有过标准,“信、达、雅”。对于计算机技术词汇,我的理解是:“信”就是要准确,“达”就是要尽量把作者的原意表达全面,“雅”就是高雅,也就是说翻译的要有美感。

简单的说,翻译的标准是要准确、有美感。前一个标准相对的客观,后一个标准比较的主观,不过对这个主观的标准仍然有迹可寻。我们看一个例子:“romantic”->“浪漫”,我们不说这个翻译的准确性(中文里面还有表达这个意思的相近词汇吗?),而其美感是勿庸置疑的,我觉得这是一个伟大的翻译。

这个翻译的美感来自于哪里呢?首先是这个翻译朦胧的表达了“romantic”的本意;另外还有,“浪漫”和“romantic”的发音有点接近。也就是说:朦胧的表达本意和接近的发音可以产生美感。

另外,最初的翻译和翻译传播广度对翻译本身的影响也很大。如果一个外文词汇经过最初的翻译并被广泛的接受了,那么即便有更好的翻译,也很难改过来了。这是习惯和大众的力量。

使用上面表达的意思,我们来考察一些有争议的翻译。首先是public/private,这个的翻译有“公有/私有”,或者“公用/私用”, 裘宗燕老师对此有过论述(http://www.is.pku.edu.cn/%7Eqzy/books/cppl/words.htm):

许多人将pablic/private说成是“公有/私有”,这两个词的意义与这里所需要的意义根本不符。public/private描述的是使用权:谁有权去访问/使用这些成分:是公众普遍可用,还是内部使用。因此我选择“公用/私用”这一对词。成员的所有权原本就非常清楚,完全不需要额外的描述。

在与网友“虫虫”的讨论中,他提出了一个很好的例证:假定类 B 将其成员 m 定义为 private,类 D 由类 B 派生。在类 D 的对象 d 中有没有成员 m?回答当然是“有”!但是 d 能使用其成员 m 吗?“不能”!因为其基类 B 已经将成员 m 保留为“自己用的了”,这也就是“私用”!由于基类将成员保留为私用,派生类的对象即使“有”此成员但却不能用。这又是“有没有”和“可不可以使用”确实不一样的一个明显实例。

首先从准确性的角度来说,public/private表达是可访问性,而不是可见性,而这两者有很大的差别,关于这点可以参考Hurb Sutter的《C++ Exceptional Style》。既然public/private表达是成员“访问权”的有无,而不是成员本身的有无(这基本上反驳了虫虫的例证),那么翻译为“公有/私有”是准确的了。

再者,从美感的角度来看,“有无”表达的是一种存在的状态,是一个中性的词汇,不带感情色彩。“有用/没用”,表达了一种个人的观点,一般是带有比较强的个人色彩的。比如:说某人(物)没用,某人(物)有用;骂人说“没用的东西”。所以“用”相对来说比较庸俗一些。表达“访问权”的有无,使用“有”比“用”更合适,更有美感。

所以我更加倾向于“公有/私有”的翻译方法。

C++ Java C#的比较-成员归属权的表示

标题:成员归属权的表示

 

成员归属权的表示的简洁性

优劣比较

C++

-1

9

Java

+1

10

C#

+1

10

 

讨论:

类的成员分成两类:实例成员和静态成员;命名空间的成员只是包含类型成员(C++还包含函数和数据成员)。这里所说的成员归属权就是指得这两类成员的归属权。

1.         由于C++支持引用和指针,分别使用”.””->”表示;类的静态成员和命名空间的类型、函数和变量的归属权使用”::”表示。也就是说,归属权在C++中有3种不同的表示法。相对于Java C#,这增加了记忆、理解和书写的复杂性,要减少一分。

2.         Java对于归属权的表示只是使用”.”表示。

3.         C#对于归属权的表示只是使用”.”表示。

 

实例:

// C++成员归属权的表示

{

       Foo foo;

       foo.someFun();      //引用归属权

      

Foo *p = new Foo();

       p->someFun();       //指针归属权

      

Foo::StaticValue = 0; //类静态成员归属权

      

std::vector<int> vec; //命名空间归属权

}

 

// Java成员归属权的表示

{

       Foo foo;

       foo.someFun();      //引用归属权

      

       Foo.StaticValue = 0; //类静态成员归属权

      

java.lang.Vector vector vec = new java.lang.Vector(); //命名空间归属权

}

 

// C#成员归属权的表示

{

       Foo foo;

       foo.someFun();      //引用归属权

      

       Foo.StaticValue = 0; //类静态成员归属权

      

System.Int32  i = 0; //命名空间归属权

}

 

 

 

C++ Java C#的比较-命名空间

标题:命名空间

 

 

支持多范型编程

引用的简洁性

主体的简洁性

优劣比较

C++

+1

+1

-1

9

Java

-1

+1

-1

8

C#

-1

-1

+1

8

讨论:

1.   C++通过namespace关键字支持命名空间,并且由于C++支持多范型编程,允许在命名空间里面声明变量、函数,相对C#Java,对用户来说这提供了更加丰富的选择。

2.    C#通过namespace关键字支持命名空间,但是由于C#不支持多范型编程,因此相对于C++,要扣掉一分。

3.    Java通过package关键字支持命名空间,但是Java不支持多范型编程,扣掉一分;而且当引用某个命名空间的部分或者整体的类型的时候,语法上更加复杂一些(见下例),所以还要扣掉一分;但是由于命名空间主体的定义部分更加的简略,所以加一分(见下例)。

实例:

namespace test // C++的命名空间定义

{

       using n.a;              //引入一个类型

       using n;          //引入n的部分或者所有的类型

       int i;

       int fun();//支持多范型

       class foo{};

}

 

package test; // Java的命名空间定义

//不必象C#C++那样把命名空间的主题放在{}当中,更加的简略

import n.a;      //引入一个类型

import n.*;     //引入n的部分或者所有的类型,语法相对于C++C#更加的复杂一些

class foo{};

 

namespace test // C#的命名空间定义

{

       using n.a;              //引入一个类型

       using n;          //引入n的部分或者所有的类型

       class foo{};

}

 

 

实施软件开流程控制的前提

软件开发的规模进入到一定的程度的时候, 需要软件开发流程来保证软件开发的可控性. CMM, RUP, ISO9001, 这些都是耳熟能详的流程控制方法, 但是能够成功应用这些方法的软件企业和单位并不是很多; 而且, 很多的软件企业和公司并没有采用这些流程方法, 但是仍然取得了很好开发的软件产品(例如微软, 他使用了别的方法).

我觉得这有两个方面的结论: 1 软件开发流程的采用是无需置疑的; 2 具体的流程控制方法应该结合自己开发团队的特点具体实施, 不一定是CMM, RUP或者其他的方法.

一般来说,一个开发团队总是从小到大逐步发展的, 那么在什么条件就可以实施流程控制的方法了呢? 我认为至少需要三个条件:

1.         足够的开发经验

没有足够的开发经验, 不足以实施流程控制. 要求刚刚毕业的大学生或者研究生来实施流程控制的方法, 是注定要失败的. 不是说大学生或者研究生不可以实施流程控制方法, 而是所不能够单单是他们., 必须由经验丰富的人来配合.

2.         程序的规模足够大

如果仅仅是一个很小程序, 完全没有必要实施流程控制方法. 试想你实现老师布置的数据结构作业的时候, 有必要实施流程控制吗? 程序的规模究竟要达到什么样的程度的时候才有必要实施流程控制的方法, 我想只要是一个团队而不是单独的个人, 就有必要实施流程的控制.

3.         具有对程序设计和开发技巧相当熟悉的牛人

代码控制的一个重要的方面就是代码审查(Code Review), 保证代码的风格一致和效率, 可读性和易维护性. 我想这就是公司的技术总监的职责吧. 这样的人不但要精于代码设计, 也要精于代码撰写. 从整体结构到函数实现, 从设计模式到数组的使用, 技术总监都要非常的精通.

Prefactor

Prefactor (http://www.artima.com/weblogs/viewpost.jsp?thread=147332)

老美真是会制造概念(不过我想这仍然不同于国内有些人, 只有概念而没有概念背后的东西, 说的俗一点, 总是给你玩虚的), refactor就是一个创造的单词, 通过Martin Flower相信大家已经相当的熟悉了, 国内翻译为重构. 这次又在artima上面看到一个prefactor, 按照我得理解, 翻译为”前构”也许比较合适

文章给了一些prefactor的大纲, 列举如下:
1. 策略和实现分开(Separate Policy from Implementation)
这是减少在refactor实施”方法提取”的手段. 实施策略和代码分开, 在代码中你就可以定义更多的方法. 从而不必在重构的时候事实更多的方法提取.(虽然可以通过工具支持实施方法提取, 但是那至少证明你原来的代码存在缺陷, 能预先避免当然更好; 而且不是所有的都可以通过方法提取所能够实现的)
2. 最容易调试的代码就是不写代码(The Easiest Code to Debug is That Which is Not Written)
你要实现的概念可能在有些开源的项目中已经被别人完美的实现了, 所以很多的情况下你不需要从头作起, 站在巨人的肩上会使你更加的伟大. 这意味着编程的首要工具是Google.
3. 工欲善其事, 必先利其器(Think About the Big Picture)
一般的编码是在一定的框架下实施的(比如J2EE, .NET), 所以首先要熟悉这些环境和框架所提供的功能.

refactor是敏捷开发的重要内容, 作者也把prefactor放在敏捷开发的流程中. Hurb sutter 在C++ Coding Standard中强调不要过早的优化, 但同时也不要过早的悲观(不理会优化), 是为了寻求一个恰到好处的平衡点. 我想 refactor和prefactor也是为了找到那个平衡点吧.

类成员变量的命名

在类的成员函数当中,可能需要访问两种类型的变量, 局部变量和成员变量, 例如:
class MyClass
{
 private int m_var; //成员变量命名方式1
 private int _var1;  //成员变量命名方式2
 private int var2_;  //成员变量命名方式3
 
 void someFunc (int var)
{
 int local = 10;
 if(local < var){
  m_var = var;
  _var2 = var;
  Var3_ =var;
}
}
}

上面三种成员变量的命名是很经典的, 现在仍然大行其道. 可以看到, 成员变量的命名方式只是一种约定俗成, 就是为了在局部函数中表明和局部变量的区别. 如果局部变量也使用上面三种命名方式的话, 那么这种命名方式便毫无意义了(我确实见过有人这么做的). 变量的命名方式是和变量的使用方式联系在一起的.
 我想提供一种另外的命名方式如下:
class MyClass
{
 private int var;   /成员变量1
 private int var2;  //成员变量2
 private int var3;  //成员变量3
 
 void someFunc (int var)
{
 int local = 10;
 if(local < var){
  this.var =var;
  this.var1= var;
  this.var2 = var;
}
}
}

我认为这种命名方式和使用方式更加的合理和清晰. 理由如下:
1. 从命名方式来看, 这样更加的清晰,自然,  更加的符合逻辑. 一般的变量命名总是和其意义联系在一起, 无论是添加”m_”, 前下划线和后下划线, 都没有必然的逻辑根据, 而且非常的不自然;
2. 从使用方式来看, 这样使得成员变量的归属感更强. 变量的前面增加”this.”, 显式的告诉读者, 这个变量是属于类的成员.

 

编程语言的终极梦想

        相对于C语言, C++语言更加灵活, 因为它是一种OOP的语言. C#更加灵活, 因为它还支持垃圾回收, 定制属性, 匿名方法. Ruby语言是一种动态语言, 它比C#更加的灵活.
        每一种语言都有自己的特点, 这也是它们如今仍然继续存在的原因. 微软在.Net体系下面定义了IL中间语言, 从而把各种使用.Net兼容的语言编写的代码之间可以相互的调用. 对于程序员来说, 这是一个巨大的进步.
       编程语言的更加灵活是其发展的一个趋势, 各种编程语言生成的代码之间的相互调用也是一个趋势. 从最终的角度来说, 编程语言语法可以通过某些特定的限制由编程人员自己定义, 并且通过工具生成相应的编译器.
        我认为这几乎是编程语言发展的一个终极梦想, 要达到不容易, 首要的是要定义一种类似与IL的中间语言, 并且抽象一个操作系统的编程接口.
        我想这或许对MDA很大的帮助.

ADO.NET数据库访问的一个缺陷和补救思路

在C#中,访问数据库的典型方法是:
// connect to my local server, northwind db        
         string connectionString = "server=(local)\NetSDK;" +
         "Trusted_Connection=yes; database=northwind";

         // get records from the customers table
         string commandString =
         "Select CompanyName, ContactName from Customers";

         // create the data set command object
         // and the DataSet
         SqlDataAdapter DataAdapter =
         new SqlDataAdapter(
         commandString, connectionString);

         DataSet DataSet = new DataSet( );

         // fill the data set object
         DataAdapter.Fill(DataSet,"Customers");
其访问数据库的模型是:DB-> SqlDataAdapter ->DataSet. 
 我们看到,在初始化SqlDataAdapter对象的时候,传入了SQL命令字符串和数据库的连接字符串;其中SQL命令字符串指明了访问数据库的表名。但是在填充数据集对象DataSet.的时候,仍然需要指明表名。你可以不指定表名,系统会给你一个默认的表名Tablen(n是一个递增的数字)。
 我们认为这是一个缺陷。理想的结果应该是,在初始化DataSet对象的时候,如果不指定表名,默认的结果应该是在SQL命令字符串中指明的表名,而不是Tablen;除非你想显式的改变表的名称。
 造成目前这个结果的原因可能是由于SQL命令字符串是非结构化的,也即SQL命令字符串不能显式的设定表名。如果使用一个类来包装SQL命令字符串,那么SqlDataAdapter对象可以显式的知道表名,而DataSet对象也就可以使用这个表名作为默认的表名了。(不过这是ADO.NET设计人员的事情了:)。

IEnumerator and IEnumerable

C# defines two interfaces to enumerate a collective object. Now let us see them:
public interface IEnumerator
{      
    object Current { get; }
    bool MoveNext();
    void Reset();
}
public interface IEnumerable
{
   IEnumerator GetEnumerator();
}
Now the question is: why does need the two interface? Maybe IEnumerator is enough. The Example is below::
class MyData : IEnumerator
{
   private int[] data;
   private int index;
      
   public MyData()
   {
       data = new int[10];
       index = -1;
    }
    public object Current
    {
        get { return data[index]; }
    }
    public bool MoveNext()
    {
        return ++index < data.Length;
    }
    public void  Reset()
    {
        index = -1;
    }
}

Is there any problem with this?
 Yes, there is. If you implement IEnumerator interface, not IEnumerable, there will be subtle problematic in some special cases. Now begin another example with MyData class object:
IEnumerator e = new MyData();
while (e.MoveNext())
{
  object o = e.Current;
  {
e.MoveNext();
    object o2 = e.Current;
    DoSomethingWith(o,o2);
   }
 }
 Do you see the potential problem above? This is caused by the essence of the IEnumberator interface. This interface force us to use the enumerator in this way:

 Set the original iterative position before the starting (Reset)
 Call MoveNext() function to move the position to the next, and check the current element is existing
 Get the current element from the Property Current
So , the IEnumberator is often implemented by using some internal state( in our MyData example it is the class instance member index). When you use the IEnumberator variable, you have to use it in the special way illuminated above. But when you use it in a nested way, you maybe destroy the object internal state, and the behavior is undefined.
 So, the .NET Framework provide another interface IEnumberable, which creates an IEnumerator variable by a function GetEnumberator(), and the internal state is saved in the variable returned. When you use it, you change the variable internal state, not the class instance variable implementing the interface IEnumerable, and the undefined behavior will never happen again.
 And remember, foreach is for IEnumerable, not for IEnumberator.

C#的资源回收

C#的GC机制,使得在C#中内存自动管理,在一定的程度上,这省去了程序员的很多麻烦,这可以看成是一个很大的进步。 在C++中,有一条很重要的原则,即资源的获取就是初始化(“resource acquisition is initialization”),也即是在构造函数中获取资源,在析构函数中释放资源。由于对象的构造函数和析构函数发生在特定的时刻,并且析构函数的调用是自动并且强加的,所以一般这可以保证不会产生资源得不到释放的情况(只要你正确的释放了资源)。C++的这条原则对于资源的管理很有帮助。 内存也是很重要的一种资源,C#中把内存的管理自动化。GC仅仅是把内存的管理自动化,但是其他的资源管理仍然需要程序员手动的代码管理,问题是:除去内存的资源管理的代码放在哪里? C#也提供了析构函数,但是几乎还不如不提供这种机制。因为:析构函数的执行时机是不确定的;析构函数不能显式的调用;析构函数在.NET框架下是Finalize虚函数的override,但是又不能按照override的一般规则进行。C#的析构函数几乎是无效的、混乱的。 也许是为了弥补这个缺陷,C#提供了IDispose标准化接口,使得用户可以显式的调用;同时为了资源管理的自动调用,C#提供了了using用法。 using (T obj1 = new T(), obj2 = new T()) { //……… } 上面的代码要求T类型实现了IDispose接口,然后C#会自动的调用接口的方法Dispose。 如果你使用的某种方法式成功的(C++的析构函数机制),那么你会总是使用它。而现在,C#提供了新的用法;虽然它是不得已的;但是对于C++的老用户来说,又得改变自己的习惯了。