框架设计原则和规范(一)

此文是《.NET:框架设计原则、规范》的读书笔记,本文内容较多,共分九章,将分4天进行推送,今天推送1-3章。

1. 什么是好的框架

2. 框架设计原则

3. 命名规范

4. 类型设计规范

5. 成员设计规范

6. 扩展性设计

7. 异常

8. 使用规范

9. 设计模式

1. 什么是好的框架

1.1 简单的

一个好的框架必定是使用起来非常简单的,如果超过2个小时都还没能写出基本的使用代码,就一定不够好用。我们自己设计的框架很多时候都要使用者去了解框架设计的原理才能用起来,就是一种失败。

1.2. 充满利弊权衡的

一个好的框架必然要对很多方面进行取舍,比如灵活性和易用性,性能和开发效率,内存使用和CPU使用等等。如果只考虑其中一方面,会大大降低框架的可用范围,考虑过多极端情况会让框架变得一无是处。

1.3. 借鉴过去经验的

我们的框架主要是为了解决那些以前碰到过的问题,并且考虑的是那些习惯于己有工具的一般用户,所以不适合在框架上对使用方式做很大的创新。但是也要设法解决以前一直存在的不合理状态。

1.4. 需要很多设计工作,代价不菲的

框架的设计工作本身是一项艰巨的任务,比起只是实现一个功能要困难的多,所以不能简单的认为框架设计仅仅是开发了一些可重用的代码而已。它还包括了对于最优实践的总结,对于未来需求的扩展性规划。

1.5. 考虑未来发展

在设计框架的时候,应该判断最终的决定对框架在将来的发展会有怎样的影响。

1.6. 良好的集成性

简单来说就是要能方便的部署、安装、编译、链接、运行。对于外部的依赖应该尽量的少和尽量的简单。

1.7. 一致性

一致的框架可以让开发人员举一反三,从框架中已知的部分推理知道不了解的部分。一致性还可以帮助开发人员很快的认识到,设计的哪些部分是某个特定领域独有的,需要特别加以注意,而哪些仅仅是常有的模式和惯用法。

2. 框架设计原则

2.1 渐进式框架原则

2.1.1 学习成本随功能复杂程度提升

开发人员能够从较简单的使用场景中积累知识,并应用到更高级的使用场景中去。

2.1.2 强大,同时易用

由多个部分组成的框架,可以针对不同的目标来设计,从而达到强大和易用的平衡。

2.1.3. 能针对不同的用户群体

用户群体的技术水平会有很大差别,框架既要能提供简单易学的使用方法,也要提供复杂高级的用法。

2.1.4. 适应不同语言

2.2. 围绕场景设计原则

在设计框架时,必须从一组使用场景以及实现这些场景的样例代码开始。框架设计者最常犯的错误就是一开始就设计对象模型。没有明确的场景,就不会有已用的API设计,也没有决定冲突权衡的依据。框架如果把在针对各种不同的使用场景下提供的API,全部混合到一起,这个框架将会变得非常庞大复杂,同时没有人能学会如何使用。所以我们必须要按照场景来把框架的设计逐步划分开来。

2.2.1. API规格是框架设计的核心

不是其内部实现方案,API是影响用户的关键部分,也是整个框架最大的约束条件部分。必须认真对待API的设计。

2.2.2. 定义最常见的场景

否则框架设计将缺乏目标和重点,沦为大堆功能代码的集合,最终既不强大也不灵活。

2.2.3. 场景和抽象层次适应

框架代码所抽象的概念,最好能和使用场景一一对应,而不是提供一套需要理解和解释的抽象。比如某个业务功能(比如读取配置)需要从文件中加载内容,应该直接提供一个LoadFromFile(),而不是提供一个OpenFile()、ReadFromFile()、CloseFile()的文件操作套件。

2.2.4. 先写使用样例,再设计API

我们喜欢开发有趣而强大的新功能,但是要发现新功能的需求和最佳使用方法,最好的莫过于实际的使用场景。在有客户需求的情况下来设计API,会让这些程序变得更加实用。当一个功能我们觉得很有用的时候,应该先不考虑加入,直到有客户强烈的需求,此时我们对需求的理解将会更加深入,加入框架的方式会更恰当。

2.2.5. 考虑多语言下的情况,包括动态语言

2.2.6. 不要拘泥标准OO设计规范,要考虑使用的简便性

2.3. 低门槛原则

框架必须以易于试验的方式来为普通用户提供一个低门槛。

