转自:微点阅读 https://www.weidianyuedu.com
如何给变量命名?
在程序中,变量、符号常量、枚举、函数、类、接口、命名空间的名称都称为标识符。无论你做得好还是不好,标识符的命名都是一个大问题。如果标识符的命名不够清晰,将会给日后的开发带来很大的障碍。在某种程度下,清晰易懂的标识符甚至比冗长的注释文字更有效。标识符的命名也是代码风格的一个重要组成部分,既然是风格,只要合理统一,也就并没有什么硬性的规定。我这里给出的仅是一种在 .NET开发中较为通行的指导方案。 基本约定 标识符名称应仅包含英文大小写字母和数字,不要使用下划线、连字符以及其它非字母数字字符。不要使用中文或其它非英语语言符号。
string UserName; // 正确的命名 bool _isAdult; // 错误:不要使用下划线 string 用户名称; // 错误:不要使用中文或非英语符号
标识符的名称应该描述了它的含义,例如UserName、ColumnType等等。不应使用无意义的a、b、c、s、t等名称。除非是用于循环变量的i、j、k或者是本身只有一个字母的情况(如表示坐标的X、Y)。
DateTime LastModifiedDate; // 正确的命名 double X, Y; // 虽然只有一个字母,但是表示了它的含义 for (int i = 0; i < length; i++) // 使用i作为循环变量已经成为传统习惯 { ...} int s, t, u, v; // 错误:没有意义的名称 bool aaa; // 错误:没有意义的名称
应使用规范、通顺、准确、易于理解的英语,绝不要使用汉语拼音等。
Alignment HorizontalAlignment; // 正确的命名 Color ForegroundColor; // 正确的命名 Alignment AlignAtX; // 错误:表述难以理解 Color QianJingYanSe; // 错误:不要使用汉语拼音
不要使用“匈牙利命名法”。这是在早期的一种流行的命名方式,它将变量的数据类型包含在变量名称中,例如:intAge、strName、frmMain。由于现在的语言大多是强类型语言,因此这显得冗长而且完全没有必要。 string FileName; // 正确的命名
int Age; bool IsPrime; string strFileName; // 错误:不要使用“匈牙利命名法” int intAge; bool boolIsPrime;
除非极有必要(例如在类型转换中的ToUInt16方法),否则不要在标识符中包含类型名称。当使用类型名称时,请使用通用的类型名,而不是特定语言的关键字。例如System.UInt32在Visual Basic中的等价关键字是Integer,在C# 中的等价关键字是int。那么使用在标识符中的名称应该是UInt32,而非Integer或int。因为其它语言的程序员也许并不理解这个词在你所使用的语言中的意义。
// 这里只是为了节省版面空间,因此将函数体及其大括号直接连在后面,在正式的代码中,这是 // 不应该出现的。后同。 int ConvertToUInt32(object value) { ... } // 正确的命名:使用通用类型名称 int ConvertToInt(object value) { ... } // 错误:不要使用语言特定的名称
大小写字母的使用 有些语言区分字母大小写,它们的语法大多来自于C语言,包括C++、C#、Java等;而有些语言则不区分大小写,比如Visual Basic和Delphi,不过它们都在代码中保留了最初的大小写形式。对于同样的一个名称,可以有如下几种大小写方式: Pascal大小写 这种方式对于Basic用户来说非常熟悉,它将每个单词的首字母都大写。比如:BackColor、GoBack、GetIndexOfValue等等。Pascal大小写与英文习惯上的标题大写法不同,即使是of、and、the、as、by等词仍需要将首字母大写。 当遇到首字母缩写词的时候,如果只有两个字母,则两个字母都大写,比如:DBNull、IOStream等;如果是三个字母以上,则视为普通的单词,第一个字母大写,其余小写,如:XmlElement、HtmlDocument等。 对于命名空间、类、接口、以及类与接口中的公开成员(包括属性、方法、事件等)的名称,都应使用Pascal大小写。
public class Photo // 正确的命名 { public event EventHandler changed; // 错误:事件名称应使用Pascal大小写 // 应为:Changed public int Width { get { ... } } // 正确的命名 public int HEIGHT { get { ... } } // 错误:公开属性名称应使用Pascal大小写 // 应为:Height public void resizeTo(int width, int height) { ... } // 错误:公开方法名称应使用Pascal大小写 // 应为:ResizeTo }
Camel大小写 在早期的C/C++ 语言以及Java语言中,这种方式比较多见。它是将第一个单词小写,后面的其它单词首字母大写。比如:backColor、goBack、getIndexOfValue。 当遇到首字母缩写词的时候,如果位于最开始处,那么一律全部小写,如:dbTable、xmlStream等;如果该单词位于中间或末尾,则按照Pascal大小写的规则,如:getDBName、parentXmlNode等。 对于函数的形参应使用Camel方式命名,类中的保护成员及私有成员可以使用Camel大小写,有时也可以使用Pascal大小写,但必须保持统一。
class PhotoExhibitor { // 错误:函数的参数应使用Camel大小写方式 // 应为:usingTransaction public void ShowNext(bool UsingTransaction) { ... } // 正确的命名 public void ShowPrevious(bool usingTransaction) { ... } private Photo[] photos = ...; // 正确的命名。私有成员通常使用Camel大小 private int currentIndex = 0; // 写方式,整体保持一致。 // 错误:函数的参数应使用Camel大小写方式 // 应为:photoToShow private void showPhoto(Photo phototoshow) { ... }
字母缩写词与复合词 首字母缩写词是指由多个单词的首字母组成的缩写,例如HTML是Hyper-Text Markup Language的缩写。除非确实有必要,而且它的缩写广为大众所理解时,才应使用首字母缩写词。另外一种常见的简写方式并不在此列,如ID是identity的缩写,它是单个单词的简写,对于大小写命名规则来说,它应视为一个单词而非一个缩写。因此ID即使在Pascal大写中也应被写为Id。 如果某个复合词已经进化为了一个单独的单词,那么就不要再将它们分开大写。比如Hashtable等。对于普通的复合词,还应按照标准的方式,如:FileName(fileName)、UserName(userName)、SignOut(signOut)。
class MediaRecorder { public string Filename { get {...} }; // 错误:复合词应该作为不同的单词处理。 // 应为:FileName public long ID { get {...} }; // 错误:ID是单个单词的简写。 // 应为:Id public string XMLInfo; // 错误:首字母缩写词超过两个字符,应 // 作为普通单词来处理大小写规则。 // 应为:XmlInfo }
不要依赖大小写区分标识符 定义大小写准则只是为了便于阅读和理解,并不能提供语法上的便利,更不应通过区分大小写来区分两个不同的变量,或者是规避系统类库中的同名标识符。 正如前面所述,并不是所有的语言都区分大小写,在C# 中编写的组件也许会被用在VB.NET中。因此,不要在同一个范围内定义两个仅在大小写上存在差别的标识符。唯一的例外是:对私有的字段名称使用Camel大小写,而对相应的公开属性名称上使用Pascal大小写。
private int num; // 这本身没有问题 private double Num; // 错误:它与int num变量仅仅在大小写上存在差异 private long NUM; // 错误:它与前两个变量仅仅在大小写上存在差异 private int age; public int Age // 正确的命名。虽然它与私有成员int age仅在大小写上存在差异, { // 但是属于我们所说的特例。 get { ... } }
使用适当的前缀与后缀 对于普通的变量名等大多数标识符来说,都不需要也不应该使用前缀或后缀。下面例出的是一些需要特殊说明的情况: 类 不要为类名添加“C”前缀。对于熟悉Visual C++ 的程序员来说,在类名前加上“C”前缀已经习以为常。由于 .NET中的语言是完全基于面向对象之上的,并不存在C++ 中的“游离”函数,因此给每个类再加上“C”前缀显得没有必要。 class Photo // 正确的命名 { ... } class CPhoto // 错误:不要为类名添加“C”前缀 { ... } 接口 必须为接口名称使用“I”前缀,例如:IDisposable、IEnumerable等等。
public interface IExpressionItem // 正确的命名 { ... } public interface ExpressionItem // 错误:必须为接口名称添加“I”前缀 { ... }
如果某个接口附有一个该接口的标准实现类时,这个类必须与接口除前缀“I”之外的部分名称完全相同。如IList接口和List类、IAsyncResult接口和AsyncResult类。
泛型类型参数 如果泛型类只有一个类型参数,可以使用单字母“T”作为类型参数的名称,如List。如果包含多个类型参数,则应分别使用描述性的名称,并使用“T”作为前缀,如Dictionary。对于有特定类型约束的类型参数,可以使用约束类型的名称,如约束于ISession的类型参数可命名为TSession。
public class BinaryTree // 正确的命名 { ... } public class Converter // 正确的命名 { ... } public class Converter<T, U> // 错误:T、U并没有描述出它的意义 { ... } public class Converter<Input, Output> // 错误:类型参数必须具有“T”前缀 { ... } public interface ISessionChannel where TSession : ISession { ... } // 正确的命名
事件与委托 不要为委托添加“Delegate”前缀或后缀。对于用于事件处理的委托,应该加上“EventHandler”后缀;对于其它情况下的委托,应该加上“Callback”后缀。传递事件参数应使用System.EventArgs类,或由其派生,其名称因为事件名称加上“EventArgs”后缀。
public delegate void ClickDelegate(); // 错误:不要为委托添加“Delegate”后缀 // 应为:Click或ClickCallback public delegate void ProcessPhoto(); // 错误:非事件委托应加上“Callback”后缀 // 应为:ProcessPhotoCallback // 这是一个正确使用事件及委托的例子 public class Transaction { // 事件名称使用Pascal大小写:Completed public event CompletedEventHandler Completed; ... } // 为事件委托添加了“EventHandler”后缀 public delegate void CompletedEventHandler(...); // 事件数据类从EventArgs类派生,并以“EventArgs”为后缀 public class CompletedEventArgs : EventArgs { ... }
不要为事件添加“Before”或“After”的前缀,而应使用其相应的分词形式。具体参看“措辞及用词选择”一节。 自定义属性类 为自定义属性类添加“Attributes”后缀。
// 正确的命名 [System.AttributeUsage(System.AttributeTargets.Class)] public class AuthorAttributes : System.Attribute { ... } // 错误:应为自定义属性添加“Attributes”后缀 [System.AttributeUsage(System.AttributeTargets.Class)] public class Author : System.Attribute { ... }
枚举 不要为枚举添加“Enum”前缀或后缀。不要在枚举值中使用枚举类型的名称作为前缀或后缀。例如:在Weekday枚举中应该定义Monday而不是WeekdayMonday。
public enum Color // 正确的命名 { Red, // 正确的命名 Green, Blue } public enum ColorEnum // 错误:不要为枚举类型名称添加“Enum”后缀 { RedColor, // 错误:不要在枚举值名称中添加枚举类型的名称 GreenColor, BlueColor }
措辞及用词选择 无论是哪一种名称,都应尽可能地反映其准确含义,包括其中可能存在的隐喻。命名的目的是为了让类库的使用者更快更准确地了解该类库的用法,而不是解释该类库内部是如何工作的。命名要从使用者的角度出发,便于他们找到需要的东西。 命名空间 命名空间因指示其中包含的类型所提供的功能,应使用名词或名词短语作为命名空间的名称。为了避免冲突,命名空间应遵循下列命名格式: <公司名称>.<产品或技术名称>[.<特性名称>][.<子命名空间>] 虽然通常认为命名空间名称应该使用Pascal大小写方式,但是如果您公司或品牌的名称有独特的大小写方式,那么当然应该遵照您公司或品牌原有的写法。例如:IBM虽然是首字母缩写词,但不宜写为 Ibm。AutoCAD也不宜写成AutoCad等等。 类 类通常用来表示实体或是某种概念,因此应用名词或名词短语作为命名空间和类的名称。
public class PhotoExhibitor // 正确的命名 { ... } public class ExhibitPhoto // 错误:不要使用动词或者动词短词作为类名称 { ... }
类名称不应与所在的命令空间名称相同(不考虑大小写差异),这在某些语言中会造成开发上的不便。类名称也不要与系统核心类库中的类名称相同。在尽可能已知的范围内(至少是Visual Basic.NET、Visual C++.NET和Visual C#.NET),类名称不应与这些语言的关键字相同。
namespace PhotoExhibitor { public class Application // 正确的命名 { ... } public class Photo // 正确的命名 { ... } } namespace PhotoExhibitor { public class PhotoExhibitor // 错误:类名称不应与所在的命名空间名称相同 { ... } public class Image // 错误:类名称与System.Drawing.Image相同 { ... } }
接口 接口通常表示类具有的某方面特性或共性。当接口主要用于标识类的某种身份时,应使用与该身份相关的名词或名词短语作为接口的名称。例如:IList、IPermission等。当接口用于表示该类支持某种特定的操作时,应使用表示其能力的形容词作为接口的名称,例如:IEnumerable、IComparable等。
public interface IExpressionItem // 正确的命名 { ... } public interface IDispatchable // 正确的命名 { ... } public interface IPresentsAnItem // 错误:不应使用动词短语作为接口名称 { ... } public interface ICanBeDispatched // 错误:不应使用系表结构作为接口名称 { ... }
法与参数 方法通常表示对数据进行操作,因此应使用动词或动词短语作为方法的名称。该动词描述应该让使用该方法的人理解它所执行的操作,而不需要解释方法具体是如何实施的,除非调用该方法的人必须知晓以便于区分多个方法。
public string Name() { ... } // 错误:不应使用名词作为方法的名称 // 应为:GetName public void StoreUsingBinaryTree(...) // 错误:不需要通过名称解释方法的细节 { ... } // 应为:Store public void BubbleSort(...) { ... } // 正确的命名:虽然它解释了方法的细节, public void QuickSort(...) { ... } // 但由于存在多个类似的方法,必须通过其 // 细节来进行区分。
重载函数的参数名应与基类中被重载函数的参数名称保持一致。例如在重载Object.EqualTo(Object obj) 方法的时候,也应该使用“obj”作为参数名称,以保持一致性。
public abstract class Transaction { // 这是一个抽象函数,请注意它的参数名称 public abstract void Switch(Photo before, Photo after); } public class FadeInTransaction : Transaction { // 正确的命名。它使用了与基类中一致的参数名称 public override void Switch(Photo before, Photo after) { ... } } public class FadeInTransaction : Transaction { // 错误:应该使用与基类方法声明中一致的参数名称。 public override void Switch(Photo photo1, Photo photo2) { ... } }
属性 属性通常用于描述状态,因此应使用名词、名词短语或者形容词作为属性的名称。对于逻辑类型的属性,可以为其添加“Is”、“Can”或“Has”的前缀,但应使用自然、得当。切勿使用否定式的表述方式,如CannotSeek、IsNotEnabled等等都是不合适的。
class PhotoCollection { public int Count { get { ... } } // 正确的命名 public int GetAmount { get { ... } } // 错误:属性名称不应为动词短语 public bool IsEmpty { get { ... } } // 正确的命名 public bool HasNoPhoto { get { ... } } // 错误:逻辑类型属性不应使用否定式 }
可以使用与类型名称相同的名称作为属性名称。例如用于表示其颜色的属性,被定义为Color类型,属性的名称通常也可以命名为“Color”。
class Photo { public ImageSource ImageSource { get { ... } } // 正确的命名
如果提供了可读的属性(如Age),就不要再提供Get方法(如GetAge),这会使程序员对到底应该使用哪个产生疑惑。
class Photo { public Size Size { get { ... } } // 这本身没有问题 public Size GetSize() // 错误:该方法与前一个属性的功能至少看 { ... } // 来类似,会使开发人员感到疑惑。 }
事件 事件表示一个状态的改变或者发生,它隐含着一个动作的发生,因此应使用动词或动词短语作为事件的名称。为了强调事件发生的时间前后关系,使用现在分词表示在动作准备开始时引发的事件,如Validating、Closing等;而使用过去分词表示动作结束之后引发的事件,如Validated、Closed、Changed等等。不要用“Before”或“After”作为前缀或后缀。
class Table { public event EventHandler RecordRemoving; // 正确的命名 public event EventHandler RecordRemoved; public event EventHandler BeforeRemoveRecord; // 错误:不要使用“Before” public event EventHandler AfterRemoveRecord; // 或“After”作为前后缀。 }
对于事件委托,坚持使用命名为“sender”和“e”的两个参数。“sender”的类型为Object,表示触发该事件的对象,“e”的类型为EventArgs或其派生类,带有与该事件相关的数据信息。事件委托总是以“EventHandler”为后缀,事件参数类总是以“EventArgs”为后缀。
// 这是一个正确使用事件及委托的例子 public class Transaction { // 事件名称使用Pascal大小写:Completed public event CompletedEventHandler Completed; // 这是标准的用来引发事件的函数 protected virtual void OnCompleted(CompletedEventArgs e) { if(Completed != null) { Completed(this, e); } } ... } // 为事件委托添加了“EventHandler”后缀,并使用了标准的参数格式 public delegate void CompletedEventHandler(object sender, CompletedEventArgs e); // 事件数据类从EventArgs类派生,并以“EventArgs”为后缀 public class CompletedEventArgs : EventArgs { ... }
枚举 对于普通枚举的名称,请使用名词的单数形式。虽然枚举中包含了多种可能,但由于每个枚举类型的变量只会保存一种可能性(例如Color.Red等),因此使用复数是不恰当的。 反之,对于位域值的枚举(标志枚举),则应该使用名词的复数形式,因为这种类型的变量可能包含多个枚举值的复合值(例如FontStyles.Bold与FontStyles.Italic的复合)。
public enum Color // 正确的命名 { Red, Green, Blue } public enum Colors // 错误:每个枚举值在同一时间只会用到一次,因此不 { // 应在枚举名称中使用名词的复数形式。 Red, Green, Blue } [Flags] public enum Permissions // 正确的命名 { // 由于这是一个标识枚举,有可能复合使用,因此在名 None = 0, // 称中使用名词的复数形式是正确的。 Read = 1, Write = 2, ReadWrite = Read | Write }