代码整洁之道

来源:互联网 发布:淘宝怎么上图 编辑:程序博客网 时间:2024/05/02 01:42

记住沃德原则:“如果每个例程都让你感到深合己意,那就是整洁代码。”要遵循这一原则,多半工作都在于为只做一件事的小函数取个好名字。函数越短小、功能越集中,就越便于取个好名字。

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());
}

如何写出这样的函数:

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

我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。

 然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。

0 0