22、软件开发规则与技术选择解析

软件开发规则与技术选择解析

软件开发规则

在软件开发过程中,有一些重要的规则值得我们关注和遵循。

  1. 人是复杂的
    代码要么能运行,要么不能运行。编译器和链接器每次使用时的工作方式都是相同的,当出现意外情况时,通常是因为对这些工具的内部逻辑或语义理解不够。然而,软件是由人来开发的,而人是复杂的。人们会结婚、离婚、切除阑尾、被困在佛罗里达群岛,还可能不喜欢穿袜子,却喜欢在共享办公空间脱鞋。所有这些意想不到且令人沮丧的事情都会在一定程度上影响项目。我们能做的就是认识到这种情况会发生。当这些杂乱的事情干扰到我们的进度或职责时,耸耸肩说一句“人是复杂的”,反而会让人感到平静。

  2. 项目有始有终
    虽然这听起来很明显,但有趣的是,软件开发项目几乎总是从头到尾采用相同的管理方式,尽管项目开始和结束时人们所做的工作截然不同。建议项目开始时采用瀑布式方法进行管理,项目进行到中期时采用敏捷方法。对于正在完成的工作、完成方式以及所需时间的预期,都应根据项目所处的阶段(开始、中期或结束)来考虑。

  3. 所有软件都抗拒发布
    无论你的发布日期是什么时候,总会有一些最后时刻的功能变得至关重要,同时也会发现一些最后时刻的漏洞。所有这些都会重置你的发布时间表。此外,还有一些非代码、非技术活动会拖慢进度,比如许可证审查和出口管制文件。项目越大,就会有越多的人提出理由和障碍,迫使重置发布时间表。所以,不要误以为写完最后一行代码后,困难的部分就结束了,你得像用棍子把软件赶出门一样才能完成发布。建议在项目接近尾声时,进行正式发布的演练。这将暴露出许多问题,并揭示出你之前没有看到的额外工作。如果可以的话,确保开发计划中有时间进行至少三次演练发布,以使发布过程更加顺畅。

  4. 间接层次会随时间衰减
    间接层次使得代码复用成为可能,并且可以用最少的额外工作构建许多不同的东西。然而,这种架构的代价是增加了复杂性。从认知角度来看,在使用软件时,需要更多的脑力来记住间接层次。就像现实世界中的能量一样,熵最终会追上高能量水平。曾经参与过一个项目,从同一个源代码可以构建出三种不同版本的产品,支持多种语言(包括为原始设备制造商产品进行品牌定制)。但当离开公司并培训接替者时,那个人讨厌所有的间接层次,坚持将系统拆分成三个不同的源代码树,并手动保持同步。虽然感到震惊,但老板也只能说“这就是接替你工作的人,你想留下来继续维护吗?”采用 PIM 策略总是一个好主意,因为它会让你的工作更轻松、更灵活、更高效。但要注意,这需要在人员层面进行持续的关注和投入。也就是说,需要不断地对团队进行培训,并与团队就正在做的事情以及如何支持团队目标进行持续沟通,以确保一切顺利进行。

  5. 一个可用的原型既重要又不重要
    一个可用的原型是世界上最棒的东西,它证明了你脑海中的想法可以变成现实。不幸的是,当管理层看到原型时,他们往往认为 80% 的工作已经完成,产品发布指日可待。但实际上,从功能上来说,原型只完成了不到 1% 的工作。尽管这可能很困难,但在原型可用后,你应该把它放在一边,创建所有的设计文档。这会更容易,因为原型已经向你展示了需要完成的工作。完成设计文档后,不要回去扩展原型,而是从零开始实现你的设计。

  6. 设计评审比代码评审更重要
    根据经验,代码评审的一个长期问题是只见树木不见森林。也就是说,代码评审往往关注代码的语法,而没有真正关注软件的语义或整体设计。代码评审可以发现实现错误,但往往不能暴露设计缺陷。而设计缺陷比实现错误更难修复,成本也更高。在理想情况下,你既需要进行代码评审,也需要进行设计评审。但如果时间紧迫,最好通过询问“这个类或模块的语义是什么?你的代码在哪里实现了这些语义?”或者“这个函数的算法是什么?你的代码是如何实现它的?”来进行代码评审。

