加州理工-CS420-软件工程笔记-全-

加州理工 CS420 软件工程笔记(全)

001:集成开发环境 (IDE) 教程

在本节课中,我们将学习什么是集成开发环境,并重点介绍 Visual Studio Code 这款流行的 IDE。我们将从基本概念讲起,逐步学习如何安装、配置 VS Code,并使用它进行 Python 开发、Git 版本控制以及团队协作。课程内容设计简单直白,适合初学者。

什么是集成开发环境 (IDE)?💻

上一节我们介绍了本课程的目标,本节中我们来看看什么是 IDE。

一个 IDE 本质上是一个软件应用程序,它集成了软件开发所需的各种工具。它通常包含以下组件:

以下是 IDE 的核心组件列表:

  • 代码编辑器:用于编写和编辑代码,通常包含语法高亮和代码自动补全功能。
  • 编译器与解释器:将代码转换为机器可读格式或直接运行脚本的工具。
  • 调试器:通过逐步运行代码来测试和调试程序的工具,允许开发者检查变量和控制流。
  • 构建自动化工具:用于自动化编译代码、运行测试和打包应用程序等任务的功能。
  • 版本控制集成:用于管理代码版本的工具,通常集成 Git 等系统。

流行的 IDE 示例包括我们今天要重点介绍的 Visual Studio Code,以及 PyCharm 和 Eclipse。它们通过将所有开发工具集成到一个应用程序中,来简化编码流程。

什么是 Visual Studio Code?🚀

了解了 IDE 的基本概念后,本节我们聚焦于本次教程的主角:Visual Studio Code。

Visual Studio Code,常被称为 VS Code,是由微软开发的免费开源代码编辑器。它因其轻量级和功能强大而深受开发者欢迎,支持广泛的编程语言和开发任务。

以下是 VS Code 的主要特性:

  • 智能代码编辑器:提供智能感知、自动补全、语法高亮和代码导航。
  • 丰富的扩展生态系统:可通过安装扩展来支持额外的语言、框架、工具和功能。
  • 集成终端:内置终端,可直接在项目文件夹中运行命令,无需额外打开系统终端。
  • 内置版本控制:对 Git 提供原生支持,便于管理源代码和团队协作。
  • 强大的调试器:支持设置断点、检查变量值,方便调试程序。
  • 跨平台:可在 Windows、Mac、Linux 上运行,甚至支持浏览器版本。
  • 高度可定制:可以自定义外观、快捷键和行为,以适应个人工作流程。

总体而言,VS Code 因其快速、灵活和易用性,在初学者和经验丰富的开发者中都广受欢迎。

如何下载与安装 Visual Studio Code?📥

现在我们已经了解了 VS Code 的优势,接下来看看如何获取并安装它。

以下是下载和安装 VS Code 的步骤:

  1. 打开浏览器,搜索 “Visual Studio Code”。
  2. 访问官方网站 code.visualstudio.com。
  3. 点击页面上的 “Download for Windows” 按钮(根据你的操作系统选择)。
  4. 运行下载的安装程序,接受许可协议,并按照提示完成安装。
  5. 安装完成后,可以在桌面找到快捷方式,或在系统搜索栏中搜索 “Visual Studio Code” 来启动它。

重要提示:如果你计划使用 Python,需要先确保 Python 已安装在你的电脑上。可以访问 python.org 下载并安装 Python。安装后,可以在终端输入 python --version 来验证安装是否成功。

在 VS Code 中使用 Python 🐍

成功安装 VS Code 后,本节我们将学习如何在其中创建和运行一个简单的 Python 项目。

以下是创建和运行 Python 项目的步骤:

  1. 安装必要扩展:点击左侧边栏的扩展图标,搜索并安装 “Python” 扩展(由 Microsoft 发布)和 “Code Runner” 扩展。Code Runner 可以让你通过点击按钮轻松运行多种编程语言的代码。
  2. 创建项目文件夹:点击 “文件” -> “打开文件夹”,创建一个新文件夹(例如 PythonProjects)并打开它。
  3. 创建 Python 文件:在项目文件夹内,点击 “新建文件” 图标,将文件命名为 test.py。
  4. 编写代码:在文件中输入简单的 Python 代码,例如:
    print("Hello World")
    
  5. 运行代码:保存文件后,你可以通过以下两种方式运行:
    • 使用 Code Runner:点击文件右上角的三角形 “Run Code” 按钮。
    • 使用 集成终端:打开底部终端,输入命令 python test.py 或 py test.py。

两种方法都会在输出区域显示 “Hello World”。

Git 集成:初始化仓库与提交推送 🔄

现代软件开发离不开版本控制。上一节我们运行了代码,本节我们将学习如何在 VS Code 中集成 Git 来管理项目。

以下是初始化 Git 仓库并进行首次提交的步骤:

  1. 安装 Git:首先,确保你的电脑上安装了 Git。访问 git-scm.com/downloads 下载并安装。安装后,在终端输入 git --version 验证。
  2. 在 VS Code 中登录 GitHub:建议在 VS Code 左下角点击账户图标,登录你的 GitHub 账户以同步设置。
  3. 初始化仓库:在 VS Code 中打开你的项目文件夹。点击左侧源代码管理图标,然后点击 “初始化仓库” 按钮。
  4. 创建文件并暂存更改:在项目内创建或修改文件(如 README.md)。在源代码管理面板,文件会出现在 “更改” 区域。点击文件旁边的 “+” 号来暂存更改。
  5. 提交更改:在上方的输入框输入提交信息(例如 “Initial commit”),然后按 Ctrl+Enter 或点击勾号图标进行提交。
  6. 发布(推送)到远程仓库:提交后,点击 “...更多操作” 菜单,选择 “推送”,或直接点击 “发布分支” 将本地仓库推送到 GitHub。

Git 进阶:克隆仓库与创建分支 🌿

在团队协作中,我们通常不会直接在主分支上修改。本节学习如何克隆他人的仓库并在独立分支上工作。

以下是克隆仓库和创建新分支的流程:

  1. 克隆仓库:在 VS Code 欢迎页面点击 “克隆 Git 仓库”,或按 Ctrl+Shift+P 打开命令面板,输入 “Git: Clone”。粘贴你想克隆的 GitHub 仓库 URL,并选择本地存储位置。
  2. 创建新分支:打开克隆的项目后,点击左下角显示当前分支(如 main)的地方,选择 “创建新分支”,输入分支名称(如 feature-branch)。
  3. 在新分支上工作:现在你处于新分支中,可以自由修改代码而不影响主分支。修改后,按照上一节的方法暂存并提交更改。
  4. 推送分支与创建拉取请求:提交后,需要将新分支推送到远程仓库。你可以安装 “GitHub Pull Requests” 扩展,以便在 VS Code 内直接创建拉取请求,请求将你的分支合并到主分支。

代码协作:使用 Live Share 扩展 👥

团队协作时,实时共享代码和调试环境能极大提升效率。本节介绍 VS Code 的 Live Share 扩展。

Live Share 允许你与他人实时共享整个工作区(包括代码、终端和调试会话)。

以下是使用 Live Share 的步骤:

  1. 安装扩展:在扩展市场中搜索并安装 “Live Share”。
  2. 启动协作会话:打开你的项目,点击左下角的 “Live Share” 按钮,选择 “开始协作会话”。VS Code 会自动生成一个邀请链接。
  3. 共享链接:将链接发送给你的队友。
  4. 实时协作:队友通过链接加入后,双方可以同时查看和编辑代码,光标位置会实时显示。主持人还可以共享集成终端,允许队友共同运行命令和调试程序。

代码历史洞察:使用 GitLens 扩展 🔍

在大型或团队项目中,追踪代码的修改历史和责任人至关重要。本节介绍强大的 GitLens 扩展。

GitLens 增强了 VS Code 内置的 Git 功能,提供了详细的代码历史洞察。

以下是 GitLens 的核心功能:

  • 行级注释:将鼠标悬停在代码行上,可以看到是谁、在何时、因何提交修改了这行代码。
  • 文件标注:点击编辑器右上角的图标,可以开启“文件追溯”模式,在侧边栏查看每一行代码的完整修改历史。
  • 提交图谱:点击活动栏的 GitLens 视图或使用“可视化提交图谱”功能,可以图形化查看项目的整个提交历史、分支和合并情况。
  • 强大的搜索:可以按提交信息、作者、文件等条件搜索特定的提交记录。

其他实用扩展与技巧 ✨

除了上述核心扩展,VS Code 生态中还有许多工具能提升你的开发体验。

以下是一些推荐的扩展和技巧:

  • GitHub Copilot:AI 编程助手,能根据上下文提供代码建议和自动补全,加速开发。
  • Turbo Console Log:快速插入 console.log 语句进行调试的快捷工具。
  • Code Spell Checker:代码拼写检查器,帮助保持代码清洁。
  • 实用快捷键:
    • Ctrl+P:快速搜索并打开文件。
    • Ctrl+Shift+P:打开命令面板,执行任何命令。
    • Ctrl+/:注释/取消注释当前行或选中块。
    • Ctrl+B:切换侧边栏的显示与隐藏。

为什么选择 VS Code?🎯

市面上有许多优秀的 IDE,如专为 Python 设计的 PyCharm。那么为什么我们推荐 VS Code 呢?

以下是选择 VS Code 的主要理由:

  1. 轻量且快速:相比 PyCharm 等大型 IDE,VS Code 启动更快,资源占用更少。
  2. 跨语言支持:通过扩展,它可以完美支持数十种编程语言,而 PyCharm 社区版主要专注于 Python。
  3. 深度定制化:拥有海量扩展和主题,可以打造完全个性化的开发环境。
  4. 卓越的 Git 集成:内置的 Git 支持非常直观和强大。
  5. 活跃的社区:拥有庞大的用户和开发者社区,问题容易得到解决,软件更新频繁。
  6. 完全免费开源:VS Code 完全免费,而 PyCharm 的高级功能需要付费。

对于需要接触多种语言或追求灵活、高效工具的软件工程师来说,VS Code 是一个极佳的选择。

总结 📚

本节课中我们一起学习了集成开发环境的核心概念,并深入实践了 Visual Studio Code 的使用。我们从安装配置开始,逐步掌握了如何用它进行 Python 开发、利用 Git 进行版本控制、通过分支和拉取请求进行协作、使用 Live Share 实时协同编码,以及借助 GitLens 洞察代码历史。最后,我们还探讨了 VS Code 相比其他 IDE 的优势。希望本教程能帮助你顺利开启高效的编程之旅。

002:API 🚀

在本节课中,我们将学习应用程序编程接口(API)的核心概念。我们将从理解HTTP协议开始,这是现代API通信的基础。接着,我们会探讨API是什么、它如何工作,以及RESTful API的设计原则。最后,我们将通过一个FastAPI的实践工作坊,学习如何快速构建自己的API。


理解HTTP协议 🌐

在深入了解API之前,理解HTTP协议至关重要,因为它是现代API通信的基石。

HTTP代表超文本传输协议,它是浏览器与互联网之间通信的协议。这种通信遵循一套规则,规定了数据如何在彼此之间传输。它专为网页设计,采用请求-响应、客户端-服务器的模式。

当你在浏览器地址栏输入一个网址时,实际上是在向服务器发送一个HTTP请求。这个过程可以分为三个部分:

  1. 请求行:包含请求方法(如GET)、URL和协议版本。
  2. 请求头:包含发送给服务器的额外信息。
  3. 请求体:通常为空,因为服务器将用它来返回信息。

服务器收到请求后,会返回一个响应。响应也由三部分组成:

  1. 状态行:包含协议版本、状态码和状态文本。状态码是关键:
    • 2xx:成功。
    • 3xx:重定向。
    • 4xx:客户端错误。
    • 5xx:服务器错误。
  2. 响应头:包含关于返回文件的信息。
  3. 响应体:服务器返回的实际资源内容。

HTTP的一个重要特性是无状态。这意味着一旦客户端发送请求,服务器返回响应后,两者之间的连接就会断开,不再维持。


什么是API? 🤝

上一节我们介绍了HTTP协议,它是数据传输的规则。本节中,我们来看看API如何利用这些规则来连接不同的软件。

API(应用程序编程接口)本质上是一种服务,它允许两个或多个程序通过发送和接收数据进行通信。它充当服务器和客户端之间的中间人,确保数据在它们之间顺畅、高效地交换。

一个简单的类比是餐厅:

  • 客户(客户端) 通过点餐发送请求。
  • 服务员(API) 接收请求并传达给厨师(服务器)。
  • 厨师(服务器) 完成请求后,将食物作为响应通过服务员送回。
  • 服务员(API) 负责将食物送回给客户。

API充满了抽象。这意味着程序员不需要理解幕后发生的复杂细节,他们只需要知道如何发送正确的输入并处理输出即可。

另一个有用的类比是驾驶汽车:大多数司机不需要知道引擎或变速箱如何工作,他们只知道转动方向盘可以控制方向,踩油门能让车前进,踩刹车能让车停止。同样,使用API时,你可以专注于你需要做什么,而不必担心背后发生的复杂过程。


API的优势与应用 💡

API在构建轻量级应用方面具有巨大优势。

通过允许应用程序连接到外部服务,API可以外包诸如数据存储和繁重计算等任务。例如:

  • 一个使用Google地图的应用不需要在内部存储所有地图数据。它可以在需要时调用Google Maps API来访问数据,从而使应用更小、更快、更高效。
  • YouTube的情况类似:海量的视频存储在YouTube服务器上。你的设备只是通过YouTube API接收并点击视频,而不是在本地存储或处理所有这些数据。

有许多有用的第三方API,例如用于在线商店的Shopify、用于支付的PayPal、用于安全的Cloudflare以及用于用户身份验证的Google登录。这些API通过集成现成的、可信赖的服务,为开发者节省了大量时间。

