代码优雅之道

有意义的命名

有意义的命名

	软件中随处可见命名。我们给变量、函数、参数、类和包命名。既然有这么多命名要做,
	不妨做好它。下面理出了取个好名字的几条简单规则。

名副其实

	名副其实说起来简单。但需要强调,这事很严肃。选个好名字要花时间,但省下来的时间比花掉的多。而且一旦发现有更好的名称,就应该换掉旧的。这么做,读你代码的人(包括你自己)都会更开心。
	变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那么旧不算是名副其实。
	int d; // 资产数量,按类型统计
	名称d什么也没说明。它没有引起对资产的感觉,更别说按类型统计了。我们应该使用指明了计量对象和计量方式的名称:
	int assetCountByType;
	选择体现本意的名称能让人更容易理解和修改代码。下列代码的目的何在?
	public List<Integer[]> getThem(List<Integer[]> theList) {
		List<Integer[]> list1 = new ArrayList<>();
		for (int[] x: theList) {
		    if (x[0] == 4)
		        list1.add(x);
		}
		return list1;
	}
	代码里面没有复杂的表达式,空格和缩进中规中矩,变量数量很少,但你能理解这段代码要做什么事吗?
	问题不在于代码的简洁度,而是在于代码的模糊度:即上下文在代码中未被明确体现的程度。上面的代码要求我们了解一下问题的答案:
	1. theList中是什么类型的东西?
	2. theList零下标条目的意义是什么?
	3. 值4的意识是什么?
	4. 我怎么使用返回的列表?
	问题的答案没体现在代码段中,可那就是它们该在的地方。
	比方说,我们在开发一个扫雷游戏,我们发现盘面名为theList的单元格列表,那就将其改名为gameBoard。盘面上每个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值,而该状态值为4表示“已标记”。只要改为有意的名称,代码就会得到相当程度的改进:
	public List<Integer[]> getFlaggedCells(List<Integer[]> gameBoard) {
	    List<Integer[]> flaggedCells = new ArrayList<>();
	    for (int[] cell: gameBoard) {
	        if (cell[STATUS_VALUE_INDEX] == FLAGGED)
	            flaggedCells.add(cell);
	        }
	    return flaggedCells;
	}
	代码几乎保持不变,但代码变的明确多了。
	还可以更进一步,不用int数组表示单元格,而是另写一个类。该类包括一个名副其实的函数(名称为isFlagged),替代那个魔法数。

public List getFlaggedCells(List<Integer[]> gameBoard) {
List flaggedCells = new ArrayList<>();
for (Cell cell: gameBoard) {
if (cell.isFlagged())
flaggedCells.add(cell);
}
return flaggedCells;
}
只要简单改一下名称,就能轻易之道发生了什么。这就是选用好名称的力量。

避免误导

	我们必须避免留下掩藏代码本意的错误线索。应当避免使用与本意相悖的词。例如,hp、aix和sco都不应该用作变量名,因为它们都是UNIX平台的专有名词。
	别用accountList来指代一组账号,除非它真的是List类型。如果包纳账号的容器并非List,就会引起错误的判断。所以accountGroup或accounts都会好一些。
	提放使用不同之处较小的名称。想区分模块中某处的controllerForHandlingOfStrings和另一处的controllerForStorageOfStrings会花多上时间呢?这两个词外形实在太相似了。
	以同样的方式拼写出同样的概念才是信息。拼写前后不一致就是误导。

做有意义的区分

	若我们只是为满足编译器或解释器的需要而写代码,就会制造麻烦。例如,以数字系列命名(a1、a2、…aN),这样的名称纯属误导,完全没有提供导向作者意图的信息。试看:
	public static void copyChars(char a1[], char a2[]) {
		for (int i =0; i < a1.length; i++) {
			a2[i] = a1[i];
		}
	}
	如果参数名改为source和target,这个函数就会像样很多。
	废话时另一种没有意义的区分。假设你有一个Product类。如果还有一个ProductInfo或ProductData类,那么他们的名称虽然不同,意义却无区别。
	废话就是冗余。Variable一次永远不应当出现在变量名中,table永远不应当出现在表名中。NameString会比Name更好吗?难道Name会是一个浮点数?
	要区分名称,就要以读者能鉴别不同之处的方式来区分。

