码农高天
以位运算符替代逻辑与并借反射函数名执行测试不仅严重破坏语义与可读性,更将原本三行循环即可完成的断言膨胀为数十行冗余样板。
AI-assisted summary of 【Code Review】把&当and用可是不行!测试写成这样也有点离谱哦! — original source. Check the original for full context.
Source published:
Key ideas
- 在开源与正式项目中,不建议将包含本地配置的 .vscode 文件夹纳入版本控制提交到代码仓库。 (0:00)
- Python 逻辑判断中切勿用按位与运算符 & 代替 and,即便运行结果偶然一致也会破坏代码语义并误导读者。 (5:00)
- 测试用例将数据抽离到字典后,手写几十个 test_n 函数并用反射获取自身名称反查执行是毫无必要的过度设计,应直接循环遍历断言。 (10:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 2 月 5 日发布。视频简介说明这是 Code Review 系列改版后的新期内容,旨在通过审查粉丝编写的时间处理工具 date_range_prefix,指出可以进一步优化与提升代码水平的地方。
## TL;DR 本期视频对粉丝编写的日期前缀生成工具进行了系统性代码审阅。高天首先指出了项目管理与接口设计的不足,包括不应在仓库中提交 .vscode 私有配置、单字母变量命名易混淆,以及输入输出日期分隔符不一致的问题。接着重点批评了在 Python 中错误使用按位与运算符 & 代替逻辑运算符 and、深层分支缺乏行内注释和封装度不足等可读性坏味道。最后剖析了测试文件中用反射获取函数名、反查字典并机械重复几十个测试用例的“离谱”设计,演示了如何用简单的循环重构大幅精简测试代码。
## Atmosphere & style 整体节奏轻快且信息密度极高,告别了过往直播的漫长拖沓,直奔代码痛点。高天的讲解既保持了资深程序员的技术敏锐度,又带着亲切幽默的吐槽风格,尤其在面对用黑科技反射反查测试用例的奇葩写法时展现出哭笑不得的真实反应。整场审阅兼顾工程严谨性与趣味性,让观众在会心一笑中直观体会到代码语义与规范的重要性。
## Bulletin - [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) 在开源与正式项目中,不建议将包含本地配置的 .vscode 文件夹纳入版本控制提交到代码仓库。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) Python 逻辑判断中切勿用按位与运算符 & 代替 and,即便运行结果偶然一致也会破坏代码语义并误导读者。 - [10:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=600s) 测试用例将数据抽离到字典后,手写几十个 test_n 函数并用反射获取自身名称反查执行是毫无必要的过度设计,应直接循环遍历断言。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) 编辑器私有配置(如 .vscode 文件夹)不应上传至版本控制,应保持公共仓库干净整洁。 - [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) Docstring 中的示例排序应具备递进逻辑,建议从基础场景(按天/整月)过渡到大粒度(整年)再到缺省参数展示。 - [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) 避免使用 s、e 等单字母变量名混淆起止,也不建议直接覆盖原变量名引起类型变化,推荐使用 start_dt 等具名变量。 - [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) 避免无意义或易引起歧义的缩写,如将结果列表命名为 rng 极易被误读为随机数生成器(Random Number Generator)。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 严禁在 Python 条件判断中滥用 & 代替 and,保留清晰明确的布尔逻辑语义并减少不必要的括号嵌套。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 第三方库返回元组切片缺乏可读性时(如 calendar.monthrange(...)[1]),应封装为具名辅助函数(如 month_end_date)。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 将长生成逻辑拆出 range 赋予临时变量 days 能够有效避免长单行代码破坏可读性。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 长逻辑分支与深层嵌套应补充行内注释,避免读者在相隔数十行的 if-else 之间来回翻找上下文。 - [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 警惕分支下的恒成立条件判断,当下游生成器已自带边界处理(如空列表)时应精简冗余判断。 - [10:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=600s) API 接口设计应保持输入与输出格式的一致性或兼容性,避免输入短横线却只输出斜杠带来的认知负担。 - [10:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=600s) 基于表格驱动测试时,切忌使用反射获取当前函数名反查字典的黑魔法,简单清晰的循环断言才是更优解法。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=0s) 仓库结构、函数设计与参数规范
Code Review 系列正式改版回归,由以往的直播形式改为节奏更快、信噪比更高的视频录制方式,并在知识星球中提供报名和要点总结。本次 Review 的项目是粉丝编写的小工具 `date_range_prefix`,目标非常清晰:根据输入的起止日期生成一个前缀日期列表。
在仓库的根目录中,作者上传了核心 Python 源码、测试文件、LICENSE、README、`.gitignore`,以及一个包含本地测试配置的 `.vscode` 文件夹。针对 `.vscode` 目录的提交,高天给出了明确的工程规范建议:“在这里我个人是强烈建议不要上传这个东西的……一般来说我们建立 repo 是不太会把这个.vscode 文件夹传上来的。”虽然个人在多台机器间同步配置尚可理解,但在正规或较大型的开源项目中,不应将编辑器私有配置纳入版本控制。
```mermaid flowchart LR accTitle: date_range_prefix 核心逻辑流程 accDescr: 展示函数从参数解析、单位判断到格式化输出的整个处理分支
subgraph Input["输入解析"] A["输入 start & end"] --> B{"参数校验 & 默认值"} B --> C["转换为 datetime (S & E)"] end
subgraph Logic["区间粒度判断"] C --> D{"同一年?"} D -->|"是"| E{"同一月?"} D -->|"否"| F["跨年处理:掐头去尾 + 整年补全"] E -->|"是"| G["整月/按天展开"] E -->|"否"| H["跨月处理:首尾月 + 递归补全"] end
subgraph Output["结果聚合"] G --> Out["返回 RNG 日期列表"] H --> Out F --> Out end ```
进入代码正文,函数实现了一段 docstring,但英文说明读起来并不容易理解。好在作者提供了详尽的调用示例(例如输入 `2023-01-30` 到 `2023-03-01`,输出 `['01/30', '01/31', '02', '03/01']`),直观展示了函数的核心逻辑——以天为基础粒度表达时间段,遇到整月或整年时进行聚合折叠。然而,示例中已经暴露了格式不一致的疏漏(原本应为斜杠却写成了横杠)。针对函数开头的默认参数与变量命名,高天指出了几个需要重构的点:
- **示例排序改进**:建议先展示“按天/整月”,接着是“整年”,最后放“缺省参数(默认当天)”,层层递进更易阅读。 - **默认参数处理**:未提供 `start` 时默认为 `today`,未提供 `end` 时默认为 `start`,判断逻辑使用了 `is None`,写法规范。 - **避免单字母变量名**:代码将字符串转为 datetime 对象后命名为 `s` 与 `e`。应避免使用单字母命名,以防后续读代码时混淆起止;同时也不建议直接覆盖原变量名引起类型变化,推荐使用 `start_dt` 这种具名变量。 - **避免无意义的缩写**:返回值列表被命名为 `rng`,极易被误读为随机数生成器(Random Number Generator)。在不需要极度压缩长度的地方应写清楚,例如直接使用 `date_range` 或 `result`。
---
[05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 位运算符误用、封装与代码可读性问题
在核心分支逻辑中,代码根据是否同一年、同一月分别进入不同处理路径。当起止日期在同一月且刚好覆盖整月时,代码出现了一个典型的 Python 语法与语义误区——使用按位与运算符 `&` 代替逻辑运算符 `and`。
高天强调了语义在编程中的核心地位:“大家千万不要在 Python 里用这个 `&` 符号来代替 `and`,这是非常错误的一种写法啊。我很多次跟大家强调语义是有意义的,就哪怕你运行的结果是没错的,你展示出来的别人理解的第一项,它如果看到的是 `and`,他就非常明确的知道这是一个逻辑与;但是他看到你这个 `&` 符号,他就会想一下你这块是不是做了一些比较 tricky 的事情。”不仅如此,改成 `and` 后由于运算符优先级明确,连外层冗余的圆括号也可以一并拿掉。
此外,代码在该段逻辑还存在以下重构与可读性优化空间:
- **第三方库调用的封装(Wrapper)**:计算月份天数时直接调用了 `calendar.monthrange(...)[1]`,代码长且下标 `[1]` 缺乏语义。读者很难立刻确定 `[0]` 和 `[1]` 谁是起始日、谁是结束日。建议封装成一个名为 `month_end_date` 之类的辅助函数。 - **长单行(One-liner)与临时变量**:非整月提取天数时,作者将 `range` 单独提取赋给 `days` 变量再拼装列表,这种拆分临时变量的做法值得肯定,避免了单行代码可读性恶化。 - **长逻辑与嵌套分支缺乏注释**:从第 52 行开始的跨月递归补齐逻辑过长,作者全篇除 docstring 外完全没有行内注释。尤其到第 60 行突然出现一个 `else`,读者需要向上翻近 20 行才能对应到第 41 行的判断(年份是否相等)。如果在分支处加上简单注释说明分支场景,阅读负担会大幅降低。 - **恒成立的冗余条件判断**:在第 63 行处理年份时,判断条件本意是检查两年份差值是否大于 1(效仿月份的处理),却误写成了判断是否相等;而在当前分支下年份必然不相等,导致判断恒成立。事实上,下方的 `range` 生成器在无差值时会自动产生空列表,该 `if` 完全可以移除或补充说明直接使用的理由。
---
[10:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=600s) API 输入输出格式设计与离谱的测试用例写法
在探讨完实现细节后,高天指出了该工具在接口设计上的体验问题:用户输入的是短横线格式(`-`),输出的日期列表却是斜杠格式(`/`),甚至作者自己在顶部的注释中都写错混淆了。这充分说明不同日期分隔符极易引起困惑。因此,函数应当优化输入兼容性,使接口能够同时容纳和识别横杠与斜杠。
代码审阅的最后一部分落在了测试文件上。测试文件采用了基于表格的测试风格(Table-based Test),将用例数据与断言逻辑分离,原本是非常规范优秀的实践;但其具体的执行方式却十分离谱:
```mermaid flowchart TD accTitle: 冗余测试用例执行流程 accDescr: 展示测试用例使用反射获取自身函数名并反查字典的低效重复设计
subgraph Data["测试数据集"] Cases["cases 字典 (包含测试数据 1 到 N)"] end
subgraph BadDesign["作者实现的冗余测试方法"] T1["test_1()"] --> G1["get_func_name()"] T2["test_2()"] --> G2["get_func_name()"] TN["test_n()..."] --> GN["get_func_name()"] end
G1 -->|"反射拿名字匹配 key"| Cases G2 -->|"反射拿名字匹配 key"| Cases GN -->|"反射拿名字匹配 key"| Cases
subgraph CorrectDesign["推荐的重构方案"] Loop["一个简洁的循环遍历 cases 字典并断言 (3行搞定)"] end ```
作者把所有数据抽离到了一个字典中,却在下方手写了几十个一模一样的测试函数(如 `test_1`、`test_2`、`test_3`……)。每一个测试函数内部没有任何独立逻辑,而是调用黑魔法函数 `get_func_name` 获取当前测试自身的名字,再拿着名字去数据字典里反查对应用例并执行。
面对这种实现,高天哭笑不得地指出:“我就完全无法理解他为什么要这么写了……你的每一个测试都是差不多的,为什么要把每一个测试单独的变成一个 test 呢?关键这些 test 也就叫 test 1 2 3 4 5 6 7,又不是你把它分门别类的归类到了一种 test 里。这个写法就是非常非常没有意义的,他们明明用这么三行代码就可以解决的问题,他写了好几十行。关键这还来了一个非常黑科技的函数 `get_func_name` 去拿到我当前运行的这个函数的名字,这是一件完全没有必要的事情。”整个测试框架写得冗长且毫无必要,只需用一个简单的循环遍历测试数据集进行断言即可优雅解决。
## Notable quotes > [05:00](https://www.youtube.com/watch?v=ERosfjjY40Y&t=300s) 大家千万不要在 Python 里用这个 & 符号来代替 and,这是非常错误的一种写法啊。 — 高天
## Mentioned 高天, date_range_prefix, Python, VS Code, GitHub, 知识星球
## Video description 其他Code Review:https://www.youtube.com/playlist?list=PLSo-C2L8kdSNr5yUJYhyDArnM4FU9iG1S
Code Review是提升代码水平最快的方式之一,这期视频我们来看一个处理时间的代码,看看他哪里写的还可以更好!
本次repo:https://github.com/randomseed42/date_range_prefix
[Source](https://www.youtube.com/watch?v=ERosfjjY40Y)