代码整洁之道学习

目录

第2章 有意义的命名

2.1 介绍

2.2 名副其实

2.3 避免误导

2.4 做有意义的区分

2.5 使用读的出来的名称

2.6 使用可搜索的名称

2.7 避免使用编码

2.8 避免思维映射

2.9 类名

2.10 方法名

2.11 别扮可爱

2.12 每个概念对应一个词

2.13 别用双关语

2.14 使用解决方案领域名称

2.15 使用源自所涉问题领域的名称

2.16 添加有意义的语境

2.17 不要添加没有用的语境

第3章 函数

3.1 短小

3.2 只做一件事

3.3 每个函数一个抽象层级

3.4 switch语句

3.5 使用具有描述性的名称

3.6 函数参数

3.6.1 一元函数的普遍形式

3.6.2 标识参数

3.6.3 二元函数

3.6.4 三元函数

3.6.5 参数对象

3.6.6 参数列表

3.6.7 动词与关键字

3.7 无副作用

3.8 分隔指令与询问

3.9 使用异常替代返回错误码

3.10 别重复自己

3.11 结构化编程

3.12 善用优化

3.13 小结

第4章 注释

4.0 前言

4.1 注释不能美化糟糕的代码

4.2 用代码来阐述

4.3 好注释

4.3.1 法律信息

4.3.2 提供信息的注释

4.3.3 对意图的解释

4.3.4 阐释

4.3.5 警示

4.3.6 TODO注释

4.3.7 放大

4.3.8 公共API中的Javadoc

4.4 坏注释

4.4.1 喃喃自语

4.4.2 多余的注释

4.4.3 误导性注释

4.4.4 循规式注释

4.4.5 日志式注释

4.4.6 废话注释

4.4.7 可怕的废话

4.4.8 能用函数或变量时就别用注释

4.4.9 位置标记

4.4.10 括号后面的注释

4.4.11 归属与署名

4.4.12 注释掉的代码

4.4.13 HTML注释

4.4.14 非本地信息

4.4.15 信息过多

4.4.16 不明显的联系

4.4.17 函数头

4.4.18 非公共代码中的Javadoc

第5章 格式

5.0 前言

5.1 格式的目的

5.2 垂直格式

5.2.1 向报纸学习

5.2.2 概念间垂直方向上的区隔

5.2.3 垂直方向上的靠近

5.2.4 垂直距离

5.2.5 垂直顺序

5.3 横向格式

5.3.1 水平方向上的区隔与靠近

5.3.1 水平对齐

5.3.2 缩进

5.3.4 空范围

5.4 团队规则


第2章 有意义的命名

2.1 介绍

软件开发中命名随处可见,做好命名是必要且重要的。这一章列出了起个好名字应遵从的几条简单规则

2.2 名副其实

变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。

// bad case 
int d; // 意义不明确
// good case 意义明确
int elapsedTimeInDays;
int daysSinceCreation;
...

2.3 避免误导

  • 应当避免使用与本意相悖的词,例如,hp,aix,sco 都不该用作变量名,因为它们都是 Unix 平台或类 Unix 平台的专有名称。
  • 别用 accountList 来指称一组账号,除非它真的是 List 类型。List 一词对于程序员来说具有特定意义。如果包纳账号的容器并非真是一个 List,就会引起错误的判断。所以,用 accountGroup 甚至直接用 accounts 都会好一些。
  • 提防使用不同之处较小的名称,对于相似度高的长的变量名,区分往往会花费较长的时间。例如:XYZControllerForEfficientHandingOfStrings和XYZControllerForEfficientStorageOfStrings
  • 以同样的方式拼写出同样的概念才是信息。拼写前后不一致就是误导。误导性真正可怕的例子,是用小写字母l和大写字母O作为变量名,尤其是在组合使用的时候。
// bad case
int a = l;
if (O == l)
a = O1;
else
l = 01;

2.4 做有意义的区分

  • 如果程序员只是为满足编译器或解释器的需要而写代码,就会制造麻烦。例如,因为同一作用范围内两样不同的东西不能重名,我们可能会随手改掉其中一个的名称,有时干脆以错误的拼写充数,结果就会出现在更正拼写错误后导致编译器出错的情况。例如:因为class已有他用,就给一个变量命名为kclass的做法是极其可怕的
  • 不要以数字系列命名(a1、a2、...、aN)。这样的命名需要结合上下文,甚至上下文也很难给这样的变量名准确的解释。
