第3章 命名准则
1 大小写约定
1 标识符的大小写规则
-
DO
: 命名空间 、 类型 、 成员 和 泛型参数 ,使用 PascalCasing 风格命名。
-
DO
:参数,使用 camelCasing 风格命名。
标识符 | 大小写 | 示例 |
---|---|---|
命名空间 | Pacal | namespace System.Security |
类型 | Pacal | public class StreamReader |
接口 | Pacal | public interface IEnumerable |
方法 | Pacal | public class Object { public virtual string ToString(); } |
属性 | Pacal | public class String { public int Length { get; } } |
事件 | Pacal | public class Process { public event EventHandler Exited; } |
字段 | Pacal | public class MessageQueue { public static readonly TimeSpan InfiniteTimeout; } public struct UInt32 { public const MinValue = 0; } |
枚举值 | Pacal | public enum FileMode { Append, ... } |
泛型方法中的类型参数 | Pacal | public partial class Enum { public static TEnum Parse } |
泛型类型中的类型参数 | Pacal | public partial class Task ... } |
元组的元素 | Pacal | public partial class Range { public (int Offset, int Length) GetOffsetAndLength(int length) { ... } } |
参数 | camel | public class Convert { public static int ToInt32(string value); } |
表中虽然标注了 public 的实例字段,但一般来说不应该把实例字段暴露在外界,而应该用属性。
2 大写首字母缩写词
-
DO
:两个字母的首字母缩写词需全部大写,除非它是 camelCasing 命名法的第 一 个单词。System.IO public void StartIO(stream ioStream, bool closeIOStream);
-
DO
:三个字母(含)以上的首字母缩写词, 有且仅有 第一个字母大写。如果首字母缩写词是 camelCasing 标识符的第一个单词,则 全部小写 。System.Xml public void ProcessHtmlTag(string htmlTag)
-
DON'T
:不论缩写词是长还是短,都不要在驼峰格式标识符的开头大写任何缩写词的任何字母。
禁止在任何标识符中使用单词缩写!首字母缩写词 ≠ 单词缩写。
3 大写复合词和常用术语
-
DON'T
:不要将闭合复合词(合成词)中的每个单词都大写。复合词(合成词)应当作一个单词,如 endpoint。如果想知道某个单词是否为闭合形式的复合词,请查阅最新的英语词典。
Pascal | camel | 错误示例 |
---|---|---|
ButFlag | bitFlag | Bitflag |
Callback | callback | CallBack |
Canceled | canceled | Cancelled |
DoNot | doNot | Don't |
Endpoint | endpoint | EndPoint |
Filename | filename | FileName |
Gridline | gridline | GridLine |
Hashtable | hashtable | HashTable |
Id | id | ID |
Indexes | indexes | Indices |
Logoff | logoff | LogOut |
Logon | logon | LogIn |
Metadata | metadata | MetaData, metaData |
Multipanel | multipanel | MultiPanel |
Multiview | multiview | MultiView |
Namespace | namespace | NameSpace |
Ok | ok | OK |
Pi | pi | PI |
Placeholder | placeholder | PlaceHolder |
SignIn | signIn | SignOn |
SignOut | signOut | SignOff |
Timestamp | timestamp | TimeStamp |
Username | username | UserName |
WhiteSpace | whiteSpace | Whitespace |
Writable | writable | Writeable |
上一节我们强调禁止使用单词缩写,Ok 和 Id 是个例外。
相较于上表提出的术语要求,保持一致性更重要。如 white space 在计算机领域被视作复合词,在继承老的代码时,应继续使用 WhiteSpace
-
DON'T
:不要假设所有编程语言都区分大小写,不能只依赖大小写来区分名称。例如:VB 不区分大小写,我们要考虑到这种情况的兼容性。
2 通用命名约定
1 词汇选择
-
DO
:考虑英语语法。例如,名为
HorizontalAlignment
(形容词 + 名词)的属性比AligmentHorizontal
(名词 + 形容词)更易于阅读。
-
DO
:可读性 > 简洁性。属性名
CanScrollHorizontally
要胜过ScrollableX
(X 轴坐标的引用不明显)。
-
DON'T
:禁止使用下划线、连字符或其他非字母数字字符。单元测试中的命名约定违反了该准则,但单元测试库不会作为可复用组件来分发,因此本书的准则通常不适用于测试代码
-
DON'T
:禁止使用匈牙利命名法。
-
AVOID
:避免使用其他常用编程语言的关键字做标识符。在 C#中我们可以使用 @ 符号作为标识符的转义机制,但用转义序列比较麻烦,最好还是避免使用常见的关键字。
-
DO
:要仅使用 ASCII 字符来作为标识符名称。
2 使用简写和首字母缩写词
-
DON'T
: 禁止 使用缩写词和缩略词作为标识符名字的组成部分。例如,要用
GetWindow
,而不是GetWin
;要用WillNot
,而不是Won't
-
DON'T
: 禁止 使用少见的首字母缩写词。即使常见,也只在必要时使用。如何判断一个首字母缩写词是否众所周知?我们可以利用搜索引擎。使用搜索引擎搜索该首字母缩写词,如果搜索的前几个结果与期望相符,可以认它众所周知。
如果未通过测试,也不要直接把完整单词作为标识符,而应该考虑怎样才能使名字具有描述性。
3 名字避免含有关键字
-
DO
:要使用有语义的名称,而非特定于语言关键字的类型名称。例如,
GetLength
这个名字比GetInt
要好。
-
DO
:使用 CLR 的类型名称,而不是特定于语言的名称。例如,将类型转化为
System.Int64
(CLR 类型名)的方法应该被命名为ToInt64
,而不是ToLong
(C#特有的别名)
C# | Visual Basic | C++ | CLR |
---|---|---|---|
Sbyte | SByte | char | SByte |
Byte | Byte | unsigned char | Byte |
short | Short | short | Int16 |
ushort | UInt16 | unsigned short | UInt16 |
int | Integer | int | Int32 |
uint | UInt32 | unsigned int | UInt32 |
long | Long | __int64 | Int64 |
ulong | UInt64 | unsigned __int64 | UInt64 |
float | Single | float | Single |
double | Double | double | Double |
bool | Boolean | bool | Boolean |
char | Char | wchart_t | Char |
string | String | String | String |
object | Object | Object | Object |
-
DO
:使用通用名称(如value
、item
),而不是重复类型的名字。下面是一个很好的例子:
void Write(double value); void Write(float value); void Write(short value);
4 命名现有 API 的新版本
-
DO
:在创建已有 API 的新版本时,使用与旧 API 相似的名字。这有助于突出 API 之间的关系
class AppDomain{[Obsolete("AppDomain.SetCachePath has been deprecated. Please use AppDomainSetup.CachePath instead.")]public void SetCachePath(String Path) { ... } }class AppDomainSetup{public string CachePath { get { ... }; set { ... }; } }
-
DO
:优先使用后缀(而非前缀)来表示已有 API 的新版本。这样做新旧 API 的位置会非常接近,方便查阅。
-
CONSIDER
:使用全新但有意义的标识符,而不是添加后缀或前缀。
-
DO
:要使用 数字 后缀来表示已有 API 的新版本,尤其是现有名称是唯一合理的名称或不宜改名时。仅当 API 的名字不适宜更改或添加后缀(比如它是工业标准),才应该添加数字后缀标识版本。
// old API [Obsolete("This type is obsolete. Please use the new version of the same class, X509Certificate2.")] public class X509Certificate { ... } // new API public class X509Certificate2 { ... }
-
DON'T
: 禁止 在标识符中使用“Ex”(或类似的)后缀来区分相同 API 的不同版本。[obsolete("This type is obsolete. ...")] public class Car { ... }// new API public class CarEx { ... } // 错误 public class CarNew { ... } // 错误 public class Car2 { ... } // 可接受 public class Automobile { ... } // better
-
DO
:已有 32 位的 API,要引入对 64 位整数(long)API 时,应使用“ 64 ”后缀。只有当已存在 32 位的 API 时才需要采用此方法,对只有 64 位版本的全新 API 不需要这么做。
public class Process {// old APIspublic int PeakWorkingSet { get; }public int PagedMemorySize { get; }// ...// new APIspublic long PeakWorkingSet64 { get; }public long PagedMemorySize64 { get; } }
注意:本规范仅适用于改造已发布的 API。在设计全新 API 时应避免后缀“32”、“64”。
3 程序集和 DLL 的命名
-
DO
:要以大的功能块命名 DLL。如:
System.Data.dll
。该 dll 中包含System.Configuration
、System.Data
、System.Xml
等诸多命名空间,其中System.Data
的子空间最多,因此命名为System.Data.dll
-
CONSIDER
:通过以下模式命名 dll:
<公司名>.<组件名>.dll
其中 < 组件名 > 可包含多个分隔,如:
MyCompany.Controls.Button.dll
4 namespace 的命名
模板:
<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]
<!--例如-->
Microsoft.VisualStudio
Microsoft.VisualStudio.Design
Fabrikam.Math
Litware.Security
-
DO
:前缀应使用 公司名称 。这可以避免与其他公司使用相同的名字。
如:微软的 Microsoft Office 自动化 API 的 namespace 应为
Microsoft.Office
。
-
DO
:第二级应使用稳定的、与版本无关的 产品名称 。用于宣传的市场名称会随时间而变化,我们要选择技术上叫得响,又不会随着营销而变化的名称。
-
DON'T
:不要出现公司的组织架构命名。公司的组织架构随时可能变更,不够稳定。
-
DO
:使用 PascalCasing 命名法,并用“.
”来分隔命名空间各部分。如:
Microsoft.Office.PowerPoint
。若用商标做命名空间,则应遵循商标原大小写。
-
CONSIDER
:适当的时候使用复数形式。如:
System.Collections
。商标和首字母缩写词例外,如:使用
System.IO
,而不是System.IOs
。
-
DON'T
:类名不可与 namespace 重名假设我们的 namespace 为
Debug
,则不要在该空间定义名为Debug
的类
class 命名冲突
-
DON'T
:类型名不要过于一般化。如
Element
、Node
、Log
、Message
这样的名字很可能在常见的场景中引起类型名冲突。应该为它们加上限定符:FormElement
、XmlNode
、EventLog
、SoapMessage
为避免不同类别命名空间中的类型名称冲突,现有一些特定的准则。命名空间可以被分为如下几类:
- 应用模型 namespace
- 基础设施 namespace
- 核心 namespace
- 技术领域 namespace 分组
1 应用模型 namespace
-
DON'T
:单个应用模型中,即使 class 在不同的 namespace 中,命名也不可相同。例如:
System.Web.UI
已包含类Page
,则System.Web.UI.Adapters
内不可定义同名 class。
2 基础设施 namespace
此类 namespace 很少会在通用应用程序的开发中导入。例如 .Design
namespace 主要用于开发编程工具。并没有特别要求避免与此类 namespace 中的类型名冲突。
3 核心 namespace
-
DON'T
:任何 class 的名称都不得与核心 namespace 中的 class 重名。例如:不要用 Stream 作为类型名,它与
System.IO.Stream
重名。常见的核心 namespace 包括
System
、System.IO
、System.Xml
、System.Net
等。
4 技术领域 namespace 分组
该类别指 <Company>.<Technology>
下的所有 namespace。如 Microsoft.Build.Utilities
和 Microsoft.Build.Tasks
。
-
DON'T
:单个技术领域 namespace 内,class 不可重名。
-
DON'T
:技术领域与应用模型之间,class 不可重名。如果不和该应用模型一起使用则可以同名。
如
Microsoft.VisualBasic
中不可添加名为Binding
的类,因为System.Windows.Forms
已包含该类。
5 class、struct、interface 的命名
-
DO
:class 和 struct 命名应使用 名 词或 名 词短语,采用 Pascal Casing 命名法,类名前不要添加前缀(如“C”)类名用名词,方法名用动词。
-
CONSIDE
:建议用 基类的名称 作为派生类名称的结尾。这样命名可读性会更好,但不一定必须这么做:
public class FileStream : Stream { ... } public class Button : Control { ... }
从
FileStream
的名字我们可以得知它继承自Stream
;而Button
即使没有“Control
”后缀,我们仍能推断出它是一种“Control
”,不加后缀反而更好。
-
DO
:interface 命名应使用 形容 词短语,偶尔也可以使用 名 词或 名 词短语,以字母“I
”开头,以标识该类型是接口。如:
IComponent
(描述性名词)、ICustomAttributeProvider
(名词短语)、IPersistable
(形容词)。优先使用描述词。名词和名词短语可能会让人误以为是抽象类而非接口,最好还是少用。
-
DO
:如果某个类是某个接口的标准实现,则该类和该接口名应只差一个“I
”前缀。如
List
和IList
:public interface IList { ... } public class List : IList { ... }
1 泛型参数的命名
-
DO
:类型参数命应使用描述性名字。如果只有一个类型参数,可以使用单个字母T
命名。如果用一个字母就能说明含义则无需描述性名称:
public interface ISessionChannel<TSession> { ... } public delegate TOutput Converter<TInput, TOutput>(TInput from); public class Nullable<T> { ... } public class List<T> { ... }
-
DO
:类型参数名应添加前缀 T
,且在类型参数名中显示施加于该类型参数上的 约束 。// TSession受ISession限制,因此名字带有“Session” public interface ISessionChannel<TSession> where TSession : ISession {TSession Session { get; } }
2 通用类型的命名
-
DO
:派生、实现.NET 框架的类应遵循表中描述的规范。基类型 派生/实现类型的准则 System.Attribute DO
:要为自定义 Attribute 类的名称添加后缀“ Attribute ”System.Delegate DO
:要为事件委托的名称添加后缀“ EventHandler ”DO
:要为非事件委托的名称后缀添加“ Callback ”DON'T
:不要为任何委托的名称添加后缀“ Delegate ”System.EventArgs DO
:要添加后缀“ EventArgs ”System.Enum DON'T
:不要从该类派生任何子类;使用语言支持的关键字,例如,在 C#中,使用enum
关键字DON'T
:不要使用“ Enum ”或“ Flag ”后缀System.Exception DO
:要添加后缀“ Exception ”IDictionary
IDictionary<TKey, TValue> DO
:要添加后缀“ Dictionary ”。注意:虽然 IDictionary 是一种特定类型的集合,但是此准则优先于后面的更通用的集合准则。IEnumerable
ICollection
IList
IEnumerable
ICollection
IList DO
:要添加后缀“ Collection ”,除了可复用的特定的数据类型,如“Queue”和“HashSet”System.IO.Stream DO
:要添加后缀“ Stream ”
Warn
以上后缀不得滥用,如:
public class ElementStream : Object { ... } public class WindowsAttribute : Control { ... }
以上类型并非继承自
Stream
或Attribute
,也不应该添加该后缀。
3 枚举的命名
-
DO
:位域(bit field)类型的枚举(即标记枚举 flag enum)使用 复 数名词命名,其他枚举使用 单 数名词命名。public enum ConsoleColor {Black,Blue,Cyan,... }[Flags] public enum ConsoleModifiers{Alt,Control,Shift }
-
DON'T
:枚举类型名禁止添加“Enum”、“Flag”、“Flags”后缀;枚举成员禁止添加前缀。不好的命名:
// 不好的命名 public enum ColorEnum {... } [Flags] public enum ColorFlags{... } public enum ImageMode{ImageModeBigmap = 0, // 多余的前缀:ImageModeImageModeGrayScale = 1,ImageModeIndexed = 2,ImageModeRgb = 3, } // 好的命名 public enum ImageMode {Bitmap = 0,GrayScale = 1,Indexed = 2,Rgb = 3, }
6 类型成员的命名
1 方法的命名
-
DO
:方法的命名应使用 动 词或 动 词短语。public class String {public int CompareTo(...);public string[] Split(...);public string Trim(); }
2 属性的命名
-
DO
:属性的命名应使用 名 词、 名 词短语或 形容 词。public class String {public int Length { get; } }
-
DON'T
:属性名不应该同“Get”方法对应。public string TextWriter { get {...} set {...} } public string GetTextWriter(int value) {...}
以上情况属性应该被定义成一个方法。
-
DO
:集合类型的属性,应使用名称的 复数 形式。不要使用单数形式 +“List/Collection”后缀的形式。public class ListView {// good namingpublic ItemCollection Items { get; }// bad namingpublic ItemCollection ItemCollection { get; } }
-
DO
:布尔属性应使用 肯定 性短语。如果需要,可以添加“Is”、“Can”、“Has”等前缀。-
CanSeek
比CantSeek
好; -
CanRead
比Readable
更容易理解; IsCreated
却不如Created
好。
前缀通常是多余的,如:
MyObject.Enabled =
与MyObject.IsEnabled =
含义同样明了,后者反而冗长。 -
bool 属性是否要加前缀?我们可以用 if 语句来测试:
// 好命名 if (collection.Contains(item)) if (regularExpression.Maches(text))// 坏命名 if (collection.IsContained(item)) if (regularExpression.Match(text))
此外,如果各方面没差别,则优先选择主动语态而非被动语态:
if (stream.CanSeek) // better than ... if (stream.IsSeekable)
-
CONSIDER
:可以用属性的 类型 名来命名属性。public enum Color { ... } public class Control {public Color Color { get {...} set {...} } }
3 事件的命名
-
DO
:应使用 动 词或 动 词短语命名事件,并通过现在时
和过去时
区分事件发生的时间。禁止使用“Before”、“After”表示事件发生的先后顺序。如:
Clicked
、Painting
、DroppedDown
等。窗口关闭前,close 事件应命名为
Closing
;窗口关闭后,close 事件应命名为Closed
-
DO
:事件委托(自定义事件)要使用“ EventHandler ”后缀,且使用 sender 和 e 作为两个参数的名字。public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);
应避免创建自定义事件委托(委托类型),推荐直接使用
EventHandler<TEventArgs>
。
只有很少情况下才需要自定义“EventHandler”委托。
-
DO
:自定义事件参数类要使用“ EventArgs ”后缀。public class ClickedEventArgs : EventArgs { ... }
事件触发器的定义,另见:事件实例和触发事件
最后,我们需要编写一个
protected
的 虚 方法来触发事件。方法名必须和 事件名称 一致,以 On
作为前缀,并接收唯一的 EventArgs
参数:
protected virtual void OnPriceChanged (PriceChangedEventArgs e) {PriceChanged?.Invoke (this, e); }
4 字段的命名
本节规则仅限静态字段(公有 + 受保护)
-
DO
:要使用 名 词、 名 词短语或 形容 词,遵循 PascalCasing 命名法,且不要添加前缀。public class String {public static readonly string Empty = ""; } public struct UInt32{public const Min = 0; }
7 参数的命名
-
DO
:应使用具有描述性的词语,遵循 camelCasing 命名法。最好能做到通过参数名和类型就能推测出它的用意。
public class String {public bool Contains(string value);public string Remove(int startIndex, int count); }
-
CONSIDER
:建议基于参数的 含义 来命名参数,而非参数的 类型 。现代 IDE 的智能提示可以提供与类型相关的有用信息,参数名称可以更好地用于描述语义,而非类型。偶尔使用基于类型的参数名是可以的,但因此回退到匈牙利命名法是不可取的。
重载运算符的参数的命名
-
DO
:一元运算符的重载方法,如果参数无具体含义,应使用 * value * 作参数名。public static BigInteger operator- (BigInteger value);
-
DO
:二元运算符的重载方法,如果参数无具体含义,应使用 * left * 和 * right * 作参数名。public static TimeSpan operator- (DateTimeOffset left, DateTimeOffset right); public static bool operator== (DateTimeOffset left, DateTimeOffset right);
-
CONSIDER
:如果可以,重载运算符的参数名优先考虑有意义的名字。public static BigInteger Divide(BigInteger dividend, BigInteger divisor);
-
DON'T
:禁止使用简写或数字编号作为重载运算符的参数。// 不正确的参数名 public static bool operator== (DateTimeOffset d1, DateTimeOffset d2);
8 资源的命名
-
DON'T
:不要将本地化的资源直接暴露为公开的(或受保护的)成员。由资源编辑器自动生成的类型和成员应使用 internal 修饰。
第二版准则(附录 B)
资源字符串很少作为公开 API 暴露出去,因此我们在新版中规定不要暴露本地化资源。如果要暴露,则要像对待公开(或受保护)成员一样进行规范。
以下内容针对公开(受保护)的资源文件。
本地化资源好比是属性,应遵循属性的规范
-
DO
:命名资源键(resource key)应采用 PascalCasing 命名法,且仅使用字母、数字和下划线。
-
DO
:资源键名的描述性比简洁更重要。名字应该尽可能短,但不应该牺牲可读性。
-
DO
:命名异常消息的资源,其资源键应是“ 异常名 + 异常标识符 ”的格式。ArgumentExceptionIllegalCharacters ArgumentExceptionInvalidName ArgumentExceptionFileNameIsMalformed
-
DON'T
:禁止使用 CLR 所支持的编程语言的特有关键字。