【问题标题】:Things to be considered while building a framework构建框架时要考虑的事项
【发布时间】:2009-08-26 07:25:00
【问题描述】:

我们正计划构建一个框架:一个成本估算框架,将在我们组织的各个领域中使用。

高级要求是这样的: 如果我开发某种产品,我会花多少钱?生成的成本将用于与供应商报价的成本进行比较,并决定选择哪个供应商。

现在,我的问题是: 开发框架时要考虑哪些事项?

我的一些想法:

  1. 通过抽象类和接口实现高级需求
  2. 提供可能对框架用户有用的实用程序类。
  3. 考虑不应该向框架用户显示的内部元数据(元数据类型)。
  4. 设计模式可以像模板一样使用。
  5. 输入类的属性和方法。

【问题讨论】:

    标签: java frameworks


    【解决方案1】:

    一些想法:

    • 添加有用的功能比删除已证明设计不当或有害的功能更容易。
    • 设计继承或禁止继承:继承引入了一个额外的复杂层,因为您需要计算超类和子类之间的交互。这并不是说它是邪恶的,但应该非常仔细考虑。
    • 根据我的经验,接口通常比抽象类更简洁,因为它们促进了组合而不是继承。
    • 对于接口,记录调用者应该期待什么和实现者应该期待什么。基本上从双方考虑合同,并记录下来。特别是文档无效性约束——方法应该接受 null 还是不接受?他们是否应该保证永远不会返回 null?
    • 为您的框架和使用您的框架的其他人设计可测试性。框架的哪些部分可以合理地用于测试代码,哪些应该被模拟出来?
    • 从一开始就使用您自己的框架。构建一个示例应用程序,其他人可以使用它来理解框架。

    【讨论】:

    • 我还希望将依赖注入添加到直接实例化/服务查找(尽管可测试性设计通常要求这样做)。
    • 只是为了反驳论点。对于快速发展的框架(因此也是大多数新框架),通常不鼓励使用接口。请参阅“接口被高估” - artima.com/weblogs/viewpost.jsp?thread=142428
    【解决方案2】:

    下面的一些建议取决于您是在构建一个框架,只供少数项目的小团队内部使用,还是构建供许多匿名开发人员使用的东西。另外,请记住,这在很大程度上取决于您的框架的大小以及它的范围有多宽或多窄。我的一些建议实际上只适用于更大的多用途框架。

    一般建议

    • 花时间与一些将使用您的框架的开发人员交谈。确保您真正了解什么有用,什么没有用。
    • 花时间确保您创建的抽象对于您的未来用户来说是可理解的和直观的。
    • 除非您正在构建独特和革命性的东西,否则花点时间查看您领域中存在的其他框架。避免他们的错误,但不要害怕借鉴成功的概念……并非所有事物都必须由您发明。
    • 制作文档 - 以正式 API 文档和示例代码的形式。如果人们不明白如何应用你的框架,它就不会成功。
    • 名称简洁明了。这可能是框架开发人员最困难的任务之一——尤其是在实现抽象或概括概念时。在确定名称之前先与人交谈。有些名称具有多种含义,并且可能会与您预期的不同。
    • 考虑让您的框架与其他低级框架兼容(例如用于日志记录、模拟和依赖注入的框架)。这将使您的用户满意并减少采用的障碍。
    • 投资于良好的单元测试。 框架通常具有很大的表面积,用户可以与之交互...许多类、许多方法、派生、接口实现。如果没有快速回归测试的能力,框架可能会迅速发展到超出可以维护的水平。
    • 让您的公共接口保持狭窄和小巧。 在框架中设计功能时,请牢记单一职责原则......在这个级别上真的很重要。

    设计建议

    • 设计和测试并行性。 如今,大多数框架都用于构建具有一些(通常很多)并行处理活动的应用程序。尤其是打算在 Web 应用程序中使用的框架,需要仔细考虑多线程访问将如何影响它们。我遵循的一些一般原则是:
      • 尽可能避免锁定。如果不可能,请使用显式锁定对象而不是 .NET lock( ... ) 语句。
      • 对读取频率高于写入频率的共享对象使用读取器/写入器锁
    • 将功能设计到分层中。对于大型框架,这不仅有助于保持功能解耦,还允许用户更轻松地仅使用他们实际需要的框架部分。
    • 对于用于 .NET 2.0 及更高版本的框架,请使用泛型集合! 很难推断对象[]、ArrayList 或 Hashtable 中存储的内容......尤其是在大型框架中此类集合在内部传递。
    • 避免在公共接口中使用 object 或 object[] ...这在框架中是邪恶的,并且是导致错误和可维护性问题的主要原因。您几乎总能找到一个接口或使用泛型来代替它。在极少数情况下,您无法准确地记录您希望传入或传出的内容......并为其断言。
    • 更喜欢聚合而不是继承。虽然继承是一个有用且强大的概念,但框架作者经常过度使用或误用它。仔细考虑继承是否真的是解决设计问题的正确工具。
    • 尽可能避免类型转换和运行时类型检查。根据我的经验,这些通常是设计问题。大量转换表明您没有有效地利用接口、泛型或泛型约束。这可能会导致开发人员对如何使用您的框架产生错误和困惑。
    • 尽可能允许调用者使用策略注入功能。对于某些类型的专业化来说,继承是多余的。随着 .NET 生态中作为公民获得的支持功能水平,您应该考虑使用它们(以 lambda 或委托的形式)以允许消费者专门化您提供的功能。

    实施建议

    • 避免使用 getter 产生副作用的属性。 有时这是不可避免的(例如执行内部缓存的 getter,或延迟实例化对象)。但是,根据我的经验,具有副作用的 getter(尤其是在框架级代码中)是引入 heisenbug 并使用户在调试时诅咒您的名字的最快方法。
    • 如果可能,使小的瞬态对象不可变。使用语言中的 readonly(或等效)关键字强制执行此操作。不变性的好处是巨大的 - 在 .NET(分配成本降低)中它并不像您想象的那么昂贵。
    • 尽可能使用self-encapsulation此技术通过避免内部调用者和外部调用者对数据的不同访问语义来帮助避免错误并简化重构。
    • 避免使用幻数和硬编码常量。 框架是一种很难预测用户确切需求的代码。通过使用配置而不是编译常量来保留一些灵活性。
    • 保持方法的参数数量较少(少于 7 个)。当您需要传入许多参数时,请考虑创建一个轻量级的数据对象来支持该方法。
    • 对于不同类型的多个方法重载,更喜欢泛型方法。 尽可能让语言和编译器完成工作。这也使您的代码更加灵活和有用。检查 LINQ 的设计,了解其在实践中的工作原理。

    【讨论】:

      【解决方案3】:

      下面是我的下一步:

      1. 进行广泛的高级设计 - 例如它是 Web 应用程序还是胖客户端、是否有中间层、数据库交互将如何发生、将使用哪些 API/技术等。
      2. 选择一项允许您在应用程序的所有层级执行代码的功能(通常称为尖峰)。
      3. 实现使该功能正常工作的最低要求 (Keep It Simple, Stupid) 并进行测试以证明其有效。
      4. 选择另一个功能转到 3(根据需要进行重构)。

      我一直发现,这种工作方式可以使系统不断发展,并且实际上使某些工作能够使您的设计专注于重要的事情(而不是纸面设计可能发生的幻想飞行)。

      还有我认为我的敏捷者已经消失了 ;-)

      【讨论】:

        【解决方案4】:

        除了一般的编程建议之外,还值得研究一些软件框架设计理论的基础知识。首先看一下“热点”和“冻结点”的概念。虽然这可能不会立即有用,但最好在开发时将其放在脑海中。

        一如既往,维基百科是一个很好的起点:

        http://en.wikipedia.org/wiki/Software_framework

        这里也是一个很好的总结:

        http://www.acm.org/crossroads/xrds7-4/frameworks.html

        如果您想深入了解,请查看这两篇文章中的引文。

        【讨论】:

          【解决方案5】:

          在 API 合约中使用接口。这使您可以将杂乱的细节完全解耦,并在需要时轻松装饰它们。 (只需查看伪装成地图的属性)。

          一个很好的技巧是使用测试驱动设计 - 即首先编写测试然后实现。这迫使您像用户而不是设计者一样思考,最终会产生更好的 API。

          【讨论】:

            猜你喜欢
            • 2011-03-25
            • 2011-05-26
            • 2014-03-28
            • 1970-01-01
            • 2011-02-15
            • 2011-09-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多