高天审查NVDA插件代码 厘清assert与raise语义边界

【Code Review】raise还是assert?降低重复代码?给视障人士的支持! — 码农高天

码农高天

代码中选择assert还是raise应取决于语义而非运行优化模式,程序逻辑正确时不应发生的情况须用assert断言,外部不可控的运行时故障方适用raise。

AI-assisted summary of 【Code Review】raise还是assert?降低重复代码?给视障人士的支持! — original source. Check the original for full context.

Source published:

Key ideas

  • 码农高天对一个为视障屏幕阅读器 NVDA 切换国内镜像源的开源插件进行了 Code Review,并联动 B 站投币活动向 NVDA 官方组织发起公益捐款。 (0:00)
  • 在防御性编程与错误处理中,高天澄清了 `assert` 与 `raise` 的选择标准在于语义而非工具开关:`assert` 应用于代码正常时不应发生的内部不变量破坏,而 `raise` 用于即使逻辑正确也可能出现的外部不可控异常。 (5:00)
  • 针对插件核心函数 `swap_url` 和退出清理逻辑中的重复代码,高天提出了提取公共执行逻辑与利用参数化开关合并函数等重构方案。 (7:20)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2024 年 9 月 4 日发布。视频内容源自高天对其知识星球用户编写的 NVDA 镜像源切换插件进行的 Code Review,旨在通过具体代码探讨重构技巧,并呼吁公众关注与支持服务于视障群体的开源屏幕阅读软件 NVDA。

## TL;DR 高天以一个 NVDA 镜像切换插件为例,深入分析了 Python 项目中的代码规范与重构思路。他首先梳理了命名风格冲突时的权衡策略,以及如何合理界定模块级常量的范围。随后针对关键的 URL 替换函数,他探讨了分支缺失的问题,并从语义学角度厘清了 `raise` 与 `assert` 的根本差异——`assert` 用于声明不应发生的不变量,而 `raise` 用于处理即便代码无误也可能出现的外部不可控异常。最后,高天演示了如何提取分支中的公共重构逻辑,并通过增加可选参数将高度雷同的缓存清理操作合并复用。

## Atmosphere & style 视频整体呈现出一种沉稳专业且兼具人文关怀的技术探讨氛围。高天延续了其一贯从容、循循善诱的讲解风格,没有生硬地套用教条,而是从实际工程体验出发剖析语法与结构取舍。伴随着对视障群体与开源精神的呼吁,技术审查不仅停留在代码技巧层面,更带有一种务实且温和的公益温度。

## Bulletin - [00:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=0s) 码农高天对一个为视障屏幕阅读器 NVDA 切换国内镜像源的开源插件进行了 Code Review,并联动 B 站投币活动向 NVDA 官方组织发起公益捐款。 - [05:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=300s) 在防御性编程与错误处理中,高天澄清了 `assert` 与 `raise` 的选择标准在于语义而非工具开关:`assert` 应用于代码正常时不应发生的内部不变量破坏,而 `raise` 用于即使逻辑正确也可能出现的外部不可控异常。 - [07:20](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=440s) 针对插件核心函数 `swap_url` 和退出清理逻辑中的重复代码,高天提出了提取公共执行逻辑与利用参数化开关合并函数等重构方案。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=0s) NVDA 是一款开源免费的视障屏幕阅读器软件,完全依赖社区捐款支持开发运作。 - [01:15](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=75s) 在参与既有开源项目时,若原有代码库自身的命名风格不统一,开发者无需过度纠结,依赖 IDE 自动补全即可。 - [02:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=120s) 在模块级别仅被赋值一次的量,即使是通过计算得出,也应作为常量并遵循全大写加下划线的命名规范。 - [02:30](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=150s) 核心功能函数如 `swap_url` 的命名应当明确具体职责并封装进插件类中,同时明确声明返回值的作用。 - [03:30](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=210s) 在版本判断逻辑中缺失 `else` 分支会导致隐式返回 `None`,应改为显式处理或提前校验返回,消除歧义。 - [05:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=300s) 选择 `assert` 还是 `raise` 的核心判断依据是语义,而非 Python 的 `-O` 优化跳过机制。 - [05:20](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=320s) `assert` 适用于假设程序编写正确情况下绝不应该触发的内部断言,用于保证内部逻辑一致性。 - [05:40](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=340s) `raise` 适用于即使代码完全无误,也会因外部输入或无法控制的外部环境而不可避免发生的异常。 - [06:40](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=400s) 在多版本分支判断中,应尽量只在分支内配置差异化的变量与模块,将后续共同操作提取到分支外部统一执行。 - [07:20](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=440s) 文件与缓存清理逻辑应直接复用已知路径变量,避免通过文件路径反射查找父目录的多余操作。 - [07:40](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=460s) 针对主体逻辑完全一致、仅存在个别额外校验步骤的清理函数,应通过引入配置参数进行合并抽象以消除代码冗余。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=0s) NVDA 插件背景与命名风格