这类似于专业厨师不需要自己种植作物或饲养动物来烹饪美食。相反,厨师依赖专门提供高质量食材的供应商。这允许厨师专注于烹饪本身。同样,开发者使用第三方服务来简化开发流程,依赖支付处理、身份验证、电子商务和网络安全等领域的专家,从而可以专注于其核心应用功能。


使用命令行工具测试API 🛠️

了解了API的概念后,我们来看看如何快速测试一个API。使用命令行工具是一种高效的方法。

curl 是一个在macOS和Windows上默认预装的命令行工具,无需额外下载。它的用处在于,如果你想快速测试一个API,只需写一行命令就能看到输出。

以下是一个使用Pokemon API的例子:

curl https://pokeapi.co/api/v2/pokemon/pikachu

然而,直接在命令行中输出的JSON文件可能难以阅读。你可以通过JSON格式化工具来改善可读性。

jq 是一个强大的命令行JSON处理器。安装后,你可以通过管道将curl的输出传递给jq,从而获得格式清晰、易于阅读的JSON。

curl -s https://pokeapi.co/api/v2/pokemon/pikachu | jq

这将会输出结构清晰、带缩进的Pokemon数据,比原始输出易读得多。


在Python中使用API 🐍

除了命令行,我们更常在编程语言中调用API。Python因其简洁性成为热门选择。

要在Python中使用API,你需要安装 requests 库。它并非Python原生自带,安装非常简单:

pip install requests

安装后,你可以开始编写代码。首先导入requests库,然后定义一个基础URL。以Pokemon API为例,其结构通常是基础URL后跟不同的端点。

一个GET请求的示例代码如下:

import requests

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_55.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_57.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_58.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_60.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_62.png)

BASE_URL = “https://pokeapi.co/api/v2"

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_64.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_66.png)

# 获取所有Pokemon
response = requests.get(f“{BASE_URL}/pokemon”)
print(response.status_code) # 输出状态码,如200
data = response.json() # 将JSON响应转换为Python字典
print(data)

# 获取特定Pokemon(例如皮卡丘,ID为25)
response = requests.get(f“{BASE_URL}/pokemon/25”)
pikachu_data = response.json()
print(f“Name: {pikachu_data[‘name’]}, ID: {pikachu_data[‘id’]}”)

这段代码演示了如何调用API、检查状态码以及处理返回的JSON数据。

API的结构化意味着不同的端点(如/pokemon, /berry, /move)拥有不同的属性。一些属性(如id和name)是通用的,而另一些属性只对特定端点有意义。


RESTful API 设计原则 📐

在构建自己的API时,遵循一套良好的设计原则至关重要。RESTful API就是一套被广泛采用的指导原则。

REST(表述性状态转移)本身是一套开发者创建API时遵循的指南或规则,它不是一个标准或技术,而是一种风格。如果你的API遵循这套规则,它就可以被称为RESTful API。

以下是REST的主要规则:

  1. 客户端-服务器架构:客户端和服务器独立运作。
  2. 无状态性:与HTTP类似,每次请求完成后连接不维持,每个请求必须包含所有必要信息。
  3. 可缓存性:响应必须明确表明自己是否可缓存,以提高性能。
  4. 统一接口:每个请求和响应都遵循相同的规则和模式,使得不同系统更容易通信。
  5. 使用HTTP方法:
    • GET:检索数据。
    • POST:创建数据。
    • PUT:更新/替换数据。
    • DELETE:删除数据。

使用RESTful API的好处:

  • 简单标准化:无需担心每次如何格式化数据请求。
  • 可扩展且无状态:随着复杂度增长,易于修改,无需跟踪客户端-服务器间的状态数据。
  • 高性能:支持缓存,即使服务变复杂,性能也能保持高位。
  • 语言无关且易于集成:可与不同编程语言一起使用,允许不同软件系统轻松协作。

FastAPI 工作坊 ⚡

理论铺垫完成后,现在让我们进入实践环节,使用FastAPI框架来快速构建我们自己的API。

FastAPI是一个现代、高性能的Python Web框架,用于构建API。它简单直接,性能优异,并能自动生成交互式API文档。

1. 创建第一个端点

首先,导入FastAPI并创建一个应用实例。

from fastapi import FastAPI

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_74.png)

app = FastAPI()

然后,使用装饰器定义一个处理根路径/的GET请求端点。

@app.get(“/”)
async def root():
    return {“message”: “Hello World”}

使用async定义异步函数可以让服务器在处理一个等待IO的请求时,同时处理其他请求,提高效率。

运行应用后,访问 http://localhost:8000 会看到返回的JSON消息。更棒的是,访问 http://localhost:8000/docs 会自动生成完整的交互式API文档,你可以直接在浏览器中测试接口。

2. 路径参数与查询参数

API通常需要接收参数。

  • 路径参数:成为URL路径的一部分,例如 /items/5。
    @app.get(“/items/{item_id}”)
    async def read_item(item_id: int):
        return {“item_id”: item_id}
    
    FastAPI会自动验证item_id是否为整数。
  • 查询参数:出现在URL的?之后,例如 /items2/?skip=0&limit=10。未在路径中声明的函数参数会被自动识别为查询参数。
    @app.get(“/items2/”)
    async def read_user_item(item_id: int, username: str, q: str | None = None, short: bool = False):
        # 处理逻辑...
        return {“item_id”: item_id, “username”: username}
    
    有默认值的参数(如q和short)是可选参数。

3. 请求体与Pydantic模型

当需要客户端发送数据(如创建新项)时,使用请求体。不要在GET请求中使用请求体。

FastAPI使用Pydantic库来定义请求体的数据结构,并自动进行数据验证和转换。

from pydantic import BaseModel
from typing import Optional

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_80.png)

items_db = []

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_82.png)

@app.post(“/items/”, status_code=201)
async def create_item(item: Item):
    # 检查是否已存在同名项
    if item.name in [i.name for i in items_db]:
        from fastapi import HTTPException
        raise HTTPException(status_code=409, detail=“Item already exists”)
    items_db.append(item)
    return item

在上面的代码中,我们定义了一个Item模型,并在POST端点中使用它。FastAPI会自动从请求体中提取数据,验证其类型(例如,price必须是数字),并将其转换为Item实例。如果验证失败或业务逻辑不允许(如重复创建),我们可以抛出HTTPException。

4. 后台任务与异步操作

对于需要长时间处理但客户端无需立即等待结果的任务,可以使用后台任务。

from fastapi import BackgroundTasks
import time

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_86.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_88.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_90.png)

def delayed_print(message: str):
    time.sleep(10) # 模拟耗时操作
    print(f“Delayed message: {message}”)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_92.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_94.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_96.png)

@app.get(“/background/”)
async def background_task(background_tasks: BackgroundTasks, message: str = “Hello”):
    background_tasks.add_task(delayed_print, message)
    return {“message”: “Task started in background”}

这样,服务器会立即响应客户端,然后在后台执行耗时任务,而不会阻塞其他请求。

如果客户端需要等待操作完成,则应使用async/await模式,但确保等待的操作本身是异步的(例如,使用asyncio.sleep而不是time.sleep)。

5. 测试API

为API编写测试至关重要。FastAPI提供了TestClient,使得测试变得简单。

from fastapi.testclient import TestClient
from .main import app # 导入你的FastAPI应用

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_108.png)

client = TestClient(app)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/bb2ea41e84c7519b60956e42cfce338b_110.png)

def test_read_root():
    response = client.get(“/”)
    assert response.status_code == 200
    assert response.json() == {“message”: “Hello World”}

def test_create_item():
    item_data = {“name”: “Test Item”, “price”: 100.0}
    response = client.post(“/items/”, json=item_data)
    assert response.status_code == 201
    assert response.json()[“name”] == “Test Item”

使用 pytest 命令即可运行所有测试。


总结 🎯

本节课中,我们一起学习了API的完整知识链。我们从HTTP协议这个网络通信基础开始,理解了请求与响应的过程。接着,我们探讨了API作为软件“中间人”的角色、其抽象价值以及广泛的应用场景。通过命令行和Python的实践,我们掌握了调用第三方API的方法。然后,我们深入学习了设计良好、易于使用的RESTful API应遵循的原则。最后,我们通过FastAPI工作坊,亲手体验了如何快速构建一个具备路径参数、查询参数、请求体验证、错误处理、后台任务等功能的现代化API,并了解了为其编写测试的方法。掌握这些知识,你便具备了连接不同软件服务和构建强大后端应用的基础能力。

003:技术文档编写 📝

在本节课中,我们将学习软件工程中至关重要的一个环节:技术文档编写。我们将探讨什么是文档、为什么它很重要,并详细介绍几种实用的文档工具,包括README文件、提交信息、GitHub Pages、Wiki以及自动文档生成工具Sphinx。

什么是文档?

文档就像是程序的“使用手册”。它包含了代码的详细信息、运行程序的说明,以及如何将各个部分组合起来的细节。一份好的文档应该包含阅读者理解和使用你的代码所需的一切信息。

文档之所以重要,是因为它是实现良好工程实践和设计原则的一种方式。好的文档能提供清晰度,促进与其他程序员的协作,使你的代码易于维护和阅读,有助于调试,并能提升你个人及项目在雇主和合作伙伴面前的可见度。

README文件 📄

上一节我们介绍了文档的基本概念,本节中我们来看看最基础也最常见的文档形式:README文件。

README文件是一个项目的“门面”,它向用户和协作者传达项目的重要信息,对于开源社区和协作项目尤为关键。一个完整的README文件可以包含以下几个部分:

  • 项目标题:清晰的项目名称。
  • 项目描述:简要说明项目是做什么的。
  • 安装与使用说明:如何安装和运行你的项目。
  • 贡献指南:其他人如何为项目做贡献。
  • 许可证:项目采用的开源许可证。
  • 联系信息:如何联系项目维护者。
  • 可选部分:如常见问题解答(FAQ)。

README文件通常使用Markdown语言编写。Markdown语法直观、易于理解、可移植且高效。在GitHub上,你还可以使用GitHub风格的Markdown(GFM)或内嵌HTML来增强格式。

为了编写方便,可以使用一些工具。以下是两个实用的工具:

  • Visual Studio Code扩展:如“Markdown Preview Enhanced”,可以实时预览Markdown渲染效果。
  • 在线编辑器:如“readme.so”网站,提供模块化编辑和实时预览。

保持README文件的更新至关重要,因为它是项目的“第一印象”。一个简洁、准确的README能节省协作者的时间,鼓励协作,并展现出项目的组织性和专业性。

以下是一个README文件的示例模板,包含了标题、描述、徽章、图片和清晰的章节结构。

提交信息规范 💬

了解了项目级的README文档后,我们再来看看代码级别的文档:提交信息。

提交信息是对你在项目中具体更改的简短描述。它们记录了项目的演变历史。使用规范的提交信息非常重要,原因如下:

  • 追踪变更:可以清晰地跟踪项目随时间的变化。
  • 便于协作与调试:能快速定位错误引入的位置。
  • 加速代码审查:让审查者一目了然地了解更改内容。

以下是一些提交信息的最佳实践:

  • 使用大写字母开头。
  • 不使用句末标点(如句号)。
  • 使用祈使语气(例如,“Add feature”而非“Added feature”)。
  • 可选的提交类型前缀:如 feat:(新功能)、fix:(修复bug)、refactor:(重构)、docs:(文档更新)、style:(代码格式调整)。
  • 长度限制:标题行最好在50个字符以内。
  • 内容明确:清晰说明本次提交做了什么(例如,“Update README formatting”)。

一个糟糕的提交信息例子是“F”或仅包含日期,因为它们没有提供任何有效信息。

你可以通过多种方式提交代码:

  • 在VS Code中:在源代码管理侧边栏中输入提交信息并点击提交按钮。
  • 在终端中:使用命令 git commit -m “Your commit message here”。如果需要添加详细描述,可以使用 git commit -m “Title” -m “Description”。
  • 在GitHub网页端:通过“Add files”按钮上传文件,并在底部填写提交信息。

GitHub Pages 与静态网站 🖥️

在掌握了代码层面的文档规范后,让我们把视野扩大到项目展示层面,看看如何利用GitHub Pages创建项目网站。

GitHub Pages是一项免费的静态网站托管服务。每个GitHub账户都可以创建一个用户站点(作为个人作品集),并且每个仓库都可以拥有一个项目站点。这对于展示项目、创建在线文档或作品集非常有用。

设置GitHub Pages非常简单:

  1. 创建仓库:对于用户站点,仓库名必须为 {用户名}.github.io。
  2. 克隆仓库到本地。
  3. 创建入口文件:例如 index.html 或 README.md。
  4. 推送代码:通过 git add, git commit, git push 将文件推送到仓库。
  5. 启用Pages:在仓库的“Settings” -> “Pages”中,选择源分支(如 main 或 master)和文件夹(如 /root 或 /docs)。

GitHub Pages由Jekyll驱动,Jekyll是一个静态网站生成器。你可以选择使用Jekyll主题(需要本地安装Jekyll环境),也可以直接上传自定义的HTML、CSS、JavaScript文件来构建网站。网站内容只有在您推送更新后才会重新部署。

创建GitHub Pages网站的好处包括:免费托管、提升专业形象、便于向雇主展示项目。网站应做到内容详细、及时更新、结构清晰、易于导航。

项目Wiki 📚

除了独立的项目网站,GitHub还提供了一个内置的长文档管理工具:Wiki。

Wiki是每个GitHub仓库的一个独立板块,用于托管详细的、结构化的文档。它的优点是可以在浏览器中直接编辑,无需克隆仓库到本地(尽管本地编辑配合预览工具效率更高)。

一个典型的Wiki包含主页、页面列表,并可以自定义侧边栏和页脚。设置Wiki极其简单:只需在仓库的“Settings”中勾选启用Wiki选项,然后就可以在仓库顶部的“Wiki”标签页中开始创建和编辑页面了。