术语定义

以下是一些软件开发中常用术语的定义:
| 术语 | 定义 |
| ---- | ---- |
| 抽象接口 | 在 PIM 上下文中,这是定义行为的任何接口,其实现具有延迟绑定,即不是源时绑定。 |
| 组件 | 相当于 C++ 命名空间,包含零个或多个模块或子组件。在文件系统中,组件用目录表示。 |
| 组件作用域 | 类似于 Java 中的包作用域,与 C++ 中的受保护作用域或私有作用域限定符不同。当接口、文件、方法或类被指定为组件作用域时,只有同一组件或其子组件中的其他模块可以访问。 |
| 声明 | 向程序中引入一个或多个名称。声明变量、函数、类等时,意味着项目中有具有该名称和该类型的东西。 |
| 定义 | 是对象、变量、函数、类等的唯一规范。创建定义意味着指定创建该事物所需的所有信息。 |
| DIP | 依赖倒置原则 |
| DMA | 直接内存访问 |
| EEPROM | 电可擦可编程只读存储器 |
| ISP | 接口隔离原则 |
| 松耦合 | 单个模块在很少或没有先验知识的情况下使用其他模块。另一种定义是,松耦合组件遵循依赖倒置原则(DIP),只依赖于不太可能改变的实体(如抽象接口)。 |
| LSP | 里氏替换原则 |
| 模块 | 是一组功能的定义或实现,类似于 C++ 中的类。典型的模块由单个 .h 文件和单个 .c|.cpp 文件组成。与 C++ 允许有纯虚类、父类、子类等一样,PIM 对模块的定义允许定义具有多个源文件的模块,这些源文件可以位于不同的组件(或目录)中。 |
| OCP | 开闭原则 |
| 包 | 是正式版本化和发布的组件集合。在文件系统中,包是其所有组件的父目录。 |
| PIM | 即 Patterns in the Machine 的缩写 |
| 平台 | 是操作系统(如果有)、物理硬件(MCU/CPU)和编译器工具链的组合。 |
| 公共作用域 | 类似于 C++ 中的公共作用域限定符。当接口、文件、方法、类等被指定为公共作用域时,任何其他模块都可以访问。 |
| SA | 软件架构的缩写 |
| SCM | 软件配置管理的缩写,是用于存储、跟踪和控制源代码更改的过程或工具,例如 git 就是一个 SCM 工具。 |
| SDD | 软件详细设计 |
| SDP | 软件开发计划 |
| SPI | 串行外设接口,是一种用于短距离通信的同步串行通信接口。 |
| SRP | 单一职责原则 |
| 目标硬件 | 应用程序运行的嵌入式硬件,例如产品的目标硬件可能是 ARM Cortex - M 微控制器。 |
| 目标平台 | 应用程序运行的嵌入式平台,例如应用程序的目标平台可能是运行 FreeRTOS 的 ARM Cortex - M 微控制器,使用 gcc - arm - none - eabi 交叉编译器在 Windows 机器上构建。 |
| 技术债务 | 反映了选择部分、有限或简单的解决方案而不是最终、完整、健壮的解决方案所带来的未来额外工作的隐含成本。 |
| UUT | 被测单元 |

状态机表示法

在软件开发中,状态机的表示法有多种,不同的状态机有不同的特点。

  1. 统一表示法的重要性
    在很多开发团队中,每个开发者对状态机都有自己的理解,这导致了新成员加入时需要重新学习,并且很多个性化的状态机概念无法在状态图中实现。同样,开发者使用自定义的状态图表示法时,结果往往内部不一致,无法验证实现是否符合状态图的意图。

  2. 推荐的状态机类型及标准
    建议使用 UML 状态图。不过,还有其他几种状态机类型,如 Mealy 机、Moore 机、Harel 状态图等。如果不使用 UML 状态图,至少要选择一种正式定义的机器类型或表示法(如 Moore、Mealy - Moore 等)。同时,建议为团队定义“状态机标准”,这类似于软件开发计划中提到的编码标准。这样可以在开发团队内提供一致性,简化跨职能团队成员(如系统工程师、测试人员等)的学习曲线。此外,遵循正式的状态机语义,可以将图表与标准化的实现模式和代码生成器一起使用。