public static void copyChars(char a1[], char a2[]){
	for (int i = 0; i < a1.length; i++){
  	a2[i] = a1[i]
  }
}
  • 废话是另一种没意义的区分。假设你有一个 Product 类。如果还有一个 ProductInfo 或ProductData类,那它们的名称虽然不同,意思却无区别。Info和Data就像a、an和the一样,是意义含混的废话。
  • 废话都是冗余。variable一词永远不应当出现在变量名中。table一次永远不应当出现在表名中。
// badcase
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();

程序员怎么能知道该调用哪个函数呢?如果缺少明确约定,变量 moneyAmount 就与 money 没区别,customerInfo 与 customer没区别,accountData与account没区别,theMessage也与message没区别。要区分名称,就要以读者能鉴别不同之处的方式来区分”

2.5 使用读的出来的名称

变量命名,做到见名知意,好读。

// bad case
Date genymdhms
// good case
Date generationTimestamp

2.6 使用可搜索的名称

  • 单字母名称和数字常量很难在一大篇文字中找出来,这是一个常见的问题。

  • 单字母名称仅用于短方法中的本地变量。名称长短应与其作用域大小相对应。若变量或常量可能在代码中多处使用,则应赋予其便于搜索的名称。

2.7 避免使用编码

  • 匈牙利标记法。Java是强类型,不需要类型编码

  • 成员前缀。不必使用m_前缀来标明成员变量

  • 接口和实现。推荐接口不要以“I”开头,实现带“Impl”后缀

2.8 避免思维映射

不应当让读者在脑中把你的名称翻译为他们熟知的内容,这种问题经常出现在选择是使用问题领域术语还是解决方案领域术语时。明确是王道。

2.9 类名

类名和对象应该是名词或名词短语。“如Customer、WikiPage、Account和AddressParser。避免使用Manager、Processor、Data或Info这样的类名。类名不应当是动词。

2.10 方法名

  • 方法名应当是动词或动词短语。如:“如postPayment、deletePage或save。属性访问器(accessor)、修改器(mutator)和断言(predicate)应该根据其值命名,并依Javabean标准加上前缀get/set/is。

  • 重载构造器时,使用描述了参数的静态工厂方法名。例如:Complex fulcrumPoint = Complex.fromRealNumber(23.0);通常好于Complex fulcrumPoint = new Complex(23.0);

可以考虑将相应的构造器设置为private,强制使用这种命名手段。这样可以方便了解构造器构造出来的对象

  • 避免混用。如:query/fetch/find/search。应该有一个规范来确定单词的使用。

2.11 别扮可爱

言到意到。别用俗语或俚语。

2.12 每个概念对应一个词

给每个抽象概念选一个词,并且一以贯之。例如,避免使用fetch、retrieve和get来给在多个类中的同种方法命名。

2.13 别用双关语

  • 避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关语了

  • 把代码写得让人能一目了然。代码作者应尽力写出易于理解的代码。

2.14 使用解决方案领域名称

记住,只有程序员才会读你的代码。所以,尽管用那些计算机科学术语、算法名、模式名、数学术语吧。依据问题所涉领域来命名可不算是聪明的做法,因为不该让协作者老是跑去问客户每个名称的含义,他们早该通过另一名称了解这一概念了。

2.15 使用源自所涉问题领域的名称

如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。至少,负责维护代码的程序员就能去请教领域专家了。

2.16 添加有意义的语境

例如:设想你有名为firstName、lastName、street、houseNumber、city、state和zipcode的变量。当它们搁一块儿的时候,很明确是构成了一个地址。

不过,假使只是在某个方法中看见孤零零一个state变量呢?你会理所当然推断那是某个地址的一部分吗?可以添加前缀addrFirstName、addrLastName、addrState等,以此提供语境

2.17 不要添加没有用的语境

只要短名称足够清楚,就比长名称好。别给名称添加不必要的语境。

第3章 函数

3.1 短小

  • 函数的第一规则是要短小。第二条规则是还要更短小。

  • 函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。当然,短小的函数易于阅读和理解。