有效的Wiki应遵循与GitHub Pages类似的原则:内容详实、及时更新、结构清晰。应使用Markdown格式化来区分章节,并且要避免简单重复README中的信息,而应提供更深层次的说明。

自动文档生成(Sphinx)🤖

对于大型项目,尤其是包含大量模块、类、函数和参数的Python项目,手动维护文档非常繁琐。这时,自动文档生成工具就派上用场了,Sphinx是其中非常流行的一个。

Sphinx可以将用reStructuredText或Markdown编写的纯文本源文件,自动生成美观的HTML网页、PDF等格式的文档。

安装和设置Sphinx的基本步骤如下:

  1. 安装Python:确保已安装Python 3.x,并正确配置了pip。
  2. 安装Sphinx:通过pip安装:pip install sphinx。
  3. 创建文档项目:在命令行中,进入项目目录,运行 sphinx-quickstart 并按提示配置项目名称、作者等信息。
  4. 编写文档源文件:在 source 目录下,用 .rst 文件编写文档内容。index.rst 是入口文件,可以定义目录树。
  5. 配置conf.py:在此文件中设置项目信息、扩展(如 sphinx.ext.autodoc 用于自动从代码提取文档)和主题。
  6. 构建文档:运行 sphinx-build -b html sourcedir builddir 或 make html 来生成HTML文档。
  7. 托管文档:将生成的 build/html 目录下的内容推送到GitHub Pages,或使用专门的文档托管服务(如Read the Docs)。

Sphinx生成的文档专业、结构化,并且能自动生成API参考,非常适合大型开源项目。

实践练习与总结 🎯

现在,让我们运用所学知识来分析一个反例。请看这个README文件,它存在哪些问题?如何改进?

  • 项目标题和描述毫无意义。
  • 图片链接损坏,无法显示。
  • 虽然结构章节存在,但内容空洞或错误。

通过这个练习,我们可以巩固对优秀README标准的理解:清晰、准确、有用、美观。

本节课总结
在本节课中,我们一起学习了软件工程中技术文档编写的核心内容。我们从宏观的文档概念出发,深入探讨了:

  1. README文件:项目的门面介绍。
  2. 提交信息:代码变更的微观记录。
  3. GitHub Pages:项目的静态展示网站。
  4. 项目Wiki:仓库内置的长篇文档空间。
  5. 自动文档生成(Sphinx):适用于大型项目的自动化文档工具。

良好的文档贯穿软件开发的各个层面,从细微的提交信息到面向用户的完整网站,它对于促进协作、保障项目可维护性以及提升个人专业度都至关重要。请务必在你们的项目中实践这些工具和方法。

004:软件测试 🧪

在本节课中,我们将要学习软件测试的核心概念、不同测试类型以及如何使用 PyTest 等工具进行有效的测试。我们将从单元测试开始,逐步深入到集成测试、功能测试,并介绍测试替身、测试驱动开发和行为驱动开发等高级主题。


概述 📋

软件测试是确保代码质量、防止错误和优化性能的关键环节。通过编写测试,我们可以验证代码的各个部分是否按预期工作,并在代码修改后快速发现问题。本节课将系统性地介绍测试的层次、工具和最佳实践。


单元测试

上一节我们概述了测试的重要性,本节中我们来看看最基础的测试类型:单元测试。单元测试专注于验证代码中最小可测试单元(通常是函数或方法)的正确性。其核心思想是将代码分解为独立的模块进行测试。

单元测试的主要目的是确保每个独立的代码单元都能按预期运行。例如,对于一个执行加法运算的函数,单元测试会验证其输入特定参数时是否能返回正确的结果。

以下是使用 PyTest 编写的一个简单单元测试示例:

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

在 PyTest 中,我们使用 assert 语句来声明预期结果。如果断言失败,测试将不通过。


集成测试

在确保各个单元正常工作后,我们需要验证它们组合在一起时是否能协同工作。这就是集成测试的目的。它关注不同模块或组件之间的接口和交互。

例如,一个计算器应用可能包含独立的运算函数、历史记录功能和结果格式化功能。集成测试会检查这些功能组合使用时,整个计算流程是否正确。

以下是一个集成测试的示例,它测试了计算器多个功能的交互:

# 假设已有 Calculator, History, Formatter 类
def test_calculator_integration():
    calc = Calculator()
    history = History()
    formatter = Formatter()

    result = calc.add(10, 5)
    history.add_entry('10 + 5', result)
    formatted_output = formatter.format(result)

    assert result == 15
    assert len(history.get_entries()) == 1
    assert formatted_output == 'Result: 15'

集成测试对于验证 API 连接、数据库交互等场景尤为有用。


功能测试

单元测试和集成测试关注代码的内部结构,而功能测试(或端到端测试)则从用户角度出发,验证整个软件系统是否满足业务需求。它是最高层次的测试。

用一个乐高积木的比喻来说:

  • 单元测试:检查单个积木块的质量。
  • 集成测试:检查积木块之间能否正确拼接。
  • 功能测试:检查拼好的乐高模型(如一辆车)能否正常滚动。

在 Web 开发中,功能测试可能涉及模拟用户点击按钮、填写表单并验证页面响应。

以下是使用 PyTest 和 TestClient 对 FastAPI 接口进行功能测试的示例:

from fastapi.testclient import TestClient
from main import app  # 你的 FastAPI 应用

client = TestClient(app)

def test_add_endpoint():
    response = client.get("/add?a=10&b=5")
    assert response.status_code == 200
    assert response.json() == {"result": 15}

这个测试发送一个 HTTP 请求到 /add 接口,并断言返回的状态码和结果数据符合预期。


测试替身

在进行单元测试时,我们经常需要隔离被测代码,避免受到外部系统(如数据库、网络服务)的影响。测试替身就是用来模拟这些外部依赖的对象。

以下是几种常见的测试替身:

  • Mock:模拟对象的行为,用于验证交互(如某个方法是否被调用、调用参数是什么)。它不关心功能实现。
    from unittest.mock import Mock
    mock_email_service = Mock()
    mock_email_service.send_email.return_value = True
    # 测试代码会使用 mock_email_service 而不是真实的邮件服务
    
  • Stub:提供预定义的固定响应,用于模拟特定输入下的输出。
  • Fake:一个轻量级的、功能简化但可实际工作的实现,通常用于替代缓慢或复杂的真实依赖(如内存数据库)。
  • Faker:一个生成逼真假数据的库(如随机姓名、地址),用于在测试中快速构建测试数据。

Mock 和 Fake 的主要区别:Mock 主要用于验证行为(是否调用了某个函数),而 Fake 是一个实际可工作的简化实现,用于模拟功能。


测试驱动开发与行为驱动开发

测试驱动开发

测试驱动开发是一种先写测试,后写实现代码的开发流程。它遵循“红-绿-重构”的循环:

  1. 红:编写一个会失败的测试(定义需求)。
  2. 绿:编写最少量的代码使测试通过。
  3. 重构:优化代码结构,同时保持测试通过。

这种方法有助于提升代码设计质量和可测试性。

行为驱动开发

行为驱动开发是 TDD 的延伸,它使用一种近乎自然语言的领域特定语言(如 Gherkin)来描述软件行为,促进开发者、测试者和业务人员之间的沟通。

一个 Gherkin 特性文件示例(calculator.feature):

Feature: Calculator
  Scenario: Adding two numbers
    Given I have a calculator
    When I add 10 and 5 together
    Then the result should be 15

然后,可以使用 pytest-bdd 等工具将这些描述解析并转化为可执行的测试代码。


测试代码编写建议

编写清晰、可维护的测试代码同样重要。以下是一些核心建议:

  • 保持测试独立:每个测试应设置自己的环境,不依赖其他测试的状态。
  • 命名清晰:测试函数名应明确描述其测试的内容(如 test_add_positive_numbers, test_withdraw_insufficient_funds)。
  • 遵循 AAA 模式:组织测试逻辑为 准备(Arrange)、执行(Act)、断言(Assert) 三个阶段,使结构清晰。
  • 避免过度断言:只断言与测试目标直接相关的内容,无关的断言会造成干扰。
  • 利用参数化:当需要用多组数据测试同一逻辑时,使用参数化测试避免代码重复。

PyTest 参数化测试示例:

import pytest

@pytest.mark.parametrize("a, b, expected", [(1, 2, 3), (5, -5, 0), (100, 200, 300)])
def test_add_multiple_cases(a, b, expected):
    assert add(a, b) == expected

总结 🎯

本节课中我们一起学习了软件测试的多层次方法:

  1. 单元测试验证独立代码单元的正确性。
  2. 集成测试确保多个模块能协同工作。
  3. 功能测试从用户视角验证系统整体行为。
  4. 测试替身帮助我们模拟外部依赖,实现隔离测试。
  5. 测试驱动开发通过先写测试来驱动设计。
  6. 行为驱动开发用通用语言描述行为,促进团队协作。

掌握这些测试理念和工具,能够显著提升代码的可靠性、可维护性和开发效率。实践是学习测试的最佳方式,建议从为一个小功能编写单元测试开始。

005:Git版本控制 🗂️

在本节课中,我们将要学习Git版本控制的核心概念、基本操作以及高级技巧。Git是现代软件开发中不可或缺的协作工具,它能帮助我们高效地管理代码变更、协同工作并维护项目历史。


Git与GitHub简介

Git是一个分布式版本控制系统,用于跟踪文件随时间的变化。它由Linus Torvalds于2005年创建,最初用于管理Linux内核的开发。Git在本地机器上运行。

GitHub是一个基于Web的平台,用于托管Git仓库。它由Tom Preston-Warner、Chris Wetra和PJ Hitt于2008年创立,旨在使Git更易于访问和协作。Git是底层系统,而GitHub是增强其协作功能的在线服务。

基本检查:在终端或命令提示符中输入以下命令,可以检查是否已安装Git。

git --version


GitHub基础

在深入学习Git命令之前,我们先了解一些GitHub的基本概念。

创建仓库:可以通过GitHub网站、GitHub桌面应用或Git命令行来创建新的代码仓库。

克隆仓库:可以将远程仓库复制到本地计算机。

Fork(复刻):复制一个仓库到自己的账户下,形成独立的副本。这是为开源项目做贡献的常见方式,你可以在自己的副本上修改,然后通过拉取请求(Pull Request)提议合并到原项目。

权限:仓库可以设置不同的访问权限,例如:

  • 读取(Read):只能查看和克隆代码。
  • 写入(Write):可以推送更改到仓库。
  • 管理员(Admin):拥有仓库的完全控制权。

GitHub桌面版

GitHub桌面版是一个图形用户界面(GUI)应用程序,它简化了Git的使用。

核心功能:无需输入命令即可完成提交(Commit)、推送(Push)、拉取(Pull)和分支(Branch)等核心操作。

局限性:GitHub桌面版并非Git的完全替代品,它缺少一些高级功能,例如我们后面会讲到的拣选提交(Cherry-pick)。

.gitignore文件的重要性:在项目开始时创建.gitignore文件至关重要。它可以指定哪些文件或目录不应被Git跟踪(例如编译产生的二进制文件、依赖库、本地配置文件等),避免将无关的大文件提交到仓库。


Git基础概念与命令

上一节我们介绍了GitHub平台,本节中我们来看看Git本身的核心概念和基本工作流。

仓库(Repository):包含项目所有文件及其完整变更历史的目录。它是版本控制的中心。

工作目录(Working Directory):本地机器上项目文件的当前状态,是你直接修改文件的地方。

暂存区(Staging Area):一个临时区域,用于准备下一次提交的更改。它允许你审查和控制哪些修改将被包含在提交中。

以下是必须掌握的基本Git命令:

  • git init:初始化一个新的Git仓库。
  • git clone <url>:克隆一个已存在的远程仓库到本地。
  • git add <file>:将文件的更改添加到暂存区。
  • git commit -m “描述信息”:将暂存区的更改保存为一个新的提交,并附上描述性信息。
  • git push:将本地提交上传到远程仓库。
  • git pull:从远程仓库获取并合并更改到当前分支。
  • git status:查看工作目录和暂存区的状态。
  • git log:显示提交历史。

版本控制的重要性与最佳实践

理解了基本操作后,我们来看看为什么版本控制如此重要,以及如何更好地使用它。

版本控制主要带来三大好处:

  1. 增强协作:允许多名开发者同时工作,无需担心覆盖他人的修改。所有变更都被记录(谁、何时、为何修改),并支持代码审查。
  2. 维护代码完整性:完整的版本历史允许你回退到任何之前的版本。它还能帮助解决合并冲突。
  3. 简化工作流:通过分支策略高效管理不同版本和发布,并支持持续集成/持续部署(CI/CD)。

为了充分发挥这些优势,请遵循以下提交代码的最佳实践:

1. 编写清晰的提交信息
避免使用模糊的信息如“修复bug”或“更新”。应清晰说明做了什么以及为什么这么做。

不佳示例:add validation
良好示例:add validation for user email input to prevent SQL injection

团队应统一使用动词开头,如 fix:, feat:, docs:。例如:feat: update navbar styling for mobile responsiveness

2. 进行频繁、小粒度的提交
不要一次性编写数千行代码后再提交。应完成一个独立的功能或修复后就提交。这样做的好处是:

  • 便于代码审查。
  • 若需要回退,可以精准地回退单个有问题的提交,而不影响其他工作。
  • 更好地跟踪进度和定位问题。

3. 保持分支与主分支同步
在开始工作前和工作中,频繁地从主分支(如 main)拉取(git pull)最新更改。这能最小化合并冲突的风险,并确保你的工作与团队进度整合。


分支策略介绍

分支是Git的超级功能,它允许你在独立的线上开发,而不影响主代码库。本节我们来探讨不同的分支管理策略。

分支(Branch) 是一个独立的开发线。

