《代码中的软件工程》总结与思考

前言

我本科的专业是计算机科学与技术,巧的是当时我也有一门名字叫做《高级软件工程》的课程,但是作为大三下学期的选修课,当时选修课学分已经修满,错失了一次接触本专业最精华课程的机会,直到来到科大孟老师的课堂。这也算弥补了之前的缺憾。

0. 软件工程 - 码农的自我修养

首先老师从经典的瀑布模型出发,提出了软件工程这门课应该是怎样学习和训练。

  • 需求分析:分析现有软件, 归纳初步需求;基础差的同学学习技术
  • 设计阶段:用快速发布来证明设计是有效的, 能适应变化的。
  • 实现阶段:用各种软件工程的衡量手段来证明大家实现的能力。
  • 稳定阶段:证明测试能否覆盖代码的大部分。
  • 发布阶段: 如期发布, 用户量, 用户评价。
  • 维护阶段:网上的观众或下一个年级的同学能很愿意接手你们的软件。

通过在练习上“数量”和“质量”的关系对比,对我们提出多练习、多总结、玩中学习、不怕失败的美好期望。

1. 工欲善其事必先利其器

如标题所言,这节课从“码农的必备技能”出发,向我们介绍了当前软件工程从业者需要掌握的基本技能和工具。主要包括:

打字

基本的打字能力和打字速度是程序员的必备技能。谁能拒绝一个代码敲得飞快的程序员呢?酷!

“代码编辑工具” - Visual Studio Code

为什么要在代码编辑工具上打引号呢?那是因为VSCode作为轻量强大的代码编辑器,不仅支持跨平台和多语言,而且其插件已经丰富到可以将其打造成一个VSCode宇宙。

这里也从VSCode成功的产品定位和良好的团队协作出发,解释了优秀产品背后一定存在一个优秀的团队。

VSCode的强大之处:

  • 进程隔离的插件模型:VSCode把插件们放到单独的进程里,插件进程怎么折腾也无法干扰主进程代码的执行,主程序的稳定性得到了保障。
  • UI渲染与业务逻辑隔离,保证了一致的用户体验。
  • 代码理解和调试插件的实现,使用了高效的两大协议:Language Server Protocol(LSP)、Debug Adapter Protocol(DAP)。
  • 成功的远程工作方式VSCRD。

可以说,团队在架构上的前瞻性决策和扎实的工程基础,将VSCode打造成了一款同类产品中集大成的耀眼明星。

Git

作为广大程序员必备的代码版本控制工具,课程也不吝笔墨地介绍了许多关于git的常见场景和用法,如:

  • git本地版本库的基本用法
  • git远程版本库的基本用法
  • 团队项目中的分叉合并
  • git rebase
  • fork + pull request

通过对这些常见场景的介绍,学习git的基本命令和使用方法,可谓是在实践中学习。

Vim

Vim作为类Unix系统中普遍自带的vi文本编辑器的加强版,是编辑器中的“活化石”。

Vim的三种模式:

  • 命令模式
  • 输入模式
  • 底线命令模式

Vim使用介绍:

  • 安装
  • 切换模式
  • 移动光标
  • 删除
  • 复制粘贴
  • 复原和重做
  • 自动化执行宏命令
  • 搜索替换
  • 添加注释

正则表达式

正则表达式是对字符串操作的一种逻辑公式,其使用非常广泛,通过一种简单快速的方法可以达到对于字符串的控制。可以说,运用得当,它就是程序员手中的利器!

正则表达式使用介绍:

  • 基本的字符串搜索方法
  • 同时搜索多个字符串的方法
  • 在匹配字符串时的大小写问题
  • 通配符的基本用法
  • 匹配具有多种可能性的字符集
  • 贪婪匹配 vs. 懒惰匹配
  • 一些特殊位置和特殊字符
  • 使用捕获组复用模式
  • 基本的字符串搜索替换方法
  • 复用捕获组的方式进行替换

2. 工程化编程实战

在这一课中,我们从完善构建C++代码的环境开始,逐步掌握编写代码最重要的基本功——代码规范和代码风格。

构建开发环境