本期 Code Review 的代码来自知识星球的一位用户,他编写了一个用于 NVDA 的小插件来切换国内镜像源。码农高天首先向观众普及了 NVDA:它代表 Non-Visual Desktop Access(非视觉桌面访问),并不是显卡厂商英伟达,而是一款完全开源且免费的屏幕阅读软件,能通过语音帮助视障人士了解屏幕内容。由于支撑该软件开发的唯一经济来源是社区捐助(donation),高天发起了一项公益联动:在视频发布一周内,B 站视频每获得 100 个投币,他就会向 NVDA 组织捐赠 1 澳元(约合 5 元人民币,即每个投币 1 澳分),以此呼吁大家关注视障人士这一特殊群体。

```mermaid flowchart TD accTitle: NVDA Plugin Lifecycle accDescr: Global plugin initialization swapping URLs and termination reverting them. subgraph InitStage ["Plugin Init (init)"] A["Save original URL constant"] --> B["Replace with mirror URL"] B --> C{"API Version Check"} C -->|"Version Meets Requirement"| D["Call swap URL function"] end subgraph TerminateStage ["Plugin Terminate (terminate)"] E["Restore original URLs"] --> F["Delete Cache Directories"] end InitStage -.->|"On NVDA Exit / Reload"| TerminateStage ```

进入代码分析,插件的核心任务是替换 NVDA 主程序中的部分下载网址,使国内用户可以顺畅使用镜像源下载。阅读代码开头时,高天指出了一些值得探讨的代码风格问题: - **特殊的第三方模块与命名风格**:导入的几个模块在本地常规 Python 环境中并不存在,且采用了 Python 中较少见的 `lowerCamelCase`(小驼峰)命名,这属于 NVDA 原生代码的历史包袱。 - **变量风格不一致的应对**:模块下方声明常量与版本号时,又切换回了小写下划线风格。当面临“已有项目的代码风格与自身习惯冲突”时,高天认为通常应优先遵循现有代码库风格;但如果原有代码自身风格就不统一,开发者便“想怎么写怎么写,不用太纠结”,毕竟现代 IDE 的补全提示已经非常完善。 - **常量的定义范畴**:“对于这种整个 module 只赋值一次的量,哪怕它是计算得出的,你也可以把它当做常量看待。”高天强调,只要赋值后不再改变,即便依赖其他数据计算而来(例如当前 API 版本号或 JSON 文件路径),也应当遵循常量的规范使用全大写字母加下划线命名。

---

[02:30](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=150s) 函数职责与 `swap_url` 的防御性逻辑

插件的本体结构遵循了 NVDA 的规范,通过继承 `globalPlugin` 类,在初始化(`init`)时执行 URL 替换,在退出前(`terminate`)将原本的 URL 恢复并清理缓存。但在具体实现细节上,代码存在不少模糊地带:

原插件定义了一个全局函数 `swap_url`,用来替换不同 API 版本下的网络 Base URL,同时重新初始化 Data Manager 并清理缓存。高天指出: 1. **命名过于宽泛**:初次阅读时很难一眼看出函数具体替换了什么、返回了什么。建议将其命名为更具针对性的名字(如表明是替换 network base URL),并建议直接作为方法封装进 `globalPlugin` 中,附带注释说明会返回“被替换前的旧 URL”。 2. **缺失的 else 分支**:在 `swap_url` 内部,代码仅写了 `if` 和 `elif` 对应不同的版本号判断,完全没有 `else` 分支。虽然外层调用前做过前置版本校验,使得进入函数时必然满足某一分支,但高天直言:“就你看着它,你并不知道如果这个 Current API Version 太小了应该发生什么事。他现在的结果是 return None 对吧,这个事不是不能接受,但是如果你真要这么写的话,你最好把它写的显式化。”

若要改善这种模糊性,有两种处理方向:一种是若版本过低就显式提前 `return None`,调用方省去外层校验直接通过返回值是否为 `None` 来处理;另一种是将其定位为一个强假设函数——版本不足时压根不准调用该函数。