使用可搜索的名称

	单字母名称和数字常量有个问题,就是很难在一大段文字中找出来。
	找MAX_WEEK_DAYS很容易,但想找到数字7就麻烦了,它可能是某些文件名称或其他常量的一部分。
	同样,e也不是个便于搜索的好变量名。它是英文中常用的字母,在每个程序中都有可能出现。由此可见,**长名称胜于短名称**,搜得到的名称才是有效的命名。
	单字母名称仅用于短方法中的本地变量。**名称长短应与其作用域大小相对应**。若变量或常量可能在代码中多处使用,则应赋予其便于搜索的名称。再比较:
	for (int j = 0; j < 34; j++) {
		s += (t[j]*4)/5;
	}
	和
	int realDaysPerIdeaDay = 4;
	int WORK_DAYS_PER_WEEK = 5;
	int sum = 0;
	for (int j = 0; j < NUMBER_OF_TASKS; j++) {
		int realTaskDays = taskEstimate[j] * realDaysPerIdeaDay;
		int realTaskWeeks = (realDays / WORK_DAYS_PER_WEEK);
		sum += realTaskWeeks;
	}
	采用能表达意图的名称,貌似拉长了函数代码,但要想想看,WORK_DAYS_PER_WEEK要比数字5好找的多,而且也更好体现作者的意图。
	
	注意:不要添加没用的语境。只要短名称足够清楚,就要比长名称好。别给名称添加不必要的语境。

避免使用编码类型

	编码类型已经太多,不要再给我们自找麻烦了。把类型编进名称里面,徒然增加了解码的负担。尤其是Java语言不需要编码类型。对象是强类型的,代码编译环境已经在编译开始前就侦测到类型的错误。所以,编码类型增加了修改变量、函数或类的名称或类型的难度。增加了阅读代码的难度,有时更会造成误导。
	PhoneNumber phoneString; // 类型变化时,名称并没变化。造成歧义
	
	也不必用m_之类的前缀来标明成员变量。因为人们很快会无视前缀(或后缀),只看名称中有意义的部分。最终前缀变成了废料。
	
	有时也会出现采用编码的特殊情况。比如,你在做一个创建形状的抽象工厂,该工厂是个接口,要用具体类来实现。你怎么来命名工厂和具体类呢?IShapeFactory和ShapeFactory吗?我喜欢不加修饰的接口。前导字母I被滥用了,而且容易产生误导。如果接口和实现必须选一个来编码的话,我选择实现。ShapeFactoryImpl比对接口名称编码来的好。

每个概念对应一个词

	给每个抽象概念选一个词,并且一以贯之。例如使用fetch和get来给多个类中的同种方法命名。你怎么记得住哪个类中是哪个方法呢?
	add、insert、append命名的函数之间是否有区别?
	对于那些会用到你代码的人,一以贯之的命名法就是福音。

最后的话

	取好名字最难的地方在于需要良好的描述技巧和共有文化背景。这已经不属于技术或管理问题。
	多数时候我们并不记忆类名、方法名。我们使用IDE对付这些细节,好让自己集中精力于把代码写得像词句文章。
	你不妨试试这些规则,看看你得代码可读性是否有所提升。如果你在维护别人的代码,使用重构工具来解决问题。效果会立竿见影的

函数

代码清单

在这里插入图片描述
在这里插入图片描述

短小

在这里插入图片描述

只做一件事

在这里插入图片描述

每个函数一个抽象层级

在这里插入图片描述

使用描述性的名称

	函数越短小、功能越集中,就越便于取个好名字。
	别害怕长名称。长而具有描述性的名称,要比短而令人费劲的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功能的名称。
	选择描述性的名称能理清你关于模块的设计思路,并帮你改进。追索好名称,往往会触发代码的改善性重构。
	命名方式要保持一致,使用与模块名一脉相承的词语。

