代码整洁之道 第二章 有意义的命名


2.1、 介绍 

在给变量、函数、参数、类、包的命名、jar包、war包、ear文件的命名都需要一个好的命名。

2.2、名副其实(见名知意

变量

//		不推荐
		int d ;  //消失的时间,以日计
//		推荐
		int elapsedTimeInDays; 
		


例子:选择体现本意的名称能让人更容易理解和修改代码。下列代码的目的何在?

//  不推荐
	public List<int[]> getThem( List<int[]> theList){
		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、这个方法是要处理什么的?


问题的答案没体现在代码段中,可那就是它们该在的地方。比方说,我们在开发一种扫雷游戏,我们发现,盘面是名为theList的单元格列表,那就将其名称改为gameBoard。

盘面上每个单元格都用一个简单数组表示。我们还发现,零下标条目是一种状态值,而该种状态值为4表示“已标记”。只要改为有意义的名称,代码就会得到相当程度的改进:


public List<int[]> getFlaggedCells()  {

  List<int[]> flaggedCells = newArrayList<int[]>();

  for (int[] cell : gameBoard)

    if (cell[STATUS_VALUE] == FLAGGED)

      flaggedCells.add(cell);

  return flaggedCells;

}

注意,代码的简洁性并未被触及。运算符和常量的数量全然保持不变,嵌套数量也全然保持不变。但代码变得明确多了。

还可以更进一步,不用int数组表示单元格,而是另写一个类。该类包括一个名副其实的函数(称为isFlagged),从而掩盖住那个魔术数[9]。于是得到函数的新版本:


public List<Cell> getFlaggedCells()  {

  List<Cell> flaggedCells = newArrayList<Cell>();

  for (Cell cell : gameBoard)

    if (cell.isFlagged())

      flaggedCells.add(cell);

  return flaggedCells;

}

只要简单改一下名称,就能轻易知道发生了什么。这就是选用好名称的力量。


2.3、避免误导

1、避免使用与本意是相悖的词。

例子1、hp、aix  因为他们都是unix平台或类 Unix平台的专有名称。

例子2、别用词义容易误导的名称:

定义一个变量名称为一组帐号的变量。


不推荐 String accountList=null;  // 容易误导成是List类型。

推荐    accountGroup 、bunchOfAccounts 或accounts.


例子3、别用外形相似两个词


XYZControllerForEfficientHandingStrings 

XYZControllerForEfficientStorageOfStrings

形相似的字母和数字组合。

int a=1;
if(o==1)
   a=o1;
else
  1=01;

2.4、做有意义的区分

不推荐
public static void copyChars(char a1[],char a2[]){
		for(int i=0;i<a1.length;i++){
			a2[i]=a1[i];
		}
	}
推荐
public static void copyChars(char source[],char destination[]){
		for(int i=0;i<source.length;i++){
			destination[i]=source[i];
		}
	}

2.5、使用读得出来的名称

不推荐
class DtaRcrd102{
		private Date genymdhms;
		private Date modymdhms;
		private final String pszqint ="102";
	}

推荐
class Customer{
		private Date generationTimestamp;
		private Date modificationTimestamp;
		private final String recordId="102";
	}

2.6、使用可搜索的名称

不推荐
    String s ="";
    for(int j=0; j<34;j++){
        s+=(t[j]*4/5);
    }

读者读起来,根本不知道34是代表着什么,4和5也不知道他们是什么数字。
应给每个变量起一个可以读得懂的名称。
int realDaysPerIdealDay = 4;

const int WORK_DAYS_PER_WEEK = 5;

int sum = 0;

for (int j=0; j < NUMBER_OF_TASKS; j++) {

  int realTaskDays = taskEstimate[j] *realDaysPerIdealDay;

  int realTaskWeeks = (realdays /WORK_DAYS_PER_WEEK);

  sum += realTaskWeeks;

}





2.7、避免使用编码

避免把类型或作用域带到名称里面。
不推荐
String phoneString;

成员前缀
private Strig m_dsc;

接口实现
推荐
接口名称
ShapeFactory
实现类名称
ShapeFactoryImp
不推荐
IShapeFactory  前导字母I被滥用到了说好听点是干扰,说难听点根本就是废话的程度

2.8、避免思维映射


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

计数器:

单字母变量名就是个问题。在作用域较小、也没有名称冲突时,循环计数器自然有可能被命名为i或j或k。(但千万别用字母l!)这是因为传统上惯用单字母名称做循环计数器。然而,在多数其他情况下,单字母名称不是个好选择;

聪明程序员和专业程序员之间的区别在于,专业程序员了解,明确是王道。专业程序员善用其能,编写其他人能理解的代码。

 自己的问题:有时候想给一个变量起一个名词,但是不知道怎么起,然后就习惯性的写 temp。


2.9、类名称

类名和对象名应该是名词或者名词短语。

2.10、方法名

方法名 应当是动词或者动词短语。
post、delete、save、update 带这些前缀。

2.11、添加有意义的语境

有些名称是能自我说明的,多数是不能的。比如下面的地址信息。
firstName、lastName、street、hoseNumber、city、state和zipcode。
如果单独来看这些词比如state,大家可能会去想像一下。
添加前缀 addFirstName、addLastName、addStreet、addHoseNumber。

更好的方案是:创建名为Address的类。这样,即便是编译器也会知道这些变量隶属某个更大的概念了。

以下变量是否需要更有意义的语境呢?函数名仅给出了部分语境;算法提供了剩下的部分。遍览函数后,你会知道number、verb和pluralModifier这三个变量是“测估”信息的一部分。不幸的是这语境得靠读者推断出来。第一眼看到这个方法时,这些变量的含义完全不清楚。
	private static void printGuessStatistics(String candidate,int count){
		String number;
		String verb;
		String pluralModifier;
		if(count ==0){
			number="no";
			verb="are";
			pluralModifier="s";
		}else if(count==1){
			number="1";
			verb="is";
			pluralModifier="";
		}else{
			number=count+"";
			verb="are";
			pluralModifier="s";
		}
		String guessMessage =String.format("there %s %s %s", number,verb,candidate,pluralModifier);
		System.out.println(guessMessage);
	}


上列函数有点儿过长,变量的使用贯穿始终。要分解这个函数,需要创建一个名为GuessStatisticsMessage的类,把三个变量做成该类的成员字段。这样它们就在定义上变作了GuessStatisticsMessage的一部分。语境的增强也让算法能够通过分解为更小的函数而变得更为干净利落。(如代下)

public class GuessStatisticsMessage{
	private String number;
	private String verb;
	private String pluralModifier;
	
	public String make(char candidate,int count){
		createPluralDependentMessageParts(count);
		return String.format("there %s %s %s", number,verb,candidate,pluralModifier);
	}
	private void createPluralDependentMessageParts(int count){
		if(count==0){
			thereAreNoTettle();
		}else if (count==1){
			thereAreOneLetter();
		}else{
			thereAreManyLitters(count);
		}
	}
	private void thereAreNoTettle(){
		number ="no";
		verb="are";
		pluralModifier="s";
	}
	private void thereAreOneLetter(){
		number ="1";
		verb="is";
		pluralModifier="";
	}
	
	private void thereAreManyLitters(int count){
		number =count+"";
		verb="are";
		pluralModifier="s";
	}

2.17、不要添加没用的语境

设若有一个名为“加油站豪华版”(Gas Station Deluxe)的应用.

你在GSD应用程序中的记账模块创建了一个表示邮件地址的类,然后给该类命名为GSDAccountAddress。稍后,你的客户联络应用中需要用到邮件地址,你会用GSDAccountAddress吗?这名字听起来没问题吗?在这17个字母里面,有10个字母纯属多余和与当前语境毫无关联。

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

对于Address类的实体来说,accountAddress和customerAddress都是不错的名称,不过用在类名上就不太好了。Address是个好类名。如果需要与MAC地址、端口地址和Web地址相区别,我会考虑使用PostalAddress、MAC和URI。这样的名称更为精确,而精确正是命名的要点。


2.18、总结

取好名字最难的地方在于需要良好的描述技巧和共有文化背景。与其说这是一种技术、商业或管理问题,还不如说是一种教学问题。其结果是,这个领域内的许多人都没能学会做得很好。

我们有时会怕其他开发者反对重命名。如果讨论一下就知道,如果名称改得更好,那大家真的会感激你。多数时候我们并不记忆类名和方法名。我们使用现代工具对付这些细节,好让自己集中精力于把代码写得就像词句篇章、至少像是表和数据结构(词句并非总是呈现数据的最佳手段)。改名可能会让某人吃惊,就像你做到其他代码改善工作一样。别让这种事阻碍你的前进步伐。

不妨试试上面这些规则,看你的代码可读性是否有所提升。如果你是在维护别人写的代码,使用重构工具来解决问题。效果立竿见影,而且会持续下去。


最后拿一个例子来结束第二章的学习


不建议

publicstatic String testableHtml(
   PageData pageData,
   boolean includeSuiteSetup
) throwsException {

 WikiPage wikiPage = pageData.getWikiPage();
 StringBuffer buffer = new StringBuffer();
 if (pageData.hasAttribute("Test")) {
   if (includeSuiteSetup) {
     WikiPage suiteSetup =
       PageCrawlerImpl.getInheritedPage(
           SuiteResponder.SUITE_SETUP_NAME, wikiPage
       );

     if (suiteSetup != null) {
       WikiPagePath pagePath =
         suiteSetup.getPageCrawler().getFullPath(suiteSetup);
       String pagePathName = PathParser.render(pagePath);
       buffer.append("!include -setup .")
              .append(pagePathName)
              .append("\n");
     }
   }

   WikiPage setup =
     PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
   if (setup != null) {
     WikiPagePath setupPath =
       wikiPage.getPageCrawler().getFullPath(setup);
     String setupPathName = PathParser.render(setupPath);
     buffer.append("!include -setup .")
            .append(setupPathName)
            .append("\n");
   }
  }

 buffer.append(pageData.getContent());

 if (pageData.hasAttribute("Test")) {

   WikiPage teardown =
     PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);

   if (teardown != null) {
     WikiPagePath tearDownPath =
       wikiPage.getPageCrawler().getFullPath(teardown);
     String tearDownPathName = PathParser.render(tearDownPath);
     buffer.append("\n")
            .append("!include-teardown .")
            .append(tearDownPathName)
            .append("\n");
   }

   if (includeSuiteSetup) {
     WikiPage suiteTeardown =
       PageCrawlerImpl.getInheritedPage(
           SuiteResponder.SUITE_TEARDOWN_NAME,
           wikiPage
       );

     if (suiteTeardown != null) {
       WikiPagePath pagePath =
         suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
       String pagePathName = PathParser.render(pagePath);
       buffer.append("!include -teardown .")
              .append(pagePathName)
              .append("\n");
     }
   }
  }
 pageData.setContent(buffer.toString());
 return pageData.getHtml();

}


搞懂这个函数了吗?大概没有。有太多事发生,有太多不同层级的抽象。奇怪的字符串和函数调用,混以双重嵌套、用标识来控制的if语句等,不一而足。

下面这段代码对程序做了重构


publicstatic String renderPageWithSetupsAndTeardowns(
   PageData pageData, boolean isSuite
) throwsException {
 boolean isTestPage = pageData.hasAttribute("Test");
 if (isTestPage) {
   WikiPage testPage = pageData.getWikiPage();
   StringBuffer newPageContent = new StringBuffer();
   includeSetupPages(testPage, newPageContent, isSuite);
   newPageContent.append(pageData.getContent());
   includeTeardownPages(testPage, newPageContent, isSuite);
   pageData.setContent(newPageContent.toString());
  }
 return pageData.getHtml();
}



第二章:有意义的命名总结:

  1、有意义,名副其实:降低代码的模糊度,明确说明代码的用途;

         2、避免误导:accountList的类型最好就是List;

         3、避免使用多个不同之处较小的名称;

         4、避免使用字母l和O,因为它们像数字1和0;

         5、做有意义的区分,只有意义不同时才使用不同的名字;

         6、废话是无意义的区分,是冗余;

         7、使用可搜索的名称:用宏定义数字比直接用数字好,避免使用单字母变量和数字常量;

         8、不必使用带类型的匈牙利标记法;

         9、避免成员变量使用类名前缀;

         10、类名和对象名一般都是名词和名词短语;

         11、方法名一般是动词和动词短语;get,set,is前缀;

         12、使用解决方案领域内的名称;

         13、添加有意义的语境:使用有意义的前缀,创建一个类并将变量声明为成员变量;

         14、命名要精确:不要添加无意义的语境;




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值