以下是不同状态机标准的比较:
| 状态机类型 | 状态和转换 | 转换有动作 | 状态有动作 | 分层、复合状态 | 正交区域/并发状态 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Mealy | Y | Y | Y | Y | |
| Moore | Y | Y | Y | Y | |
| Mealy - Moore | Y | Y | Y | Y | |
| Harel | Y | Y | Y | Y | Y |
| UML | Y | Y | Y | Y | Y |

基本的 UML 状态图表示法包含一些特定的元素,如初始状态(伪状态)、最终状态(伪状态)、选择点(伪状态)、连接点(伪状态)等,以及状态转换的表示方式,如 event/ 表示状态转换,event[guard]/ 表示只有当条件为真时才进行的状态转换,event/action1 表示带有动作的状态转换,还有状态的入口、执行和退出动作的表示。

下面是一个 UML 状态图表示法的使用示例:

stateDiagram-v2
    [*] --> S1
    S1 --> S2: ev1/
    S1 --> S3: ev2/
    S2 --> S3: ev3/actionA
    S3 --> S2: ev3/
    state S3 {
        [*] --> S3.1
        S3.1 --> S3.2: [guard1]/actionB
        S3.2 --> [*]
    }
    S2 --> [*]: [else]/actionC
UML 速查表

在使用 UML 约定绘制图表来说明关系时,有一些常用的约定需要了解。

图形 关系 示例
圆形 泛化(is - a 关系) 例如,“形状”和“圆形”,圆形是一种形状
表格 - 行 组合(has - a 且有所有权关系) 例如,“表格”包含“行”
主管 - 员工 聚合(has - a 关系) 主管管理员工
成人 - 汽车 关联(使用、交互关系) 成人驾驶汽车
printf - const char* 依赖(对类型的引用关系) printf 函数引用 const char* 类型

此外,还有多重性的表示,如:
- * 表示零个或多个
- 1 表示恰好 1 个
- n 表示恰好 n 个
- 0..1 表示零个或一个
- 1..* 表示一个或多个
- n..m 表示 n 到 m 个

箭头表示关系的方向,例如文件 A.h 中包含语句 #include “B.h” 就体现了这种依赖方向。

为什么选择 C++