2.3.1. 要使用强类型

类型本身是对功能的一种说明。用户可以通过类型的描述了解使用的方法。

此条规范同时符合自说明对象原则。

2.3.2 简单的初始化

复杂的初始化让用户难以开始正确的“试验性”编程,从而更难以学习

2.3.3 尽量提供默认参数,减少参数个数

参数个数越多越难以学习,API的格式也越复杂难以记忆。

2.3.4. 名字空间内只包含常见类

命名空间中比较单一的包含有限的类,便于用户浏览所有的类来学习如何使用。

2.3.5 用异常提示对API的误用

比起用文档和返回值,使用异常能让用户在试验性开发时就教育他们如何正确使用API。

同时符合自说明对象原则

2.4. 自说明对象原则

2.4.1. API要直观,无须查询手册都可使用

2.4.2. 要提供优秀的文档

2.4.3. 命名
  • 要投入大量精力审查API ,讨论标识名称
  • 不要担心标识符太长
  • 考虑早期让用户教育专家参与
  • 最好的名字留给最常用的类型

2.4.4. 通过异常来告诉开发人员对框架的误用【重复规范】

同时符合低门槛原则

2.4.5. 尽可能提供强类型API【重复规范】

同时符合低门槛原则

2.4.6. 确保和其他常用框架一致

2.4.7. 限制抽象的数量

2.5. 分层架构模式

分层设计使得在单个框架中同时提供强大的功能性和易用性成为可能。

2.5.1 名字空间可以成为分层的界限

不同的名字空间可以成为分层的标记,这种名字空间代表了一个常用层级中的所有概念,可以更容易被学习。大多数框架应该使用这种方法来划分名字空间。

2.5.2 高层API提供最佳的开发效率

2.5.3. 底层API提供强大的功能和最丰富的表现力

2.5.4 避免把非常复杂的低层API和高层API混合到一个命名空间中

2.5.5. 确保单个特性域中不同的层能良好的集成在一起

3. 命名规范

3.1 大小写约定

3.1.1. PascalCasing用于多个单词构成的名字空间、类型以及成员名字

3.1.2. camelCasing用于参数的名字

3.1.3. 两个字母的缩写全部大写,除非是camelCasing的参数
  • ‍ IO
  • IP

3.1.4. 三个及以上个字母的缩写只有首字母大写,除非是cameCasing的参数
  • Xml
  • Html

3.1.5. camelCasing的任何缩写单词,首字母不得大写
  • xmlParser
  • htmlReader
  • mfcLibrary

3.1.6 不要把闭合的复合词中的字母大写

  • id:Id

ID

  • ok:Ok

OK

  • endPoint:Endpoint

EndPoint

  • callback:Callback

CallBack

  • Hashtable:Hashtable

HashTable

3.1.7. 不要以为所有语言都是区分大小写的,不得设计忽略大小写后重复的名字

3.2 通用命名约定

3.2.1 单词的选择

3.2.1.1. 要易于阅读,不要使用生僻的词

3.2.1.2. 更看重可读性而不是简洁性

3.2.1.3. 不要使用下划线、减号、以及所有非字母非数字的字符

3.2.1.4. 不要使用匈牙利命名法

3.2.1.5. 避免使用编程语言中的关键字

3.2.2. 和语言类型(或者其他内置名字)相关的命名

3.2.2.1. 缩写的选择

3.2.2.1.1. 不要使用缩略后的词

GetWindow

GetWin

3.2.2.1.2. 使用广泛接受的首字母词组缩写

UI

HTML

3.2.2.2. 避免使用编程语言中特有的关键字,而应该用有语意的名字

GetInt

GetLength

3.2.2.3. 使用CLI标准的类型名字,而不是某种语言的别名

ToLong

ToInt64

3.2.2.4. 使用常见的名字,不要重复类型的名字

void Write(double value)

void Write(doubledouble)

3.2.3. 旧API出了新版本应该如何命名

3.2.3.1. 新旧API名字相似

AppDomain

AppDomainSetup

3.2.3.2. 新版本应该增加后缀,而不是前缀,便于按名字排序时发现

AppDomain

AppDomainSetup

3.2.3.3. 考虑使用全新但有意义的名字,而不是增加后缀

Date

Calendar

3.2.3.4. 万不得已才增加数字后缀来代表新版本的API

TimeZone

TimeZone2

TimeZoneInfo

3.2.3.5. 不要使用Ex后缀来区分新旧版本API

