学径 LearnPath

编程语言基础

软件工程

如何去开发一个真正能用于实践的程序。

讲解模式

通过本节课,你将理解:

#goal

前面各课里,程序多半装在一个文件里:写完、跑通、读懂即可。真实世界里的软件往往更大——多人接力、长期修改、出错代价更高。软件工程要回答的不是「还能不能再写一行」,而是:怎样把可执行的思想组织成可分工、可检验、可演进的形态。

本节课介绍几件贯穿现代软件的骨架:模块化与分层(含常见的 B/S 结构)、单元测试、编程框架、版本管理。目标是建立概念地图,而不是立刻搭起一整套工程。下一课讲氛围编程时,你会看见这些骨架如何被工具加速——也如何被草稿悄悄绕开。

从一份脚本到一份工程

#hook

一份课堂作业可以只有一个 .py。一款要上线的应用,却通常拆成许多文件、约定许多接口、留下许多「怎样才算做对」的检查,并且每一次改动都要能回看、能回退。

玛格丽特·汉密尔顿与阿波罗导航软件清单
玛格丽特·汉密尔顿与阿波罗导航软件清单,1969年。原件:Draper Laboratory;Adam Cuerden 修复。来源:Wikimedia Commons,公有领域。

清单之所以能堆到身高,不是因为有人喜欢纸张,而是因为思想必须被拆开、编号、复核。软件工程继承的正是这种纪律:把「能跑」升级为「别人接得住、机器查得了、历史说得清」。

模块化:把能力拆成可替换的单元

#explore-modular

模块化的想法你在第 10 课已经见过:共用的能力放进模块,当前页面只处理当前问题。工程尺度上,它还意味着:

  • 按职责拆文件:画图的放一处,计算的放一处,读配置的放一处。
  • 用接口交接:别人只需要知道「调用什么、传入什么、返回什么」,不必打开你文件的每一行。
  • 改一处,别处尽量不碎:修正 shapes.py 里的正方形,不应逼你重抄三份粘贴出来的副本。

下面是一个极小的示意——不是完整项目,只说明「拆」与「接」:

python
# shapes.py(示意:几何)
def square_side_count():
    return 4


# main.py(示意:使用方)
import shapes

print('正方形边数', shapes.square_side_count())

抄函数进许多文件,和 import 不是一回事。模块化要消灭的,正是「同一规则的许多份失同步副本」。

分层与 B/S:谁负责看见,谁负责计算

#explore-layers

模块化解决「拆成哪些盒子」。分层解决「盒子之间谁可以叫谁」——通常只允许上层依赖下层,避免所有人互相牵扯。

一个常见的三层说法(名字因领域而异,结构相似):

  1. 表现层:人看见、点选、输入的地方(网页、窗口、海龟画布旁的按钮)。
  2. 业务层:规则与流程(如何计分、如何生成下一帧、何种输入合法)。
  3. 数据层:保存与读取(文件、数据库、远程接口)。

B/S 结构(Browser / Server,浏览器—服务器)是分层在网络上的一种落地:浏览器负责展示与交互,服务器负责业务与数据。你打开的网页是 B 端;算分、存档、鉴权往往在 S 端。同校同学的成绩不应只存在某台浏览器的临时内存里——那是分层要保护的边界。

对照本课的在线环境:编辑与画布在浏览器一侧;解释器也可能在远端执行。即便只有你一人使用,「谁看见结果、谁保存状态」 仍然是工程问题。大系统用防火墙与接口把边界写硬;小程序用文件与函数把边界写清。

分层不是越多越好。多一层,就多一处交接与命名。问题变大时再分层;问题仍小却先搭三层空架子,属于写法带来的负担。

单元测试:让「做对了」可重复执行

#explore-test

人眼看一次输出,容易漏。单元测试把「这一小块在这些输入下应得到什么」写成可再跑的程序。改完代码后整批重跑,哪一条红了,就知道哪一块被碰坏。

Python 里可以用最朴素的 assert,也可以用 unittest / pytest 等工具。下面用 assert 说明思想——测的是一个小函数,不是整幅画布:

python
def exterior_angle(sides):
    return 360 / sides


assert exterior_angle(4) == 90
assert exterior_angle(6) == 60
# exterior_angle(0) 应如何失败?测试也会逼你想清楚边界。

测试写的是契约:合法输入的结果、非法输入是否拒绝。它不替代阅读,但把验收从「我记得昨天好像对」变成「现在再跑一遍仍对」。

框架:先有骨架,再填血肉

#explore-framework

编程框架是别人写好的结构与约定:你按它的插槽填入自己的逻辑,而不是从空白文件发明全部流程。Web 框架规定请求如何进来、响应如何出去;游戏框架规定每帧如何更新;GUI 框架规定按钮如何挂上事件。

框架与模块的差别可以这样记:

  • 模块像工具箱:你决定何时拿起哪把扳手。
  • 框架像装配线:主流程由它驱动,你的代码被「叫进去」干活(所谓控制反转)。

学框架要付的学费是:先读懂它的地图(目录、入口、生命周期),再写自己的三行。跳过地图直接粘贴示例,短期能跑,长期迷路。氛围编程尤其容易生成「某个流行框架的完整脚手架」——即便你的目标只是课堂海龟动画。

版本管理:给思想做可回退的快照

#explore-vcs

版本管理(常见工具是 Git)记录每一次有意义的改动:谁、何时、改了什么、为何改。它让你能:

  • 回到「昨天还能画对五角星」的那一版;
  • 并行尝试两个方案再合并;
  • 看清一行可疑代码是哪次提交引进的。

一次提交应当是一个说得清的意图,而不是「下午随便保存」。提交说明写的是思想,不是心情:「修好左枝未后退」比「update」有用。

课堂练习未必强制使用 Git,但要建立这个概念:没有历史的工程,只能靠人脑记忆;人脑不是可靠的备份介质。

本节练习:平均雨量的一张草图

#practice

不必写完整程序。在纸上为「由一组雨量求出平均值」画一张最小草图:计算放在一个模块里,入口只负责把读数交进去并取出结果。契约用这组数据:读数为 1、2、3 时,平均值应为 2。边界也写死:读数为空列表 [] 时,不计算平均值,而是说明「没有读数」。一份来源、一条可再跑的验收,比把同一公式抄进多处更接近工程。

本节要点

#review
  • 软件工程把「能跑」扩展为可分工、可检验、可演进。
  • 模块化按职责拆分并用接口交接;分层规定依赖方向,B/S 是网络上的常见分层。
  • 单元测试把验收写成可重复执行的契约。
  • 框架提供主流程骨架;版本管理给改动留下可回退的历史。

单选题

模块化在保护什么

把同一函数复制进三个文件,与写成一个模块再 import,主要差别是?

单选题

B/S 在说什么

关于 B/S 结构,哪一项更准确?

单选题

单元测试测什么

单元测试更接近哪一件事?

多选题

工程骨架

下列哪些属于本节介绍的软件工程手段?可以多选。

可多选

未登录时不会保存学习进度