在软件开发中,C++ 是一种常用的编程语言,它有很多优点,但也存在一些缺点。

  1. C++ 的优势

    • 类型检查 :C++ 比 C 提供了更多的编译时类型检查。考虑模板的工作方式,以及 C++ 不支持隐式类型。而且,由于 C++ 中的类继承,使用显式类型转换(如将 void 转换为 MyType_T )的需求大大减少。任何能让编译器检测错误的功能都是好的,对于使用过预 ANSI C 编译器编写 C 代码的人来说,严格的类型检查是一个巨大的优势。
    • 命名空间 :C 只提供全局命名空间(和文件作用域命名空间)。理论上,如果应用程序很小,全局命名空间中名称冲突的可能性较低。然而,如果将第三方代码引入小应用程序,名称冲突的可能性会显著增加。而 PIM 强调提前避免名称冲突,因为解决名称冲突意味着要编辑现有的、经过验证的源代码。正如所说“预防胜于治疗”。
    • 引用 :面试新员工时,常问“C++ 引用有什么优点”,常见的回答是在方法内部可以使用点符号而不是指针符号。确实,引用提供了更简洁的语法,但更重要的是,引用保证指向某个东西,而指针不一定。这意味着开发者在使用引用之前不必检查空指针引用。如果每个 C 程序员在解引用指针之前都检查指针是否为 NULL,C 语言的使用会更安全。
    • 封装 :C++ 中的类结构将数据与一组函数绑定在一起,比 C 中数据结构可以传递给任意函数集合的方式更简洁,并且由编译器强制执行。此外,类的构造函数/析构函数机制保证了类的初始化和清理,而不是依赖开发者手动完成。
    • 运行时绑定 :C++ 中的运行时绑定(动态多态)通过“虚拟”和“纯虚拟”方法实现。如前面所述,延迟绑定在解耦依赖关系时是有益的。虽然并非每个类都需要多态性,对于嵌入式项目来说,可能大多数类也不需要,但它是一种干净且编译安全的创建和使用运行时绑定的方式。虽然可以在 C 中实现类似的虚拟函数,但并不美观,还需要类型转换,这意味着会失去编译器强制的类型安全。
    • 字符串处理 :拥有一个 String 类而不是原始的 char* 指针和一组字符串函数(如 strcpy、strcmp 等)是一个巨大的优势。很多有经验的开发者仍然不理解 strncpy() 的语义,例如 strncpy() 不能保证结果以空字符结尾,也不能保证复制的最大字节数会为空字符保留空间。将 98% 的 C 字符串处理封装到一个 String 类中,就值得使用 C++。一个好的 String 类不仅是一种便利,更是一种自我保护。不过,PIM 示例代码不使用 C++ 的 std::string 类,因为 std::string 类和标准模板库(STL)普遍大量使用动态内存分配,而在大多数嵌入式系统中,动态分配是不被提倡的。虽然 string 类和 STL 提供了自定义内存分配器的能力,但每次实例化 string 类时都要提供分配器实例会变得很麻烦。拥有一个不依赖或不假设动态内存的 String 类是更简洁的方法。
  2. C++ 的缺点

    • 编译器支持不足 :对于较旧的微控制器或专用硬件架构,可能几乎没有或完全没有编译器支持。
    • 编译和链接时间长 :可能存在编译和链接时间较长的问题,但随着现代编译器和多核 PC 的发展,这可能只是一个次要问题。
    • 团队经验缺乏 :开发团队普遍缺乏 C++ 经验。
  3. C++ 项目的类型

    • 带类的 C :这种实现本身没有问题,但没有充分利用 C++ 更简洁、更快速的实现方式,会产生机会成本。
    • 对象失控 :这是最糟糕的 C++ 项目类型。很容易陷入对象和设计模式的奇妙和强大之中,但如果导致复杂的类层次结构和过多的间接层次,会影响运行时性能和可维护性,最终得到只有原作者才能理解的代码。
    • 恰到好处 :显然,这是介于前两种类型之间的项目。这也非常主观,一个开发者认为恰到好处的项目,另一个开发者可能认为是对象失控。只有事后回顾,也许还有代码的长期使用情况,才能衡量你的 C++ 项目是否是一个合理使用 C++ 语言的项目。

综上所述,在软件开发中,我们需要遵循一些重要的规则,合理选择状态机表示法和 UML 约定,同时根据项目的具体情况权衡是否使用 C++ 以及如何使用 C++ 才能达到最佳效果。

软件开发规则与技术选择解析

软件开发规则的实际应用与案例分析