3.2 只做一件事

  • 函数应该做一件事。做好这件事。只做这一件事。

  • 如果函数只是做了该函数名下同一抽象层上的步骤,则函数还是只做了一件事。编写函数毕竟是为了把大一些的概念(换言之,函数的名称)拆分为另一抽象层上的一系列步骤。

  • 要判断函数是否不止做了一件事,还有一个方法,就是看它是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。

3.3 每个函数一个抽象层级

  • 要确保函数只做一件事,函数中的语句就要在同一抽象层级上。

  • 自顶向下读代码:向下规则。我们想要让代码拥有自顶向下的阅读顺序。我们想要让每个函数后面都跟着位于下一抽象层级的函数,这样一来,在查看函数列表时,就能循抽象层级向下阅读了。

  • 向下规则是保持函数短小、确保只做一件事的要诀。让代码读起来像是一系列自顶向下的TO起头段落是保持抽象层级协调一致的有效技巧。

3.4 switch语句

  • 写出短小的switch语句很难。即便是只有两种条件的switch语句也要比我想要的单个代码块或函数大得多。

  • 写出只做一件事的switch语句也很难。Switch天生要做N件事。

  • 不幸我们总无法避开switch语句,不过还是能确保每个switch都埋藏在较低的抽象层级,而且永远不重复。当然,我们利用多态来实现这一点。

3.5 使用具有描述性的名称

  • 别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。

  • 别害怕花时间取名字。你当尝试不同的名称,实测其阅读效果。使用这些IDE测试不同名称,直至找到最具有描述性的那一个为止。选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。追索好名称,往往导致对代码的改善重构。

  • 命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。例如:includeSetupAndTeardownPages、includeSetupPages、includeSuiteSetupPage和includeSetupPage等。

3.6 函数参数

  • 最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)。

  • 从测试的角度看,尽量避免三参数及以上。参数越多测试覆盖所有可能的值得组合就越多。

  • 参数过多就该封装成参数对象了。

  • 输出参数比输入参数还要难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数中输出。我们不太期望信息通过参数输出。所以,输出参数往往让人苦思之后才恍然大悟。

3.6.1 一元函数的普遍形式

  • 向函数传入单个参数有两种极其普遍的理由。你也许会问关于那个参数的问题,就像在boolean fileExists("myFile")中那样。也可能是操作该参数,将其转换为其他什么东西,再输出它。例如,InputStream fileOpen("MyFile")把String类型的文件名转换为InputStream类型的返回值。

  • 事件(event)。不那么普遍但极其有用的单参数函数形式。在这种形式中,有输入参数而无输出参数。例如:“void passwordAttemptFailedNtimes(int attempts)”

3.6.2 标识参数

标识参数丑陋不堪。向函数传入Boolean值的做法骇人听闻。这样做,方法签名立刻变得复杂起来,大声宣布本函数不止做一件事。

3.6.3 二元函数

有两个参数的函数要比一元函数难懂。我们应该尽量利用一些机制将其转换成一元函数。

3.6.4 三元函数

有三个参数的函数要比二元函数难懂得多。排序、琢磨、忽略的问题都会加倍体现。

3.6.5 参数对象

如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。

3.6.6 参数列表

有可变参数的函数可能是一元、二元甚至三元。超过这个数量就可能要犯错了。

3.6.7 动词与关键字

给函数取个好名字,能较好地解释函数的意图,以及参数的顺序和意图。

3.7 无副作用

  • 副作用是一种谎言。函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动。

  • 它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。

3.8 分隔指令与询问

函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干常会导致混乱。

3.9 使用异常替代返回错误码

  • 从指令式函数返回错误码轻微地违反了指令与询问分隔的规则。它鼓励了在if语句判断中把指令当作表达式使用。这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。

  • 当返回错误码时,就是在要求调用者立刻处理错误。另一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化。

  • Try/catch代码块丑陋不堪。它们搞乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。

  • 函数应该只做一件事。错误处理就是一件事。因此,处理错误的函数不该做其他事。

  • 使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。

3.10 别重复自己

重复的代码容易导致问题,因为代码因此而臃肿,且当算法改变时需要修改的地方会较多。而且也会增加放过错误的可能性。因为我们不总是能记住重复代码的使用地点,代码修改所耗费时间也会增加。