别重复自己

	回头看第一段代码,你会注意到,有个算法在SetUp、SuiteSetUp、TearDown和SuiteTearDown中被重复了4次。识别重复不太容易,因为这4次重复与其他代码混在一起,而且也不完全一样。这样的重复还是会导致问题,因为代码因此而臃肿,且当算法改变时需要修改4处地方。会增加4次改错的可能性。
	重复可能是软件中一切问题的根源。许多原则与实践规则都是为了控制与消除重复和创建。例如,面向对象编程中将代码集中到基类,从而避免了冗余。面向切面编程也是消除重复的一种策略。
	软件开发领域的大部分创新都是在不断尝试从源码中消除重复。

如何写出这样的函数

	写代码和写别的东西很像。在写论文或文章时,你先想到什么就写什么,然后再打磨它。初稿也许粗糙无序,你就斟酌推敲,直至到达你心目中的样子。
	函数刚写完时,一般都会冗长而复杂。有太多的缩进和嵌套循环。名称也是随意取的,也会有重复的代码。
	然后我们需要不断打磨这些代码,分解函数、修改名称、消除重复。缩短、重新安置方法,有时还需要拆散类。同时保持测试通过。
	最后,遵循前面列出的规则,再组装好些函数。

最后的话

	每个系统都是使用某种领域特定语言搭建,而这种语言是程序员设计来描述那个系统的。函数是语言的动词,类是名词。编程是一种艺术,一直就是语言设计的艺术。
	**大师级程序员把系统当作故事来讲,而不是当作程序来写。**他们使用选定编程语言提供的工具构建一种更为丰富且更具表达力的语言,用来讲述那个故事。

格式

开篇的话

	当有人查看你写的代码时,你肯定希望他们为其整洁、一致及所感知到的对细节的关注而震惊。我们希望他们高高扬起眉毛,一路看下去。但若他们看到的只是一堆“鬼画符”,那他们多半会得出结论,认为项目其他任何部分也同样对细节漠不关心。
	我们应该保持良好得代码格式。应该选用一套管理代码格式的简单规则,然后贯彻这些规则。在团队中,所有团队成员应该采用同一套格式规则。

格式的目的

	代码格式很重要,代码格式不可忽略,必须严肃对待。代码格式关乎沟通,而沟通是专业开发者的头等大事。
	或许你认为“让代码能工作”才是专业开发者的头等大事。然而,我希望你能转变那种想法。你今天编写的功能,极有可能在下一个版本被修改,代码代码的可读性却会对以后可能发生的修改行为产生深远影响。原代码修改之后很久,其代码风格和可读性仍会影响到可维护性和扩展性。即便代码已不复存在,你的风格和律条仍然存活下来。
	你的代码不仅仅会与其他人“沟通”,也会与未来的你“沟通”。

垂直格式

	向报纸学习
	想想看写得很好的报纸文章。你从上到下阅读。在顶部,你期望有个头条,告诉你故事主题,好让你决定是否要读下去。第一段是整个故事的大纲,给出粗线条的概述,但隐藏了故事细节。接着读下去,细节逐层增加,直至你了解所有的日期、名字、引用、说法及其他细节。
	源代码也要像报纸文章那样。名称应当简单且一目了然。名称本身应该足够告诉我们是否在正确的模块中。源代码最顶部应该给出高层次概念和算法。细节应该往下逐层展开,知道找到源代码中最底层的细节。
	报纸由许多篇文章组成,多数短小精悍,有些稍微长点。很少有占满一整页的。这样做,报纸才可用。假若一份报纸只登载一篇长篇故事,其中充斥着毫无组织的事实、日期、名字等,没人会去读它。

概念间处置方向上的隔离

在这里插入图片描述在这里插入图片描述

垂直距离

在这里插入图片描述

横向格式

	一行代码应该有多宽?我比较推崇遵循无需拖动滚动条的原则。建议使用宽屏。

注释

开篇的话

在这里插入图片描述

坏注释

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值