编写可读代码的艺术:初·代码审美

2017-04-16  本文已影响101人  sunnyaxin

前言:人生有很多第一次,作为一只不谙世事的小程序媛,第一次踏出校门,开始实习,感触良多,其中,颇为震撼的一点是,原来一直给人印象邋里邋遢的程序猿们,竟然在写代码上有那么多讲究,原来科学看待程序媛的方法真的不是“以貌取人”,而是“以代码取人”。本文为《编写可读代码的艺术》一书的读书笔记,并加上一些个人理解,用于记录,也用于分享共勉。为了编写可读代码,本书主要从四个方面对代码进行提升改进,此文主要记录第一部分,即更多的是对代码表明的一些改进,如名字命名,注释的书写等,并建立代码审美意识。之后还会有同系列的文章用来继续记录后面三部分内容。


编写可读代码何以称作艺术?

相信不少道行尚浅的小程序猿们如我一样,在技术的海洋里混乱遨游之际,只追求写出能够运行的代码,然后窃喜以为已然顿悟,但过一段时间回来再看,即使是自己的亲儿子代码,依旧不知所云。为什么呢?这里就要说到代码对人的友好性和可读性,优秀的代码具备良好的可读性,编写的代码要使其他人(或者一段时间后的自己)能在最短的时间内理解才行。写代码不仅要在乎宏观架构和整体设计,同时也要注重细节,可以说每个程序员每天更多接触的是是代码的细枝末节,有时一个不小心整个工程就会在一个不起眼的小错误中纠结度过。因此,这样看来,编码,不仅仅是一种技术,也是一门艺术

如何编写可读代码

  1. 简化命名、注释和格式的方法,使每行代码都言简意赅。
  2. 梳理程序中的循环、逻辑和变量来减小复杂度并理清思路。
  3. 在函数级别解决问题,例如重新组织代码块,使其一次只做一件事。
  4. 编写有效的测试代码,使其全面简洁,同时可读性更高。

起名字的学问

写代码如同写文章,每个函数每个变量都会有一个名字,以便需要时进行调用,好的名字能够让人一目了然,将信息装到名字里,把名字当做一条小小的注释,能够使得代码更加精简清晰。那么如何起名字,才能携带更多的信息呢?

1.选择专业的词

eg:GetPage(url)函数用于获取页面
Q:从本地缓存获取?从数据库获取?从互联网获取?
---> 从互联网获取:FetchPage()或者 DownloadPage()

2.找到更有表现力的词

可以从同义词中寻找,多思考。

单词 更多选择
send deliver, dispatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new
3.避免像tmp和retval这样泛泛的名字
4.用具体的名字代替抽象的名字

eg:ServerCanStart() 函数,检测服务器是否可以监听某个给定的TCP/IP端口
Q: 名字太抽象,范围大
---> CanListenOnPort() 直接描述方法做的事情

5.为名字附带更多信息
情形 变量名 better变量名
一个“纯文本”格式的密码,需要加密后才能进一步使用 password plaintext_password
一条用户提供的注释,需要转义后才能用于显示 comment unescaped_comment
已转换为 UTF-8 格式的 html 字节 html html_utf8
以“uml方式编码”的输入数据 data data_urlenc
6.利用名字的格式来传递含义

对下划线、连字符和大小写的使用方式可以把更多的信息装到名字中。对不同的实体使用不同格式就像语法高亮显示的形式一样,能够帮助更容易阅读代码。例如:

格式 意义
CamelCase 表示类名
lower_separated 表示变量名
kConstantName 表示常量
MACRO_NAME 表示宏
offset_ 表示类成员变量

名字应该有多长

  1. 在小的作用域里可以使用短的名字。
    如果一个标识符有较大的作用域,那么它的名字就要包含足够的信息以便含义更清楚。
  2. 长名字也不用担心------编辑器上自动补全功能。
  3. 首字母缩略词和缩写的使用。
    原则:团队新成员能够理解名字含义。
  4. 丢掉没用的词
    eg: ConvertToString() ---> ToString()

准确命名(没有歧义)

  1. 使用 min 和 max 来表示(包含)极限
    eg:CART_TOO_BIG_LIMIT 是个二义性名字导致“大小差一”问题
    ---> MAX_ITEMS_IN_CART 表示包含极限的最大值

  2. 用 first 和last 来表示包含的范围


  3. 用 begin 和 end 来表示包含/排除范围