---

[05:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=300s) `raise` 与 `assert` 的语义之争及代码去重

针对前述函数前置条件未满足的情况,代码应选用 `assert` 还是 `raise`,往往在社区中引发激烈争论。有人主张 Python 在开启优化选项(`-O` 模式运行)时会跳过 `assert` 检查,因此不应使用 `assert`。高天纠正了这一误区,指出工具开关并不是决定语法的根本标准:

“一个地方用 assert 还是用 raise,核心考量是语义。如果这个地方在我程序编写正确的情况下压根就不应该出现,那用的就是 assert,他传达的语义是这件事不应该发生。但如果这个地方的情况是我程序就算写的没有问题,它依然有可能因为种种原因发生,同时这个事我还解决不了,这块就应该用 raise。”

为了说明两者的差异,高天举例说明: - **应使用 `raise` 的场景**:比如除法中除数为零,这并非由程序自身逻辑 Bug 引起,而是由无法预料的外部用户输入所致,程序在正常运行下无法计算,此时必须 `raise` 异常。 - **应使用 `assert` 的场景**:函数内部对内部调用的前置条件做防御,在调用栈闭环、代码逻辑正确时不应该被触发的分支,使用 `assert` 声明内部不变量最为切合语义。

```mermaid flowchart TD accTitle: Refactoring Redundant Code in swap_url accDescr: Moving common post-replacement logic outside the API version branch. subgraph BeforeRefactor ["Before: Redundant Code"] direction TB B1{"API Version Branch"} B1 -->|"Branch A"| BA1["Set Mod A Base URL"] --> BA2["Re-init Data Manager"] --> BA3["Clear Cache & Return URL"] B1 -->|"Branch B"| BB1["Set Mod B Base URL"] --> BB2["Re-init Data Manager"] --> BB3["Clear Cache & Return URL"] end subgraph AfterRefactor ["After: Extracted Common Logic"] direction TB A1{"Early Validation"} -->|Version Check| A2{"Select Target Module"} A2 -->|"Branch A"| AM1["Module A Config"] A2 -->|"Branch B"| AM2["Module B Config"] AM1 --> AC["Unified Execution: Re-init Data Manager + Clear Cache + Return Old URL"] AM2 --> AC end ```

除了语义判断,`swap_url` 中还存在明显的重复代码:在 `if` 和 `elif` 两个分支中,只有开头提取和替换 URL 的具体模块名和属性有细微差别,后续重新初始化 Data Manager、清理缓存以及返回旧 URL 的操作完全一致。高天建议通过重构提炼公共操作,在分支内部只做区分性的变量赋值,随后统一执行公共逻辑。这样既消除了冗余,又能在开头对非法版本做“早期返回(early return)”或异常断言,让代码的执行主线清晰易读。

---

[07:20](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=440s) 缓存删除逻辑的复用与抽象

在插件清理缓存的代码实现中,高天指出了两处可以进一步优化和重构的设计:

| 关注代码段 | 现存设计问题 | 优化方案与建议 | |:--- |:--- |:--- | | `delete_addon_store_cache` | 通过把文件名与路径拼接成 JSON 完整路径,读取后再通过路径反射找其父文件夹并删除 | 直接复用上文已经拼装好的存储目录变量,避免反复从文件反推父目录,逻辑更清晰直接 | | `terminate` vs `delete_addon_store_cache` | 退出函数中清理缓存的逻辑与专门的删除缓存函数高度雷同,仅多了一道检查条件 | 增加参数(如 `check_mirror=True`)统一为一个函数,独立抽离“检查镜像是否匹配”的逻辑 |

高天对比了 `delete_addon_store_cache` 函数与 `terminate` 阶段的缓存清理代码,发现两者做的事情几乎完全一致:定位 JSON 缓存、读取内容、按条件删除目录。唯一的差异在于是否需要额外校验当前的 URL 是否确实为镜像源地址。

针对这种高度相似的代码,高天给出的重构方案是避免复制代码,而是在原有函数中增加一个默认形参(例如 `check_mirror=True`)。如果不传参,则默认先检查镜像地址是否存在于该 JSON 中;如果明确不需要检查,则直接执行删除操作。通过把“检查 Mirror URL 是否存在”抽象为一个辅助判定或可配置开关,两个函数的主要删除逻辑便可以合并通用。

视频最后,高天再次呼吁大家多关注视障人士以及视障程序员这个相对特殊且需要支持的群体,并提醒观众视频投币将会全额转化成捐款支持 NVDA 项目的长期开源开发。