3.2.3.6. 引入对64位支持的API的新版本可以加后缀64,但如果没有旧的32位版本则不应该加

尽量不用带64后缀的名字,特别是在新写的,直接同时支持32和64位的API的时候。

3.3. 程序集或DLL命名

3.3.1. 一个程序集对应一个DLL

3.3.2. 考虑遵循命名空间

3.4 名字空间的命名

3.4.1 要使用公司名字作为前缀

3.4.2. 要用稳定的,和版本无关的产品名字作为名字空间的第二层

3.4.3. 不要使用公司组织架构作为命名空间的名字,以为这是易变的

3.4.4. 使用PascalCasing命名名字空间,使用.号来区分层次

3.4.5. 考虑使用复数形式作为名字空间的名称

3.4.5.1. System.Collections

3.4.6. 名字空间的名称不要和类型重复

Debug::Debug

3.4.7 名字空间和类型名的冲突

3.4.7.1. 不要使用太一般化的类名

Element

Node

Log

Message

3.4.7.2. 不要在多个名字空间中使用设置相同的类名

System.Web.UI::Page

System.Web.UI.Adapter::Page

3.4.7.3. 那些开发使用的少的名字空间,不用太在意其命名冲突

3.4.7.3.1. System.Windows.Forms.Design
3.4.7.3.2. *.Permissions

3.4.7.4. 不要让核心命名空间的名字和类型冲突

System.IO.Stream

MyNameSpace::Stream

3.4.7.5. 不要在可能共同使用的名字空间里设定有冲突的类名

Microsoft.VisualBasic::Binding

System.Windows.Forms::Binding

3.5. 类、结构和接口的命名

3.5.1. 使用名词和PascalCasing命名类和结构

3.5.2. 使用形容词命名接口,少数情况下使用名字和名词短语

3.5.3. 不要给类名加前缀,例如C

3.5.4 考虑在派生类的末尾使用基类的名字

ArgumentOutOfRangeException

Exception

FileStream

Stream

3.5.5. 接口以I开头

有些人喜欢接口前有I,因为COM是这样做的

一个字母还算简洁,所以也可以使用

3.5.6. 如果一个类是一个接口的标准实现,名字应该只差一个I

IComponent

Component

3.5.7 泛型类型参数命名:

3.5.7.1. 使用描述性名字,除非一个字母就可以说明

3.5.7.2. 使用单个字母T,如果只有一个类型参数

3.5.7.3. 给描述性参数名字加上T前缀

TSession 、TInput

3.5.8 常用类型命名,遵循.Net核心类型的命名规则

3.5.8.1. Attribute:修饰属性类

3.5.8.2. EventHandler:事件处理委托

3.5.8.3. Callback:非事件处理的委托

3.5.8.4. 不要使用Delegate后缀

3.5.8.5. EventArgs:事件参数应该使用此后缀

3.5.8.6. 不要派生Enum类型,而应该用enum关键字定义枚举类型

3.5.8.7. 枚举类型不要增加Enum或者Flag后缀

3.5.8.8. 异常应该添加Exception后缀

3.5.8.9. 容器类应该增加Collection后缀

3.5.8.10. 流类型应该使用Stream后缀

3.5.8.11. 权限使用Permission后缀

3.5.9 枚举类型的命名

3.5.9.1. 除非是bit field,使用单数命名类型

3.5.9.2. 复数类型表示bit field

[Flags]

public enum ConsoleModifiers{

Alt,

Control,

Shift

}

3.5.9.3. 不要增加Enum后缀

3.5.9.4. 不要加Flag或者Flags后缀

3.5.9.5. 不要给枚举值加上类型的前缀

ImageMode

  • ‍ImageModeBitmap
  • ImageModeRgb
  • ImageModeGrayScale
  • ImageModeIndexed

ImageMode

  • Bitmap
  • Rgb
  • GrayScale
  • Indexed

3.6 类型成员的命名

3.6.1. 方法的命名

3.6.1.1. 动词和动词短语

3.6.2. 属性的命名

3.6.2.1. 使用名词、名词短语、形容词

3.6.2.2. 不要让名字看起来像Get方法

publicstring TextWriter{ get{...} set{...} }

publicstring GetTextWriter(int value){...}

3.6.2.3. 集合属性用复数形式命名,而不是加Collection或者List后缀

publicItemCollection Itmes{ get; };

publicItemCollection ItemCollection { get;}

