阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

怎样写出可读性高的代码?

  • 2021-03-29
  • 本文字数:2672 字

    阅读完需:约 9 分钟

怎样写出可读性高的代码?

清楚你的优先级

代码的写法有很多种:有的运行起来很快,有的只会占用少量内存,有的更容易测试,而有的代码则有很高的可读性。


若要编写思路清晰的代码,第一步就是要将可读性放在第一位。


这也意味着势必要降低其他因素的优先级。如果把所有因素都作为最高优先级,就意味着没有优先级。

培养清晰的意识

想要写出好代码,首先要知道什么才是好代码,想要写出思路清晰的代码,也要了解什么才是思路清晰。多阅读一些质量上乘的代码可以让我们对好代码有个大概的认知。


了解什么才是优秀代码并不能杜绝我们继续写出糟糕的代码,但至少能让我们知道代码的哪里不对劲。

修订

编写代码时,我们最初所想的思路未必清晰。在大多数情况下,只有在第一次完成代码后,我们才能找到更适合的思路。反复阅读已完成的代码才会带来更改的空间。

从解释开始

如果我们还搞不清代码结构,那么可以试着想象一下怎样向他人解释清楚或者把逻辑思路写下来,比如“如果删除账户,那么我们需要跳过 xxx。如果 xxx 的进程还没有结束,那么……”。然后把这套逻辑翻译成代码就很顺了。


写程序时,带入人类沟通方式而不是计算机中的抽象概念要更容易。

注释

代码中的注释可以解释某段代码的用处,或者是程序结构为什么要这么写。


单单是阅读程序并不会告诉我们作者所想就是正确的逻辑。里面可能会有我们不了解的商业规则:美国境外的用户有时会把街道名写到地址栏第一行的最末尾。里面也可能有一些技术小技巧:以某种奇怪的方式构造查询,从而让 Postgres 正确地优化它。诸如此类的代码细节,都是只有了解逻辑背后的背景情况下才能彻底明白为什么要这么写的。


代码不会说话。如果我们决定跳过某些步骤,但又懒得留下注释解释为什么,过两天再回来看这段代码恐怕就真没人知道你当时在想什么了。


部分代码可能读两遍就能想明白个中缘由,但为了保险起见,还是不要给自己的大脑添加不必要的负担。

不要搞混层次

不要搞混函数中的抽象层次。


这段“欢迎”代码层次混乱:


def welcome(self):  results = db.query(    'SELECT EXISTS 1 FROM emails WHERE kind = ? AND user = ?',    'welcome_email', self.user.id,  )  if results[0]:    return  self.send_welcome_email()
复制代码


这段则是相对整齐的:


def welcome(self):  if not self.has_sent_welcome_email():    self.send_welcome_email()
复制代码


函数中混乱的抽象层次会让读者思考代码用途和实现方式时被迫进行思维跳跃。当前抽象层次的代码告诉我们代码在做什么,而下一层次的代码则是关于代码要如何实现的。


在例子里的“welcome”函数中,我们首先在数据库中查询是否有过往邮件记录,如果没有则发送一封欢迎邮件。请注意,第二个版本中的“welcome”函数将查询部分放到了另一个函数中,“welcome”中仅仅关注“做什么”,这就是将函数中的抽象层次保持在了同一层,逻辑也更加清晰。不同函数分散在不同抽象层次,将较低层次的实现细节委托给较低抽象层次的函数。

分解函数

有时,分解大体积函数到子函数会更便于阅读。


对于分步骤执行的函数,将函数中的每个步骤都分解成子函数效果会更好。而对于其他如决策类的函数,不同的决策会引向不同的函数:有的部分负责制定决策,有的则是负责执行决策。分解函数的方法有很多种维度,只有通过不断的练习才能一眼看穿哪种才是正确的。


小体积函数有以下几点好处:


  • 每一部分的逻辑都有自己函数名。知道每一块逻辑负责什么更方便我们找到这些函数应当被放在哪

  • 作用域中变量更少

  • 在运行堆栈轨迹和调试时能更清晰地看出函数的作用

  • 小型函数可以被单独测试


其实,没有任何函数计算机也能运行得好好的,函数的存在只是为了服务于程序员,所以还请多多利用它们。

不要分解函数