在实际的软件开发项目中,上述规则有着不同程度的体现和应用。

  1. 人是复杂的规则应用
    在一个大型的企业级软件项目中,团队成员众多,不同成员的生活状况和个人情绪都可能影响到项目进度。例如,有一位核心开发人员突然生病住院,导致他负责的模块开发进度严重滞后。按照“人是复杂的”规则,项目管理者提前认识到这种情况可能发生,在项目规划时预留了一定的缓冲时间,并且安排了备份人员可以在必要时接手部分工作。当问题出现时,虽然项目进度受到了一定影响,但通过缓冲时间和备份人员的介入,最终项目还是能够按照大致的时间表推进。

  2. 项目阶段管理规则应用
    以一个互联网产品的开发项目为例,在项目开始阶段,采用瀑布式方法进行了详细的需求分析、设计和规划。明确了产品的整体架构、功能模块和技术选型等。进入项目中期,随着市场需求的变化和用户反馈的增加,及时切换到敏捷开发方法。团队可以快速响应变化,对产品进行迭代优化。例如,原本计划的某个功能模块,在中期发现用户需求不高,通过敏捷方法及时调整,将资源投入到更有价值的功能开发上,提高了产品的用户满意度和市场竞争力。

  3. 软件发布规则应用
    在一款移动应用的开发过程中,临近发布日期时,发现了一些严重的漏洞和需要紧急添加的功能。按照“所有软件都抗拒发布”的规则,项目团队提前安排了多次发布演练。在演练过程中,发现了很多之前没有注意到的问题,如兼容性问题、性能瓶颈等。通过对这些问题的及时修复和优化,最终在正式发布时,应用的稳定性和用户体验都得到了显著提升。同时,由于提前进行了演练,团队对发布流程更加熟悉,发布过程也更加顺利。

  4. 间接层次规则应用
    在一个多平台的软件开发项目中,一开始采用了高度间接层次的架构,实现了代码的高度复用。例如,从同一个源代码可以构建出适用于不同操作系统和设备的版本。然而,随着项目的发展和团队成员的更替,新加入的开发人员对复杂的间接层次结构理解困难,导致开发效率下降。这就体现了“间接层次会随时间衰减”的规则。为了解决这个问题,团队进行了架构调整,简化了间接层次结构,同时加强了对新成员的培训和沟通,确保大家对架构有一致的理解和认识。

  5. 原型与设计规则应用
    在一个创新型软件项目中,开发团队快速搭建了一个可用的原型,向管理层和潜在用户展示了产品的概念和基本功能。管理层看到原型后,误以为大部分工作已经完成。但开发团队遵循“一个可用的原型既重要又不重要”的规则,没有被管理层的误解所影响。他们将原型放在一边,认真进行设计文档的编写。在设计过程中,发现了很多原型中没有考虑到的细节和问题,如系统的扩展性、安全性等。完成设计文档后,从零开始实现设计,最终开发出了一个功能完善、性能稳定的产品。

  6. 评审规则应用
    在一个金融软件项目中,开发团队在代码评审过程中,发现单纯的代码评审只能发现一些语法错误和实现细节问题,而对于软件的整体设计和语义理解不够深入。于是,团队加强了设计评审的环节。在设计评审中,邀请了不同领域的专家和相关人员参与,从多个角度对软件的设计进行评估。通过设计评审,发现了一些潜在的设计缺陷,如数据处理逻辑的不合理、模块之间的耦合度过高等。及时对这些问题进行了修正,避免了在后期开发和维护过程中出现更大的问题。

状态机表示法的深入理解与应用场景

不同类型的状态机表示法在不同的应用场景中有各自的优势。

  1. UML 状态图的应用场景
    UML 状态图适用于复杂系统的建模和设计。在一个大型的工业自动化控制系统中,系统的状态和转换非常复杂,涉及到多个设备的协同工作和不同状态的切换。使用 UML 状态图可以清晰地描述系统的各种状态、状态之间的转换条件和动作。例如,一个自动化生产线的控制系统,通过 UML 状态图可以表示生产线的启动、运行、暂停、故障等状态,以及不同状态之间的转换逻辑,如在运行状态下检测到故障信号时,系统会转换到故障状态并执行相应的故障处理动作。

  2. Mealy 机和 Moore 机的应用场景
    Mealy 机和 Moore 机在数字电路设计和嵌入式系统中有广泛的应用。在一个简单的电子设备中,如一个智能门锁系统,Mealy 机可以根据输入信号(如密码输入、指纹识别等)和当前状态,立即产生输出信号(如开锁、报警等)。而 Moore 机的输出只取决于当前状态,适用于一些对输出稳定性要求较高的场景。例如,在一个恒温控制系统中,Moore 机可以根据当前的温度状态输出控制信号,确保温度的稳定控制。

  3. 状态机标准的实施步骤
    为团队定义“状态机标准”可以按照以下步骤进行:

    • 确定团队需求 :了解团队的项目特点、技术水平和开发流程,确定适合团队的状态机类型和表示法。
    • 制定标准文档 :详细描述状态机的定义、语法规则、使用规范等。例如,规定状态图中状态和转换的表示方式、动作的定义和执行顺序等。
    • 培训团队成员 :组织团队成员进行状态机标准的培训,确保大家理解和掌握标准的内容。
    • 监督和反馈 :在项目开发过程中,监督团队成员对状态机标准的执行情况,及时收集反馈意见,对标准进行优化和完善。