3.11 结构化编程

  • 有些程序员遵循Dijkstra的结构化编程规范。Dijkstra认为,每个函数、函数中的每个代码块都应该有一个入口、一个出口。遵循这些规则,意味着在每个函数中只该有一个return语句,循环中不能有break或continue语句,而且永永远远不能有任何goto语句。

  • 我们赞成结构化编程的目标和规范,但对于小函数,这些规则助益不大。只有在大函数中,这些规则才会有明显的好处。

  • 所以,只要函数保持短小,偶尔出现的return、break或continue语句没有坏处,甚至还比单入单出原则更具有表达力。另外一方面,goto只在大函数中才有道理,所以应该尽量避免使用。

3.12 善用优化

写代码和写别的东西很像。在写论文和文章时,你先想什么写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。

3.13 小结

  • 本章所讲述的是有关编写良好函数的机制。如果你遵循这些规则,函数就会短小,有个好名字,而且被很好地归置。

  • 不过别忘记,真正的目标在于讲述系统的故事,而你编写的函数必须干净利落地拼装到一起,形成一种准确而清晰的语言,帮助你讲故事。

  • 规范不是教我们做什么,它是教我们不要去做什么。

第4章 注释

4.0 前言

  • 什么也比不上放置良好的注释来得有用。什么也不会比乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧、提供错误信息的注释更有破坏性。

  • 注释会撒谎。也不是说总是如此或有意如此,但出现得实在太频繁。注释存在的时间越久,就离其所描述的代码越远,越来越变得全然错误。原因很简单。程序员不能坚持维护注释。

  • 真实只在一处地方有:代码。只有代码能忠实地告诉我们它做的事情。那是唯一真正准确的信息来源。所以,尽管有时也需要注释,我们也该多花心思尽量减少注释量。

4.1 注释不能美化糟糕的代码

带有少量注释的整洁而有表达力的代码,要比带有大量注释的零碎而复杂的代码像样的多。与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。

4.2 用代码来阐述

有时,代码本身不足以解释其行为。不幸的是,许多程序员据此以为,代码很少——如果有的话——能做好解释工作。这种观点纯属错误。只要想上那么几秒钟,就能用代码解释你大部分的意图。

很多时候,简单到只需要创建一个描述与注释所言同一事物的函数即可。

//愿意看到这个
// Check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) &&(employee.age > 65))
//还是这个?
if (employee.isEligibleForFullBenefits())

4.3 好注释

有些注释是必须的,也是有利的。来看看一些我认为值得写的注释。不过要记住,唯一真正好的注释是你想办法不去写的注释。

4.3.1 法律信息

有时,公司代码规范要求编写与法律有关的注释。“// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.”

4.3.2 提供信息的注释

有时,用注释来提供基本信息也有其用处。

// format matched kk:mm:ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

4.3.3 对意图的解释

有时,注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。

4.3.4 阐释

有时,注释把某些晦涩难明的参数或返回值的意义翻译为某种可读形式,也会是有用的。通常,更好的办法是尽量让参数或返回值自身就足够清楚;但如果参数或返回值是某个标准库的一部分,或是你不能修改的代码,帮助阐释其含义的代码就会有用。

4.3.5 警示

有时,用于警告其他程序员会出现某种后果的注释也是有用的。

/**
 * Autogenerated by Thrift Compiler (0.8.0)
 *
 * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
 *  @generated
 */

//code
......

4.3.6 TODO注释

  • 有时,有理由用//TODO形式在源代码中放置要做的工作列表。

  • TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。它可能是要提醒删除某个不必要的特性,或者要求他人注意某个问题。

  • 它可能是恳请别人取个好名字,或者提示对依赖于某个计划事件的修改。无论TODO的目的如何,它都不是在系统中留下糟糕代码的借口。

  • 大多数好的IDE都提供了手段来定位所有的TODO注释,这些注释看来丢不了。你不会愿意代码因为TODO的存在而变成一堆垃圾,所以要定期查看,删除不再需要的。

4.3.7 放大

注释可以用来放大某种看起来不合理之物的重要性。

String listItemContent = match.group(3).trim();
// the trim is real important. It removes the starting
// spaces that could cause the item to be recognized
// as another list.
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

4.3.8 公共API中的Javadoc

没有什么比被良好描述的公共API更有用和令人满意的了。标准Java库中的Javadoc就是一例。没有它们,写Java程序就会变得很难。

4.4 坏注释

大多数注释都属此类。通常,坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正,基本上等于程序员自说自话

4.4.1 喃喃自语

如果只是因为你觉得应该或者因为过程需要就添加注释,那就是无谓之举。如果你决定写注释,就要花必要的时间写出好的注释。

