设计模式学习笔记 - 开源实战二(上):从Unix开源开发学习应对大型复杂项目开发

概述

软件开发的难度无外部两点,一是技术男,代码量不一定多,但要解决的问题比较难,需要用到一些比较深的技术解决方案或者算法,不是靠 “堆人” 就能搞定的,比如自动驾驶、图像识别、高性能消息队列等;而是复杂度,技术不难,但项目很庞大,业务复杂,代码量多,参与开发的人多,比如物流系统、财务系统等。第一点涉及细分专业的领域知识,跟设计、编码无关,所以接下来重点讲解第二点,如何应对如阿健开发的复杂度。

简单的 “hello world” 程序,谁都能写出来。几千行的代码谁都能维护。但是,当代码超过几万行、十几万,甚至几十万、上百万行的时候,软件的复杂度就会呈指数级增长。这种情况下,我们不仅仅要求程序运行得了,运行得正确,还要求代码看得懂、维护得了。实际上,复杂度不仅仅体现在代码本身,还体现在协作开发上,如何管理庞大的团队,来进行有条不紊地协作开发,也是一个很复杂的难题。

如何应对复杂软件开发呢?

Unix开源项目就是一个值得学习的例子。

Unix 从 1969 年诞生,一直演进至今,代码量有几百万行,如此庞大的项目开发,能够如此完美的协作开发,并且长期维护,保持足够的代码质量,这里面有很多成功的经验可以借鉴。所以,接下来就以 Unix 开源项目的开发作为引子,分三个章节,通过下面三个话题,详细的讲讲应对复杂软件开发的方法论。

  • 从设计原则和思想的角度来看,如何应对庞大而复杂在的项目开发?
  • 从研发管理和开发技巧的角度来看,如何应对庞大而复杂在的项目开发?
  • 聚焦在 Code Review 上来看,如何通过 Code View 保持项目的代码质量?

封装与抽象

在 Unix、Linux 系统中,有一句经典的话, “Everything is a file”,翻译成中文就是 :一切皆文件。这句话的意思是,在 Unix、Linux 系统中,很多都系都被抽象成 “文件” 这样一个概念,比如 Socket、驱动、硬盘、系统信息等。它们使用文件系统的路径作为统一的命名空间(namespace),使用统一的 read、write 标准函数来访问。

比如,我们要查看 CPU 信息,在 Linux 系统中,我们只需要使用 vimGedit 等编辑器或者 cat 命令,像打开其他文件一样,打开 /proc/cpuinfo,就能查到相应的信息。此外,我们还可以通过查看 /proc/uptime 文件,了解系统运行了多久,查看 /proc/version 了解系统的内核版本等。

实际上,“一切皆文件” 就体现了封装和抽象的设计思想

封装了不同类型设备的访问细节,抽象为统一的文件访问方式,更高层的代码就能基于统一的访问方式,来访问底层不同类型的设备。这样做的好处是,隔离底层设备访问的复杂性。统一的访问方式能够简化上层代码的编写,并且代码更容易复用。

此外,封装和抽象还能有效的控制代码复杂性的蔓延,将复杂性封装在局部代码中,隔离实现的易变性,提供简单、统一的访问接口,让其他模块来使用,其他模块基于抽象的接口而非具体的实现编程,代码会更加稳定。

分层与模块化

前面规范与重构章节也提到过,模块化是构架复杂系统的常用手段。

对于像 Unix 这样的复杂系统,没有人能掌控所有的细节。之所以我们能开发出如此复杂的系统,并且能维护得了,最主要原因就是将系统划分成各个独立的模块,比如进程调度、进程通信、内存管理、虚拟文件系统、网络接口等模块。不同的模块之间通过接口来进行通信,模块之间的耦合很小,每个小的团队聚焦于一个独立的高内聚模块来开发,最终像搭积木一样,将各个模块组装起来,构建成一个超级复杂的系统

此外,Unix、Linux 等大型系之所以能做到几百、上千人有条不紊地协作开发,也归功于模块化做得好。不同的团队负责不同的模块开发,这样即便在不了解全部细节的情况下,管理者也能协调各个模块,让整个系统有效地运转。

