记住沃德原则:“如果每个例程都让你感到深合己意,那就是整洁代码。”要遵循这一原则,多半工作都在于为只做一件事的小函数取个好名字。函数越短小、功能越集中,就越便于取个好名字。
1.不写重复代码
2.先让代码易读,才能轻松写代码。
(1)每个函数都一目了然。每个函数都只说一件事。而且,每个函数都依序把你带到下一个函数。这就是函数应该达到的短小程度!
要判断函数是否不止做了一件事,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。
只做一件事的函数无法被合理地切分为多个区段。
要确保函数只做一件事,函数中的语句都要在同一抽象层级上。
(2)代码块和缩进
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}
if语句、else语句、while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。
这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层级不该多于一层或两层。当然,这样的函数易于阅读和理解。
(3)自顶向下读代码:向下规则。:程序就像是一系列TO起头的段落,每一段都描述当前抽象层级,并引用位于下一抽象层级的后续TO起头段落。
3.一个类不要超过200行,一个方法不要超过20行
4.易读命名。
(1)名副其实,见名知意。变量,函数,参数,类,包的命名要表达它为什么存在,它做什么事,它应该怎么用。
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
上面代码存在问题:
(1)theList零下标条目的意义是什么?
(2)值4的意义是什么?
(3)我怎么使用返回的列表?
零下标条目是一种状态值,而该种状态值为4表示“已标记”.返回的列表标识已经标记的单元格。
public List<int[]> getFlaggedCells() {
List<int[]> flaggedCells = new ArrayList<int[]>();
for (int[] cell : gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
flaggedCells.add(cell);
return flaggedCells;
}
(2)避免误导。别用accountList来指称一组账号,除非它真的是List类型。
(3)做有意义的区分。
以数字系列命名(a1、a2,„„aN)是依义命名的对立面。这样的名称纯属误导—完全没有提供正确信息;没有提供导向作者意图的线索。试看:
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++)
{
a2[i] = a1[i];
}
} 如果参数名改为source和destination,这个函数就会像样许多。
(4)类名 。
类名和对象名应该是名词或名词短语,如Customer、WikiPage、Account和AddressParser。避免使用Manager、Processor、Data或Info这样的类名。类名不应当是动词。
(5)方法名 。方法名应当是动词或动词短语,如postPayment、deletePage或save。
(6)函数参数。
最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)—所以无论如何也不要这么做。
因为,读者每次看到它都得要翻译一遍。参数与函数名处在不同的抽象层级,它要求你了解目前并不特别重要的细节。
从测试的角度看,参数甚至更叫人为难。
输出参数比输入参数还要难以理解。读函数时,我们惯于认为信息通过参数输入函数,通过返回值从函数中输出。我们不太期望信息通过参数输出。所以,输出参数往往让人苦思之后才恍然大悟。
6.1一元函数的普遍形式
转换形式(有输入参数,有输出参数):像在boolean fileExists("MyFile")中那样。也可能是操作该参数,将其转换为其他什么东西,再输出之。
例如,InputStream fileOpen("MyFile")把String类型的文件名转换为InputStream类型的返回
值
事件形式(有输入参数而无输出参数):程序将函数看作是一个事件,使用该参数修改系统状态,例如void passwordAttemptFailedNtimes(int attempts)。小心使用这种形式。应该让读者很清楚地了解它是个事件。谨慎地选用名称和上下文语境
6.2标识参数
标识参数丑陋不堪。向函数传入布尔值简直就是骇人听闻的做法。这样做,方法签名立刻变得复杂起来,大声宣布本函数不止做一件事。如果标识为true将会这样做,标识为false则会那样做!
5.使用异常替代返回错误码
if (deletePage(page) == E_OK) {
if (registry.deleteReference(page.name) == E_OK) {
if (configKeys.deleteKey(page.name.makeKey()) == E_OK){
logger.log("page deleted");
} else {
logger.log("configKey not deleted"); }
} else {
logger.log("deleteReference from registry failed"); }
} else {
logger.log("delete failed"); return E_ERROR;
}
另一方面,如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化:
try {
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
logger.log(e.getMessage());
}
6.抽离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(Exception e) {
logger.log(e.getMessage());
}
如何写出这样的函数:
写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。
我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。
然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。