## Notable quotes > [00:00](https://www.youtube.com/watch?v=vbF5M1L2SnU&t=0s) 对于这种整个 module 只赋值一次的量,哪怕它是计算得出的,你也可以把它当做常量看待。 — 码农高天

## Mentioned NVDA, 码农高天, Python, B站, 知识星球, NV Access

## Video description Code Review是提升代码水平最快的方式之一,这期视频我们review一个NVDA的插件。代码比较简单,但是也有不少可以讨论的地方。更重要的是,希望大家了解视障人士这个群体。(投币捐款的活动是在B站做的,已经结束啦,大家就看code review就好)

本次代码:https://github.com/hwf1324/NVDAUpdateMirror/blob/feature-API-Update/addon%2FglobalPlugins%2FupdateMirror.py 支持NVDA:https://www.nvaccess.org/support-us/

[Source](https://www.youtube.com/watch?v=vbF5M1L2SnU)

中文简报

高天审查NVDA插件代码 厘清assert与raise语义边界 代码中选择assert还是raise应取决于语义而非运行优化模式,程序逻辑正确时不应发生的情况须用assert断言,外部不可控的运行时故障方适用raise。 高天以一个NVDA镜像切换插件为例,深入分析了Python项目中的代码规范与重构思路。他首先梳理了命名风格冲突时的权衡策略,以及如何合理界定模块级常量的范围。随后针对关键的URL替换函数,他探讨了分支缺失的问题,并从语义学角度厘清了raise与assert的根本差异——assert用于声明不应发生的不变量,而raise用于处理即便代码无误也可能出现的外部不可控异常。最后,高天演示了如何提取分支中的公共重构逻辑,并通过增加可选参数将高度雷同的缓存清理操作合并复用。 码农高天对一个为视障屏幕阅读器NVDA切换国内镜像源的开源插件进行了代码审查,并联动B站投币活动向NVDA官方组织发起公益捐款。 (0:00) 在防御性编程与错误处理中,高天澄清了assert与raise的选择标准在于语义而非工具开关:assert应用于代码正常时不应发生的内部不变量破坏,而raise用于即使逻辑正确也可能出现的外部不可控异常。 (5:00) 针对插件核心函数swap_url和退出清理逻辑中的重复代码,高天提出了提取公共执行逻辑与利用参数化开关合并函数等重构方案。 (7:20)

Reading summary

码农高天通过对一款 NVDA 镜像源切换插件的代码审查,指出开发中应根据语义本质区分 raise 与 assert,并示范了如何通过消除分支冗余与参数化抽象来精简代码逻辑,同时呼吁公众关注视障辅助软件的开源生态。 公益背景与项目代码风格规范 (0:00) 本期审查的代码为开源屏幕阅读软件 NVDA 的国内镜像切换插件。NVDA 作为非视觉桌面访问工具,依靠社区捐款维系开发,高天为此发起视频投币与向 NVDA 捐款(每 100 投币捐 1 澳元,约合 5 元人民币)的公益联动,以支持视障群体。在代码风格层面,针对原生模块中小驼峰命名与现代 Python 习惯冲突的问题,高天建议若旧代码风格不统一则无需过度纠结,但在模块级别赋值后不再改变的量,即便由计算得出,也应视作常量并使用大写下划线命名。 防御性编程与接口显式化设计 (2:30) 在插件核心的 URL 替换逻辑中,全局函数 swap_url 存在命名宽泛、职责不明确的问题,缺乏直观指明其替换 network base URL 并返回旧 URL 的注释与封装。此外,该函数在处理不同 API 版本时仅编写了 if 与 elif 分支,完全缺失 else 处理。高天指出,尽管外部已有前置版本校验,但内部逻辑若不显式处理版本过低的情形,会导致隐式返回 None。改进方案应在设计上二选一:要么将其改为版本不满足时显式 return None,调用方省去外层校验;要么将其严格定义为强假设函数,禁止低版本进入。 raise 与 assert 的语义边界界定 (5:00) 针对未满足调用前置条件时应选用 raise 还是 assert 的社区争议,高天纠正了“因 Python -O 优化模式跳过断言而弃用 assert”的认知误区,强调核心选择依据是语义。assert 用于声明程序编写正确时不应该出现的内部不变量;而 raise 则用于处理程序逻辑完全正常、但由于不可控的外部输入(如除法运算中除数为零)导致无法解决的异常情况。针对内部调用栈闭环的非法情况,使用 assert 契合防御性语义。 分支公共逻辑提炼与缓存删除重构 (7:20) 针对 swap_url 中不同 API 版本分支内部重复执行的数据管理器重构、缓存清理和返回操作,高天演示了提取公共逻辑的重构方案,使各分支仅负责关键变量的差异化赋值,主干逻辑统一执行。在缓存清理逻辑方面,delete_addon_store_cache 函数通过反推父目录删除路径的方式不够直观,应直接复用已有存储目录变量;同时,该函数与退出阶段 terminate 的缓存删除高度重复,只需增加如 check_mirror=True 的默认参数,将镜像匹配检查抽离为配置开关,即可实现两处清理逻辑的统一复用。 代码审查不仅关乎局部的语法正确性,更在于通过明确的语义选择与抽象降低维护成本。高天在完成技术重构建议的同时,再次提醒开发者与观众关注视障人士与视障程序员的需求,共同支持开源辅助软件的持续发展。

Source quotes

[0:00] 码农高天: 对于这种整个 module 只赋值一次的量,哪怕它是计算得出的,你也可以把它当做常量看待。

Original transcript

[0:00] 我们这次的code review 依然选择的是一位知识星球用户的代码他写了一个NVDA的小插件去切换这个镜像源在做code review之前我想先介绍一下NVDANVDA不是英伟达啊它是一款帮助视障人士阅读电脑的软件它的全称是Non-Visual Desktop Access它可以通过声音帮助视障人士了解他们正在浏览的内容值得一提的是这块软件是完全开源并且免费的而支撑他们开发的唯一经济来源就是donation捐助那这期视频呢我也尽一个绵薄之力啊从视频发布开始的一周之内这个视频没有一个投币我就会捐赠1澳分到NVDA这个组织也就是每100个投币捐1澳元啊差不多是5块钱人民币钱不算多啊 主要是希望更多人可以了解到这个特殊的弱势群体并且在自己力所能及的范围之内帮助这个群体好我们来进入正式的code review就像我之前介绍的这段代码呢是一个NVDA的插角它的核心是替换NVDA主程序里面的一些网址让中国的NVDA用户可以从镜像源去进行下载前面这个部分的impress没什么问题接下来这里呢大家可能看着就比较怪了首先是因为这几个module在我们当前环境下是不存在的其次这几个猫都采用了python里面比较少见的小驼风的命名方式那当然这些是NVDA开发者的选择啊我们也没有办法下面是一些长量啊网址包括一些版本的长量那接下来这个部分呢他的命名又变成了小写下滑线这里可能就是很多人面临的一个比较纠结的问题就是如果现在已经存在的代码风格和我习惯的代码风格不符怎么办一般来说都是遵循现有的代码风格当然我们可以看到他现有的代表风格本身也不一致对吧那遇到这种情况呢我们就不用太纠结了啊想怎么写怎么写毕竟现在ID的提示还挺全的不过这里说一下啊对于这种整个module只复值一次的量,哪怕它是计算得出的,你也可以把它当做常量看待。就只要这个值你复值之后不改了哪怕它依赖其他的数据它依然可以是一个常量所以这个Current API Version跟这个Json File Path都可以是大写效果件好那我们先来看这个插件的本体啊global plugin这里应该是这个NVDA插件要求的格式大概意思呢应该是调用这个插件的时候呢会调用这个引力弹数结束之前保证调用一下这个terminate他这里要完成的功能其实还是比较简单的就是把这个update check module里面的常量给变一下他把原来这个常量先保存起来然后把它换成我想要的常量那如果version大于议定版本,它就做一个swap URL这个swap URL我们等会看啊也是一个变URL的方式在Termin这边呢他做了一个相反的操作换回来然后删掉了cash整体逻辑非常好赌啊我们来抠一下细节首先就是swap U2L函数你在第一眼看上去的时候你是不容易猜到它的返回值是什么东西的这个函数名本身呢起的也是过于宽泛了那我们看一下这个函数到底在干什么我们看这个函数实际上就是替换了这个Network的base URL但是针对不同的版本它的module名字包括他module里面的常亮名字都有一个小变化在替换之后,他需要重新initialize这个Data Manager,然后还要清掉cache最后他返回的是那个被替换的URL那这里我的第一个意见可能是把这个函数名换一下Beauty can use your swapped network based URL.这样就很清晰你换的是什么的院啊第二呢我会推荐把这个函数当成一个method写进global plugin然后这边如果有注视稍微说明一下我会把之前的URL返回会更好那在逻辑上有两点需要注意的首先我们看这个函数里面有一个if和elf它是没有else case的那这个else case可能出现吗我们可以看到在这里调用之前他先检查这个API version是不是大于等于这个required private API version因此在进入这个函数的时候这个if跟alif必然有一个会成立但是在这个函数里面它是不明确的就你看着它你并不知道如果这个Current API Vision太小了应该发生什么事他现在的结果是return run对吧这个事不是不能接受,但是如果你真要这么写的话,你最好把它写的显示化。也就是你可以写成这个样子如果API version太低了我就直接返回一个NUN什么都不干那与之对应的这个部分就可以直接swap然后判断返回值是不是难不用提前去检查Current API Version是不是大了但是呢还有一种可能性就是我这个函数如果被调用我默认 [5:00] 一定是大的也就是说如果你version不到你就不应该调用我这个函数那这个时候呢就两种选择第一个前面加Rsurp然后考虑把这个Lif变成else当然也可以在else case里面 assert一个false无论哪种形式都是要显示的告诉读这段代码的人你不应该在API version不够的情况下调用我这个函数好,在上次我们聊到这个问题的时候,有人就在争论说这块应该是用assert还是应该用race基本上提到这个assert在python开优化的时候是不会触发的这个事没错但是一个位置用race还是assert并不是由他决定的一个地方用assert还是用raise,核心考量是语意如果这个地方在我程序编写正确的情况下压根就不应该出现那用的就是assert他传达的语意是这件事不应该发生但如果这个地方的情况是我程序就算写的没有问题它依然有可能因为种种原因发生同时这个事我还解决不了这块就应该用race比如说在除法中除数为零的情况这就跟我们程序写的有没有bug没有关系它是因为用户的输入导致的我们没有办法计算这种时候用race所以这个部分怎么处理要看你对swap url这个函数的定位究竟是这个Current API version到了才有可能调用这个函数还是说调用这个函数的时候有可能Current API Version不到我需要报个错当然也可能是我们最开始说的那种情况如果API version不够就直接返回一个nunt或者返回一个constraint好 这是我们说的这个if else逻辑的问题他这里还有另外一个问题就是我们所说的重复代码我们可以看到这两段代码里只有前面的部分是不一样的后面的部分是一模一样的那在这种情况下啊我们就应该尽量避免把这个代码写两遍这样会增强它的可读性我们把代码变成这样大家看是不是就更好读了非常明确的可以看到根据API version的不同 我们做的不同的事是什么 相同的事是什么当然这个代码跟之前是不等价的对吧不等价的部分就在于那个else case但是又回到我们刚才讨论那个问题了对吧这API version 到底是怎么算我们完全可以在最开始的时候判断一下这个API version比如我们可以把代码写成这个样子对吧这个就是尽早判断特殊情况 要么就是返回一个特殊值 要不然就是报错好 那我们接下来看一下这个Delete Add-on Store Cache这个函数呢写的还是挺直观的如果这个JSONFile存在,把它打开读一下检查UIL如果满足一定条件把整个文件夹删除这里写的基本没有问题啊唯一的一个小点呢是我有可能会把这个部分换成上面的那个add-on store字大家看这个JSON文件呢本来是两个路径拼起来的你下面呢又去找这个J桑文件所在的文件夹感觉可能直接用上面的文件夹更清晰一些我们稍微记一下函数的样子然后我们看一下下面这个terminate函数我们看一下Termin函数这个部分是不是非常眼熟它同样在Delete Measure Cache。这段代码和上面非常的相似唯一的区别在于是不是多做一个检查那遇到这种情况 我们更常见的方式应该是在上面那个函数里面增加一个argument比如我们可以在这个函数里面增加一个argument check mirror 等于 true如果不带argument调用就是要检查一下这个mirror是否存在否则的话就见识就身这样我们可以把中间这个部分单独抽象出来它就是检查这个Mirror URL是不是在当前这个JSON里而剩下的部分他们两个是可以通用的好了 今天这段视频的代码量并不多但是还是希望对大家能有所帮助更重要的是呢希望大家了解视障人士啊尤其是视障人士程序员这个比较特殊的群体不要忘了你的每一个投币都会化成一份爱心送到NVDA的开发者手中进而帮助更多的市长人士好在这一边这里喜欢的话记得点赞关注别忘了一键三雷哦优优独播剧场——YoYo Television Series Exclusive