4.4.2 多余的注释

如果读注释花的时间比读代码的长,那么这里的注释就太糟糕了。

4.4.3 误导性注释

4.4.4 循规式注释

所谓每个函数都要有Javadoc或每个变量都要有注释的规矩是愚蠢可笑的。这类注释徒然让代码变得散乱,满口胡言,令人迷惑不解。

4.4.5 日志式注释

  • 在模块开始处添加一条注释。这类注释就像是一种记录每次修改的日志。

  •  很久以前,在模块开始处创建并维护这些记录还算有道理。那时,我们还没有源代码控制系统可用。如今,这种冗长的记录只会让模块变得凌乱不堪,应当全部删除。

4.4.6 废话注释

这类注释废话连篇,我们都学会了视而不见。读代码时,眼光不会在它们上面停留。最终,当代码修改之后,这类注释就变作了谎言一堆。

4.4.7 可怕的废话

仔细阅读下面例子注释,你会发现剪切-粘贴错误。作者在写(或粘贴)注释时都没花心思,怎么指望读者从中获益呢?

** The name. */

private String name;

/** The version. */

private String version;

/** The licenceName. */

private String licenceName;

/** The version. */

private String info;

4.4.8 能用函数或变量时就别用注释

// does the module from the global list <mod> depend on the
// subsystem we are part of?
if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
//可以改成以下没有注释的版本:
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if (moduleDependees.contains(ourSubSystem))

4.4.9 位置标记

如果标记栏不多,就会显而易见。所以,尽量少用标记栏,只在特别有价值的时候用。如果滥用标记栏,就会沉没在背景噪音中,被忽略掉。如:

// Actions //

4.4.10 括号后面的注释

  • 有时,程序员会在括号后面放置特殊的注释,尽管这对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱。

  • 如果你发现自己想标记右括号,其实应该做的是缩短函数。

4.4.11 归属与署名

  • 源代码控制系统非常善于记住是谁在何时添加了什么。没必要用那些小小的签名搞脏代码。

  • 你也许会认为,这种注释大概有助于他人了解应该和谁讨论这段代码。不过,事实却是注释在那儿放了一年又一年,越来越不准确,越来越和原作者没关系。

  • 重申一下,源代码控制系统是这类信息最好的归属地。

4.4.12 注释掉的代码

同上,源代码控制系统可以取代对代码的注释掉,前者可以为我们记住不要的代码。我们无需再用注释来标记,删掉即可。

4.4.13 HTML注释

IDE中的代码本来易于阅读,却因为HTML注释的存在变得难以卒读。

4.4.14 非本地信息

如果你一定要写注释,请确保它描述了离它最近的代码。别在本地注释的上下文环境中给出系统级的信息。

4.4.15 信息过多

别在注释中添加有趣的历史性话题或者无关的细节描述。

4.4.16 不明显的联系

注释及其描述的代码之间的联系应该显而易见。如果你不嫌麻烦要写注释,至少让读者能看着注释和代码,并且理解注释所谈何物。

4.4.17 函数头

 短函数不需要太多描述。为只做一件事的短函数选个好名字,通常要比写函数头注释要好。

4.4.18 非公共代码中的Javadoc

虽然Javadoc对于公共API非常有用,但对于不打算作公共用途的代码就令人生恶了。

第5章 格式

5.0 前言

  • 你应该保持良好的代码格式。你应该选用一套管理代码格式的简单规则,然后贯彻这些规则。

  • 如果你在团队中工作,则团队应该一致同意采用一套简单的格式规则,所有成员都要遵从。使用能帮你应用这些格式规则的自动化工具会很有帮助。

5.1 格式的目的

为了保证代码的可读性,因为现在编写的功能,接下来会被修改,但是代码的格式会一直保留,维护代码的可读性。

5.2 垂直格式

每个文件中的代码行数。

5.2.1 向报纸学习

  • 源文件要像报纸文章一样,名称应该简单并且一目了然,源文件最顶部给出高层次的概念和算法,再依次往下对细节进行展开,直至找到源文件中最底层的细节。

  • 报纸由许多篇文章组成;多数短小精悍。有些稍微长点儿。很少有占满一整页的。这样做,报纸才可用。假若一份报纸只登载一篇长故事,其中充斥毫无组织的事实、日期、名字等,没人会去读它。