这里我们安装并使用VSCode中C++语言插件,并介绍了在Windows环境下配置C++代码环境的过程。

代码最初是如何生长起来的?

UML三巨头之一的Ivar Jacobson曾说“银弹不存在,我们需要的仅仅是明智的软件开发方法( smart oftware development ),软件必须从一个小的可运行的 kinny system 开始,逐渐充实生长称为 ull-fledge 的成熟系统。”

通过一个menu小程序,我们了解了一份代码和程序可以如何逐渐完善代码、增加功能。

简约而不简单——代码规范和代码风格

代码风格的原则:简明、易读、无二义性。

通过对几组程序头部注释的分析,我们总结出了不同的项目代码需要怎样的注释,而一般情况下我们也有一些被大家广泛认可的代码规范可以去学习,而不是仅仅受限于各种语言允许的范围。

另外,课程还介绍了一些优化代码、编写高质量代码的基本方法:

  • 通过控制结构简化代码
  • 通过数据结构简化代码
  • 错误处理
  • 重构代码

现代计算机计算资源不断丰富,我们应当摒弃效率至上的观念,合理的换行、注释和更易懂的语句更应该是我们追求的目标。

模块化编程

模块化(Modularity)是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离 (SoC, Separation of Concerns),是由软件工程领域的奠基性人物Edsger Wybe Dijkstra(1930~2002)在1974年提出,没错就是Dijkstra最短路径算法的作者。

  • 耦合度(Coupling)。耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。
  • 内聚度(Cohesion)。内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
  • KISS(Keep It Simple & Stupid)原则。

可重用软件设计

消费者重用

消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。软件开发者在重用已有的软件模块代码时一般会重点考虑如下四个关键因素:

  • 该软件模块是否能满足项目所要求的功能;
  • 采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;
  • 该软件模块是否有完善的文档说明;
  • 该软件模块是否有完整的测试及修订记录;

如上四个关键因素需要按照顺序依次评估。

生产者重用

我们清楚了消费者重用时考虑的因素,那么生产者在进行可重用软件设计时需要重点考虑的因素也就清楚了,但是除此之外还有一些事项在进行可重用软件设计时牢记在心,我们简要列举如下:

  • 通用的模块才有更多重用的机会;
  • 给软件模块设计通用的接口,并对接口进行清晰完善的定义描述;
  • 记录下发现的缺陷及修订缺陷的情况;
  • 使用清晰一致的命名规则;
  • 对用到的数据结构和算法要给出清晰的文档描述;
  • 与外部的参数传递及错误处理部分要单独存放易于修改;
接口

接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。换句话说,接口具体定义了软件模块对系统的其他部分提供了怎样的服务,以及系统的其他部分如何访问所提供的服务。

在面向过程的编程中,接口一般定义了数据结构及操作这些数据结构的函数;而在面向对象的编程中,接口是对象对外开放(public)的一组属性和方法的集合。函数或方法具体包括名称、参数和返回值等。

接口规格包含五个基本要素:

  • 接口的目的;
  • 接口使用前所需要满足的条件,一般称为前置条件或假定条件;
  • 使用接口的双方遵守的协议规范;
  • 接口使用之后的效果,一般称为后置条件;
  • 接口所隐含的质量属性。
微服务

由一系列独立的微服务共同组成软件系统的一种架构模式;

每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈;

每个微服务为独立的业务功能开发,一般每个微服务应分解到最小可变产品(MVP),达到功能内聚的理想状态。微服务一般通过RESTful API接口方式进行封装;

系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体系统的业务功能。

为了强化练习,这里我们将通用的Linktable模块集成到我们的menu程序中,在实践中学习和体会接口的相关知识。

可重入函数与线程安全

线程

线程(thread)是操作系统能够进行运算调度的最小单位。它包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一般默认一个进程中只包含一个线程。

函数调用堆栈

借助函数调用堆栈可以将我们写的函数调用代码整理成一个顺序执行的指令流,也就是一个线程,每一个线程都有一个独自拥有的函数调用堆栈空间,其中函数参数和局部变量都存储在函数调用堆栈空间中,因此函数参数和局部变量也是线程独自拥有的。除了函数调用堆栈空间,同一个进程的多个线程是共享其他进程资源的,比如全局变量是多个线程共享的。