UML 约定在项目中的应用与实践

UML 约定在软件开发项目中可以帮助团队更好地理解和沟通系统的设计。

  1. UML 图的绘制与使用
    在一个大型的软件项目中,使用 UML 图来描述系统的各个方面。例如,使用类图来表示系统的类结构和类之间的关系,使用用例图来描述系统的功能需求和用户与系统的交互。在项目的不同阶段,不同的 UML 图发挥着不同的作用。在需求分析阶段,用例图可以帮助需求分析师和用户进行沟通,明确系统的功能边界。在设计阶段,类图可以帮助设计师进行系统的架构设计和模块划分。

  2. UML 约定的团队共识
    为了确保 UML 图在团队中能够有效沟通和使用,团队需要达成对 UML 约定的共识。可以通过以下方式实现:

    • 制定 UML 规范文档 :明确团队使用的 UML 符号、术语和绘制规则。
    • 组织 UML 培训 :让团队成员了解 UML 的基本概念和使用方法。
    • 定期评审 UML 图 :在项目开发过程中,定期对 UML 图进行评审,确保图的准确性和一致性。
C++ 在不同项目中的选型与实践

在选择 C++ 进行软件开发时,需要根据项目的具体情况进行权衡。

  1. 适合使用 C++ 的项目场景

    • 嵌入式系统开发 :对于一些对性能要求较高、资源有限的嵌入式系统,如智能家电、汽车电子等,C++ 的高效性和灵活性使其成为一个不错的选择。例如,在一个智能手表的开发项目中,使用 C++ 可以更好地控制硬件资源,实现实时响应和低功耗运行。
    • 游戏开发 :游戏开发需要处理大量的图形渲染、物理模拟和用户交互等任务,C++ 的高性能和丰富的库支持使其成为游戏开发的主流语言之一。例如,很多大型的 3D 游戏都是使用 C++ 开发的,能够提供流畅的游戏体验和高质量的画面效果。
  2. C++ 项目的开发策略

    • 合理使用 C++ 特性 :在开发过程中,根据项目需求合理使用 C++ 的特性,避免过度使用导致代码复杂度增加。例如,对于不需要动态多态的类,尽量避免使用虚函数。
    • 团队技术提升 :针对团队成员 C++ 经验不足的问题,可以组织内部培训、技术分享等活动,提高团队整体的技术水平。
    • 代码审查与优化 :定期进行代码审查,发现和解决代码中的问题,优化代码性能和可维护性。
总结与展望

软件开发是一个复杂的过程,需要遵循一系列的规则和原则,合理选择技术和工具。通过对软件开发规则、状态机表示法、UML 约定和 C++ 语言的深入理解和应用,可以提高软件项目的开发效率、质量和可维护性。

在未来的软件开发中,随着技术的不断发展和创新,这些规则和技术也会不断演进和完善。例如,随着人工智能和机器学习技术的应用,软件开发可能会面临新的挑战和机遇。我们需要不断学习和探索,将新的技术和方法融入到软件开发中,以适应不断变化的市场需求和技术环境。

同时,团队的协作和沟通也是软件开发成功的关键因素之一。通过建立良好的团队文化和沟通机制,团队成员可以更好地理解和执行规则,共同推动项目的顺利进行。

总之,软件开发是一个不断学习和进步的过程,我们需要不断总结经验,探索新的方法和技术,以提高软件开发的水平和质量。

以下是一个简单的 mermaid 流程图,展示了一个软件开发项目的基本流程:

graph LR
    A[需求分析] --> B[设计阶段]
    B --> C[开发阶段]
    C --> D[测试阶段]
    D --> E[发布阶段]
    E --> F[维护阶段]
    F --> C{是否有新需求或问题}
    C -- 是 --> B
    C -- 否 --> END

通过这个流程图可以清晰地看到,软件开发是一个循环迭代的过程,不断根据用户反馈和市场需求进行优化和改进。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值