2.14.2 用法约定
在任何开发环境中,通常有一些传统的编程风格。这些风格不是语言的一部分,而是约定,例如,变量如何命名,类、方法或函数如何使用等。如果使用某语言的大多数开发人员都遵循相同的约定,不同的开发人员就很容易理解彼此的代码,有助于程序的维护。例如,Visual Basic 6的一个公共(但不统一)约定是,表示字符串的变量名以小写字母s或str开头,如Dim sResult As String或 Dim strMessage As String。约定主要取决于语言和环境。例如,在Windows平台上编程的C++开发人员一般使用前缀psz或 lpsz表示字符串:char *pszResult; char *lpszMessage;,但在UNIX机器上,则不使用任何前缀:char *Result; char *Message;。
从本书中的示例代码中可以总结出,C#中的约定是命名变量时不使用任何前缀:string Result; string Message;。
注意:
用带有前缀字母的变量名来表示某个数据类型,这种约定称为Hungarian表示法。这样,其他阅读该代码的开发人员就可以立即从变量名中了解它代表什么数据类型。在有了智能编辑器和IntelliSense之后,人们普遍认为Hungarian表示法是多余的。
但是,在许多语言中,用法约定是从语言的使用过程中逐渐演变而来的,Microsoft编写的C#和整个.NET Framework都有非常多的用法约定,详见.NET/C# MSDN文档说明。这说明,从一开始,.NET程序就有非常高的互操作性,开发人员可以以此来理解代码。用法规则还得益于20年来面向对象编程的发展,因此相关的新闻组已经仔细考虑了这些用法规则,而且已经为开发团体所接受。所以我们应遵守这些约定。
但要注意,这些规则与语言规范是不同的。用户应尽可能遵循这些规则。但如果有很好的理由不遵循它们,也不会有什么问题。例如,不遵循这些用法约定,也不会出现编译错误。一般情况下,如果不遵循用法规则,就必须有一个说得过去的理由。规则应是一个正确的决策,而不是让人头痛的东西。在阅读本书的后续内容时,应注意到在本书的许多示例中,都没有遵循该约定,这通常是因为某些规则适用于大型程序,而不适合于本书中的小示例。如果编写一个完整的软件包,就应遵循这些规则,但它们并不适合于只有20行代码的独立程序。在许多情况下,遵循约定会使这些示例难以理解。
编程风格的规则非常多。这里只介绍一些比较重要的规则,以及最适合于用户的规则。如果用户要让代码完全遵循用法规则,就需要参考MSDN文档说明。
1. 命名约定
使程序易于理解的一个重要方面是给对象选择命名的方式,包括变量名、方法名、类名、枚举名和命名空间的名称。
显然,这些名称应反映对象的功能,且不与其他名称冲突。在.NET Framework中,一般规则也是变量名要反映变量实例的功能,而不是反映数据类型。例如,Height就是一个比较好的变量名,而IntegerValue就不太好。但是,这种规则是一种理想状态,很难达到。在处理控件时,大多数情况下使用ConfirmationDialog 和 ChooseEmployeeListBox等变量名比较好,这些变量名说明了变量的数据类型。
名称的约定包括以下几个方面:
(1) 名称的大小写
在许多情况下,名称都应使用Pascal大小写命名形式。 Pascal 大小写形式是指名称中单词的第一个字母大写: EmployeeSalary, ConfirmationDialog, PlainTextEncoding。注意,命名空间、类、以及基类中的成员等的名称都应遵循该规则,最好不要使用带有下划线字符的单词,即名称不应是employee_salary。其他语言中常量的名称常常全部都是大写,但在C#中最好不要这样,因为这种名称很难阅读,而应全部使用Pascal 大小写形式的命名约定:
const int MaximumLength; |
我们还推荐使用另一种大小写模式:camel大小写形式。这种形式类似于Pascal 大小写形式,但名称中第一个单词的第一个字母不是大写:employeeSalary、confirmationDialog、plainTextEncoding。有三种情况可以使用camel大小写形式。
● 类型中所有私有成员字段的名称都应是camel大小写形式:
public int subscriberId; |
但要注意成员字段名常常用一个下划线开头:
public int _subscriberId; |
● 传递给方法的所有参数都应是camel大小写形式:
public void RecordSale(string salesmanName, int quantity); |
● camel大小写形式也可以用于区分同名的两个对象—— 比较常见的情况是属性封装一个字段:
private string employeeName;
public string EmployeeName
{
get
{
return employeeName;
}
}
|
如果这么做,则私有成员总是使用camel大小写形式,而公共的或受保护的成员总是使用Pascal 大小写形式,这样使用这段代码的其他类就只能使用Pascal 大小写形式的名称了(除了参数名以外)。
还要注意大小写问题。C#是区分大小写的,所以在C#中,仅大小写不同的名称在语法上是正确的,如上面的例子。但是,程序集可能在VB .NET应用程序中调用,而VB .NET是不区分大小写的,如果使用仅大小写不同的名称,就必须使这两个名称不能在程序集的外部访问。(上例是可行的,因为仅私有变量使用了camel大小写形式的名称)。否则,VB .NET中的其他代码就不能正确使用这个程序集。
(2) 名称的风格
名称的风格应保持一致。例如,如果类中的一个方法叫ShowConfirmationDialog(),其他方法就不能叫ShowDialogWarning()或 WarningDialogShow(),而应是ShowWarningDialog()。
(3) 命名空间的名称
命名空间的名称非常重要,一定要仔细设计,以避免一个命名空间中对象的名称与其他对象同名。记住,命名空间的名称是.NET区分共享程序集中对象名的惟一方式。如果软件包的命名空间使用的名称与另一个软件包相同,而这两个软件包都安装在一台计算机上,就会出问题。因此,最好用自己的公司名创建顶级的命名空间,再嵌套后面技术范围较窄、用户所在小组或部门、或类所在软件包的命名空间。Microsoft建议使用如下的命名空间:<CompanyName>. <TechnologyName>,例如:
WeaponsOfDestructionCorp.RayGunControllers WeaponsOfDestructionCorp.Viruses |
(4) 名称和关键字
名称不应与任何关键字冲突,这是非常重要的。实际上,如果在代码中,试图给某个对象指定与C#关键字同名的名称,就会出现语法错误,因为编译器会假定该名称表示一个语句。但是,由于类可能由其他语言编写的代码访问,所以不能使用其他.NET语言中的关键字作为对象的名称。一般说来,C++关键字类似于C#关键字,不太可能与C++混淆,Visual C++常用的关键字则用两个下划线字符开头。与C#一样,C++关键字都是小写字母,如果要遵循公共类和成员使用Pascal风格的名称的约定,则在它们的名称中至少有一个字母是大写,因此不会与C++关键字冲突。另一方面,VB的问题会多一些,因为VB的关键字要比C#的多,而且它不区分大小写,不能依赖于Pascal风格的名称来区分类和成员。
表2-12列出了VB中的关键字和标准函数调用,无论对C#公共类使用什么大小写组合,这些名称都不应使用。
表 2-12
|
Abs |
Do |
Loc |
RGB |
|
Add |
Double |
Local |
Right |
|
AddHandler |
Each |
Lock |
RmDir |
|
AddressOf |
Else |
LOF |
Rnd |
|
And |
Empty |
Long |
SaveSettings |
|
Ansi |
End |
|
Second |
|
AppActivate |
Enum |
LTrim |
Seek |
|
Append |
EOF |
Me |
Select |
|
As |
Erase |
Mid |
SetAttr |
|
Asc |
Err |
Minute |
SetException |
|
Assembly |
Error |
MIRR |
Shared |
|
Atan |
Event |
MkDir |
Shell |
|
Auto |
Exit |
Module |
Short |
|
Beep |
Exp |
Month |
Sign |
|
Binary |
Explicit |
MustInherit |
Sin |
|
BitAnd |
ExternalSource |
MustOverride |
Single |
|
BitNot |
False |
MyBase |
SLN |
|
BitOr |
FileAttr |
MyClass |
Space |
|
BitXor |
FileCopy |
Namespace |
Spc |
|
Boolean |
FileDateTime |
New |
|
|
ByRef |
FileLen |
Next |
Sqrt |
|
Byte |
Filter |
Not |
Static |
|
ByVal |
Finally |
Nothing |
Step |
|
Call |
Fix |
NotInheritable |
Stop |
|
Case |
For |
NotOverridable |
Str |
|
Catch |
Format |
Now |
StrComp |
|
CBool |
FreeFile |
NPer |
StrConv |
|
CByte |
Friend |
NPV |
Strict |
|
CDate |
Function |
Null |
String |
|
CDbl |
FV |
Object |
Structure |
|
CDec |
Get |
Oct |
Sub |
|
ChDir |
GetAllSettings |
Off |
Switch |
|
ChDrive |
GetAttr |
On |
SYD |
|
Choose |
GetException |
Open |
SyncLock |
|
Chr |
GetObject |
Option |
Tab |
|
CInt |
GetSetting |
Optional |
Tan |
|
Class |
GetType |
Or |
Text |
|
Clear |
GoTo |
Overloads |
Then |
|
CLng |
Handles |
Overridable |
Throw |
|
Collection |
Hour |
ParamArray |
Timer |
|
Command |
If |
Pmt |
TimeSerial |
|
Compare |
Iif |
PPmt |
TimeValue |
|
Const |
Implements |
Preserve |
To |
|
|
Imports |
Print |
Today |
|
CreateObject |
In |
Private |