我们常说,计算机领域的任何问题都可以通过增加一个间接的中间层来解决,这本身就体现了分层的重要性。比如,Unix 系统也是基于分层开发的,它可以大致分为三层,分别是内核、系统调用、应用层。每一层都对上层封装实现细节,暴露抽象的接口来调用。而且,任意一层都可以被重写实现,不会影响到其他层的代码。

面对复杂系统的开发,我们要善于使用分层技术,把容易复用、根据题业务关系不大的代码,尽量下沉到下层,把容易变动、根据题业务强相关的代码,尽量上移到上层

基于接口通信

刚刚见到了分层、模块化,那不同层之间、不同模块之间,是如何通信的呢?一般来讲都是通过接口调用在设计模块(module)或者层(layer)要暴露的接口的时候,要学会隐藏实现,接口从命名到定义都要抽象一些,尽量少涉及具体的实现细节

比如,Unix 系统提供的 open() 文件操作函数,底层实现非常复杂,涉及到权限控制、并发控制、物理存储,但我们用起来却非常简单。此外,因为 open() 函数基于抽象而非具体的实现来定义,所以,我们在改动 open() 函数的底层实现时,并不需要改动依赖它的上层代码。

高内聚、松耦合

高内聚、松耦合是一个比较通用的设计思想,内聚性好、耦合少的代码,能让我们在修改或者阅读代码时,聚集在一个小范围的模块或者类中,不需要了解太多其他模块或者类的代码,让我们的焦点不至于太发散,也就降低了阅读和修改代码的难度。而且,因为依赖关系简单,耦合小,修改代码不会牵一发而动全身,代码改动比较集中,引入 bug 的风险也就减少了很多

实际上,刚刚讲到的很多方法,比如封装、抽象、分层、模块化、基于接口通信,都能有效得实现代码的高内聚、松耦合。反过来,代码的高内聚、松耦合,也就意味着,抽象、封装做的比较到位、代码结构清晰、分层和模块化合理、依赖关系简单,那代码的整体质量就不会太差。即便某个具体的类或者模块设计得不怎么合理,代码质量不怎么高,影响的范围也是非常有限地。我们可以聚集于这个模块或者类做相应的小型重构。而对于代码结构的调整,这种改动范围比较集中的小型重构的难度就小多了。

为扩展而设计

越复杂的项目,越要在前期设计上多花点时间。提前思考项目中未来可能会有哪些功能需要扩展,提前预留号扩展点,以便在未来需求变更时,在不改动代码整体结构的情况下,轻松地添加新能够。

做到代码可扩展,需要代码满足开闭原则。特别像 Unix 这样的开源项目,有 n 多人参与开发,任何人都可以提交代码到代码库中。代码满足开闭原则,基于扩展而非修改来添加新功能,最小化、集中化代码改动,避免新代码影响到老代码,降低引入 bug 的风险。

除了满足开闭原则,做到代码可扩展,在前面章节中也提到了很多方法,比如封装和抽象,基于接口而非实现编程等。识别出代码可变部分和不可变部分,将可变部分封装起来,隔离变化,提供抽象化的不可变接口,供上层系统使用。当具体的实现发生变化的时候,我们需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游代码几乎不需要修改。

KISS 首要原则

简单清晰、可读性好,是任何大型软件开发要遵循的首要原则。只要可读性好,即便扩展性不好,顶多就是花点时间、多改动几行代码的事情。但是,如果可读性不好,连看都看不懂,那就不是多花时间可以解决得了的了。如果你对现有代码的逻辑似懂非懂,抱着尝试的心态去修改代码,引入 bug 的可能性就会很大。

不管是自己还是团队,在参与大型项目开发的时候,要尽量避免过度设计、过早优化,在扩展性和可读性有冲突时,应该遵循 KISS 原则,首选可读性

最小惊奇原则

《Unix 编程艺术》一书中提到一个 Unix 的经典设计原则,叫 “最小惊奇原则”,英文是 “The Least Surprise Principle”。实际上,这个原则等同于 “遵守开发规范”,意思是,在做设计或者编码时,要遵循统一的开发规范,避免反直觉的设计。实际上,关于这一点,我们在前面的编码规范部分也讲到过。遵循统一的编码规范,所有的代码就像一个人写出来的,能有效地减少阅读干扰。在大型软件开发中,参与开发的人员很多,如果每个人都按照自己的编码习惯来写代码,那整个项目的代码风格就会千奇百怪,这个类是这种编码风格,另一个类又是另一种编码风格。在阅读的时候,我们要不停的切换去适应不同的编码风格,可读性就变差了。所以,对于大型项目的开发来说,要特别重视遵守统一的开发规范。