可重入函数

可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。

通过对menu子系统的可重用接口设计,我们能更加理解上述概念。

4. 软件科学基础概论

软件的基本构成元素

对象

一个对象作为某个类的实例,是属性和方法的集合。对象和属性之间有依附关系,属性用来描述对象或存储对象的状态信息,属性也可以是一个对象。对象能够独立存在,对象的创建和销毁显式地或隐式地对应着构造方法(constructor)和析构方法(destructor) 。
面向对象是一种对软件抽象封装的方法。

函数和变量

我们看看用函数和变量/常量作为软件基本元素的抽象方法。相对来讲面向对象是一种更高层的软件抽象封装的方法,其中方法大致对应函数,属性大致对应变量/常量。由于函数和变量/常量的相关概念相信您非常熟悉,我们这里仅讨论它们的进程地址空间分布。
从图中可以看出从低地址到高地址分别内存区分别为代码段、已初始化数据段、未初始化数据段(BSS)、堆、栈和命令行参数和环境变量。

在这里插入图片描述

面向对象的软件抽象层次更高与进程地址空间对应起来更为复杂,我们以面向过程的编程语言C语言为例简要分析一下函数、常量、全局变量、静态变量和局部变量的存储空间分布。

  • 全局常量(const)、字符串常量、函数以及编译时可决定的某些东西一般存储在代码段(text);
  • 初始化的全局变量、初始化的静态变量(包括全局的和局部的)存储在已初始化数据段;
  • 未初始化的全局变量、未初始化的静态变量(包括全局的和局部的)存储在未初始化数据段(BSS);
  • 动态内存分配(malloc、new等)存储在堆(heap)中;
  • 局部变量(初始化以及未初始化的,但不包含静态变量)、局部常量(const)存储在栈(stack)中;
  • 命令行参数和环境变量存储在与栈底紧挨着的位置。

如果我们堆和栈扩大,把命令行参数和环境变量作为调用main函数的栈空间,把已初始化数据段和未初始化数据段(BSS)作为扩大的堆空间的一个部分,我们就可以简单化为代码+堆栈的地址空间分布,而且这种简单化与面向过程的软件抽象元素函数和变量/常量保持逻辑一致。

指令和操作数

指令(instruction)是由 CPU 加载和执行的软件基本单元。一条指令有四个组成部分:标号、指令助记符、操作数、注释,其中标号和注释是辅助性的,不是指令的核心要素。一般指令可以表述为指令码+操作数。
指令码可以是二进制的机器指令编码,也可以是八进制的编码,程序员更喜欢用汇编语言指令助记符,如 mov、add 和 sub,给出了指令执行操作的线索。
操作数有 3 种基本类型:立即数。用数字文本表示的数值;寄存器操作数。使用 CPU 内已命名的寄存器;内存操作数。引用内存位置。

0和1

指令是由0和1构成的特定结构的信息,0和1就是软件的基本信息元素。

可以说,0和1组成了丰富多彩的计算机二进制世界,而我们的物理世界又岂不是由微小的基本成分组合而成。0和1编码的交织组合,不断演进,与我们物质世界到如今信息时代虚拟世界的发展演化也是密不可分的。这是一种哲学的思考,也是软件工程从软件到工程的升华。

总结

我这里主要总结了这门课程中关于软件的部分,它们大多也是我以前专业学习的内容。通过老师十数年精心编排的课程,我也对专业一些内容有了全新的见解。合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。 为山九仞,岂一日之功。虽然现在自己对于掌握的行业知识不能达到如老师般抽象到透彻,但我仍可以在不断积累的过程中,沉淀和思考,总有一天量变会产生质变。还记得刚学编程时看到源代码和文档如天书时,大学老师常常鼓励我们的一句话:不管有多么复杂的程序和库文件,都是人来完成的,不必神化它们。或许软件人的精神正应当如此。

参考资料:代码中的软件工程,https://gitee.com/mengning997/se
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值