分支的好处:

  • 隔离开发:安全地开发新功能或修复,不影响现有系统。
  • 并行工作流:多个团队可以同时在各自的分支上工作(例如前端和后端)。
  • 简化合并:功能完成后,可以轻松地合并回主分支。

Git Flow 是一种流行的分支模型,它定义了严格的分支角色:

  • main:存放可部署的生产环境代码。
  • develop:集成所有开发功能的分支。
  • feature/*:从develop创建,用于开发新功能。
  • release/*:从develop创建,用于准备发布。
  • hotfix/*:从main创建,用于紧急修复生产环境问题。

主干开发(Trunk-Based Development) 是另一种策略:

  • 所有开发者都直接向单一主干分支(如 main)提交代码。
  • 通过功能开关(Feature Flags) 来控制新功能的启用与禁用。
  • 优点:合并复杂度低,分支生命周期短,非常适合持续集成环境。
  • 挑战:需要严格的纪律、完善的测试和代码审查,否则直接提交到主干可能引入不稳定因素。

选择哪种策略取决于团队规模和项目需求。对于需要严格代码审查的课堂项目,Git Flow 或类似的基于功能分支的模型通常更合适。


分支的创建与管理

上一节我们比较了不同的分支策略,本节中我们来看看实际操作分支的命令。

以下是管理分支的核心命令:

  • git branch:列出所有本地分支。
  • git checkout -b <branch-name>:创建并切换到新分支。这是 git branch <name> 和 git checkout <name> 的组合。
  • git checkout <branch-name>:切换到指定分支。
  • git merge <branch-name>:将指定分支合并到当前分支。(稍后详细讲解)
  • git branch -d <branch-name>:删除已合并的分支。

最佳实践:

  • 使用描述性的分支名,例如 feature/user-auth, bugfix/login-error。
  • 定期与主分支同步,频繁执行 git pull origin main。
  • 限制分支生命周期,功能完成后尽快合并回主分支,避免长期存在的分支带来复杂的集成问题。

合并(Merging)技术

分支开发的最终目的是将工作成果整合起来。合并就是将不同分支的更改组合到一起的操作。

合并的类型:

  1. 快进合并(Fast-Forward Merge)

    • 条件:当目标分支(如 main)自特性分支创建以来没有新的提交时。
    • 效果:Git简单地将主分支指针向前移动到特性分支的最新提交。历史保持线性。
    • 命令:git merge feature-branch
  2. 递归合并/三方合并(Recursive Merge)

    • 条件:当目标分支和特性分支都有新的提交,历史已经分叉时。
    • 效果:Git创建一个新的“合并提交”,将两个分支的历史连接起来。这会保留完整的历史记录。
    • 命令:git merge feature-branch
  3. 压缩合并(Squash Merge)

    • 操作:将特性分支上的所有提交压缩成一个全新的提交,然后合并到目标分支。
    • 优点:保持主分支历史简洁清晰,避免了许多中间提交记录。
    • 命令:git merge --squash feature-branch,然后需要执行 git commit 来完成提交。

合并最佳实践:

  • 合并前审查:使用拉取请求(Pull Request)进行代码审查。
  • 沟通变更:通知团队成员重大的合并操作。
  • 及早解决冲突:频繁合并,并在合并前主动处理冲突。
  • 记录合并决策:在合并提交信息中说明合并原因。

变基(Rebasing)技术

除了合并,变基是另一种整合分支更改的方法。它通过“重新播放”提交来创造更清晰的历史。

变基(Rebase) 将当前分支的提交移动到另一个分支(通常是更新的主分支)的顶端,从而重写项目历史。

优点:产生一条直线的、线性的提交历史,更容易追踪项目进展。
缺点:重写了提交历史,不适用于已经共享给其他人的分支。

交互式变基(Interactive Rebase) 功能更强大,允许你在重新应用提交前编辑、重排、压缩或删除提交。

  • 命令:git rebase -i HEAD~n (n代表要回溯的提交数)
  • 常用操作:
    • pick:保留提交。
    • reword:修改提交信息。
    • edit:修改提交内容。
    • squash:将提交合并到前一个提交中。
    • drop:删除提交。

变基 vs. 合并:

  • 使用变基:用于整理私有分支的历史,然后再分享或合并。
  • 使用合并:用于整合已共享的分支,因为它保留了完整的历史记录。

变基最佳实践:

  • 不要在共享分支上变基。
  • 使用交互式变基在推送前清理提交。
  • 使用 git push --force-with-lease 进行强制推送时要格外小心,并提前通知团队成员。

解决合并冲突

当Git无法自动合并两个分支对同一文件的更改时,就会发生合并冲突。这在并行开发中很常见。

冲突发生时机:在合并或变基过程中,当两个分支修改了同一文件的相同区域。

解决步骤:

  1. Git会标记出冲突文件,文件内会包含类似 <<<<<<<, =======, >>>>>>> 的冲突标记。
  2. 打开冲突文件,决定保留哪些更改(可能是当前分支的、传入分支的,或两者的结合)。
  3. 使用编辑器(如VS Code内置的合并冲突工具)解决冲突,删除所有冲突标记。
  4. 将解决后的文件添加到暂存区:git add <file-name>。
  5. 继续完成合并或变基操作:git commit(对于合并冲突)或 git rebase --continue(对于变基冲突)。

冲突解决最佳实践:

  • 团队沟通:协调可能重叠的工作。
  • 立即解决:不要搁置未解决的冲突。
  • 理解更改:仔细审查冲突双方的修改意图。
  • 合并后测试:确保解决冲突后的代码功能正常。

高级Git命令

掌握了分支、合并和变基后,我们来看一些能进一步提升效率的高级技巧。

1. 拣选提交(Cherry-pick)

  • 作用:将某个分支上的一个特定提交应用到当前分支。
  • 使用场景:将一个重要的修复(hotfix)应用到多个分支,而无需合并整个分支。
  • 命令:git cherry-pick <commit-hash>

2. 贮藏(Stashing)

  • 作用:临时保存工作目录中的未提交更改,清空工作区以进行其他操作(如切换分支处理紧急bug)。
  • 常用命令:
    • git stash:贮藏当前更改。
    • git stash list:查看贮藏列表。
    • git stash apply:应用最新的贮藏(不删除)。
    • git stash pop:应用并删除最新的贮藏。
  • 使用场景:需要临时切换上下文,但又不想提交未完成的工作。

高级命令最佳实践:

  • 慎用拣选:过度使用会使历史复杂,需注意提交间的依赖关系。
  • 命名贮藏:为贮藏添加描述信息(git stash save “描述”)。
  • 定期清理:定期检查并删除旧的贮藏,避免混淆。

常见陷阱与总结

在课程的最后,我们总结关键要点,并提醒一些需要避免的常见错误。

需要警惕的陷阱:

  • 强制推送(Force Push):git push --force 会覆盖远程历史,可能抹掉他人的工作。务必使用更安全的 --force-with-lease 并与团队沟通。
  • 重写共享历史:避免对他人正在使用的分支进行变基。
  • 忽略合并冲突:未妥善解决的冲突可能引入错误。
  • 巨型提交:包含大量更改的提交难以审查和回退。始终拆分为小粒度的提交。

核心回顾:

  • 清晰的提交:使用描述性信息,小步快跑。
  • 高效的分支:命名规范,聚焦单一任务,频繁同步。
  • 顺畅的协作:定期推送/拉取,善用代码审查和拉取请求。
  • 冲突预防:提前沟通计划,频繁合并以减少冲突。

最终建议:精通Git能极大提升团队效率和软件质量。这是一个需要持续练习和探索的工具。利用官方文档、交互式教程(如 learngitbranching.js.org)和社区资源不断精进你的技能。


本节课中我们一起学习了Git版本控制从基础到高级的全套知识,包括仓库管理、提交规范、分支策略、合并与变基、冲突解决以及一些高效的高级操作。掌握这些技能将为你的软件工程协作打下坚实的基础。

006:代码质量优化 🛠️

在本节课中,我们将学习代码质量的概念、其重要性,并介绍几种用于提升和保证代码质量的工具。我们将通过具体的例子和操作演示,帮助你理解如何编写更清晰、更易维护的代码。


什么是代码质量?🤔

代码质量是开发可维护、可读且无错误软件的基础。高质量的代码更容易阅读和理解,能帮助团队早期发现潜在问题,并强制执行统一的编码标准。每种编程语言都有其推荐的命名规范和约定。

低质量的代码是一个涵盖多种问题的总称,包括:

  • 存在缺陷的代码行为。
  • 低效的实现。
  • 结构混乱、耦合度高、内聚性低的“意大利面条式代码”。
  • 难以维护的代码。
  • 使用了过时的库和框架。
  • 代码重复导致重构成本高昂。

糟糕代码示例画廊 🖼️

上一节我们介绍了代码质量的概念,本节我们来看一些具体的反面教材。以下是糟糕代码的几个典型例子:

  • 命名不当的代码:变量和函数名无法清晰表达其意图。
  • 缺乏文档:代码没有任何注释或说明。
  • 变量名糟糕:使用无意义的缩写或单个字母作为变量名。

例如,观察下面这个简单的计算器模块:

def AS(a, b):
    c = a + b
    return c

如果不看任何文档,仅从函数名 AS 和变量名 a, b, c,很难快速理解这个函数的功能。

为了改进它,我们可以重新命名所有的函数和变量:

def add_numbers(num1, num2):
    result = num1 + num2
    return result

现在,代码的意图就清晰多了:这是一个将两个数字相加的函数。


代码质量工具:SonarQube 📊

上一节我们看到了如何通过手动改进来提升代码质量,本节我们将介绍一个自动化工具。SonarQube 是一个用于检查代码质量的软件。它通过设置一系列规则来验证你的代码是否符合预设的标准。

SonarQube 的主要功能包括:

  • 检查代码中的安全漏洞。
  • 检测代码中的“坏味道”。
  • 检查代码是否符合编码标准。
  • 可以与代码仓库集成,实现实时检查。
  • 当有人提交不符合标准的代码时,可以通过邮件通知,甚至可以设置为拒绝此次推送。

它是一个服务器端工具,可以在本地运行,也可以部署在网络服务器上供团队使用。

设置 SonarQube 示例

由于 SonarQube 主要面向企业,其初始设置可能稍显复杂。以下是基本的设置步骤概述:

  1. 下载并解压 SonarQube 的压缩包。
  2. 配置 Java 环境:SonarQube 需要特定版本的 Java SDK 来运行。你需要在系统环境变量中指定 JDK 的路径,或者在启动脚本中配置。
    • 命令示例(Windows PowerShell):$env:SONAR_JAVA_PATH="C:\Program Files\Java\jdk-xx.x.x"
  3. 启动服务器:导航到 SonarQube 解压目录下的 bin 文件夹,根据你的操作系统(如 Windows)运行对应的启动脚本(例如 StartSonar.bat)。
  4. 访问界面:启动成功后,控制台会显示一个本地 HTTP 地址(如 http://localhost:9000)。在浏览器中访问该地址即可进入 SonarQube 的 Web 管理界面。

在 Web 界面中,你可以管理项目、查看问题、定义编码规则、设置质量门禁(Quality Gates)和进行系统管理。质量门禁允许你自定义检查标准,例如代码覆盖率、重复率等阈值。

与 CI/CD 集成

SonarQube 可以与 GitHub Actions 等 CI/CD 流水线集成。你可以在仓库的配置文件中(如 .github/workflows/sonarcloud.yml)定义分析步骤。这样,每次代码推送或拉取请求时,都会自动触发 SonarQube 分析,并根据预设的质量门禁判断是否通过,从而在早期阻止低质量代码进入主分支。


代码质量工具:Linter (以 Pylint 为例) 🔍

上一节我们介绍了服务器端的代码质量分析工具,本节我们来看一个更轻量级的本地工具。Linter 是一种分析代码以检测潜在错误的工具,包括风格问题、可能的错误以及违反编码约定的情况。

不同编程语言有各自流行的 Linter 和命名约定:

  • Python:通常使用 snake_case(小写加下划线),由 PEP 8 风格指南定义。
  • Java:通常使用 camelCase(驼峰命名法)和 SCREAMING_SNAKE_CASE(全大写加下划线,用于常量)。

Pylint 是 Python 语言的一个 Linter 工具。它会检查代码错误、执行编码标准,并“嗅探”代码问题(即在不运行代码的情况下进行静态分析)。

安装与使用 Pylint

  1. 安装:通过 pip 包管理器安装。
    • 命令:pip install pylint
  2. 在 VS Code 中使用:你可以安装 Pylint 扩展,它会在你编写代码时实时高亮显示问题,点击错误代码可以链接到官方文档查看详细说明。

示例:改进代码评分

假设我们有一个简单的加法函数,初始代码如下:

def add(a, b):
    result = a + b
    return result

使用 Pylint 检查,初始得分可能只有 2.5/10。报告指出缺少模块文档字符串(module docstring)和函数文档字符串(function docstring),并且常量名 result 不符合全大写的命名规范。

第一步改进:添加模块和函数文档字符串。

"""
This module demonstrates a simple function for adding two numbers.
"""

def add(a, b):
    """Add two numbers and return the result."""
    result = a + b
    return result

再次运行 Pylint,得分提升到 7.5/10。

第二步改进:处理常量命名警告。如果我们不想将 result 改为全大写,可以在该行上方添加一个特殊注释来禁用此条规则的检查。

def add(a, b):
    """Add two numbers and return the result."""
    # pylint: disable=invalid-name
    result = a + b
    return result

现在运行 Pylint,可以获得 10/10 的满分。添加文档字符串后,你还可以在 Python 交互环境中使用 help() 函数查看该函数的说明。


代码质量工具:Prettier 💅

上一节我们介绍了用于检查代码问题的 Linter,本节我们来看一个专注于代码格式化的工具。Prettier 是一个强大的代码格式化工具,支持多种语言,并能集成到大多数编辑器中。

它的核心作用是自动格式化代码,确保整个代码库的风格一致,从而提高可读性,并减少因格式差异引起的合并冲突。开发者可以更专注于代码逻辑,而非排版细节。

为什么使用 Prettier?

  • 统一代码风格:避免团队在缩进、分号、换行等格式细节上争论。
  • 提升可读性:自动将冗长的代码行拆分成多行,避免水平滚动。
  • 节省时间:无需手动调整格式。
  • 易于集成:在主流编辑器(如 VS Code, Sublime, WebStorm)中安装扩展即可使用。

在 VS Code 中设置 Prettier

  1. 安装扩展:在 VS Code 扩展商店中搜索并安装 “Prettier - Code formatter”。
  2. 配置设置:
    • 打开设置(Command/Ctrl + ,),搜索 formatter。
    • 将 “Default Formatter” 设置为 “Prettier”。
    • 勾选 “Format On Save”。
  3. (可选)创建配置文件:在项目根目录创建 .prettierrc 文件。即使内容为空 {},也能确保 Prettier 正常工作,并防止它格式化从别处导入的、已有自身配置的代码。
  4. 使用:保存文件时,Prettier 会自动将代码格式化为统一的风格。

总结 📝

本节课中,我们一起学习了代码质量优化的核心内容。

我们首先明确了代码质量对于软件可维护性、可读性和健壮性的重要性。接着,我们通过反面例子认识了低质量代码的常见问题。

然后,我们深入介绍了三种提升代码质量的实用工具:

  1. SonarQube:一个功能全面的服务器端代码质量管理平台,适合团队协作和集成到 CI/CD 流程中,用于进行深度分析和设置质量门禁。
  2. Linter (如 Pylint):一个本地代码静态分析工具,用于实时检查代码中的错误、风格问题和违反约定的情况,帮助开发者在编写阶段就保持代码规范。
  3. Prettier:一个专注于代码格式化的工具,能够自动统一代码风格,节省开发者调整格式的时间,并提升代码的整体可读性。

合理利用这些工具,可以显著提升个人和团队的开发效率与代码质量。

007:图形用户界面 👨‍💻

在本节课中,我们将学习图形用户界面的基础知识,包括其定义、历史、核心组成部分,并通过Python的Tkinter库进行实践。课程最后将探讨如何将手势控制与GUI结合。

概述

图形用户界面是用户与计算机程序进行视觉交互的部分。本节将介绍GUI的构成、发展历史,并指导你使用Python的Tkinter库创建简单的GUI应用。


什么是图形用户界面?🖥️

图形用户界面是计算机程序或应用程序中,用户通过视觉进行交互的部分。用户界面泛指用户与计算机的任何交互。GUI的核心在于,用户无需输入命令,而是通过点击按钮、图标或图像等“指向并点击”的界面来操作。你可以将其理解为网站或应用程序上看到的屏幕布局,通过点击、轻触和拖拽来触发功能。

例如,在一个窗口中,所有可以点击和交互的元素都被称为部件。

GUI的三个核心部分

一个GUI程序主要包含三个部分:

  1. 框架:即主窗口,包含所有视觉元素(部件)。
  2. 代码:定义GUI功能和行为的编程逻辑或指令。例如,可以使用API连接GUI前端和功能后端。
  3. 事件:触发特定响应的用户动作。

GUI的发展历史 📜

GUI的历史可以追溯到1973年,这距离现在并不遥远。有趣的是,当时的许多计算机科学家甚至没有鼠标,只有计算机和键盘。1973年诞生的第一个GUI并未商用,仅限计算机科学家使用。

第一个商业销售的GUI由苹果公司在1984年推出,同时售出的还有鼠标。这个早期的商业GUI已经包含了菜单栏,用于访问文件、应用程序、系统功能、计算器、闹钟、记事本、控制面板等。许多这些功能至今仍在沿用。

如今,我们使用的GUI无处不在,例如智能电视的点播界面、手机、平板电脑等移动设备。视觉设计,尤其是色彩心理学,在现代GUI开发中扮演着重要角色。


如何创建和使用GUI?🔧

上一节我们回顾了GUI的历史,本节我们将动手实践,学习如何使用Tkinter创建GUI。

准备工作

开始前,请确保已安装:

  1. Visual Studio Code
  2. Python(Tkinter已包含在最新版本的Python中)

引入Tkinter

在Python文件中,通常以下列方式导入Tkinter:

import tkinter as tk
from tkinter import *
from tkinter import ttk
  • import tkinter as tk:导入Tkinter并简写为tk以便引用。
  • from tkinter import *:导入所有函数和内置模块。
  • from tkinter import ttk:导入Themed Tkinter,它提供外观更现代的部件,并能适应用户的操作系统。

Tkinter简介

Tkinter是Python的标准GUI库。它是一个开源、跨平台的部件工具包,被多种编程语言用于构建GUI,因其简单直接而深受初学者推荐。

一个Tkinter应用包含三个主要部分:

  1. 导入Tkinter语句。
  2. 创建主窗口:root = tk.Tk()。root是主窗口的变量名,代表整个GUI的根或顶部。
  3. 启动主事件循环:root.mainloop()。这使程序进入事件循环,监听用户的交互操作。

GUI应用示例 🧮

以下是几个使用Tkinter创建的GUI应用示例。

示例一:简易计算器

这是一个包含两个部件的计算器GUI:一个输入框部件和多个按钮部件。点击按钮,数字和运算符会显示在输入框中,并具备完整的计算功能。它还能根据操作系统切换深色模式。

核心代码结构如下:

import tkinter as tk
from tkinter import ttk

# 创建主窗口
root = tk.Tk()
root.title("计算器")
# ... 此处添加输入框、按钮及计算逻辑的代码 ...

root.mainloop()

示例二:天气查询应用

这个应用使用Tkinter和requests库,允许用户输入城市名并获取实时天气数据(温度、湿度、天气状况、风速)。它通过OpenWeatherMap API获取数据。

应用的主要功能包括:

  • tk.Tk():创建主窗口。
  • root.title():设置窗口标题。
  • root.geometry():设置窗口尺寸。
  • tk.Label():创建标签(如“输入城市:”)。
  • tk.Entry():创建文本输入框,供用户输入城市名。
  • tk.Button():创建“获取天气”按钮,点击时触发查询函数。
  • mainloop():保持GUI响应,监听用户交互。

示例三:带菜单栏的文本编辑器

此示例创建了一个带有菜单栏(文件->新建、打开、保存、退出)的简单文本编辑器。它使用tk.Text部件(支持多行文本编辑),而非单行的tk.Entry部件。

代码关键点:

  • tk.Tk()初始化主应用窗口。
  • add_command向下拉菜单添加可点击项,每个项链接到一个功能函数(如new_file, open_file, save_file, exit)。
  • tk.Text()在主窗口内创建一个多行文本编辑部件。
  • mainloop()启动事件循环。

综合应用与课堂项目 🚀

前面我们介绍了几个独立的GUI应用,本节我们将它们整合,并介绍与课堂项目相关的进阶内容。

综合应用:多功能程序

我们将计算器、天气应用等小型GUI整合到一个应用程序中。该应用使用不同的框架来切换和展示各个功能。核心代码逻辑是管理一个主菜单框架,并根据用户选择切换到对应的功能框架。

课堂项目:GUI测验

作为课程交付成果的一部分,我们将创建一个GUI测验程序。该程序会显示选择题,用户选择后即时显示结果。

与Leap Motion手势控制集成

我们的课堂项目将涉及使用Leap Motion控制器,这是一个通过自然手势与数字环境交互的设备。它将使用传感器检测手部在3D空间中的运动和位置。

我们将使用Easy Li UI库来实现手势与GUI的交互。这个库简化了为GUI添加手势控制的过程,并包含一些预制的部件(如按钮、滑块、菜单),能很好地与Leap Motion集成。

工作流程

以下是手势控制GUI的基本工作流程:

  1. 设置:用户准备好Leap Motion控制器。
  2. 手势检测:Leap Motion检测用户的手部运动和位置。
  3. 数据转换:Leap Motion控制器将物理手势信号转换为数字数据。
  4. 手势映射:Easy Li UI库接收数据,并将其映射为特定操作(如调整滑块、按下按钮)。例如,“捏合”手势可能对应菜单选择。
  5. GUI响应:GUI根据映射的操作更新界面并给出反馈。
  6. 循环等待:系统准备接收下一个手势指令。

注意:由于课堂项目涉及3D交互亭,我们可能不会使用主要为2D应用设计的Tkinter,而会考虑使用Unity等支持3D GUI开发的工具。


总结与任务 📋

本节课我们一起学习了图形用户界面的核心概念、历史,并通过Tkinter库实践了创建简单GUI应用。我们还探讨了如何将手势控制技术与GUI相结合。

课程任务

  1. 基础任务:访问课程Pod仓库,跟随代码创建并运行提供的多项选择测验GUI。
  2. 进阶任务:修改提供的“多功能程序”代码,在其中添加一个新的框架,用于集成你创建的多项选择测验功能。
  3. 提交内容:修改后的代码文件,以及应用程序正常运行时的屏幕录制视频。

如有任何问题,可以随时联系我们。

008:项目管理 🗂️

在本节课中,我们将学习如何使用GitHub内置的项目管理工具来规划、组织和跟踪软件开发工作。我们将重点介绍GitHub Projects和Issues,并了解如何利用它们来提升团队协作效率。

GitHub Projects 概述 📊

上一节我们介绍了课程的整体安排,本节中我们来看看GitHub Projects。当你最初想到“项目”时,你可能会想到一个你正在努力实现的目标,例如制作一个游戏并为其创建一个代码仓库。但GitHub有一个名为“Projects”的集成工具,它围绕管理某个可交付成果的所有工作而设计。

GitHub Projects有不同的层级:

  • 组织级项目:可以跨多个不同的代码仓库跟踪工作。
  • 仓库级项目:适用于单个团队或单一项目。
  • 用户级项目:可用于跟踪个人所有的项目。

它提供了灵活、可自定义的面板,帮助团队规划、组织和跟踪任务与预算,从而简化整体工作流程。

为何使用GitHub Projects?

使用GitHub Projects主要带来效率和集中化的优势。它已嵌入GitHub,无需使用外部软件。所有任务、议题和拉取请求都集中链接到一个地方,并与你的代码仓库相连。

核心功能

以下是GitHub Projects的一些关键特性:

  • 可视化组织:主要通过看板视图进行组织,GitHub也提供其他视图类型。可自定义的列代表不同的工作流阶段,列中的卡片代表任务。
  • 自定义视图:可以根据指派人、标签或截止日期等进行筛选和排序,创建个性化视图。
  • 与Issues和PRs集成:当议题关闭或拉取请求合并时,看板会自动更新。卡片可以直接链接到具体的议题或拉取请求。
  • 自定义字段:可以添加如优先级、截止日期等元数据,方便标记和描述。
  • 项目自动化:可以设置预定义规则,让卡片在任务完成时自动移动。

看板方法 📋

上一节我们了解了GitHub Projects的功能,本节中我们来看看其核心视图之一:看板。看板是一个日语词汇,意为“可视化卡片”或“信号板”,由丰田工程师创建,旨在提高制造效率。

看板是一种使用列来描绘工作阶段的可视化工具。一个简单的看板包含“待办”、“进行中”和“已完成”列,但你可以根据需求自定义结构。卡片则代表具体的任务。

在看板中:

  • 承诺点:当你承诺开始一项任务时,将其从“待办”移至“进行中”。
  • 交付点:当任务完成时,将其从“进行中”移至“已完成”。
  • 泳道:是一个横向分隔条,可作为额外的组织层,用于按工作类型或不同团队进行区分,有助于识别瓶颈和过度承诺。

看板的好处包括视觉清晰度、灵活性、透明度(所有人都能看到工作进展)以及最终提升的团队效率。GitHub Projects采用了这种看板风格。

GitHub Issues详解 🐛

上一节我们探讨了看板视图,本节中我们来深入了解GitHub Issues。如果你点击GitHub左上角的菜单图标,可以找到“Issues”页面。这个页面可以显示来自多个项目的议题。

GitHub Issues是一个用于记录任务、错误和其他问题的项目管理功能。它的用途不仅限于记录错误,你可以发挥创意,用它记录各种事项。它与敏捷方法论配合良好,可以用于报告不同的状态和指标。

创建与查看议题

当你查看一个议题时,可以看到标签(如bug、help wanted)、指派人等信息。你也可以创建自定义标签来分类议题。议题页面集成了拉取请求信息,并且可以设置里程碑。

创建议题时,你需要填写标题和描述,并可以选择标签。例如:

标题: 数据导入错误
描述: 从`data.csv`文件导入数据时,第5行出现解析错误。需要检查文件格式或编码。
标签: bug, help wanted

完成后,点击“Create issue”按钮即可。

与CI/CD集成

GitHub Issues常与敏捷方法和CI/CD结合,形成流畅的工作流。例如,可以设置自动化集成测试:当推送的代码导致测试失败时,系统会自动创建一个议题,并记录相关的提交和推送者信息。这有助于早期发现错误,并让跟踪提交和问题变得更加容易。

进度跟踪与视图 🔄

上一节我们介绍了如何创建和管理议题,本节中我们来看看如何利用不同的视图来跟踪项目进度。GitHub Projects提供了多种视图来监控任务和里程碑。

表格视图

这是类似电子表格的视图,设计直观,易于新手使用。你可以清晰地看到所有议题的列表及其状态、指派人等信息。

看板视图

这是可视化的看板视图,适合快速概览整体进展。当议题数量众多时,这种视图尤其有用。你可以通过拖放操作将卡片在不同列之间移动。

路线图视图

这是一种日历视图,可以按时间线查看里程碑和计划的任务。它有助于规划未来的工作安排。

状态更新与里程碑

在整个项目中,你可以添加状态更新(如“按计划进行”或“偏离计划”),让所有成员了解最新情况。历史更新会被保留,形成日志。

里程碑可以基于实际截止日期创建。当议题状态从“进行中”变为“已完成”时,关联的里程碑进度会自动更新,无需手动同步。

实践演示 🛠️

上一节我们学习了各种视图,现在让我们通过一个简单的练习来实际操作。我们将创建一个项目看板并添加一些议题。

首先,在你的代码仓库中,点击“Projects”标签页,然后选择“New project”。你可以选择一个模板,这里我们使用“Board”模板。创建后,你会看到一个基础的看板。

你可以重命名列(例如将“To do”改为“Backlog”),并修改其颜色。在设置中,可以添加项目描述、设置权限以及管理自定义字段。

现在,让我们添加一个议题。例如,议题标题可以是:“树莓派手势‘捏合’未被识别”。在看板中,你可以将此草稿卡片转换为正式的议题,并为其添加标签(如good first issue)和指派人。

在看板、表格或路线图视图中,你都可以方便地管理这些议题的状态。你还可以创建迭代周期,系统会自动为后续周期生成时间框架。

总结 📝

本节课中我们一起学习了GitHub的项目管理工具。我们介绍了GitHub Projects如何通过看板等方法帮助团队可视化和管理工作。我们深入探讨了GitHub Issues的创建、分类以及与CI/CD流程的集成。我们还了解了如何使用表格、看板和路线图等多种视图来有效跟踪项目进度与里程碑。

关键在于,这些工具非常灵活,你可以根据团队的需求创造性地使用它们,从而构建一个透明、高效的工作流,提升整个团队的协作效率。

009:Leap Motion 2 SDK 🖐️

在本节课中,我们将学习Leap Motion控制器及其软件开发工具包(SDK)。我们将了解其硬件原理、软件安装步骤,并学习如何使用Python绑定进行手势追踪编程。课程内容分为三个主要部分:硬件介绍、环境安装和编程实践。

Leap Motion控制器介绍 🎥

上一节我们概述了课程内容,本节中我们来看看Leap Motion控制器本身。

Leap Motion控制器是一种硬件设备,它将安装在我们项目所使用的显示器上。该设备通过两个红外摄像头和红外LED发射器工作。它投射红外光,并由两个摄像头捕捉,利用立体视觉成像技术来获取深度信息。

因此,它可以在摄像头前方提供X、Y、Z三个维度的精细位置数据。软件支持多种安装方式,包括朝上放置、头戴式或安装在屏幕顶部,我们项目很可能会采用屏幕顶部的安装方式。

控制器内部集成了处理器,能完成大量计算工作,但这也导致其功耗较高,我们在使用树莓派时已经遇到了这个问题。

相关技术与SDK 🛠️

上一节我们介绍了硬件,本节中我们来看看Leap Motion提供的软件技术。

Leap Motion最新的技术是Ultraleap Hyperion。如果你使用的是Windows、Mac或X86架构的Linux系统,可以通过控制面板查看原始摄像头输出等信息。

然而,我们将主要使用Ultraleap Gemini技术。原因是Gemini是唯一支持树莓派ARM架构的版本。树莓派是我们用来控制显示器后方摄像头的主要计算机。

在树莓派上使用Ultraleap Gemini相对基础,但环境设置好后很容易使用。每个技术包(如Gemini)都包含Leap Motion SDK,其中提供了API和C语言库。如果你熟悉C或C++,使用起来会比较容易。考虑到本课程主要使用Python,我们已提前设置了Python绑定,稍后会介绍如何配置。

控制器的应用场景非常广泛。例如,可以将其安装在VR头显上。如果你路过实验室,有时会看到人们戴着VR头显使用它。另一个有趣的技术是“TouchFree”,它可以将任何显示器变成触摸屏,精度相当高。在日益增长的AR和VR领域,其应用可能性是无穷的。

环境安装与配置 💻

上一节我们了解了Leap Motion的基本情况,本节中我们来看看如何开始安装和下载所需的一切。

第一步是下载SDK。你需要创建一个免费账户。请注意,安装时不要更改默认安装路径,否则后续安装Python绑定时可能会遇到麻烦。

第二步是获取Python绑定。我们将从GitHub仓库克隆代码。以下是具体步骤:

  1. 打开终端。
  2. 使用 git clone 命令克隆Leap Motion Python绑定的仓库到指定文件夹。

这些步骤是为了在你的本地机器上设置环境,以便在插入Leap Motion控制器后可以运行程序。所有步骤在提供的文档中都有详细说明,你可以复制粘贴命令。

第三步是设置Python虚拟环境。我们将在VS Code的终端中运行一系列命令。首先,使用命令创建虚拟环境。然后,激活该虚拟环境。在某些电脑上可能会遇到执行策略问题,文档中也有相应的解决方法。

请注意,我们使用的都是Windows电脑,对于Mac系统的具体操作可能不完全清楚,但我们很乐意提供帮助。

第四步是构建Python绑定。在同一个终端中,按照文档的说明逐行运行命令来安装Python绑定。这个过程并不复杂,但请确保你位于正确的目录下,并指向正确的requirements.txt文件。如果你不想看视频,文档中提供了所有命令,直接复制粘贴即可。

如果你更改了默认的安装目录,可能需要设置环境变量来指向正确的路径,这可能会带来一些问题。因此,建议保持默认安装路径。如果在任何步骤遇到问题,请随时向我们求助,这是一个团队项目。

安装完成后,你可以进入examples文件夹,运行示例程序。例如,连接Leap Motion后,你可以运行一个可视化示例来测试。

使用Python绑定进行编程 🐍

上一节我们完成了环境配置,本节中我们来看看如何使用Python绑定进行实际编程。

现在假设你已经设置好了所有Python绑定,接下来需要实际开发应用程序。我们需要了解SDK中的几个重要类,Python绑定对它们进行了封装。

最重要的类是Listener(监听器)。监听器继承自Connection(连接)类,它负责处理来自摄像头的所有事件。几乎所有你关心的操作都将通过监听器处理。事件可以是任何被摄像头记录的信息,虽然大部分是手部运动,但也包括连接、断开、错误日志等。

以下是如何使用监听器:

  1. 首先,从我们提供的camera_binding文件中导入所需的模块,特别是MyListener类和Listener类,同时也要导入leap模块本身。
  2. 接下来,设置连接和监听器类。创建一个新的监听器实例和一个新的连接实例。关键步骤是使用connection.add_listener(listener)将你创建的监听器添加到连接中。这样,机器与摄像头之间的连接才能将事件传递给监听器进行处理。
  3. 最后,使用with connection.open():语句来打开连接。with关键字能确保连接在使用完毕后正确关闭,防止内存泄漏。在这个连接打开的代码块内,你可以编写一个循环来持续监听事件并执行你的逻辑。

以上就是连接摄像头并开始监听事件所需的全部核心代码。

我们提供了两个最重要的函数:

  • get_palm_position():获取手掌位置坐标。
  • is_pinching():检测是否正在做捏合手势。

下面是一个简化的示例,展示了如何打印捏合状态和手掌位置:

import time
from camera_binding import MyListener
import leap

listener = MyListener()
connection = leap.Connection()
connection.add_listener(listener)

with connection.open():
    while True:
        print(f"Pinching: {listener.is_pinching()}")
        print(f"Palm Position: {listener.get_palm_position()}")
        time.sleep(0.3)

在这个示例中,我们每秒打印几次手掌位置和捏合状态。手掌位置返回的是三维坐标(X, Y, Z)。需要注意的是,在大多数屏幕交互场景中,你主要会用到X和Z坐标,因为Y坐标代表的是距离摄像头的深度。

我们还创建了一个结合Tkinter图形库的示例,可以让一个方块跟随手掌移动。为了实现这一点,我们需要在每次循环更新中手动调用Tkinter的窗口更新函数,因为mainloop()是阻塞函数。

除了上述两个函数,我们还提供了其他一些有用的函数:

  • get_hand_type():返回追踪到的是左手还是右手。
  • get_pinching_vectors():返回食指指尖和拇指指尖在各个轴向上的差值,可用于实现缩放等功能。
  • get_tracking_frame_id():返回追踪帧的ID,可用于控制更新频率。

所有函数都有文档字符串说明其返回值和用法。如果你需要更多功能或有任何建议,请告诉我们。

Swamp Lab 介绍与总结 🏠

上一节我们学习了如何编程,本节中我们来看看为大家准备的一个开发环境。

我们还为大家准备了一个开发环境,称为“Swamp Lab”,位于走廊尽头的352房间。这是一个供全班同学开发应用程序的场所。

Swamp Lab模拟了走廊显示器的环境,包含三台电脑、三个桌面显示器和一个电视,为你提供了一个完整的开发测试平台。每台显示器都由独立的树莓派驱动,这意味着我们可以为每个显示器定制不同的程序。

Lab内将提供签到表,请大家在使用时登记。这是一个共享空间,请互相尊重。我们也正在努力解决树莓派的供电问题,以让Leap Motion运行更流畅。

此外,我们还在Discord的Swing频道设置了一个语音频道和屏幕共享功能,方便远程协作和查看Lab使用情况。

本节课总结
在本节课中,我们一起学习了Leap Motion控制器的硬件原理和Ultraleap Gemini SDK。我们逐步完成了开发环境的安装与配置,包括下载SDK、设置虚拟环境和构建Python绑定。重点学习了如何使用我们封装的Python绑定进行编程,通过监听器处理事件,并掌握了获取手掌位置、检测捏合手势等核心功能。最后,我们介绍了为大家准备的Swamp Lab开发环境。接下来,大家就可以利用这些工具开始为走廊显示器开发创新的手势交互应用了。

010:数据管理 📊

在本节课中,我们将要学习几种关键的数据序列化格式和工具,包括CSV、Pickle、JSON、SQLite以及rsync。我们将探讨它们的基本概念、使用方法、优缺点以及适用场景。


CSV:逗号分隔值 📄

CSV是一种用于存储表格数据的简单文件格式。它使用纯文本,每行代表一条数据记录,字段之间通常用逗号分隔。

CSV的结构与特点

以下是CSV文件的核心组成部分:

  • 行分隔符:通常是换行符,用于分隔不同的数据行。
  • 字段分隔符:最常用的是逗号(,),但也可以是分号(;)、制表符等。
  • 引号字符:通常用双引号(")将字符串类型的字段括起来,以区分于数字。
  • 表头:文件的第一行,用于描述每列数据的含义。

CSV的优势在于其简单性、轻量级和广泛的兼容性。几乎所有编程语言、数据库和电子表格应用都支持它。

在Python中使用CSV

Python内置了csv模块,可以方便地读写CSV文件。

读取CSV文件

以下是读取CSV文件的基本步骤:

  1. 导入csv模块。
  2. 使用with open()语句以读取模式('r')打开文件。
  3. 创建csv.reader对象来读取文件内容。
  4. 遍历读取器对象以获取每一行数据。
import csv

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_9.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_11.png)

def read_csv_file(filename):
    with open(filename, mode='r') as file:
        csv_reader = csv.reader(file)
        header = next(csv_reader)  # 读取表头
        print(f"Header: {header}")
        for row in csv_reader:
            print(f"ID: {row[0]}, Name: {row[1]}, Dept: {row[2]}, Age: {row[3]}, Salary: {row[4]}")

追加数据到CSV

若要向现有CSV文件末尾添加新数据,需要使用追加模式('a')。

def append_to_csv(filename):
    with open(filename, mode='a', newline='') as file:
        csv_writer = csv.writer(file, quoting=csv.QUOTE_MINIMAL)
        csv_writer.writerow([6, 'New Person', 'Marketing', 28, 70000])

写入(覆盖)CSV文件

若要创建一个新的CSV文件或完全覆盖现有文件,使用写入模式('w')。

def write_csv(filename):
    with open(filename, mode='w', newline='') as file:
        csv_writer = csv.writer(file, quoting=csv.QUOTE_MINIMAL)
        csv_writer.writerow(['ID', 'Name', 'Major', 'Age', 'GPA'])
        data = [
            [1, 'Alice', 'Biology', 20, 3.8],
            [2, 'Bob', 'Chemistry', 21, 3.5]
        ]
        for row in data:
            csv_writer.writerow(row)

CSV的最佳实践

使用CSV时,请注意以下几点:

  • 保持结构一致:确保所有行具有相同的列数,并使用统一的分隔符。
  • 妥善处理缺失数据:可以使用NULL或空字符串等占位符,并在读取时进行相应处理。
  • 注意安全:对于来自不可信来源的CSV文件,应进行验证和清洗,防止注入攻击。
  • 处理大文件:对于非常大的CSV文件,考虑流式读取或使用pandas等库以提高效率。


上一节我们介绍了简单通用的CSV格式,本节中我们来看看一个Python专属的强大工具:Pickle。

Pickle:Python对象序列化 🥒

Pickle是Python特有的序列化模块,用于将复杂的Python对象转换为字节流(序列化,或称“腌制”),也可以将字节流还原为对象(反序列化,或称“解腌”)。

Pickle的核心概念

序列化过程可以概括为:
Python对象 → pickle.dump() → 字节流 → 存储/传输 → 字节流 → pickle.load() → Python对象

Pickle的强大之处在于其无与伦比的灵活性。它不仅可以序列化列表、字典等基本类型,还能处理函数、类实例甚至递归数据结构,完整保留对象的状态。

Pickle的用例

在我们的项目中,Pickle可能用于:

  • 保存和加载配置:例如相机的校准参数、刷新率等。
  • 会话数据管理:存储用户的交互历史或偏好设置,实现个性化体验。
  • 缓存计算结果:将耗时的数据库查询或复杂运算结果序列化保存,下次直接加载,提升性能。
  • 进程间通信:在不同Python进程间传递复杂的对象。

在Python中使用Pickle

使用Pickle非常简单,因为它是Python标准库的一部分。

序列化(Pickling)

import pickle

data = {'task': 'Finish report', 'due': '2023-10-26', 'priority': 'High'}

def pickle_data(filename, obj):
    with open(filename, 'wb') as file:  # 注意是二进制写入模式 'wb'
        pickle.dump(obj, file)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_22.png)

pickle_data('data.pkl', data)

反序列化(Unpickling)

def unpickle_data(filename):
    with open(filename, 'rb') as file:  # 二进制读取模式 'rb'
        loaded_data = pickle.load(file)
        return loaded_data

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_24.png)

restored_data = unpickle_data('data.pkl')
print(restored_data)  # 输出: {'task': 'Finish report', 'due': '2023-10-26', 'priority': 'High'}

Pickle的缺点与注意事项

尽管强大,Pickle也有其局限性:

  • 安全风险:Pickle在反序列化时会执行字节码。加载来自不可信来源的Pickle文件可能导致恶意代码执行。
  • Python专属:序列化的数据无法被其他编程语言(如Java、C++)直接使用。
  • 版本兼容性:Pickle格式可能随Python版本变化,不适合作为数据的长期存储格式(尤其是跨版本访问时)。

Pickle虽然强大但局限于Python生态。接下来,我们看看一种语言无关、在Web开发中极其流行的数据格式:JSON。

JSON:轻量级数据交换格式 🌐

JSON是一种基于文本、易于人阅读和编写,也易于机器解析和生成的数据交换格式。它独立于语言,被广泛应用于Web API、配置文件和应用程序间的数据传递。

JSON的特点

JSON的基本结构是键值对集合,类似于Python中的字典。它支持以下几种数据类型:字符串、数字、布尔值、数组、对象(字典)和null。

JSON受欢迎的原因包括:

  • 可读性好:结构清晰,便于调试。
  • 语言无关:几乎所有主流编程语言都提供原生或库支持。
  • Web友好:是RESTful API数据传输的事实标准,轻量且适合网络传输。

JSON的局限性

JSON也有一些不足:

  • 数据类型有限:不支持日期、二进制数据等复杂类型(日期通常需转为字符串)。
  • 无模式约束:不强制数据结构,可能导致数据不一致。
  • 处理大文件效率低:文本解析比二进制格式(如Pickle)更耗内存和时间。
  • 安全考虑:解析来自不可信源的JSON数据时,需防范注入攻击。

在Python中使用JSON

Python的json模块使得JSON的编解码非常方便。

将Python对象转换为JSON字符串

import json

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_28.png)

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_30.png)

python_dict = {"name": "Alice", "age": 30, "city": "New York"}
json_string = json.dumps(python_dict)  # 序列化
print(json_string)  # 输出: {"name": "Alice", "age": 30, "city": "New York"}

将JSON字符串转换为Python对象

json_string = '{"name": "Bob", "age": 25, "city": "London"}'
python_dict = json.loads(json_string)  # 反序列化
print(python_dict["age"])  # 输出: 25

客户端-服务器通信示例(使用Flask)

以下是一个简单的例子,展示如何使用JSON在Web客户端和Python服务器之间通信:

# server.py (使用Flask框架)
from flask import Flask, jsonify, request
app = Flask(__name__)

@app.route('/get_data', methods=['GET'])
def get_data():
    data = {"status": "success", "message": "Data received"}
    return jsonify(data)  # 返回JSON响应

if __name__ == '__main__':
    app.run(debug=True)

客户端(如浏览器JavaScript)可以向/get_data发送GET请求,并接收JSON格式的响应。


JSON非常适合网络数据交换,但当我们需要在本地进行更结构化、可查询的数据存储时,就需要数据库了。下面介绍一个轻量级的数据库解决方案:SQLite。

SQLite:轻量级文件数据库 🗃️

SQLite是一个服务器less、自包含、零配置的SQL数据库引擎。整个数据库(包括表、索引和数据)都存储在一个单独的文件中,非常适合嵌入式系统和小型到中型应用。

SQLite的优缺点

优点:

  • 无需配置:无需安装或管理独立的数据库服务器进程。
  • 轻量便携:数据库是单个文件,易于迁移和备份。
  • ACID兼容:支持事务,保证数据一致性。
  • 标准SQL:支持大部分标准的SQL语法。

缺点:

  • 并发写入性能:在高并发写入场景下可能成为瓶颈。
  • 单文件限制:数据库大小受文件系统和内存限制,不适合超大型数据集(如TB级)。
  • 功能简化:缺少一些高级数据库功能,如完整的用户权限管理、存储过程等。

SQLite的适用场景

  • 移动应用:Android和iOS应用的本地存储。
  • 桌面软件:程序的配置、用户数据存储。
  • 原型开发:快速测试SQL逻辑,无需搭建完整的数据库服务器。
  • 物联网设备:在树莓派等设备上本地存储传感器数据。

在Python中使用SQLite

Python标准库内置了sqlite3模块。

连接数据库与创建表

import sqlite3

# 连接数据库(如果不存在则创建)
conn = sqlite3.connect('example.db')
cursor = conn.cursor()

# 创建表
cursor.execute('''CREATE TABLE IF NOT EXISTS students
                  (id INTEGER PRIMARY KEY, name TEXT, age INTEGER, grade TEXT)''')
conn.commit()  # 提交更改
print("Table created successfully.")

插入数据

cursor.execute("INSERT INTO students (name, age, grade) VALUES (?, ?, ?)", ('Alice', 20, 'A'))
conn.commit()

查询与过滤数据

# 查询所有数据
cursor.execute("SELECT * FROM students")
all_rows = cursor.fetchall()
for row in all_rows:
    print(row)

# 条件查询
cursor.execute("SELECT * FROM students WHERE grade = ?", ('A',))
a_students = cursor.fetchall()
for student in a_students:
    print(student)

更新与删除数据

# 更新数据
cursor.execute("UPDATE students SET age = ? WHERE name = ?", (21, 'Alice'))
conn.commit()

![](https://github.com/OpenDocCN/cs-notes-zh/raw/master/docs/caltech-cs420-swe/img/6abb0c5c857356614e7323b78c26317a_34.png)

# 删除数据
cursor.execute("DELETE FROM students WHERE grade = ?", ('F',))
conn.commit()

# 关闭连接
conn.close()

我们学习了如何在本地创建和管理数据。最后,我们来看一个用于在不同位置之间高效同步文件和目录的强大工具:rsync。

rsync:高效文件同步工具 🔄

rsync是一个用于在本地和远程系统间同步文件和目录的Linux/Unix工具。它以其高效的增量传输算法而闻名,只传输发生变化的文件部分,并支持断点续传。

rsync的核心优势

  • 高效传输:通过校验和比较,仅同步源和目标之间有差异的部分。
  • 断点续传:在网络中断或故障后,可以从上次中断的地方继续同步。
  • 功能丰富:提供大量选项用于控制同步行为,如保留权限、删除多余文件、试运行等。

基本rsync命令与常用选项

基本命令格式为:rsync [选项] 源路径 目标路径

以下是一些常用选项(“标志”):

  • -a:归档模式,相当于-rlptgoD,递归同步并保留权限、时间戳等。
  • -v:详细模式,输出同步过程中的文件信息。
  • --progress:显示传输进度。
  • -h:以人类可读的格式输出数字(如文件大小)。
  • -n或--dry-run:试运行,模拟同步过程而不实际更改任何文件。
  • --delete:删除目标目录中存在而源目录中不存在的文件,使目标成为源的精确镜像。
  • -z:在传输过程中压缩数据,节省带宽。

rsync使用示例

假设有两个目录:/path/to/source/ 和 /path/to/destination/。

  1. 基本同步(将源目录内容同步到目标目录):
    rsync -av /path/to/source/ /path/to/destination/
    
    注意:源路径末尾的斜杠(/)很重要。有斜杠表示同步源目录内部的内容;没有斜杠则表示同步源目录本身。

  1. 试运行并显示进度:

    rsync -avn --progress /path/to/source/ /path/to/destination/
    
  2. 镜像同步(使目标与源完全一致,删除多余文件):

    rsync -av --delete /path/to/source/ /path/to/destination/
    
  3. 同步后移除源文件(类似于移动操作):

    rsync -av --remove-source-files /path/to/source/ /path/to/destination/
    

rsync是维护文件备份、部署代码或同步多台服务器数据的强大工具。


总结 🎯

本节课中我们一起学习了软件工程中数据管理的几个核心工具:

  1. CSV:简单通用的表格数据存储格式,适合交换和存储结构化数据。
  2. Pickle:Python专属的对象序列化工具,功能强大但需注意安全和兼容性。
  3. JSON:语言无关的轻量级数据交换格式,是Web API通信的标准。
  4. SQLite:轻量级、文件式的SQL数据库,适用于本地存储和嵌入式系统。
  5. rsync:高效的文件和目录同步工具,支持增量传输和多种同步策略。

每种工具都有其特定的适用场景和优缺点。在实际项目中,应根据数据复杂性、安全性要求、跨平台需求以及性能考虑来选择最合适的工具或组合。

011:容器化技术 🐳

在本节课中,我们将要学习容器化技术。这是一种将应用程序及其所有依赖项打包成一个独立、可移植单元的方法。我们将探讨其核心概念、优势、与虚拟机的区别,并重点学习如何使用 Docker 这一流行的容器化工具。

容器化概述

容器化是一种将应用程序的所有依赖项、库和环境变量打包到一个独立可执行文件中的技术。

例如,假设一个人使用 Python 编程,而另一个人只使用 Java。Java 需要 JDK 环境。通过容器化,可以将 Java 代码打包到一个容器中。这样,使用 Python 的人无需安装任何 JDK 或配置环境,就能在自己的机器上运行这个 Java 代码。

容器非常轻量。

容器的优势

以下是容器化技术的一些主要优势:

  • 隔离性:由于所有内容都捆绑在一个独立的容器中,它不需要与容器外的任何东西交互。
  • 一致性:如果容器在我的机器上能运行,那么它在任何其他机器上也能运行。这意味着,我可以将应用程序所需的一切打包到一个“小盒子”里,然后分发到所有需要的机器上。
  • 高效性:容器占用的空间更少,因此效率更高。

容器的应用场景

容器化技术有多种用途。

  • 微服务架构 vs 单体架构:容器主要用于微服务架构,但这种架构成本更高。微服务由许多相互交互的容器组成。这与单体架构不同,单体架构使用一个大型服务器来处理所有事情。对于初创公司来说,单体架构成本低廉,这是一个优势。
  • 持续集成与持续部署:CI/CD 流水线本质上就是一系列相互交互的容器。
  • 易于部署:容器非常轻量,部署起来不费力。
  • 安全性:每个容器都是独立且封闭的。如果一个容器出现问题,可以轻松地用另一个相同的容器替换它。这在大型团队设置中更容易处理,因为如果有一百多个容器中的一个出问题,替换起来很简单。而单体架构则需要查看数千行日志来定位问题。可以将单体架构比作只有一个厨房的餐厅,如果厨房着火,整个餐厅都必须关闭。

单体架构 vs 微服务架构

如上所述,单体架构非常简单,它有一个数据库来控制一切,成本低廉。

而微服务架构由许多微服务组成,每个微服务可以看作一个容器。一个微服务只处理一个特定的功能或任务,非常模块化。它通常有多个不同的数据库进行交互。

虚拟机 vs 容器

虚拟机和容器有所不同。

虚拟机非常占用资源。例如,如果你在 Mac 上尝试运行 Windows 程序,就像在一个操作系统上再运行另一个操作系统。这会消耗大量内存,运行速度非常慢。

容器则很轻量,它不需要另一个操作系统来运行,而是直接与内核协作。内核是连接硬件和软件的桥梁,非常轻量。容器所需的一切都包含在自身内部。

简单来说,虚拟机通过管理程序与硬件交互,而容器通过容器引擎与内核交互。管理程序本质上是一个托管操作系统的数据库。

Docker 简介

Docker 是用于在容器中开发、交付和运行应用程序的平台。

简化来说,它是一个基于云的服务,你可以将代码打包并上传到云端。如果你的朋友想获取你的代码,他可以从 Docker 拉取。Docker 本质上是一个容器化系统。

需要了解的是,Docker 需要镜像。镜像是容器内代码运行方式的蓝图,就像一份食谱说明书。这个镜像将在容器中运行代码。

容器编排

当你构建微服务架构时,规模会迅速扩大。同时处理上百个容器可能不可行,因此需要找到方法来自动化某些操作,例如更新或部署容器。

这就是容器编排的用武之地。有一些工具可以实现这一点,例如 Kubernetes。这些工具帮助自动化容器的部署和删除。例如,如果你的服务用户达到上限,你需要部署更多容器来容纳更多用户,就可以通过容器编排来自动化此过程。

此外还有自愈功能。当出现故障的容器或微服务时,Kubernetes 等程序会自动丢弃故障容器并放入一个新的。

更新和回滚涉及 CI/CD。例如,如果你在本地计算机上开发服务更新,可以将更新推送到云端,系统会自动替换过时的容器并放入新的。

有一些容器编排工具,Kubernetes 可能是最著名的一个。AWS 也提供一系列服务,例如 Fargate 可以自动化容器并处理 CI/CD 流水线;AWS Lambda 是事件驱动编程,意味着它会根据环境变化做出反应,例如网站访问量增加时自动部署更多容器。

AWS 的 Elastic Container Service 基本上是 Amazon 版本的 Docker。还有一些其他服务也处理容器。

Docker 实践入门

Docker 允许你在松散隔离的容器中打包和运行应用程序,让你可以像管理应用程序一样管理基础设施。

Docker 工作原理

Docker 采用客户端-服务器架构。Docker 客户端与 Docker 守护进程交互。

Docker 守护进程是管理所有 Docker 对象的引擎,包括镜像、容器、网络和卷。Docker 客户端是命令行界面,我们通过它传递要执行的命令。这两个组件都安装在你的主机上。但在某些情况下,你需要将 Docker 客户端连接到服务器上的远程守护进程。

Docker 注册表

Docker 注册表是我们存放镜像以便他人拉取的地方。默认的是 Docker Hub,你可以在这里找到任何你想要的镜像。镜像被组织在仓库中。如果你想推送一个镜像,首先需要为该镜像创建一个仓库。还有其他注册表,例如 Amazon Elastic Container Registry 和 Google Container Registry。

Docker Desktop

Docker Desktop 易于安装,是一个适用于任何计算机的简单应用程序。它允许你管理所有镜像、卷和容器。它包含了 Docker 守护进程、Docker 客户端和 Docker Compose。

Docker 镜像

Docker 镜像是一个模板或配方,用于配置你想要运行的容器。它包含运行容器所需的所有文件、环境变量、二进制文件和库。我们使用 Dockerfile 来构建镜像。

镜像有两个重要原则:

  1. 不可变性:一旦创建镜像,就不允许修改它,必须重新创建。
  2. 分层结构:镜像由多个层组成,每一层都是对前一层的修改,层层叠加。最后一层是可写的。

Dockerfile 基础

每个 Dockerfile 都必须以基础镜像开始。我们也可以指定版本。指令通常使用大写,这是一种约定,有助于其他开发者理解代码。

以下是一些常见指令:

  • FROM:指定基础镜像。
  • RUN:执行命令来创建镜像层。
  • ENV:设置环境变量。
  • WORKDIR:设置工作目录,后续命令将在此目录下执行。
  • EXPOSE:暴露端口,以便容器与外部世界交互。
  • ARG:定义构建时可覆盖的参数。

有两种方式指定容器启动时运行的命令:

  • CMD:易于覆盖,只需在运行容器时传递参数即可。
  • ENTRYPOINT:难以覆盖,适用于容器总是以相同命令启动的情况。

Docker 容器

容器是一个运行你的应用程序的隔离进程。我们可以创建、启动或删除容器。

Docker 的一个关键特性是,当容器被移除时,数据不会保存,因此你需要使用持久化存储,例如卷。

容器的生命周期包括:创建容器(或直接运行容器)、暂停容器、停止容器和删除容器。

实践:创建镜像和运行容器

我们将创建一个简单的 Web 应用程序,它从用户获取输入,并在 8080 端口运行。我们需要将容器的端口映射到本地机器的端口。

首先,我们需要一个 Dockerfile。它必须以基础镜像开始,这里我们使用 Python 3。最佳实践是先安装所有依赖项,然后再复制应用程序代码。

以下是一个示例 Dockerfile:

FROM python:3
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]

要构建镜像,在 Dockerfile 所在目录下运行:

docker build -t my-image .

-t 选项用于给镜像命名。

要查看镜像列表,运行:

docker images

要使用此镜像创建并运行容器,并进行端口映射,运行:

docker run -p 8080:8080 my-image

第一个端口是主机端口,第二个端口是容器端口。

要停止容器,首先需要找到容器名称或 ID:

docker ps

然后使用名称或 ID 停止它:

docker stop <container_name_or_id>

要删除容器,运行:

docker rm <container_name_or_id>

使用 docker ps -a 可以列出所有容器(包括已停止的)。

docker run 命令会直接创建并运行容器。如果只想创建容器而不运行,可以使用 docker create 命令。--rm 标志可以在容器退出后自动删除它。如果不指定名称,Docker 会随机生成一个。

与容器交互

如果以后台模式运行容器,你可以将其附加到命令行以查看日志:

docker attach <container_name>

或者使用 docker logs 命令查看日志。

推送镜像到注册表

要将镜像推送到 Docker Hub,首先需要在 Docker Hub 上创建一个仓库。然后,你需要给本地镜像打上标签,标签名应与你的仓库名匹配(格式为 用户名/仓库名)。

docker tag my-image username/repo-name:tag

然后登录 Docker Hub:

docker login

最后推送镜像:

docker push username/repo-name:tag

如果忘记指定标签,默认会使用 latest 标签。

Docker Compose 简介

Docker Compose 用于在同一个主机上运行多容器应用程序环境。它比 Kubernetes 简单,适用于小规模场景。

Docker Compose 是一个运行多容器应用程序的工具。我们使用一个 YAML 格式的配置文件,通常命名为 docker-compose.yml。

Compose 文件有不同的配置层级:

  • 服务:定义容器的抽象配置。
  • 网络:定义容器之间通信的空间。
  • 卷:定义持久化数据存储。
  • 配置:用于运行时或平台的配置。
  • 密钥:用于敏感数据的特定配置。

我们将主要关注服务层级。

在服务层级,你可以指定:

  • build:指定 Dockerfile 的路径。
  • container_name:给容器命名。
  • depends_on:控制服务启动顺序。
  • environment:设置环境变量。
  • image:指定使用的镜像。
  • networks:指定容器加入的网络。
  • ports:进行端口映射。
  • restart:定义重启策略(如 always, no)。

常用的 Docker Compose 命令包括:

  • docker-compose up:启动所有服务。
  • docker-compose down:停止并移除所有服务。
  • docker-compose logs:查看服务日志。
  • docker-compose ps:列出服务状态。

实践:使用 Docker Compose

我们将创建一个包含应用程序和数据库(MySQL)的简单 Web 应用。应用程序将用户输入保存到数据库中。

docker-compose.yml 文件示例:

version: '3.8'
services:
  app:
    build: .
    depends_on:
      - db
    restart: always
    ports:
      - "8080:8080"
    networks:
      - my-network
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: mydb
    networks:
      - my-network
networks:
  my-network:

在包含此文件的目录下运行:

docker-compose up

这将启动应用和数据库服务。由于应用启动时数据库可能尚未完全就绪,我们配置了 restart: always 让应用不断重试。

要检查数据库中的数据,我们可以运行一个 MySQL 客户端容器连接到同一个网络:

docker run -it --rm --network=compose-demo_my-network mysql:5.7 mysql -h db -u root -p

然后输入密码并查询数据。

总结

本节课中,我们一起学习了容器化技术。我们了解了容器如何通过打包所有依赖项来实现环境一致性和隔离性,比较了容器与虚拟机的区别,并深入探讨了 Docker 这一核心工具。我们学习了 Docker 镜像和容器的概念,实践了如何构建镜像、运行容器以及使用 Docker Compose 管理多服务应用。这些知识为在现代软件工程中构建可移植、可扩展的应用程序奠定了基础。

012:持续集成与部署 🚀

在本节课中,我们将学习持续集成与持续部署的核心概念,了解它们如何通过自动化流程提升软件开发效率与代码质量。我们将重点介绍GitHub Actions这一工具,并学习如何构建一个完整的CI/CD流水线。


什么是持续集成与持续部署?

持续集成是一种软件开发实践,旨在通过自动化重复且易出错的任务来提升效率。具体来说,每次我们向共享代码仓库推送代码时,系统都会自动测试或检查代码,以确保它能与仓库中的其他代码或分支顺利集成。

持续部署是持续集成的另一面,它指的是将代码从预生产环境自动部署到生产环境的过程。这一步主要包含三个核心组件:将代码实际发布到生产环境、监控其运行状况以确保正常工作,以及接收关于其运行效果的反馈。

什么是CI/CD流水线?

CI/CD流水线本质上是将持续集成和持续部署两个阶段结合在一起。一个典型的流水线包含以下几个阶段:

  • 源码阶段:这是触发流水线的起点,例如代码推送或拉取请求。
  • 构建阶段:编译和打包你的应用程序。
  • 测试阶段:运行单元测试、集成测试或系统测试。
  • 部署阶段:将应用程序发布到生产环境。

需要注意的是,这些阶段可以根据你的仓库和开发环境的具体需求进行增减或调整。整个流程是一个循环,每次推送代码都应触发测试、构建和部署。

CI/CD流水线的好处

使用CI/CD流水线有诸多好处:

  • 基础设施版本控制:整个团队可以就软件发布所依赖的基础设施达成一致。
  • 更快的发布周期:能够以更快的速度开发和发布软件。
  • 代码质量提升:整个团队共享相同的构建和测试流程,减少了人为错误。
  • 增强团队协作:自动化流程促进了团队成员间的协作。

CI/CD的发展历史

CI/CD的发展经历了几个阶段:

  • 1990年代:手动集成与测试,例如公司定期举行的“合并日”。
  • 2000年代初期:持续集成开始被采纳,但需要在物理服务器上运行自动化构建。
  • 2000年代中期:随着软件开发需求激增,基于物理基础设施的CI服务器面临扩展性差、维护成本高和停机时间长等问题。
  • 2010年代:随着Travis CI和GitHub Actions等云服务的出现,公司无需再管理物理服务器,实现了更轻松的扩展和更自动化的CI/CD管理。

什么是GitHub Actions?

GitHub Actions是GitHub原生的CI/CD工具,它直接集成在GitHub仓库中,用于自动化工作流。我们使用YAML配置文件来定义自动化流程,如构建、测试和部署。

GitHub Actions是事件驱动的,这意味着工作流可以基于GitHub事件(如推送代码、拉取请求)或按计划触发。它高度可定制,功能非常强大。

需要区分两个概念:

  • GitHub Actions:指整个CI/CD平台。
  • Actions:指可在工作流中重复使用的预定义任务包,类似于函数。GitHub提供了内置的Actions,也有一个市场可以获取社区创建的Actions。

工作流的核心组件

一个工作流就是你的CI/CD流水线。它通过YAML文件配置,默认存储在 .github/workflows 目录下。

工作流包含以下核心组件:

  • 事件与触发器:事件是GitHub上发生的具体事情(如向main分支推送代码),触发器是工作流中定义的条件,当检测到特定事件时启动工作流。
  • 任务:任务是在虚拟机上执行的一系列步骤的集合。默认情况下,多个任务并行运行,但可以通过依赖关系让它们顺序执行。
  • 运行器:运行器是执行任务的虚拟机,由GitHub提供。每个任务在独立的运行器上执行,这意味着它们默认是隔离的,不能直接共享数据。
  • 步骤与Actions:步骤组成任务。一个步骤可以是一个shell命令,也可以是一个Action(即一个可复用的预定义任务)。

实践:解读工作流文件

让我们通过一个例子来理解工作流文件。以下是一个简单的工作流:

name: CI Pipeline
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v3
      - name: Build Docker image
        run: docker build -t myapp .
  • 触发器:当向main分支推送代码或发起拉取请求时触发。
  • 任务:定义了两个任务:test和build。
  • 依赖关系:build任务通过needs: test指定依赖于test任务,因此build会在test成功完成后才运行。
  • 步骤:
    • actions/checkout@v3:这是一个Action,用于将仓库代码克隆到运行器。
    • actions/setup-python@v4:另一个Action,用于设置指定版本的Python环境。
    • run:执行shell命令,如安装依赖、运行测试或构建Docker镜像。

演示:构建完整的CI/CD流水线

上一节我们介绍了工作流文件的基本结构,本节我们将通过一个实际演示,来看一个完整的CI/CD流水线如何运作。我们有一个简单的FastAPI任务管理应用,并希望为其添加“删除任务”的功能。

我们的开发流程和对应的GitHub Actions工作流设计如下:

  1. 开发分支:当开发者向development分支推送代码时,触发的工作流会:

    • 运行测试。
    • 构建Docker镜像。
    • 自动创建一个指向main分支的拉取请求。
  2. 主分支:当拉取请求被审核并合并到main分支后,触发的工作流会:

    • 再次运行测试(双重验证)。
    • 构建Docker镜像。
    • 将镜像推送到Docker Hub。
    • 通过SSH连接到AWS EC2服务器,部署新的Docker容器,替换旧版本。

在演示中,我们添加了删除任务的功能和对应测试,将其推送到development分支。工作流自动执行了测试、构建并创建了拉取请求。在人工审核合并后,main分支的工作流被触发,成功完成了测试、构建、推送镜像和部署到生产服务器的全过程。最终,生产环境的应用成功更新,拥有了删除任务的新端点。

这个演示展示了如何利用GitHub Actions实现从代码提交到自动部署的完整自动化流程。

结合其他工具:Kubernetes与ArgoCD

在实际生产环境,尤其是像我们课程项目中需要管理多个树莓派屏幕的场景,CI/CD流水线通常会结合更多工具。我们可以设想以下架构:

  1. GitHub Actions:作为CI引擎,在代码合并到主分支后,构建Docker镜像并推送到镜像仓库。
  2. Kubernetes:作为容器编排工具,管理部署在所有树莓派上的多个Docker容器。
  3. ArgoCD:作为持续交付工具,监控GitHub仓库中一个称为“清单文件”的配置文件。这个文件定义了在Kubernetes集群中应运行什么容器。

工作流程如下:当GitHub Actions更新了镜像并修改了清单文件后,ArgoCD会检测到这一变化,并自动将新的容器配置同步到Kubernetes集群中,从而更新所有树莓派上的应用。这样就实现了大规模、多节点的自动化部署。

总结

本节课我们一起学习了持续集成与持续部署的核心思想。我们了解到CI/CD通过自动化构建、测试和部署,能显著提升开发效率和软件质量。我们重点掌握了GitHub Actions的使用,学会了如何编写YAML工作流文件,并通过一个实际演示看到了完整的自动化流程。最后,我们还探讨了如何将CI/CD与Kubernetes、ArgoCD等工具结合,以应对更复杂的部署场景。掌握这些概念和工具,对于进行现代高效的软件开发至关重要。

posted @ 2026-03-29 09:40  布客飞龙I  阅读(27)  评论(0)    收藏  举报