《编写可读代码的艺术》读书笔记

1. 写在前面

借着春节放假的几天, 读了下《编写可读代码的艺术》这本书, 这本书不是很长,主要关注代码的一些编写细节,比如方法命名,函数命名,语句组织,任务分解等, 旨在让写的代码更加鲁棒,便于维护以及便于让别人理解。写的还是很不错的,所以这边文章整理一些不错的编码细节。

大纲如下:

  • 1. 写在前面
  • 2. 表面层次的改进
    • 2.1 起名字
    • 2.2 不会误解的名字
    • 2.3 审美
    • 2.4 注释
  • 3. 简化循环和逻辑
    • 3.1 控制流变得易读
    • 3.2 拆分超长的表达式
    • 3.3 变量与可读性
  • 4. 重新组织代码
    • 4.1 抽取不相关的子问题
    • 4.2 一次只做一件事情
    • 4.3 想法变成代码
    • 4.4 少写代码
  • 5. 测试与可读性
  • 6. 小总

2. 表面层次的改进

我们写代码的时候,考虑的最重要的一个原则应当是易于让别人理解的,代码的写法应当使得别人理解它使用的时间最小化, 这是作者书中提到的可读性的基本原理, 提高代码的可读性在实际中非常重要, 我们可以先从表面层次开始。

表面层次包括:选择好的名字、写好的注释、把代码整洁成更好的格式

2.1 起名字

关键思想:

给变量、函数、类起名字的时候, 要把信息装在名字中

介绍了6个常用的小方法:

  1. 起名字的时候,选择比较专业的词,避免"空洞"的次, 名字表达的信息越具体越好

    # 一个反例
    def GetPage(url)# get 这个词没有很多信息,这个方法是从本地缓存中得到页面,还是数据库,还是网页?  get比较宽泛,更专业的词FetchPage or DownloadPage等,更加具体
    

    另外可以找更有表现力的词, 清晰和精确比装可爱好。 比如:send -> deliver,dispatch,announce, find -> search, extract,locate, start -> launch, create, begin, open make -> create, build, add

  2. 好的名字应当描述变量的目的或它所承载的值,少用tmp和retrtval这样泛泛的名字

    # retval 除了表示返回一个值, 没有更多信息
    # 如果想计算平方和, 用sum_squares 比 retval要好很多# tmp这个名字也只应用于短期存在且临时性为主要存在因素的变量
    
  3. 循环迭代器里面往往喜欢用i,j,k这样的变量, 这个在多层循环里面不是个好的主意, 可以加上迭代器变量自身的信息

    for i in range(len(clubs)):for j in range(len(clubs[i].members)):for k in range(len(users)): ....# 换成
    for ci in range(len(clubs)):for mj in range(len(clubs[ci].members)):for uk in range(len(users)):...
    
  4. 具体的名字替代抽象的名字, 比如,假设有一个内部方法叫ServerCanStart(), 检测服务是否可以监听某个给定的TCP/IP端口。 但这个名字有点抽象,换成CanListenOnPort()更具体。

  5. 名字附带更多信息, 比如带上变量格式, 单位,其他重要的信息等

    # 格式
    id -> hex_id# 单位信息
    timestamp -> timestamp_ns
    delay -> delay_secs
    size -> size_mb
    limit -> max_kbps
    angle -> degree_cw# 其他重要信息: 如果变量表示的有需要理解的关键信息, 就把这个信息放到名字里面
    password -> plaintext_password
    comment -> unescapted_comment
    html -> html_utf8
    data -> raw_data
    
  6. 为作用域大的名字采用更长的名字,作用域小的用较短的名字, 使用缩略词和缩写的原则是团队的新成员是否能理解名字的含义, 比如把BackEndManager写成BManager可能更令人费解。

  7. 利用名字的格式来传递含义, 对不同的实体使用不同的格式,比如可以在类成员变量加_区分开普通变量

    # 有目的的使用大小写, 下划线等
    # 常量 AGE = 10
    # 类变量 age_
    # 普通变量  age
    

2.2 不会误解的名字

起名字时, 要考虑下是否会让别人产生误解,比如Filter这个名字, 可能过滤不好的,也可能保留好的,这样的名字直接看就有问题。类似的length, limit等。

这里也介绍了几个小技巧:

  1. 推荐用minmax来表示包含的极限, 即最大和最小取到多少, max_age, min_age
  2. 推荐用firstlast表示包含的范围
  3. 推荐用beginend表示起始位置和终止位置(终止位置的意思是最后一个元素的后一个位置,注意和上面的last区分)
  4. 给布尔值也命名,即赋予逻辑含义,比如is_xxx, has_xxx, can_xxx等,并且最好避免使用反义的名字,比如not_xxx,
  5. 小心用户对特定词的期望, 这个的意思是, 用户一般认为get(), size()这种方法是轻量级的方法, 可以直接索引到或用很简单的方式就能拿到,如果定义了一个方法,里面有比较大的计算量,就避免用get_xxx()来命名, 可以用count_xxx()