总结

本章,我们主要从设计原则和思想的角度,也可以说是设计开发的角度,来学习如何应对复杂软件的开发。主要总结了 7 点。这 7 点在前面我们都详细讲过,它们分别是:

  • 封装与抽象
  • 分层与模块化
  • 基于接口通信
  • 高内聚、松耦合
  • 为扩展而设计
  • KISS 首要原则
  • 最小惊奇原则(遵守开发规范)

当然,这 7 点之间并不是相互独立的,有几点是相互支持的,例如 “高内聚、松耦合” 与抽象封装、分层模块化、基于接口通信。有几点是相互冲突的,比如 KISS 原则与为扩展而设计,这都需要根据实际情况去权衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/555618.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024-4-18 群讨论:Java Agent,JFR 与 JIT 的一些讨论

以下来自本人拉的一个关于 Java 技术的讨论群。关注公众号:hashcon,私信进群拉你 命令行中带 -XX:StartFlightRecording 启动,同时带 -javaagent,那么谁先启动?jfr能采集到agent启动前后资源消耗情况不? 不…

基于深度学习的手写汉字识别系统(含PyQt+代码+训练数据集)

基于深度学习的手写汉字识别系统(含PyQt代码训练数据集) 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现参考资料 前言 本项目是基于深度学习网络模型的人脸表情识别系统&#xff0…

c++编程(6)——类与对象(4)运算符重载、赋值重载函数

欢迎来到博主的专栏——C编程 博主ID:代码小豪 文章目录 运算符重载赋值重载函数默认赋值重载函数其他运算符重载函数 运算符重载 重载这个概念在c中已经出现两次了,在前面的文章中,函数重载指的是可以用相同名字的函数实现不同的功能。而运…

【WebSocket连接异常】前端使用WebSocket子协议传递token时,Java后端的正确打开方式!!!

文章目录 1. 背景2. 代码实现和异常发现3. 解决异常3.1 从 URL入手3.2 从 WebSocket子协议的使用方式入手(真正原因) 4. 总结(仍然存在的问题) 前言: 本篇文章记录的是使用WebSocket进行双向通信时踩过的坑&#xff0c…

将gdip-yolo集成到yolov9模型项目中(支持预训练的yolov9模型)

1、yolov9模型概述 1.1 yolov9 YOLOv9意味着实时目标检测的重大进步,引入了可编程梯度信息(PGI)和通用高效层聚合网络(GELAN)等开创性技术。该模型在效率、准确性和适应性方面取得了显著改进,在MS COCO数…

「 安全工具介绍 」软件成分分析工具Black Duck,业界排名TOP 1的SCA工具

在现代的 DevOps 或 DevSecOps 环境中,SCA 激发了“左移”范式的采用。提早进行持续的 SCA 测试,使开发人员和安全团队能够在不影响安全性和质量的情况下提高生产力。前期在博文《「 网络安全常用术语解读 」软件成分分析SCA详解:从发展背景到…

Qt-饼图示范

1.效果图 2.代码如下 2.1 .h文件 #ifndef PIECHARTWIDGET_H #define PIECHARTWIDGET_H#include <QWidget> #include <QChartView> #include <QPieSeries>#include<QVBoxLayout> #include<QMessageBox> #include <QtCharts>struct PieDat…

FastAPI - uvicorn设置 logger 日志格式