3.6.2.4. 布尔属性使用肯定短语而不是否定短语

Enable

Disable

3.6.2.5. 属性的类型名可以考虑用作名字

3.6.3 事件的命名

3.6.3.1. 动词或动词短语

3.6.3.2. 用现在时和过去时表示事件之前和之后

Closing:关闭前

Closed:关闭后

3.6.3.3. 不要用Before或After的前缀或后缀,应该用现在时和过去时

3.6.3.4. 事件处理函数应该要加上EventHandler后缀

大多数情况下应该直接使用.NET的标准类模板:EventHandler<T>

3.6.3.5. 事件处理函数使用sender和e作为参数名字

public delegate void<EventName>eventHandler(object sender, <EventName>EventArgs e);

3.6.3.6. 用来保存事件参数的类应该有EventArgs后缀

public class ClickedEventArgs :EventArgs{

int x;

int y;

public ClieckedeventArgs(int x, int y){

this.x = x;

this.y = y;

}

public int X { get { return x; } }

public int Y { get { return y; } }

}

3.6.4. 字段的命名

3.6.4.1. 使用PascalCasing

3.6.4.2. 使用名词、名词短语或形容词

3.6.4.3. 不要添加前缀

不要用g_或者s_表示静态字段

3.7. 参数的命名

3.7.1. 使用camelCasing

3.7.2. 使用具有描述性的参数名

3.7.3. 根据参数的意思而不是类型命名参数

3.7.4. 重载操作符的参数的命名

3.7.4.1. 对二元操作符参数使用left/right

如果参数没有具体的含义

3.7.4.2. 对一元操作符参数使用value

如果参数没有具体的含义

3.7.4.3. 考虑使用有意义的名字

3.7.4.4. 不要使用缩写或者数字编号

d1, d2

3.8. 资源的命名

3.8.1. 使用PascalCasing

3.8.2. 要具有描述性,而不是变短

3.8.3. 不要使用编程语言的关键字

3.8.4. 仅使用字母、数字、下划线

3.8.5. 异常资源命名应该把异常放在前面

ArgumentExceptionIllegalCharacters

ArgumentExceptionInvalidName

ArgumentExceptionFileNameIsMalformed

感谢大家的阅读,如觉得此文对你有那么一丁点的作用,麻烦动动手指转发或分享至朋友圈。

原文发布于微信公众号 - 韩大(handa1740168)

原文发表时间:2015-12-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏take time, save time

桌面山寨版2048—优化篇

     博客近两天略惨淡啊,我会先在博客上不定时的更新,毕竟是想把博客做成自己的主要平台嘛,谢谢支持啦!希望大家能猛戳http://www.richinmem...

2865
来自专栏Java技术栈

6 道 BATJ 必考的 Java 面试题

请对比 Exception 和 Error,另外,运行时异常与一般异常有什么区别?

1121
来自专栏向治洪

Python高效编程技巧

###Python高效编程技巧 如果你发现一个好的程序库,符合你的要求,不要不好意思————大部分的开源项目都欢迎捐赠代码和欢迎提供帮助——即使你不是一个Pyt...

2425
来自专栏灯塔大数据

塔秘 | Python 2.7即将停止支持,请收下这份3.x迁移指南

前言 目前,Python 科学栈中的所有主要项目都同时支持 Python 3.x 和 Python 2.7,不过,这种情况很快即将结束。 去年 11 月,Num...

3619
来自专栏java一日一条

程序中减少使用if语句的方法集锦

大约十年前,我听说了反if的活动,觉得这个概念非常荒谬。如果不用if语句,又怎么能写出有用的程序呢?这简直太荒谬了。

1562
来自专栏架构师之旅

轻松学习设计模式之面向对象的设计原则

对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决...

1003
来自专栏C/C++基础

C++以智能指针管理内存资源

C++作为一门应用广泛的高级编程语言,却没有像Java、C#等语言拥有垃圾回收(Garbage Collection )机制来自动进行内存管理,这也是C++一直...

1333
来自专栏Python研发

设计模式 -- 常用设计模式

                                  ——可复用面向对象软件的基础

3311
来自专栏idealclover的填坑日常

C语言循环和switch中的break和continue

问题的关键在于循环和switch中的break和continue的不同。在switch中是响应break但不响应continue的,换句话说,在switch中使...

2421
来自专栏Java编程

Java回调机制解读

在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种:

3206

扫码关注云+社区

领取腾讯云代金券