2.3 审美

审美主要是包括代码的布局, 这里介绍的一些技巧:

  1. 如果多个代码块作相似的事情, 尝试用同样的格式和布局
  2. 把代码按 “列” 对齐,可以让代码更容易浏览
  3. 如果一段代码中提到A,B和C, 后面就一直保持这个顺序
  4. 用空行把大块的代码,逻辑上分成多个段落

2.4 注释

注释的目的是尽量帮助读着了解的和作者一样多,所以注释的写法也是有讲究的。

  1. 不需要写注释的情况: 从代码本身就能快速推断的事实, 不好的名字

    # 类的定义
    class XXX# 初始化def __init__(self):...# 上面的这种注释没有意义# 如果起的名字不好,不要写注释,而是把名字起好: 好代码 > 坏代码 + 好注释
    
  2. 注释的作用

    1. 记录思想: 比如为什么写成这样而不是那样的理由, 指导性批注, 一些背景知识等
    2. 代码中的瑕疵: 使用TODO, FIX, HACK等进行标记
    3. 常量加注释: 常量的介绍,为什么是这个值等
  3. 站在读者的角度

    1. 预料到代码中哪些部分会让读者看不懂
    2. 为普通读者意料之外的行为加注释
    3. 文件/类的级别上使用"全局观"注释来解释所有的部分是如何一起工作的
    4. 用注释来总结代码块, 使读者不迷失在细节中
  4. 写出言简意赅的注释

    1. 注释保持紧凑, 有很高的信息/空间率
    2. 避免使用it和this这样的代词
    3. 尽量精确的描述函数的行为
    4. 注释中用精心挑选的输入/输出的例子说明
    5. 声明代码的高层次意图,而非明显的细节
    6. 用嵌入的注释(如Function(/*arg=*/...))解释难以理解的函数参数
    7. 用含义丰富的词来使注释更简洁

3. 简化循环和逻辑

改变程序的“循环和逻辑”,可以让代码更有可读性。

一个原则:尽量减少代码中的"思维包袱", 思维包袱越多, 需要考虑复杂并记住更多事情,与容易理解恰好相反。

3.1 控制流变得易读

几种方法:

  1. 写一个比较时(while (bytes_expected > bytes_received)), 把改变的值写在左边并且把稳定的值写在右边会更好

  2. 可以重新排列is/else语句块, 一般先处理正确的/简单的/有趣的情况

  3. 某些变成结果,像三目运算符,do-while循环,goto最后不使用,会导致可读性变差

  4. 嵌套的代码块需要更加集中精力去理解,每层新的嵌套都需要读者把更多的山下文"压入栈", 应该改写成更"线性"的代码

  5. 提早return可以减少嵌套使得代码更加整结, 保护语句(函数顶部处理简单情况)很有用

    def xxx(value):# 保护代码if value == "xxx": return Trueif xxx: return xxxif xxx: return xxx# 逻辑代码...
    

3.2 拆分超长的表达式