怎么将日志打印到文件 在main.py加入log_config“./uvicorn_config.json” import uvicornif __name__ "__main__":uvicorn.run("app:app", host"0.0.0.0", port8000, log_config"./uvicorn_config.json")uvicorn_config.json {&qu…

“互联网+”创意创业大赛活动方案

大赛历时6个月&#xff0c;总体分两个赛程&#xff1a;一是策划创意阶段。评审的是方案。二是组织实施阶段。通过阶段一立项的项目由公司协助实施&#xff0c;最终评审的是项目落实情况。学生可两个赛程单独参加&#xff0c;也可连续参加。 具体流程及时间安排如下&#xff1a;…

ansible-tower连接git实现简单执行playbook

前提&#xff1a;安装好ansible-tower和git&#xff0c;其中git存放ansible得剧本 其中git中得内容为&#xff1a; --- - name: yjxtesthosts: yinremote_user: rootgather_facts: noroles:- testroles/test/tasks/main.yml #文件内容 --- #- name: Perform Test Task # tas…

单链表-通讯录

目录 单链表实现 通讯录代码实现 初始化 初始化函数 添加 删除 展示 查找 修改 销毁 代码展示 main.c text.c text.h list.c list.h 和前面的通讯录实现差不多这次就是实现一个以单链表为底层的通讯录 单链表实现 数据结构&#xff1a;单链表-CSDN博客 通讯…

OpenHarmony多媒体-video_trimmer

简介 videotrimmer是在OpenHarmony环境下&#xff0c;提供视频剪辑能力的三方库。 效果展示&#xff1a; 安装教程 ohpm install ohos/videotrimmerOpenHarmony ohpm环境配置等更多内容&#xff0c;请参考 如何安装OpenHarmony ohpm包 。 使用说明 目前支持MP4格式。 视频…

docker部署的nginx配置ssl证书https

申请ssl证书&#xff0c;已腾讯的免费证书为例 2.上传证书到linux服务器 2.1 映射ssql目录 首先确保容器命令已映射宿主机目录&#xff0c;不一定是ssl&#xff0c;也可以是其他路径。 2.2 上传文件到指定路径 以我映射的ssl路径为例&#xff0c;我上传到宿主机的 /usr/local…

【GEE实践应用】使用MODIS NDVI数据集绘制研究区域每日NDVI序列曲线

// 设置研究区域 var geometry table;// 选择MODIS NDVI 数据集 var modisNDVI ee.ImageCollection(MODIS/006/MOD13A2).filterBounds(geometry).filterDate(2000-01-01, 2023-12-31);// 计算每天的平均 NDVI var dailyMeanNDVI modisNDVI.map(function(image) {var date e…

(最详细)关于List和Set的区别与应用

关于List与Set的区别 List和Set都继承自Collection接口&#xff1b; List接口的实现类有三个&#xff1a;LinkedList、ArrayList、Vector。Set接口的实现类有两个&#xff1a;HashSet(底层由HashMap实现)、LinkedHashSet。 在List中&#xff0c;List.add()是基于数组的形式来添…

OpenHarmony网络组件-Mars

项目简介 Mars 是一个跨平台的网络组件&#xff0c;包括主要用于网络请求中的长连接&#xff0c;短连接&#xff0c;是基于 socket 层的解决方案&#xff0c;在网络调优方面有更好的可控性&#xff0c;暂不支持HTTP协议。 Mars 极大的方便了开发者的开发效率。 效果演示 编译…

简述Kafka的高可靠性

什么叫可靠性&#xff1f; 大家都知道&#xff0c;系统架构有三高&#xff1a;「高性能、高并发和高可用」&#xff0c;三者的重要性不言而喻。 对于任意系统&#xff0c;想要同时满足三高都是一件非常困难的事情&#xff0c;大型业务系统或者传统中间件都会搭建复杂的架构来…

万字长文带你APK反编译重签名aabapks转换

Android反编译 反编译&#xff08;Decompilation&#xff09;是将已编译的程序&#xff08;比如二进制代码&#xff09;转换回更高级别的编程语言代码的过程。这通常用于理解程序的工作原理&#xff0c;进行软件审计&#xff0c;恢复丢失的源代码&#xff0c;或者进行教学研究…

提升数据质量的三大要素:清洗prompt、数据溯源、数据增强(含Reviewer2和PeerRead)​

前言 我带队的整个大模型项目团队超过40人了&#xff0c;分六个项目组 每个项目组都是全职带兼职&#xff0c;且都会每周确定任务/目标/计划然后各项目组各自做任务拆解&#xff0c;有时同组内任务多时 则2-4人一组 方便并行和讨论&#xff0c;每周文档记录当周工作内容&…

Leetcode 4.18

Leetcode 1.无重复字符的最长子串2.最长回文子串3.整数反转4.字符串转换整数 (atoi)5.正则表达式匹配 1.无重复字符的最长子串 无重复字符的最长子串 滑动窗口&#xff0c;先让右指针右移&#xff0c;如果发现这个子串有元素和右指针当前元素重复。 则&#xff1a; 左指针右移…