2.2.2低门槛原则
今天,很多开发者都希望能快速学习新框架的基础知识,他们希望在一定的基础上对框架的某些部分进行试用,只有当他们对某些功能感兴趣或需要使用更复杂的场景时,他们才会花时间来深入了解整个架构。初次接触就遇到设计糟糕的API,会给开发者留下框架复杂难用的持久印象,进而使得一些开发者不愿使用这个框架。这就是为什么对于框架来说,为那些只是想使用框架的开发者提供一个较低的入门门槛是非常重要的。
#框架设计原则
框架必须简化上手试用的难度,为非专业用户提供较低的入门门槛。
许多开发者都希望通过对API进行试用来搞清楚它究竟做了什么,然后逐步调整他们的代码来得到其想要的结果。
&& PAUL VICK 大多数开发者,无论他们使用的是什么语言,都是边做边学的。文档可以帮助你初步了解应该发生什么,但是我们都知道,除非你深入其中,开始捣鼓并尝试做出一些有用的东西,否则你永远不会真正了解某些东西是如何工作的。特别是Visual Basic,鼓励通过这种方式来编程。虽然我们从不回避提前规划,但是我们努力使学习和编程成为一个连续的流程。编写不言而喻的API,不需要开发者掌握复杂的知识就能够使用它们,例如,如果与多个对象或API交互,这可以有效促进这一流程(事实上,这似乎适用于大多数编程语言,不仅仅是Visual Basic)。
有些API可以被测试,而有些则不然。为了便于试用,API必须做到如下几点:
1)让与常见编程任务相关的类型和成员更容易识别。对于一个用于存放通用场景API的命名空间,如果其包含了500种类型,而实际上只有其中少数类型在通用场景中很重要,这是不容易试用的。对于面向主线场景的类型来说,如果其中的很多成员都是针对高级使用场景的,那么也是同样的。
&& CHRIS ANDERSON 在Windows Presentation Foundation(WPF)项目初期,我们遇到了差不多同样的问题。我们有一个基类型Visual,几乎所有的其他元素都是从它派生出来的。但问题是,它所引入的成员和派生出元素的对象模型有冲突,特别是围绕子节点的问题。对于子视图渲染,Visual只有一个单一的层次结构,但是我们的元素想要引入特定域的子元素(例如,一个TabControl只接收TabPages)。我们的解决方案是创建一个VisualOperations类(该类具有作用于Visual的静态成员),而不是使每个元素的对象模型都变得复杂。
2)让开发者可以立即使用API,不管它是否能达到开发者最终想要得到的效果。如果一个框架需要大量的初始化过程,或者依赖几个类型的实例化,然后把它们组合到一起,这是不易于试用的。同样地,如果API没有便捷重载,(重载成员只有较短地参数列表),或者为属性设置了很糟地默认值,这也为那些想要尝试API地开发者设置了较高的壁垒。
&& CHRIS ANDERSON 把对象模型看作一张地图:你必须放置清晰的标志来解释如何从一个地方到另一个地方。你希望一个属性可以清楚地向人们展示它是做什么的、它需要什么样的值、赋值之后会发生什么。指向一个不能清楚地表明其派生类型是什么地抽象基类型是非常糟糕的事。WPF中的动画就是一个这样的例子:用于动画的类型是TimeLine,但是整个命名空间里没有什么是以单词“Timeline”结尾的。事实上,Animation继承自TimeLine,还有很多其他类型,诸如BoubleAnimation和ColorAnimation等,但是属性类型和用于填充属性的有效项之间没有任何联系。
3)让发现和修复由于API被错误使用而带来的问题变得简单。例如,API抛出的异常应该清楚地描述修复这个问题需要做什么。
&& CHRIS SELLS 在编程地过程中,我特别喜欢这样地错误信息,它们指明了我哪里做错了,以及如何去解决这个问题。但是很多时候,我得到地只有前者,而我真正关心地其实是后者。
以下准则,将帮助你确保你的框架适合那些想通过动手实验来学习的开发者。
√DO要确保每个功能领域的命名空间只包含那些用于通用场景的类型,面向高级场景的类型应该被放在子命名空间中。
例如,Syste.Net命名空间只提供了面向网络编程主线场景的API,更高级的Socket API被放在System.Net.Sockets这个子命名空间下。
&& ANTHONY MOORE 这条准则反过来讲也是成立的,可以这样表述:“不要把一个命名空间中最常用的类型埋到许多不常用的类型里面”。StringBuilder正是这样一个例子,我们后来希望在一开始就把它包含到System命名空间下它存在于System.Text中,但它的使用频率比这个命名空间里的其他类型高得多,同时它与其他类型也不是很相关。
尽管如此,这是System命名空间中唯一受此反向规则影响的类型。在大多数情况下,我们不得不忍受的是,有太多不常用的类型存在于这个命名空间下。
√DO要为构造函数和方法提供简单的重载。简单的重载意味着只有非常少的参数,且所有的参数都是基础类型。
×DON‘T不要让面向主线场景的类型拥有面向高级场景的成员。
&& BRAD ABRAMS 在.NET框架的设计中,最重要的原则之一,就是通过减法来做加法。也就是说,通过从框架中减少(或至少不增加)特性,我们实际上能够使开发人员更有效率,这是因为需要处理的概念减少了。CLR把多重继承排除在外就是通过减法来做加法的典型例子。
×DON‘T不要要求用户在最基本的场景中显式地实例化一个以上地类型。
&&KRZYSZTOF CWALINA 图书出版商说,一本书地销量与书中方程式地数量成反比。这条定律对框架设计师来说就是:框架地用户数量与实现最常见地10个场景所需显式调用构造函数地次数成反比。
×DON‘T不要要求用户在为基本使用场景编写代码之前就进行大量地初始化。
主要场景地API应该只需尽可能少地初始化。理想情况下,如果类型是为基本场景而设计的,那么使用默认的或者是只带一个简单参数的构造函数应该就足以开始工作了。
var zipCodes =new DIctornary<string,int>(); zipCodes.Add("Redmond",98052); zipCodes.Add("Sammamish",98074);
如果有一些初始化是必须的,那么当用户由于未执行初始化而引起异常时,应该在异常消息中清楚地告诉用户需要做些什么。
&& STEVEN CLARKE 自从本书地第一版出板发行以来,我们对这个领域进行了大量地可用性研究。我们一次又一次地发现,那些需要大量初始化地类型显著地提高了用户入门地门槛。其后果是,一些开发者会选择不去使用这些类型,而是变相寻找看起来可以完成这项工作的其他类型,最终,一些开发者会错误地使用这些类型,只有很少数开发者可以找到正确使用这些类型地方法。 ADO.NET就是这样一个例子,我们的用户发现某个功能领域很难使用,因为它需要大量初始化工作,即使在最简单的场景中,用户也需要理解几种类型之间复杂的交互和依赖关系,甚至在一个简单的场景中,用户也必须实例化几个对象(如DataSet、DataAdapter、SqlConnection和SqlCommand的实例)并将它们关联在一起。需要指出的是,在.NET Framework 2.0中,通过添加辅助类的方式解决了许多同类型问题,大大简化了基本场景。
√DO在可能的情况下,要为所有的属性和参数(使用便捷重载)提供合适的默认值。
关于这个概念,System.Messaging.MessageQueue是一个很好的示例。组件只需要传递一个表明路径的字符串到它的构造函数中,再调用Send方法,就可以发送消息了。消息的优先级、加密算法和其他消息属性可以通过在简单场景的基础上增加代码来进行自定义。
var prdersQueue =new MessageQueue(path); ordersQueue.Send(order); //使用默认的优先级、加密算法等。
不能盲目地运用该准则。如果默认值可能会使用户误入歧途,则框架设计者应避免提供默认值。例如,默认值永远不应该导致安全漏洞或者糟糕的可执行代码。
&& STEPHEN TOUB 在设计默认值时,了解API的主要用例是很重要的,而且应尽可能预测未来会出现的用例。我的前10个“我希望我可以重新来做”的案例之一来自System.Threading.Tasks,在这个API的设计指出,我们主要专注于基于CPU的并行性,但是随着时间的变化,主要的用例最终变成了基于IO的异步。一些初始的默认值更适合前者,而对于后者来说,其变成了危害。随着时间的推移,我们在添加易于使用的API的过程中解决了这些问题,但是对于仍在使用最早API的开发者而言,最初的问题和由此带来的困难依然存在。
&& JRRMY BARTON 这里的对比非常重要,很难找到合适的平衡点。.NETCryptographyAPI包含了大量类型,它们提供了友好且安全的默认值。遗憾的是,“友好”是不变的,但“安全”是一个动态的目标。有些时候,你为用户提供默认值是在帮助他们,但是有些时候,这又会对他们造成伤害。
√DO 要使用异常来传达API的不正确使用。
异常应该明确地描述导致异常的原因和开发者应该如何修改代码来解决问题。例如,EventLog组件要求在写入事件之前设置好Source属性,如果在WriteEntry被调用前没有设置Source,将会抛出这样的异常:“在写入事件日志之前没有设置Source属性”。
&& STEVEN CLARKE 在我们的可用性调研中,我们观察到许多开发者认为异常是API可以提供的最佳类型的文档。它提供的指导始终围绕着开发者要实现的目标,并且它真正支持被众多开发者青睐的边做边学的方法。
下一节将介绍使用对象模型尽可能自文档化的重要性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?