【苦练基本功】代码整洁之道 pt1(第1章-第3章)
代码整洁之道 pt1(第1章-第3章)
- 1 整洁代码
- 1.1 要有代码
- 1.2 糟糕的代码
- 1.3 混乱的代价
- 1.3.1 什么是整洁代码?
- 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 不要添加没用的语境
- 3 函数
- 3.1 短小
- 3.2 只做一件事
- 3.3 每个函数一个抽象层级
- 3.3.1 自顶向下读代码:向下规则
- 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.7.1 输出参数
- 3.8 分割指令与询问
- 3.9 使用异常替代返回错误码
- 3.9.1 抽离try/catch 代码块
- 3.9.2 错误处理就是一件事
- 3.10 别重复自己
- 3.11 结构化编程
1 整洁代码
1.1 要有代码
代码呈现了需求的细节,在某些层面上,这些细节无法被忽略和抽象,必须明确之。将需求明确到机器可以执行的细节程度,就是编程要做的事。而这种规约正是代码。
代码确然是我们最终用来表达需求的哪种语言,我们可以创造各种与需求接近的语言,我们可以创造帮助把需求解析和汇整为正式结构的各种工具。但我们无法抛弃必要的精确性—所以代码永存。
1.2 糟糕的代码
糟糕的代码可以毁掉一家公司,就像沼泽,蹚过代码的水域,穿过灌木密布瀑布暗藏的沼泽地。我们拼命想找到出路,期望有点什么线索能揭示发生了什么事,但目光所及,只是越来越多死气沉沉的代码。
“我们都曾经瞟一眼自己亲手造成的混乱,决定弃之而不顾,走向新一天。我们都曾经看到自己的烂程序居然能运行,然后断言能运行的烂程序总比什么都没有强。我们都曾说过有朝一日再回头清理。当然,在那些日子里,我们都没有听过勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)”
1.3 混乱的代价
代码混乱的增加,团队生产力也持续下降,以致趋向于零。当生产力下降,管理层便会增加更多人手到项目中,期望提升生产力。可是新人并不熟悉系统的设计,他们搞不清楚如何修改符合设计意图,如何修改违背设计意图,而且他们以及团队中的其他人都背负着提升生产力的可怕压力,于是,他们只会制造更多的混乱,驱动生产力不断趋向于零。
花时间保持代码整洁不但关乎效率,还关乎生存。
程序员遵从不了解混乱风险的经理的意愿,也是不专业的做法。
制造混乱无助于赶上期限。混乱只会立刻拖慢你,叫你错过期限。赶上期限的唯一方法——做得快的唯一方法——就是始终尽可能保持代码整洁。
如果不明白整洁对代码有何意义,尝试去写整洁代码就毫无意义。写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得“整洁感”,这种“代码感”就是关键所在。缺乏“代码感”的程序员,看混乱只是混乱,无处着手。有“代码感”的程序员能从混乱的代码中看出其他的可能与变化。
1.3.1 什么是整洁代码?
代码逻辑应当直截了当,令缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依赖某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码值做好一件事。——Bjarne
整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏设计者的意图,充满了干净利落的抽象和直截了当的控制语句。——Grady
整洁的代码应由作者之外的开发者阅读和增补。它应当有单元测试和验收测试。它使用有意义的命名。它只提供一种而非多种做一件事的途径。它只有尽量少的依赖关系,而且要明确地定义和提供清晰的、尽量少的API。代码应通过其字面表达含义,因为不同的语言导致并非所有必需的信息均可通过代码自身清晰表达。——Dave
整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。代码的作者什么都想到了,如果你企图改进它,总会回到原点,赞叹某人留给你的代码—全心投入的某人留下的代码。——Michael
能通过所有测试;
没有重复代码;
体现系统中的全部设计理念;
包括尽量少的实体,比如类、方法、函数等。——Ron
如果每个例程都让你感到深合己意,那就是整洁代码。如果代码让编程语言看起来像是专为解决那个问题而存在的,就可以称之为漂亮的代码。——Ward
2 有意义的命名
2.1 名副其实
变量、函数或类的名称应该已经答复了所有的大问题。它该告诉你,它为什么存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。
选择体现本意的名称能让人更容易理解和修改代码。
public List<int[]> getThem() {List<int[]> list1 = new ArrayList<int[]>();for(int[] x : theList)if(x[0] == 4)list1.add(x);return list1;
}
上述的代码中并没有复杂的表达式,空格和缩进中规中矩,只用到了三个变量和两个常量,甚至没有涉及其他类和多态方法,为什么难以说明上述代码要做什么事?
问题不在于代码的简洁度,而在于代码的模糊度:及上下文在代码中未被明确体现的程度。
代码中的各个变量和常量的意义并没有在代码段中体现,可代码段就是他们改在的地方。假如该段代码是在开发一个扫雷的游戏,只需要简单修改一下名称,就能轻易知道发生了什么:
public List<Cell> getFlaggedCells() {List<Cell> flaggedCells = new ArrayList<Cell>();for(Cell cell : gameBoard) if(cell.isFlagged) flaggedCells.add(cell);return flaggedCells;
}
2.2 避免误导
程序员必须避免留下掩藏代码本意的错误线索。
- 应当避免使用与本意相悖的词,避免使用平台专有名称重复的变量名。
例如使用hp、aix、sco等Unix平台或类Unix平台专有名称,可能会提供错误的信息。- 避免使用accountList来指称一组账号,除非它真的是List类型。
如果包含账号的容器并非真的是一个List类型,就会引起错误的判断,可以替换为accountGroup、bunchOfAccount、或accounts都会更好。(即便容器也真的是一个List,最好也别在名称中写出容器的类型名)- 提防使用外形相似度较高的名称。
例如XYZControllerForEffectiveHandlingOfStrings和XYZControllerForEffectiveStorageOfStrings。
以同样的方式拼写出同样的概念才是信息。拼写前后不一致就是误导。
2.3 做有意义的区分
如果程序员只是为满足编译器或解释器的需要而写代码,就会制造麻烦。
例如,因为class已经被使用就给一个变量命名为klass是不合理的。
- 光是添加数字系列或是废话是不够的的,如果名称必须相异,那么其意思也应该不同才对。
例如以数字系列命名(a1,a2…an)是依义命名的对立面。这样的名称纯属误导,没有提供正确的信息,也不能导向作者意图。- 废话是另一种没意义的区分。
例如有一个Product类,还有一个名为ProductInfo或者ProductData的类,那他们的名称虽然不同,但意义却无区别。Info、Data就像a、an、the一样是意义含混的废话。- 废话都是冗余的。
variable永远不该出现在变量名中,table永远不该出现在表名中,NameString不会比Name好。
要区分名称,就要以读者能鉴别不同之处的方式来区分。
2.4 使用读得出来的名称
如果名称读不出来,讨论的时候就会像个傻鸟。
编码并非只是给机器阅读,还需要和同事讨论,它本身就是一种社会活动。
2.5 使用可搜索的名称
对于单字母名称和数字常量,存在一个问题就是很难在一大篇文字中找出来。
想找到MAX_CLASSES_PER_STUDENT很容易,但是想要找到数字7就会比较麻烦了。它可能是某些文件名或常量定义的一部分,出现在不同意图采用的各种表达式中。
名称长短应与其作用于到校相对应。若变量或常量可能在代码中有多处使用,则应赋予其便于搜索的能力。
2.6 避免使用编码
编码已经太多,无谓再自找麻烦。把类型或作用域编进名称里面,会徒然增加了解代码的负担。带编码的名称通常也不便发音,容易打错。
- 匈牙利语标记法
匈牙利语标记法明确写出类型,并将类型置于实际名称前,以大写字母间隔。例如bBusy,表示一个类型为boolean,名称为Busy的变量。该语法增加了修改变量、函数或类的名称或类型的难度,增加了代码的难度,可能会让系统编码误导读者。- 成员前缀
不必用m_前缀来表明成员变量。应该吧类和函数做的足够小,以消除队成员前缀的需要。- 接口和实现
有时会出现采用编码的特殊情形。例如创建一个形状用的抽象工厂,该工厂是一个接口,要用具体类来实现。
2.7 避免思维映射
不应当让读者在脑中把你的名称翻译为他们熟知的名称,这种问题经常出现在选择使用问题领域术语还是解决方案领域术语时。
- 避免使用单字母变量
- 明确是王道,善用其能,编写其他人能理解的代码。
2.8 类名
类名和对象名应该是名词或者名词短语,例如Customer、WikiPage、Account或者AddressParser。应该避免使用Manager、Processor、Data或Info这样的类名。类名不应当是动词。
2.9 方法名
方法名应当是动词或者动词短语,例如postPayment、deletePage或save。
属性访问器(accessor)、修改器(mutator)和断言(predicate)应该根据其值命名,并依JavaBean标准加上前缀get、set和is。
2.10 别抖机灵
如果名称太耍宝,那就只有同作者一般有幽默感的人才能记得住,而且还是在他们记得那个笑话的时候才行。宁可明确、毋为好玩。
抖机灵在代码中经常体现为使用俗语或俚语。例如备用whack()来表示kill()。言到意到,意到言到。
2.11 每个概念对应一个词
给每个抽象概念选一个词,并且一以贯之。
例如使用fetch、retrieve和get来给在多个类中的同样方法命名,你怎么记得住是哪个类中的哪个方法呢?
函数名称应当独一无二而且要保持一致,这样才不会借助多余的浏览就能找到正确的方法。
2.12 别用双关语
避免将同一单词用于不同目的。同一术语用于不同的概念,基本就是双关语了。
把代码写的让别人一目了然,而不必殚精竭虑地研究,我们想要大众化的作者尽责写清楚的平装书模式,而不想要学者掘地三尺才能明白其中意义的学院派模式。
2.13 使用解决方案领域名称
- 只有程序员才会读你的代码,尽管使用计算机科学术语、算法名、模式名、数学术语。
- 依据问题所涉猎领域来命名是不太聪明的做法,因为不该让协作者老是跑去问客户每个名称的含义。
- 程序员要做太多技术性工作,给这些事起个技术性的名称,通常是最靠谱的做法。
2.14 使用源自所涉问题领域的名称
如果不能用程序员熟悉的术语来做工作命名,就采用所涉问题领域而来的名字吧。
2.15 添加有意义的语境。
大多数名称是不能自我说明的。 需要用命名良好的类、函数或名称空间来放置名称,给读者提供语境。
可以给名称添加前缀来创造语境。
例如当有firstName、lastName、street、houseNumber、city、status等变量,放到一起很明显构成一个地址。但是当取出其中的status变量,很难理解它是用来表示地址对象的。因此可以通过添加前缀的方式,将变量替换为addrFirstName、addrLastName、addrStatus会更容易让人理解。
2.16 不要添加没用的语境
只要短名称足够清楚,就比长名称好。 别给名称添加不必要的语境。
3 函数
3.1 短小
函数的第一条规则就是要短小,第二条规则还是要更短小。
- 函数不该有100行那么长,20行封顶最佳。
- 每个函数都应该一目了然。
- 每个函数都只做一件事。
- 每个函数都依序把你带到下一个函数。这就是函数应该达到的短小程度。
- 对于代码块和缩进。if语句、else语句、while语句等,其中的代码块应该只占一行,该行大抵应该是一个函数调用语句。
- 函数不应该大到足以容纳嵌套结构。所以函数的缩进层级不应该多于一层或两层。
3.2 只做一件事
函数应该做一件事。做好这件事。只做这一件事。
- 编写函数是为了把较大的概念拆分为另一抽象层上的一系列步骤。
- 要判断函数是否不只做了一件事,还有一个方法,就是看它是否能在拆出一个函数,该函数不仅只是单纯地重新诠释其实现。
- 只做一件事的函数无法被合理地切分为多个区段。
3.3 每个函数一个抽象层级
要确保函数只做一件事,函数中的语句就要在同一抽象层级上。
函数中混杂不同抽象层级,往往会让人迷惑,读者可能无法判断某个表达式是基础概念还是细节。一旦细节与基础概念混杂,更多的细节就会在函数中纠结起来。
3.3.1 自顶向下读代码:向下规则
我们想要让每个函数后面都跟着位于下一抽象层级的函数,这样一来,在查看函数列表时,就能循抽象层级向下阅读了,叫做向下规则。
遵循向下规则,写出只停留于一个抽象层级上的函数,是保持函数短小,确保只做一件事的要诀。
3.4 Switch语句
写出短小的Switch语句很难,写出只做一件事的Switch语句也很难。要确保每个Switch都埋藏在较低的抽象层级,而且永远不重复,可以利用多态来实现。
public Money calculatePay(Employee e) throws InvalidEmployeeType {switch (e.type) {case COMMOSSIONED:return caculateCommissionedPay(e);case HOURLY:return caculateHourlyPay(e);case SALARIED:return caculateSalariedPay(e);default:throw new InvalidEmployeeType(e.type);}
}
该函数有几个问题:
- 太长,当出现新的雇员类型时,还会变得更长。
- 明显做了不止一件事
- 违反但以权责原则(SRP),因为有好几个修改它的理由。
- 违反开放闭合原则(OCP),因为每当添加新类型时,就必须修改该函数。
- 到处皆有类似结构的函数。
解决方案:将switch语句埋藏到抽象工厂底下,不让任何人看到。该工厂使用switch语句为Employee的派生物创建适当的实体,不同的函数由Employee接口多态地接收派遣。
public abstract class Employee {public abstract boolean isPayday();public abstract Money calculatePay();public abstract void deliverPay(Money money);
}
--------------------------
public interface EmployeeFactory {public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
--------------------------
public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType{switch (r.type) {case COMMOSSIONED:return new CommissionedEmployee(r);case HOURLY:return new HourlyEmployee(r);case SALARIED:return new SalariedEmployee(r);default:throw new InvalidEmployeeType(r.type);}}
}
3.5 使用具有描述性的名称
Ward原则:“如果每个例程都让你感到深合己意,那就是整洁代码”。
- 函数越短小,功能与集中,就越便于起个好名字。
- 别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。
- 别害怕花时间起名字。理当尝试不同的名称,实测其阅读效果。
- 选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。追寻好名称,往往导致对代码的改善重构。
- 命名方式要保持一致,使用与模块名一脉相承的短语、名词和动词给函数命名。
3.6 函数参数
最理想的参数数量是0,其次是1,再次是2,应尽量避免3。
3.6.1 单参数传入的普遍形式
操作该参数,使其转换为其他东西再输出。
有输入参数而无输出参数的函数,程序会看做是一个事件(event)。
3.6.2 标识参数
标识(布尔值)参数丑陋不堪,向函数传入布尔值简直就是骇人听闻的做法。
这相当于宣布本函数不只做一件事,可以将函数拆分为两个。
3.6.3 双参数函数
有两个参数的函数要比单参数函数难懂。应当尽量利用一些机制将其转换为单参数函数。
3.6.4 三参数函数
有三个参数的函数要比双参数函数难懂得多。排序、琢磨、忽略的问题都会加倍体现。
3.6.5 参数对象
如果函数看起来需要2个、3个、3个以上参数,就说明其中一些参数应该封装为类了。
3.6.6 参数列表
有时我们想要向函数传入数量可变的参数,可能是单参数、双参数甚至是三参数,可以使用
void monad(Integer... args);
void ayad(String name, Integer... args);
void tried(String name, int count, Integer... args);
3.6.7 动词与关键字
给函数起个好名字,能较好地解释函数的意图,以及参数的顺序和意图。
对于单参数函数,函数和参数应当形成一种非常良好的动词/名词对形式,例如write(name)。
3.7 无副作用
函数承诺只做一件事,但还是会做其他被藏起来的事。
- 有时会对自己类中的变量做出未能预期的改动。
- 有时会把变量搞成向函数传递的参数或系统全局变量。
无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。
3.7.1 输出参数
应避免使用输出参数,如果函数必须修改某种状态,就修改所属对象的状态。
3.8 分割指令与询问
函数要么做什么事,要么回答什么事,但二者不可得兼。
函数应该修改某对象的状态,或是返回该对象的有关信息。如果两样都干,常会导致混乱。
3.9 使用异常替代返回错误码
从指令函数返回错误码略微违反了指令与询问分隔的规则,鼓励在if语句判断中把指令当做表达式使用。
if (deletePage(page) == E_OK) {...
}
这样不会引起动词/形容词混淆,但会导致更深层次的嵌套结构。当返回错误码是,要求调用者立刻处理,会使结构变得混乱。
if (deletePage(page) == E_OK) {if (registry.deleteReference(page.name) == E_OK) {if (configKeys.deleteKey(page.name.makeKey()) == E_OK) {...} else {...}} else {...}
} else {...
}
但是如果使用异常代替错误码,错误处理代码就能从主路径代码中分离出来,从而得到简化。
try {deletePage(page);registry.deleteReference(page.name);configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {logger.log(e.getMessage());
}
3.9.1 抽离try/catch 代码块
try/catch代码块丑陋不堪。它们改乱了代码结构,把错误处理与正常流程混为一谈。最好把try和catch代码块的主体部分抽离出来,另外形成函数。
public void delete(Page page) {try {deletePageAndAllReferences(page);} catch (Exception e){logError(e);}
}private void deletePageAndAllReferences(Page page) throws Exception {deletePage(page);registry.deleteReference(page.name);configKeys.deleteKey(page.name.makeKey());
}private void logError(Page page) {logger.log(e.getMessage());
}
3.9.2 错误处理就是一件事
函数应该只做一件事,错误处理就是一件事。处理错误的函数不该做其他事。
3.10 别重复自己
重复的代码会导致整体变得臃肿,进而导致其他问题。且当算法改变时,需要修改多处地方,就会增加多处调用方发生错误的可能性。
“重复可能是软件中一切邪恶的根源,许多原则与实践规则都是为控制与消除重复而创建的。”
3.11 结构化编程
迪杰斯特拉(Dijkstra)的结构化编程规则:每个函数、函数中的每个代码块都应该有一个入口、一个出口。遵循这些规则,意味着在每个函数中只能有一个return语句,循环中不能有break或continue语句,而且永远不能有任何goto语句。
但只要函数保持短小,偶尔出现的return、break或continue语句没有坏处,甚至比单入单出原则更具有表达力。另外,goto只在大函数中才有道理,所以应该尽量避免使用。
【苦练基本功】代码整洁之道 pt1(第1章-第3章)相关推荐
- 【苦练基本功】代码整洁之道 pt4(第10章-第12章)
代码整洁之道 pt4(第10章-第12章) 10 类 10.1 类的组织 10.2 类应该短小 10.2.1 单一权责原则 10.2.2 内聚 10.2.3 保持内聚性就会得到许多短小的类 10.3 ...
- 【苦练基本功】代码整洁之道 pt3(第7章-第9章)
代码整洁之道 pt3(第7章-第9章) 7 错误处理 7.1 使用异常而非返回码 7.2 先写try-catch-finally 7.3 使用未检异常 7.4 给出异常发生的环境说明 7.5 依调用者 ...
- 【苦练基本功】代码整洁之道 pt2(第4章-第6章)
代码整洁之道 pt2(第4章-第6章) 4 注释 4.1 注释不能美化糟糕的代码 4.2 用代码来阐述 4.3 好注释 4.3.1 法律信息 4.3.2 提供信息的注释 4.3.3 对意图的解释 4. ...
- 【好书推荐】你想要的编码规范都在这里 | 《代码整洁之道》
目录 一.引言 二.书籍简介 三.好代码自己会说话 1. 清晰的变量命名规范 2. 好注释与坏注释 3. 错误处理 四.总结 一.引言 你好,我是小雨青年,一名程序员. 今天为你推荐的书籍是<代 ...
- 重读【代码整洁之道】
一.前言 [代码整洁之道]很经典,但也有些过时,翻译上也有些啰嗦,但总体上是好书.通过对本书核心内容的摘抄,结合自己的经验,整理了一些精简的点,这样你就省的去啃那本400多页的书了. 软件质量 = 架 ...
- 《代码整洁之道》(Clean Code)- 读书笔记
一.关于Bob大叔的Clean Code <代码整洁之道>主要讲述了一系列行之有效的整洁代码操作实践.软件质量,不但依赖于架构及项目管理,而且与代码质量紧密相关.这一点,无论是敏捷开发流派 ...
- 代码整洁之道(一)最佳实践小结
摘要: Any fool can write code that a computer can understand. Good programmers write code that humans ...
- 2015年第11本:代码整洁之道Clean Code
前一段时间一直在看英文小说,在读到<Before I fall>这本书时,读了40%多实在看不下去了,受不了美国人啰啰嗦嗦的写作风格,还是读IT专业书吧. 从5月9日开始看<代码整洁 ...
- 《代码整洁之道:程序员的职业素养》一一1.5 参考文献
本节书摘来自异步社区出版社<代码整洁之道:程序员的职业素养>一书中的第1章,第1.5节,作者:[美]Robert C. Martin(罗伯特 C. 马丁),更多章节内容可以访问云栖社区&q ...
最新文章
- JavaScript实现私有属性
- 天润融通java面试_【天润融通面试|面试题】-看准网
- C语言学习笔记(五) 数组
- 操作系统一:内核态的开销
- hibernate教程--持久化类状态详解
- linux 6.3 vnc安装包,CentOS 6.3安装和配置VNC
- 依赖注入的几种形式及场景
- 业务异常 java_谈谈RxJava处理业务异常的几种方式
- 机器学习Tensorflow基本操作:线程队列图像
- 存储器间接寻址方式_8086中的数据存储器寻址模式
- ViewPager通过自定义适配器MyPagerAdapter实现界面导航(上标题)
- 动态规划:最长上升子序列(二分算法 nlogn)
- 360 php SQL注入,php中sql注入漏洞示例
- 右键菜单 GenericMenu
- WEB专用服务器的安全设置
- win系统连接交换机并设置固定ip地址
- MsXml创建和解析XML示例
- verbose=False(TensorFlow)
- 论文笔记:Identifying Lung Cancer Risk Factors in the Elderly Using Deep Neural Network - Chen, Wu
- 抖音小店无货源选品技巧分享,都是最简单的选品方法,一学就会
热门文章
- Anaconda及pytorch详细安装及使用教程
- www.ty66.php,韬轩阁
- ArcGIS API For Javascript 4.15 绘制地图:在地图上测距离、测面积和在不同图层上搜索
- python小程序——视频篇
- TIVA 123GXL的边沿计数模式测量低频PWM
- 关于t-SNE(T-distributed Stochastic Neighbor Embedding) t-分布随机近邻嵌入的简单理解
- Chrome播放视频时只有声音没有画面
- WLW离线编辑器的安装与使用
- 计算机钥匙英语,计算机加锁--把U盘变成打开电脑的钥匙 - 信息科学 - 小木虫 - 学术 科研 互动社区...
- 1028: 安全路径(2014年中南大学研究生复试机试题 )