首页 > 其他分享 >第2章 框架设计基础

第2章 框架设计基础

时间:2025-01-08 23:48:40浏览次数:6  
标签:DO 场景 框架 代码 基础 API 设计

第2章 框架设计基础

1 前言

  • DO​:框架应功能强大又易于使用。

    框架中 20% 的内容是常用的,这部分值得我们投入主要精力;80% 是不常用的,但仍要实现它,尽管设计的繁杂、不那么完美。

  • DO​:要从 使用者 的角度出发设计框架。

    不同的程序员(VB、C++、C#)编程风格不同、需求不同、专业水平不同,框架要站在使用者的角度去开发。就像 office 的开发者要站在办公人员的角度开发软件。

  • DO​:CLR 的目的是多编程语言支持,设计时要考虑不同编程语言的 兼容性

    开发时要注意不同的语言的兼容性。比如 CLR 支持 VB、C++、C#,其中 VB 大小写不敏感,开发时就要考虑 API 不能包含仅大小写不同的成员。

    该原则阻碍了.NET 平台的发展。近年来为了实现创新,人们不再强调这一点。如 C# 和 F# 引入了 Span<T>​,而 VB 没有。

2 框架设计的基本原则

1 场景驱动设计原则

框架设计原则

框架设计必须从一组使用场景和实现这些场景的代码示例开始。

  • DO​:设计任何功能,应以 API 设计规范 为核心(含可公开访问的 API)。

    第一步:设计规格书。

  • DO​:规格书应包含每个 主要 特性(feature area)最 常见的使用 场景。

    规格书:要有样例代码。

    API 规格书应该有一节来描述主要场景,并列出实现这些场景的样例代码。该节应该紧跟在“概要”这一节之后。一个中等特性(比如文件 I/O)应该有 5~10 个主要场景。

  • DO​:使用场景要从 用户 角度出发,思考 用户 会如何调用,进而编写用例。

    样例代码:粒度要适中,既不能过粗(不灵活),又不能过细(太繁琐)。

    例如,从文件中读取数据是个不错的使用场景(包含了打开文件、读取内容、关闭文件)。相反,打开一个文件、从文件中读入一行文本、关闭文件各算作一个场景,粒度太细了。

  • DO​:至少编写两种不同风格的编程语言(如 C# 和 F#)的场景样例代码。

    最好能保证所选编程语言的语法、风格和支持的功能都有很大差异。

    注意:要为不同的语言设计样例代码,这样设计的 API 才有更好的兼容性。

    C 风格的语言算作一种。

  • DO​:考虑为动态类型语言(比如 IRonPython 或 PowerShell)编写场景样例代码。

    如果要支持动态语言,应该为动态语言也编写样例代码。

  • DO​:设计 API 时,先为主要的场景编写样例代码,然后再定义对象模型来支持这些样例代码。

    第二步:以样例代码出发,设计 API。

    例如,在定义一个用来计时的 API 时,可能会为使用场景编写如下的样例代码:

    // scenario #1 : measure time elapsed
    Stopwatch watch = Stopwatch.StartNew();
    Console.WriteLine(watch.Elapsed);
    
    // scenario #2 : reuse stopwatch
    Dim watch as Stopwatch = Stopwatch.StartNew()
    DoSomething();
    Console.WriteLine(watch.ElapsedMilliseconds)
    
    watch.Reset()
    watch.Start()
    DoSomething()
    Console.WriteLine(watch.Elapsed)
    

    这样的代码引出了下面的对象模型:

    public class Stopwatch{
        public static Stopwatch StartNew();
    
        public void Start();
        public void Reset();
        public TimeSpan Elapsed { get; }
        public long ElapsedMilliseconds { get; }
        ...
    }
    

  • DON'T ​:设计 API 时 不要完全 依赖标准设计方法。

    实现 API 时需要注意的细节。

    标准的设计方法(包括面向对象的设计方法)是为了使设计的具体实现容易 维护 ,而不是为了使 API 易于 使用 。 应该围绕场景进行设计为主,并辅以原型制作、可用性研究以及一定数量的迭代。

  • DO​:要组织可用性调研来测试主要场景 API 的 可用 性。

    第三步:beat 版,让使用者进行使用测试,看看 API 有没有问题。

    大多数严重的可用性问题会导致 API 需要做重大修改,因此应该把可用性研究安排在开发的早期。如果发布后才发现有可用性问题,再去修正 API,开销之大超乎寻常。
    另外不要被“可用性分析”的名字吓到,把它当成非正式的研究,就像“嘿,来看看这个”那样简单。

2 低门槛原则

框架设计原则

框架必须简化上手使用的难度,为非专业用户提供较低的入门门槛

  • DO​:每个主要特性域的 namespace 应该只包含常见场景的类型(API)。高级场景的类型应放在 子 namespace 中。

    命名空间要分层:分为低级和高级两部分。

    例如,System.Net​ 命名空间提供了与网络相关的主要 API 场景,而更高级的 socket API 则位于 System.Net.Sockets​ 子命名空间中。

  • DO​:构造函数和方法应该有简化版(通过重载)。

    一个简单的重载函数不仅参数的数量非常少,而且所有的参数都是 基本 类型。

    构造函数和方法要分层:分为低级和高级两部分。

  • DON'T ​:高级场景的成员 不应该 出现在为主要场景设计的类中。

    成员要分层:分为低级和高级两部分。

  • DON'T​:不应要求用户在最基本的场景中显式地实例化 一个 以上的类型。

    低级 API 要足够简单。

  • DON'T​:基本使用场景的 API 应将大量初始化内容内部完成,而不是让使用者进行大量初始化。

    低级 API 要足够简单。

    主要场景的 API 应该只需少量初始化。理想情况下构造函数已有默认参数或只有一个简单参数:

    var zipCodes = new Dictionary<string, int>();
    zipCodes.Add("Redmond", 98052);
    zipCodes.Add("Sammamish", 98074);
    

    如果初始化是必须的,当用户因未执行初始化而引起异常时,应该在异常消息中清楚的告诉用户需要做什么。

  • DO​:尽可能地(用便利的重载函数)为所有的属性和参数提供合适的默认值。

    通过重载,将高级方法和低级方法分层。

    此处以 System.Messaging.MessageQueue ​举例。该组件的构造函数仅需传入路径,便可以调用 Send ​方法发送消息。发送消息使用的是默认参数,而用户仍可以自定义这些复杂的参数。

    var orderQueue = new MessageQueue(path);
    orderQueue.Send(order); // 使用默认优先级、加密算法等。
    

  • DO​:对 API 的误用应通过 异常 来传达。

    因做不到低门槛而产生了误用,应该用异常通知使用者。

    异常应清楚地描述其产生的原因,并告诉开发人员应该怎样修改代码才能修正错误。

3 对象模型自文档化原则

框架设计原则

在简单的场景中,框架必须可用且不需要文档。

  • DO​:API 要符合直觉,无需查阅文档就能用于基本场景。

    API 要符合直觉,很多程序员不喜欢翻看文档,更喜欢通过 API 名称推断用法。

  • DO​:为所有的 API 提供出色的文档。

  • DO​:要为通用场景中的重要 API 提供代码示例来说明其用法。

    一方面,并非所有 API 都能自说明;另一方面,还有一些开发人员想在开始使用 API 前完全理解它们。

1 命名

  • DO​:审查规格书时应投入大量的时间和精力,来讨论标识符名称的选择。

    大多数场景中常见的类型有哪些?在这些场景中,大多数人首先想到的名字是什么?用户最先想到的是通用类型的名称吗? 例如,在处理文件的 I/O 场景中,大多数人会想到 File,因此应该把访问文件的主要类型命名为 File。

    class 中常用的方法、参数也应该遵循这一准则。

  • DON'T​:无需担心标识符的名字太长。

    标识符的名字应清楚地说明相应的方法是做什么的、相应的类型和参数是表示什么的。

    最好能不看文档便知道它是做什么的。

  • CONSIDER​:在设计的早期应该让技术教育专家参与。

    他们是非常好的资源,可以指出哪些设计的名字选择不当,以及哪些设计会难以向客户解释。

    命名最好让培训师也参与进来,毕竟他们是使用者的老师,知道哪些名字更常用。

  • CONSIDER​:要为 最常用 的类型保留最佳的类型名称。
    假设下个版本会增加更常用的 API,当前版本最好的名字留给下个版本用没有问题。

    如果一个名字适合下个版本中的某个 API,留着给它吧!

2 异常

  • DO​:要利用 异常消息 来向开发者传达框架的使用错误。

    异常不仅能告诉使用者发生了错误,还能指出怎么解决错误。

    例如,用户忘记设置 EventLog ​组件的 Source ​属性,任何要求必须设置 Source ​的方法,都应该在异常消息中清楚的说明。

3 强类型

  • DO​:要尽一切可能提供 类型 API

    强类型 API 的返回值更直观,“自说明性”更强。

    不要完全依赖弱类型 API,比如属性包。在必须使用属性包时,也要为属性包中最常用的属性提供强类型支持。

4 一致性

  • DO​:确保与 .NET 框架以及客户可能会使用的其他框架保持一致。
    保持一致会让用户感觉我们的设计是自然和直观的。只有当我们的 API 确实有独特之处,才应该把它设计得不同。

5 有限的抽象

  • AVOID​:主要场景的 API 不应该使用过多的抽象。

    面向对象设计的方法着眼点是使代码最容易维护,但过度抽象会导致使用者必须对框架深入了解。

    常用场景的 API 不应该使用太多的抽象,而应该与系统中的物理实体或众所周知的逻辑实体相对应。

4 分层架构原则

框架设计原则

分层设计使单一框架能同时提供功能和易用性。

  • CONSIDER​​:建议采用分层架构,针对生产力来优化 高级 API,针对功能和表现力来优化 底层 API。

  • AVOID​​:避免在底层 API 很复杂(包含很多类型)的情况下,将底层 API 和高级 API 放在 同一 namespace 下。

  • DO​:确保一个 Feature Area(功能领域)的各个层次能被很好地集成在一起。

    开发者应该能够使用其中任意一个层次来进行编程,当他们需要将相应的代码更改为使用另一个层次来实现时,不需要重写整个应用程序。

    Eureka

    典型的我认为是 TcpClient​ 和 Socket​。可以通过 TcpClient.Client​ 获取底层的 Socket​,执行更低层次的操作。

标签:DO,场景,框架,代码,基础,API,设计
From: https://www.cnblogs.com/hihaojie/p/18660793/chapter-2-framework-design-basics-xquj7

相关文章