5.2.2 概念间垂直方向上的区隔

封包声明、导入声明和每个函数之间都会有空白行隔开,每个空白行都是一条线索,引出新的独立概念,目光会停留在空白处的那一行。尤其是每个函数之间空一行,看起来会非常舒服。

// good case
class temp{
	public A (){
    ……
	}
	public B(){
  	……
	}
}

// bad case
class temp{
	public A (){
		……}
	public B(){
  	……}
}

5.2.3 垂直方向上的靠近

空白分开了概念,靠近则表示紧密关系,因此紧密相关的代码应该互相靠近,注释容易割断两个实体变量间的联系。

// good case
public class ReporterConfig{
	private String m_className;
	private List<Property> m_properties = new ArrayList<Property>();
	public void addProperty(Property property) {
		m_properties.add(property);
	}
}

// bad case
public class ReporterConfig{
	/**
	* The class name of the reporter listener
	*/
	private String m_className;
	/**
	* The properties of the reporter listener
	*/
	private List<Property> m_properties = new ArrayList<Property>();
	public void addProperty(Property property) {
		m_properties.add(property);
	}
}

上述例子中两个变量与一个方法关系紧密,明明一眼就能看出来是关于两个变量和一个方法的类,加了注释之后,距离变远了,需要花费更多的时间才能看出来这是关于两个变量和一个方法的类。

5.2.4 垂直距离

  • 在某个类中摸索,从一个函数跳到另一个函数,想要弄清楚这些函数如何操作,如何互相相关,最后却被搞糊涂了,相信每个人都有这样的经历。

  • 关系密切的概念应该互相靠近,显然这条规则不使用分布在不同文件中,除非有很好的理由,否则尽量不要把关系密切的概念放到不同文件中,这就是避免使用protected变量的理由之一。

  • 变量声明时应尽可能靠近其使用位置,因为函数很短,本地变量应该在函数顶部出现,循环中控制的变量应该总是在循环语句中声明,实体变量应该在类的顶部声明(C++中会有“剪刀原则”,即所有实体变量都放在底部)

  • 相关函数,如果某个函数调用了另外一个,就应该尽量把它们放在一起,而且调用者尽可能放在被调用者的上面。(如checkParam,参数校验方法一般放在需要进行参数校验的方法下面)

5.2.5 垂直顺序

被调用函数应该放在执行函数的下面,读报纸的时候,我们指望最重要的概念先出来,指望以包括最少细节的方式表述它们,我们指望底层细节最后出来。

5.3 横向格式

一行代码有多宽。

5.3.1 水平方向上的区隔与靠近

使用空格字符将彼此紧密相关的事物连接到一起,使用空格字符把相关性较弱的事物分隔开,赋值操作周围需要加上空格字符,函数名和做圆括号不加空格,函数调用括号中的参数使用逗号一一隔开。

private void measureLine(String line) {
	lineCount++;
	int lineSize = line.length();
	totalChars += lineSize;
	lineWidthHistogram.addLine(lineSize, lineCount);
	recordWidestLine(lineSize);
}

空格还有一种用法强调前面的运算法,加减法优先级低,使用空格隔开,乘除法优先级高,不使用空格隔开。

public class Quadratic {
	public static double root1(double a, double b, double c) {
		double determinant = determinant(a, b, c);
		return (-b + Math.sqrt(determinant)) / (2*a);
	}
	public static double root2(int a, int b, int c) {
		double determinant = determinant(a, b, c);
		return (-b - Math.sqrt(determinant)) / (2*a);
	}
	private static double determinant(double a, double b, double c) {
		return b*b - 4*a*c;
	}
}

5.3.1 水平对齐

对齐,像是在强调不重要的东西,把目光从真正的意义上拉开,不需要使用对齐。

5.3.2 缩进

  • 类声明不缩进,类中的方法相对该类缩进一个层级,方法的实现相对方法声明缩进一个层级,代码块的实现相对于其容器代码块缩进一个层级。

  • 缩进的好处:可以从代码左边查看目前在什么范围中,快速跳过与当前无关的范围,例如if和while语句的实现之类。

  • 有时候可能会想在短小的if语句或者while语句违反缩进规则,但最好不要这么做。

5.3.4 空范围

尽量避免while或者for语句的语句体为空。

5.4 团队规则

每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队说了算。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值