不要重复你自己(don't repeat yourself, DRY)的意思经常被过度解读。


如今,抽取魔法数常量,以及针对某类特定决策的逻辑副本,已经算是公认的标准答案。此类重复的代码的确不好。而 DRY 的过度解读是指面对区区两行的重复代码,便如临大敌恨不得除之而后快。完全避免任何的重复代码意味着我们最后将面对一堆毫无意义、令人迷惑的代码,其存在只为了防止程序中的两三行重复代码。再加上由于在逻辑上毫不相干的两段代码被迫捆绑在一起,代码也更加难以修改。


判断一段代码的重复是否可容忍很简单:修改 A 段代码,保留 B 段不变,如果程序报错,那么就把 A 和 B 整理到同一段代码;如果无事发生,那么就放着别管。DRY 并不代表我们需要手动压缩代码库,而是为了避免两段代码要依赖于手动的同步。请记住,重复代码和抽象创造并不是同一件事。

避免使用可配置函数

宁可要十个零参数的小函数,也不要一个带十个参数的函数。


诸位对类似的事一定不陌生:初始干净的函数,只在三个不同的地方被调用。而当我们想要在第四处调用时,我们需要做一点小的调整,添加一个参数。但这样第一个 caller 就多了一个新功能,也需要多添加两个可配置的参数。等到第五个用例,我们还要再为它添加独特的参数,以此类推。但反过来我们就又会发现第二个 caller 跑起来太慢了,所以只好再添加另一个参数来跳过部分繁琐的程序。


不知不觉中,我们那个干净整洁的、只负责一件事的函数现在有了五个配置参数,现在能做的事情甚至可以达到 2 的五次方种!


这种情况下,将这一整个复杂的函数拆分成子函数,每个函数只负责各自的事就会好上很多。


但这样以来,又不可避免会出现重复。当这些重复的部分需要保持同步时,我们可以利用 DRY 的思路,将相同的部分抽取到子函数中。这时,做决策和考虑步骤就会容易很多。


请记住,区区几行重复代码是没问题的!像是在不同 list 上跑 for 循环的代码,这类就是可以接受的重复。


这种方法的好处之一是当其中一个用例被删除时,你可以轻松删除掉对应的函数,而不是在复杂函数的逻辑里掘地三尺试图找到对应的选项。只关注某个特定函数的读者也会更容易理解它们的用处。


(注意,当你能负责所有的 caller 时,这种方法才是正确的。如果你的函数只是公共 API 的一部分,那么请不要考虑使用这种方法。因为你并不清楚所有的用例都是什么,也不知道未来会有什么样的用例)

不要过早地进行优化

竞速赛车跑得比普通轿车要快,这点毋庸置疑。但这也是赛车在牺牲了柔软座椅、低噪音,以及车载空调的条件下。如果我们的程序不需要做竞速赛车,那就不要过早地拆掉空调。逐渐熟悉程序的构造,先从编写易于人理解的代码开始,不要一上来就试图挑战计算机的运行速度。


同理,也不应过早开始泛化。没人会在不需要处理大量物品的时候就买入一辆自卸货车,在没有过多需求的时候,我们也不用提前编写多余功能的代码。


原文链接:


[http://jeremymikkola.com/posts/2021_02_02_how_to_write_readable_code.html](

2021-03-29 13:431656
用户头像

发布了 138 篇内容, 共 74.6 次阅读, 收获喜欢 190 次。

关注

评论

发布
暂无评论
发现更多内容

一文读懂Java注解

JFound

Java

谈谈控制感(8):元控制感

史方远

职场 心理 成长

职场提问的“唐太宗”原则

大伟

系统服务构建-BFF 助力前后端分离

图南日晟

php 微服务 BFF

2020年4月北京BGP机房网络质量评测报告

博睿数据

运维 服务器 机房 数据中心 评测

Android | Tangram动态页面之路(七)硬核的Virtualview

哈利迪

android

自我革新最难的是革自己的命

史方远

职场 成长

Java 简介

编号94530

Java jdk java简介 jdk8

unittest框架

Flychen

Python 自动化测试 unittest

写给管理者的睡前故事

石云升

读书笔记 故事 管理者

力扣刷题盛行,风气由何而来?

南湾小猪

刷题

Jenkins:批量自动将 Maven 类型 Job 迁移到自由风格类型

donghui

jenkins

【写作群星榜】本周写作平台优秀作者&文章排名

InfoQ写作社区官方

写作平台 排行榜 热门活动

Android与JS的交互:JsBridge的简单使用

AR7

Java android

学会独立思考的前提

fahsa

自我提升

如何为一家移动游戏公司制定产品策略(严肃长文)

谢锐 | Frozen

游戏出海 手机游戏

【有奖调研】大数据与人工智能从业者有奖需求用研

Apache Flink

大数据 flink 流计算 实时计算

工厂模式——这一篇真够了

大头星

Java 架构 面试 设计模式 工厂模式

现代生活对我们大脑的危害

董一凡

生活质量

G-P-M 调度模型深度解析之手撸一个高性能 goroutine 池

潘建锋

并发编程 协程 Go 语言

用 R 语言打个印咋就这么费事儿呢

张利东

可视化 R

回“疫”录(23):如果岁月可回头

小天同学

疫情 个人成长 回忆录 现实纪录 纪实

Dubbo - 初识Apache Dubbo

Java收录阁

dubbo

系统化服务构建-调用链管理

图南日晟

微服务 全链路监控 链路追踪

【Howe 学 JAVA】断点续传原理精析及简单实现

Howe

Java 断点续传

突然的自我

月白

自我思考

SpringCloud之服务提供者与消费者

北漂码农有话说

从40万美元创业到执掌5500亿美元的帝国,聊聊《苏世民:我的经验与教训》这本书

万佳

读书笔记 商业 苏世民 金融 企业管理

投机者

Neco.W

投机 口罩 头盔 投机者

真香!谷歌终与美国国防部合作,签署百万美金云服务合同

神经星星

云计算 互联网巨头 互联网 谷歌Google

乙己说:LFU实现思路整理

再见小飞侠

缓存 LeetCode

怎样写出可读性高的代码?_语言 & 开发_Jeremy Mikkola_InfoQ精选文章