苏州网站开发公司电话,asp.net网站开发与应用,wordpress机械免费主题,怎么免费做公司网站6 软件架构
6.14 测试边界 和程序代码一样#xff0c;测试代码也是系统的一部分。甚至#xff0c;测试代码有时在系统架构中的地位还要比其他部分更独特一些。 测试也是一种系统组件。 从架构的角度来讲#xff0c;所有的测试都是一样的。不论它们是小型的TDD测试#xff…6 软件架构
6.14 测试边界 和程序代码一样测试代码也是系统的一部分。甚至测试代码有时在系统架构中的地位还要比其他部分更独特一些。 测试也是一种系统组件。 从架构的角度来讲所有的测试都是一样的。不论它们是小型的TDD测试还是大型的FitNess、Cucumber、SpecFlow或JBehave测试对架构来说都是一样的。 究其本质而言测试组件也是要遵守依赖关系原则的。因为其中总是充满了各种细节信息非常具体所以它始终都是向内依赖于被测试部分的代码的。事实上我们可以将测试组件视为系统架构中最外圈的程序它们始终是向内依赖的而且系统中没有其他组件依赖于它们。 另外测试组件是可以独立部署的事实上大部分测试组件都是被部署在测试环境中而不是生产环境中的所以即使是在那些本身不需要独立部署的系统中其测试代码也总是独立部署的。 测试组件通常是一个系统中最独立的组件系统的正常运行并不需要用到测试组件用户也不依赖于测试组件。测试组件的存在是为了支持开发过程而不是运行过程。然而测试组件仍然是系统中不可或缺的一个组件。事实上测试组件在许多方面都反映了系统中其他组件所应遵循的设计模型。 由于测试代码的独立性以及往往不会被部署到生产环境的特点开发者常常会在系统设计中忽视测试的重要性这种做法是极为错误的。测试如果没有被集成到系统设计中往往是非常脆弱的这种脆弱性会使得系统变得死板非常难以更改。 当然这里的关键之处就是耦合。如果测试代码与系统是强耦合的它就得随着系统变更而变更哪怕只是系统中组件的一点小变化都可能会导致许多与之相耦合的测试出现问题需要做出相应的变更。这种修改一个通用的系统组件可能会导致成百上千个测试出现问题的情况称为脆弱的测试问题fragile tests problem。 脆弱的测试问题往往会让系统变得非常死板当开发者意识到一些简单的修改就会导致大量的测试出错时他们自然就会抵制修改。要想解决这个问题就必须在设计中考虑到系统的可测试性软件设计的第一原则就是不要依赖于多变的东西。 设计这样一个系统的方法之一就是专门为验证业务逻辑的测试创建一个API。这个API应该被授予超级用户权限允许测试代码可以忽视安全限制绕过那些成本高昂的资源例如数据库强制将系统设置到某种可测试的状态中总而言之该API应该成为用户界面所用到的交互器与接口适配器的一个超集。 设置测试API是为了将测试部分从应用程序中分离出来。换句话说这种解耦动作不只是为了分隔测试部分与UI部分而是要将测试代码的结构与应用程序其他部分的代码结构分开。 结构性耦合是测试代码所具有的耦合关系中最强大、最阴险的一种形式测试专用API的作用就是将应用程序与测试代码解耦这样我们的产品代码就可以在不影响测试的情况下进行重构和演进。同样的这种设计也允许测试代码在不影响生产代码的情况下进行重构和演进。 这种对演进过程的隔离是很重要的因为随着时间的推移测试代码趋向于越来越具体和详细产品代码则会趋向于越来越抽象和通用。结构性的强耦合可能会让这种必需的演进无法进行————至少会形成强烈的干扰。 当然这种具有超级权限的测试专用API如果被部署到我们的产品系统中可能会是非常危险的。如果要避免这种情况发生应该将测试专用API及其对应的具体实现放置在一个单独的、可独立部署的组件中。 测试并不是独立于整个系统之外的恰恰相反它们是系统的一个重要组成部分。我们需要精心设计这些测试才能让它们发挥验证系统稳定性和预防问题复发的作用。没有按系统组成部分来设计的测试代码往往是非常脆弱且难以维护的这种测试最后常常会被抛弃因为它们终究会出问题。
6.15 整洁的嵌入式架构 虽然软件质量本身并不会时间推移而损耗但是未妥善管理的硬件依赖和固件依赖却是软件的头号杀手。 也就是说本可以长期使用的嵌入式软件可能会由于其中隐含的硬件依赖关系而无法继续使用这种情况是很常见的。 这里软件software应该是一种使用周期很长的东西而固件firmware则会随着硬件演进而淘汰过时来看看固件的一些定义 (1) 固件通常被存储在非可变内存设备例如ROM、EPROM或者闪存中 (2) 固件是直接编程在一个硬件设备上的一组指令或者一段程序 (3) 固件是嵌入在一个硬件中的软件程序 (4) 固件是被写入到只读内存设备中的ROM程序或数据 但实际上大家普遍所认知的固件定义是错误的或者至少是过时的固件并不一定是指存储在ROM中的代码也并不是依据其存储的位置来定义的而是由其代码的依赖关系及其随着硬件的演进在变更难度上的变化来定义的。硬件的演进是显而易见的我们在架构嵌入式代码时要时刻记住这一点。 那么如果一个产品从头到尾都与具体技术、具体硬件息息相关、无法分割时整个产品就已经成为事实上的固件了。 为什么很多嵌入式软件最后都成为了固件呢看起来很可能是因为我们在做嵌入式设计时只关注代码能否顺利运行并不太关心其结构能否撑起一个较长的有效生命周期Kent Beck描述了软件构建过程中的三个阶段 (1) “先让代码工作起来”————如果代码不能工作就不能产生价值 (2) “然后再试图将它变好”————通过对代码进行重构让我们自己和其他人更好地理解代码并能按照需求不断地修改代码 (3) “最后再试着让它运行得更好”————按照性能提升的“需求”来重构代码 而大部分“野生”的嵌入式代码都只关注“先让它工作起来”这个目标————也许还有些团队会同时痴迷于“让它更快”这个目标不放过任何一个机会加入各种微优化。在《人月神话》这本书中Fred Brooks建议我们应该随时准备“抛弃一个设计”即“在实践中学习正确的工作方法然后再重写一个更好的版本”。 这个建议对非嵌入式软件系统开发同样有用毕竟目前大部分非嵌入式应用也仅仅停留在“可用”这个目标上很少考虑为了长久使用而进行正确的设计。对于程序员来说让他的程序工作这件事只能被称为“程序适用性app-titude test”一个程序员不论他写的是否是嵌入式程序如果目标仅仅是让程序可以工作恐怕对他的老板和这个程序本身而言都是一件坏事。 嵌入式系统的程序员通常需要处理很多在写非嵌入式系统时不需要关心的事情————例如有限的地址空间、实时性限制、运行截止时间、有限的I/O能力、非常规的用户接口、感应器以及其他与物理世界的实际链接。大部分时候这些系统的硬件是和它的软件、固件并行开发的工程师在为这种系统编写代码的时候往往没有任何地方可以运行。这就是目标硬件瓶颈target-hardware bottleneck是嵌入式开发所特有的一个问题如果我们没有采用某种清晰的架构来设计嵌入式系统的代码结构就经常会面临只能在目标系统平台上测试代码的难题。如果只能在特定的平台上测试代码那么这一定会拖慢项目的开发进度。 整洁的嵌入式架构就是可测试的嵌入式架构。 我们来看一下具体应如何将架构设计的原则应用在嵌入式软件和固件上以避免陷入目标硬件瓶颈。 首先是分层分层可以有很多种方式先来看三层结构 首先底层是硬件层由于科技的进步与摩尔定律硬件是一定会改变的。旧的硬件部件将会被淘汰新的硬件部件可能耗电量更少或者性能更好或者价格更便宜不管硬件更新的原因是什么作为嵌入式工程师我们都不会希望这些不可避免的硬件变动带来更多的工作量。 硬件与系统其他部分的分隔是既定的————至少在硬件设计完成之后如此。这也是我们试图通过程序适用测试之时往往会发生问题的地方。因为没有什么东西可以真正阻碍硬件实现细节污染到应用代码。如果我们在构建代码的时候不够小心没有小心安排哪些模块之间可以互相依赖代码很快就非常难以更改了。请注意这里所说的变更不仅仅是指来自硬件的变更还包括用户的功能性变更、修复代码中的Bug。 另外软件与固件集成在一起也属于设计上的反模式anti-pattern符合这种反模式的代码修改起来都会很困难同时这种代码也很危险容易造成意外事故这导致它经历任何微小的改动都需要进行完整的回归测试如果没有完善的测试流程那么就会有无穷无尽的手工测试同时还有纷沓而来的Bug报告。 软件与固件之间的分割线往往没有代码与硬件之间的分割线那么清晰所以我们的工作之一就是将这个边界定义得更清晰一些软件与固件之间的边界被称为硬件抽象层HAL这不是一个概念它在PC上的存在甚至可以追溯到Windows诞生之前 HAL的存在是为了给它上层的软件提供服务HAL的API应该按照这些软件的需要来量身定做。例如固件可以直接将字节和字节组存入闪存中。相比之下软件需要的是从某种持久化平台保存和读取name/value对信息它不应该关心自己信息到底是被存储到闪存中、磁盘中、云端存储中还是在内存中读取/存储这些信息。总之HAL的作用是为软件部分提供一种服务以便隐藏具体的实现细节。毕竟专门针对闪存的实现代码是一种细节信息它应该与软件部分隔离。 不要向HAL的用户暴露硬件细节 依照整洁的嵌入式架构所构建的软件应该是可以脱离目标硬件平台来进行测试的。因为设计合理的HAL可以为我们脱离硬件平台的测试提供相应的支撑。 当我们的嵌入式应用依赖于某种特殊的工具链时该工具链通常会。为我们提供一些“帮助”性质的头文件这些编译器往往会自带一些基于C语言的扩展库并添加一些用于访问特殊功能的关键词这会导致这些程序的代码看起来仍然用的是C语言但实际上它们已经不是C语言了。有时候这些嵌入式应用的提供商所指定的C编译器还会提供类似于全局变量的功能以便我们直接访问寄存器、I/O端口、时钟信息、I/O位、中断控制器以及其他处理器函数这些函数会极大地方便我们对相关硬件的访问。但请注意一旦你在代码中使用了这些函数你写的就不再是C语言程序它就不能用其他编译器来编译了甚至可能连同一个处理器的不同编译器也不行。为了避免我们的代码在未来出现问题我们就必须限制这些C扩展的使用范围。 在整洁的嵌入式架构中固件将这类底层函数隔离成处理器抽象层PAL这样一来使用PAL的固件代码就可以在目标平台之外被测试了。 除HAL和PAL之外由于嵌入式系统可能使用某种实时操作系统RTOS或者某种嵌入式的Linux或Windows因此我们必须将操作系统也定义为实现细节让代码避免与操作系统层产生依赖。 整洁的嵌入式架构会引入操作系统抽象层OSAL将软件与操作系统分隔开 除了在嵌入式系统的主要分层指软件、操作系统、固件、硬件这四层之中增加HAL和OSAL之外我们还可以应用其他的设计原则这些设计原则可以帮助我们按功能模块、接口编程以及可替代性来划分系统。 分层架构的理念是基于接口编程的理念来设计的当模块之间能以接口形式交互时我们就可以将一个服务替换成另外一个服务。 由整洁的嵌入式架构所构建的系统应该在每一个分层中都是可测试的因为它的模块之间采用接口通信每一个接口都为平台之外的测试提供了替换点。
7 实现细节 数据库只是实现细节。 从系统架构的角度来看数据库并不重要————它只是一个实现细节在系统架构中并不占据重要角色。如果就数据库与整个系统架构的关系打个比方它们之间就好比是门把手和整个房屋架构的关系。请注意这里讲的是“数据库”而非“数据模型”为应用程序中的数据设计结构对于系统架构来说当然是很重要的但是数据库并不是数据模型。数据库只是一款软件是用来存取数据的工具。从系统架构的角度来看工具通常是无关紧要的————因为这只是一个底层的实现细节一种达成目标的手段一个优秀的架构师是不会让实现细节污染整个系统架构的。 数据的组织结构数据的模型都是系统架构中的重要部分但是从磁盘上存储/读取数据的机制和手段对于架构来说则不是那么重要即使类似于读取/存储性能这样的指标也应被封装在具体的数据库内部而不是作为系统架构的一部分。 Web是实现细节。 这也是在前文不断被提及的概念。GUI只是一个实现细节而Web则是GUI的一种所以也是一个实现细节作为一名软件架构师我们需要将这类细节与核心业务逻辑隔离开来————即使不是Web换一种GUI也对业务核心逻辑无影响。 应用程序框架是实现细节。 应用程序框架现在非常流行这在通常情况下是一件好事这么多框架都非常有效非常有用而且是免费的但框架并不等同于系统架构————尽管有些框架确实以此为目标。 框架的作者通常会希望我们与其框架紧密结合这意味着我们将与框架签订终身契约而他们则不需要为我们遵守任何承诺把风险全部交由我们自己承担例如框架自身架构设计可能不正确而要求我们将代码引入到业务对象或业务实体中或者框架可能会想要我们将框架耦合在最内圈代码中等比如框架自身为了演进新增了很多我们不需要的功能等。 因此我们应该将框架作为架构最外圈的一个实现细节来使用而不是让它们进入内圈不要让框架污染我们的核心代码应该依据依赖关系原则将它们当作核心代码的插件来进行管理。尽可能长时间地将框架留在架构边界之外我们的业务逻辑才不会被框架影响在我们需要替换框架时可以轻松替代。