4、给布尔值命名
(1)通常来讲,加上像 is、has、can 或 should 这样的词,可以把布尔值变得更加明确
eg:bool read_password = ture;
Q: 需要读取密码?已经读取了密码?
---> need_password 或者 user_is_authenticated
(2)避免使用反义名字
eg:bool disable_ssl = false;
---> bool use_ssl = true;

代码审美-组织代码

三条原则:
(1)使用一致的布局,让读者很快就习惯这种风格;
(2)让相似的代码看上去相似;
(3)把相关的代码行分组,形成代码块;

整洁的代码能够提升浏览速度,和使用程度,相反如果代码结构凌乱,很影响代码的阅读和进一步重构。根据三条原则,可以总结出以下几条提高代码审美的Tips:

  1. 重新安排换行来保持一致和紧凑。
  2. 用方法来整理不规则的东西。如果多个代码块做相似的事情,尝试让他们有同样的剪影。
  3. 需要时使用列对齐。按列对齐可以让代码更加容易阅读。
  4. 选一个有意义的顺序,始终一直使用它。如:变量定义的顺序,如果一段代码中提到 A、B、C,那么另一段中使用同样的顺序。
  5. 把声明按块组织起来。不要把所有的方法都放到一个巨大的代码块中,应当用空行按逻辑把他们分组。
  6. 个人风格与一致性:一致的风格比“正确”的风格更重要。

注释的使用

什么地方需要注释?

注释的目的是尽量帮助读者了解的和作者一样多。当程序员写代码时,脑海中会有很多有价值的信息;当其他人阅读代码时,这些信息已经丢失,所见到的只是眼前的代码。

什么地方不需要注释?

(1)能从代码本身中迅速推断的事实。
(2)不要给不好的名字加注释-----应该把名字改好:一个好的名字比一个好的注释更重要,因为在任何用到这个函数的地方都能看到它。也就是好代码>坏代码+好注释

注释作用:记录你的思想

记录写代码时有过的重要想法。以下几种情况建议写注释:

  1. 对于为什么代码写成这样而不是那样的内在理由。
    eg:防止做无谓的优化而浪费时间; 解释为什么代码不那么整洁;
  2. 给常量加注释。是什么?为什么是这个值?通过阅读注释,有了调整这个值的指南。但有些常量不需要注释,名字本身已经很清楚了(eg: SECONDS_PER_DAY)
  3. 为代码中的瑕疵写注释。即代码需要改进时,或代码没有完成时。
标记 通常的作用
TODO 还没有处理的事情
MAYBE_LATER 方法有次要的缺陷
FIXME 已知的无法运行的代码
HACK 对一个问题不得不采用的比较粗糙的解决方案
XXX 危险!这里有重要的问题
加注释:站在读者的角度

想象代码对于外人来讲看起来是什么样子的,这个人并不熟悉项目,这对于发现什么地方需要注释尤其有用。

  1. 用注释总结代码块,不致迷失在细节中 ---> 在包含大块的长函数中使用。
  2. 在文件/类的级别上使用“全局观”注释 ---> 熟悉代码库。解释所有的部分是如何一起工作的,迅速了解代码,比自己读源代码快很多。
//这个文件包含一些辅助函数,为我们文件系统提供了更便利的接口
//它处理了文件权限及其他基本的细节。

写注释的学问

  1. 注释保持紧凑
  2. 避免使用不明确的代词,如 it,this
  3. 润色粗糙的句子
  4. 精确地描述函数的行为
    eg:统计行数 ----> 统计换行符(/n)
  5. 用输入/输出例子来说明特别的情况
  6. 声明代码的意图:表达写代码时的想法,不是描述字面上的意思
  7. 使用嵌入的注释:大多数函数不需要,这样可以方便紧凑地解释看上去难以理解的参数
    eg:Connect(10, false); --->
    Connect(/*timeout_ms = */ 10, /*use_encryption = */ false);
  8. 用含义丰富的词来使注释简洁:如果感觉一段注释太长了,可以使用一个典型的编程场景来描述。
    eg1:
// This class contains a number of members that store the same information as in the 
// database, but are stored here for speed. When this class is read from later, those
// members are checked first to see if they exist, and if so are returned; otherwise the 
// database is read from and that data stored in these field for next time.

可以简单的说:

This class acts as a caching layer to the database.

eg2:

// Remove excess whitespace from the street address, and do lots of other cleanup
// like turn "Avenue" into "Ave." This way, if there are two different street addresses
// that are typed in slightly differently, they will have the same cleaned-up version and 
// we can detect that these are equal.

可以简单的说:

Canonicalize the street address (remove extra spaces, "Avenue" -> "Ave.", etc.)
上一篇下一篇

猜你喜欢

热点阅读