几种方法:

  1. 引入"解释性变量",代表较长的子表达式

    username = line.split(":")[0].strip()
    if username == "xxx": ...users_owns_docement = (requests.user.id == document.owner.id)
    if user_owns_docement: ...
    
  2. 德摩根定理操作逻辑表达式 (if (!(a && b)) ⇒ if (!a || !b)

  3. 更复杂的逻辑表达拆分

3.3 变量与可读性

尽量减少变量的数量和让它们"轻量级", 来让代码更有可读性。

  1. 减少变量,即妨碍的变量,并减少中间结果

    now = datetime.datetime.now()
    root_message.last_view_time = now# 这个now在这里就没有意义, 没有拆分复杂的表达式,也没有做更多的澄清,只用过一次
    root_message.last_view_time = datetime.datetime.now()
    
  2. 减少每个变量的作用域, 越小越好,尽量避免使用全局变量,因为修改了可能不知道

  3. 只写一次的变量更好,比如const, final, 常量,使得代码更容易理解,一个变量操作的越多, 就越难确定值。

4. 重新组织代码

在函数级别对代码做更大的改动,使得逻辑更加清晰。

4.1 抽取不相关的子问题

写代码时, 要把和项目不相关的代码单独拿出来, 写成一个工具库供主程序调用。这样可以使得我们关注小而定义良好的问题。

纯工具代码Utils,独立的小模块,独立的小函数等都属于这个范畴, 这样在后期维护(添加功能,改进可读性,处理边界等)变得很容易。

现在我写代码的时候,一般会有utils目录,存放一些工具函数, consts存放一些常用变量等。

4.2 一次只做一件事情

forcus, 在写代码中非常使用,一个组织代码的技巧: 一次只做一个事情。如果有很难读的代码,尝试把它所做的所有任务先列出来,其中一些任务拆分成单独的函数 或 类, 其他的可以简单成为函数中的逻辑"段落", 小事情越具体,越容易拆分,也是很有挑战的一件事情。

4.3 想法变成代码

这里主要是介绍了一个简单的技巧:用自然语言描述程序 然后用这个描述来帮助写出更自然的代码, 这个就是把一个大的问题先拆解开,然后用自然语言去描述,有点像“如何把大象装进冰箱的意思"

4.4 少写代码

最好读的代码就是没有代码, 我们很容易乐观的估计一个粗糙原型所花的时间, 但忘了将来的维护时间和成本, 所以最后的一个建议是,不要过度设计项目,减少不必要的代码,重新考虑需求,解决版本最简单的问题,另外是经常性的阅读标准库的整个API,保持对它们的熟悉程度,不要重复造轮子

每隔一段时间, 花15分钟阅读标准库中的所有函数/模块/类型的名字

5. 测试与可读性

这一部分属于比较精细的话题了,如何写出有效且可读的测试。

几个要点如下:

  1. 每个测试的最高一层应该越简明越好, 最后每个测试的输入/输出可以用一行代码描述

    def CheckXXX(input, expected_output):# 处理逻辑# 最外层的测试函数不要有过多的实现细节output = Getoutput(input)assert (output == expected_output)
    
  2. 错误消息要尽量的有可读性,这样测试失败,可以更快跟踪问题, 比如python测试的时候,可以用unittest的测试库,比较专业

    import unittest
    class MyTestCase(unittest.TestCase):def testFunction(self):a = 1b = 2self.assertEqual(a, b)unittest.main()
    

    手工打造报错信息也可以。

  3. 使用最简单且能够完整运用代码的测试输入

  4. 给测试函数取一个完整描述性的名字,使得每个测试所测到的东西很明确,不要用Test1(), 用像Test_functionname_situation这样的名字

总之,测试要易于改动和增加新的测试。

6. 小总

过年在家, 用了3个晚上, 看完了这本书, 这本书篇幅不是很长, 读完了之后还是受益匪浅的, 里面有一些细节确实之前没有在意, 想用这篇文章大概把这本书的一些方法论整理出来,后面还是得需要实践去巩固,在实践的过程中慢慢的巩固这些方法论吧。

2025年的第一篇博客,新年快乐, 继续加油哇 😉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11148.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

71.在 Vue 3 中使用 OpenLayers 实现按住 Shift 拖拽、旋转和缩放效果

前言 在前端开发中,地图功能是一个常见的需求。OpenLayers 是一个强大的开源地图库,支持多种地图源和交互操作。本文将介绍如何在 Vue 3 中集成 OpenLayers,并实现按住 Shift 键拖拽、旋转和缩放地图的效果。 实现效果 按住 Shift 键&#…

【数据结构】_复杂度

目录 1. 算法效率 2. 时间复杂度 2.1 时间复杂度概念 2.2 准确的时间复杂度函数式 2.3 大O渐进表示法 2.4 时间复杂度的常见量级 2.5 时间复杂度示例 3. 空间复杂度 3.1 空间复杂度概念 3.2 空间复杂度示例 1. 算法效率 一般情况下,衡量一个算法的好坏是…

十分钟快速上手 markdown

前言 本人利用寒假期间,将自己所学的markdown的知识,以及将自己常用的一些操作和注意事项记录下来,希望能够帮助大家 一、markdown是什么 Markdown 是一种轻量级标记语言,说白了就是可以让你利用最简单的语法达到最好的排版效果…

一文讲解Java中的ArrayList和LinkedList

ArrayList和LinkedList有什么区别? ArrayList 是基于数组实现的,LinkedList 是基于链表实现的。 二者用途有什么不同? 多数情况下,ArrayList更利于查找,LinkedList更利于增删 由于 ArrayList 是基于数组实现的&#…

Python 梯度下降法(五):Adam Optimize

文章目录 Python 梯度下降法(五):Adam Optimize一、数学原理1.1 介绍1.2 符号说明1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码2.3 遇到的问题2.4 算法优化 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法(五&a…

【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人

【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人 背景 AI数字人有很多应用目前,本文做如何生成数字人,因为后续就连我们公司自己也会有很多关于AI数字人的使用&#xff0c…

MapReduce简单应用(一)——WordCount

目录 1. 执行过程1.1 分割1.2 Map1.3 Combine1.4 Reduce 2. 代码和结果2.1 pom.xml中依赖配置2.2 工具类util2.3 WordCount2.4 结果 参考 1. 执行过程 假设WordCount的两个输入文本text1.txt和text2.txt如下。 Hello World Bye WorldHello Hadoop Bye Hadoop1.1 分割 将每个文…

tensorboard的基本使用及案例

TensorBoard 是一个可视化工具,用于展示机器学习模型的训练过程和结果。以下是 TensorBoard 的基本使用方法及一些案例。 基本使用 安装 安装 TensorBoard: pip install tensorboard 如果使用 PyTorch,还需要安装 torch 和 torchvision&…

【ArcGIS遇上Python】批量提取多波段影像至单个波段

本案例基于ArcGIS python,将landsat影像的7个波段影像数据,批量提取至单个波段。 相关阅读:【ArcGIS微课1000例】0141:提取多波段影像中的单个波段 文章目录 一、数据准备二、效果比对二、python批处理1. 编写python代码2. 运行代码一、数据准备 实验数据及完整的python位…

吴恩达深度学习——超参数调试

内容来自https://www.bilibili.com/video/BV1FT4y1E74V,仅为本人学习所用。 文章目录 超参数调试调试选择范围 Batch归一化公式整合 Softmax 超参数调试 调试 目前学习的一些超参数有学习率 α \alpha α(最重要)、动量梯度下降法 β \bet…

Alibaba开发规范_编程规约之命名风格

文章目录 命名风格的基本原则1. 命名不能以下划线或美元符号开始或结束2. 严禁使用拼音与英文混合或直接使用中文3. 类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等4. 方法名、参数名、成员变量、局部变量使用 low…

从0开始,来看看怎么去linux排查Java程序故障

一,前提准备 最基本前提:你需要有liunx环境,如果没有请参考其它文献在自己得到local建立一个虚拟机去进行测试。 有了虚拟机之后,你还需要安装jdk和配置环境变量 1. 安装JDK(以OpenJDK 17为例) 下载JDK…

智能园区管理系统助力企业安全与效率双提升的成功案例分析

内容概要 在当今迅速发展的商业环境中,企业面临着资产管理、风险控制和运营效率提高等多重挑战。为了应对这些挑战,智能园区管理系统应运而生,为企业提供了全新的解决方案。例如,快鲸智慧园区(楼宇)管理系…

nacos 配置管理、 配置热更新、 动态路由

文章目录 配置管理引入jar包添加 bootstrap.yaml 文件配置在application.yaml 中添加自定义信息nacos 配置信息 配置热更新采用第一种配置根据服务名确定配置文件根据后缀确定配置文件 动态路由DynamicRouteLoaderNacosConfigManagerRouteDefinitionWriter 路由配置 配置管理 …

Linux-CentOS的yum源

1、什么是yum yum是CentOS的软件仓库管理工具。 2、yum的仓库 2.1、yum的远程仓库源 2.1.1、国内仓库 国内较知名的网络源(aliyun源,163源,sohu源,知名大学开源镜像等) 阿里源:https://opsx.alibaba.com/mirror 网易源:http://mirrors.1…

16.[前端开发]Day16-HTML+CSS阶段练习(网易云音乐五)

完整代码 网易云-main-left-rank&#xff08;排行榜&#xff09; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name&q…

【ts + java】古玩系统开发总结

src别名的配置 开发中文件和文件的关系会比较复杂&#xff0c;我们需要给src文件夹一个别名吧 vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path// https://vitejs.dev/config/ export default defineConfig({pl…

使用Pygame制作“俄罗斯方块”游戏

1. 前言 俄罗斯方块&#xff08;Tetris&#xff09; 是一款由方块下落、行消除等核心规则构成的经典益智游戏&#xff1a; 每次从屏幕顶部出现一个随机的方块&#xff08;由若干小方格组成&#xff09;&#xff0c;玩家可以左右移动或旋转该方块&#xff0c;让它合适地堆叠在…

小程序设计和开发:什么是竞品分析,如何进行竞品分析

一、竞品分析的定义 竞品分析是指对竞争对手的产品进行深入研究和比较&#xff0c;以了解市场动态、发现自身产品的优势和不足&#xff0c;并为产品的设计、开发和营销策略提供参考依据。在小程序设计和开发中&#xff0c;竞品分析可以帮助开发者了解同类型小程序的功能、用户体…

Vue简介

目录 Vue是什么&#xff1f;为什么要使用Vue&#xff1f;Vue的三种加载方式拓展&#xff1a;什么是渐进式框架&#xff1f; Vue是什么&#xff1f; Vue是一套用于构建用户界面的渐进式 JavaScript (主张最少)框架 &#xff0c;开发者只需关注视图层。另一方面&#xff0c;当与…