[读书笔记] 代码整洁之道(一): 有意义的命名和函数定义

第二章 有意义的命名

  1. 名副其实
      变量、函数或类的名称能够发福所有的大问题,不需要注释来补充。
  2. 避免误导
      避免留下掩藏代码本意的错误线索,避免使用与本意相悖的词。
  3. 做有意义的区分
      不要用废话来代替有意义的名称。
  4. 使用能读得出来的、可搜索的名称
  5. 避免使用编码
      不需要用前缀来表明成员变量。
  6. 避免思维映射
      避免一些聪明人定向思维,根据你的名称直接翻译为他们熟知的名称。
  7. 类名
      类名和对象名应该是名词或名词短语,不应当是动词
  8. 方法名
      方法名应当是动词或动词短语。
  9. 每一个概念对应一个词,别用双关语

第三章 函数

  1. 短小
      函数的第一规则是短小。

  2. 只做一件事情
      函数应该做一件事;做好这件事;只做这一件事。要判断函数是否不止做了一件事,就是看能否再拆出一个函数(该函数不仅只是单纯的重新诠释其实现)。

  3. 每个函数一个抽象层级
      自顶向下读代码:向下规则,要让代码有自顶向下的阅读顺序,让每个函数后面都跟着位于下一抽象层级的函数。

  4. switch语句
      switch语句很难(包括if/else),天生要做N件事,可以将其埋藏在较低的抽象级层,且永不重复。但是可以利用多态来实现

     public Money calculatePay(Employee e) throws InvalidEmployeeType {
     	switch (e.type) {
     	case COMMISSIONED:
     		return calculateCommissionedPay(e);
     	case HOURLY:
     		return calculateHourlyPay(e);
     	case SALARIED:
     		return calculateSalariedPay(e);
     	default:
     		throw new InvalidEmployeeType(e.type);
     	}
     }
    

这个函数有几个问题:1)太长,当出现新的雇员类型时会更长;2)明显不止做了一件事;3)违反了单一权责原则SRP,因为有好几个修改它的理由;4)违反了开放闭合原则OCP, 每当添加新的类型时,就必须修改。
解决方案:将switch语句埋到抽象工厂底下,不让任何人看到。详见
设计模式——抽象工厂模式(Coming out...)
5. 使用描述性名称
  描述性名称能清理关于模块的设计思路。
6. 函数参数
  函数参数最理想是零,其次是一,再次是二,尽量避免三,有特殊理由才能用三个以上的参数。

* 一元函数有两种普遍理由:1)转换:例如将String转换为InputStream类型的返回值;2)事件:使用参数修改系统状态。
* 标识参数:标识函数丑陋不堪,向函数传入布尔值更是骇人听闻,直接表明该函数不止做了一件事。
* 二元函数:一定要注意参数的顺序,很可能被忽略。二元函数可以通过一些机制转换成一元函数。
* 三元函数:更加容易忽略参数。
* 参数对象:如果函数看上去需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。
7. 无副作用
  函数承诺只做一件事,但还是会做其他被隐藏起来的事。有时会导致未能预期的改动。

	public class UserValidator {
		private Cryptographer cryptographer;
			
		public boolean checkPassword(String userName, String password) {
			User user = UserGateway.findByName(userName);
			if (user != User.NULL) {
				String codedPhrase = user.getPhraseEncodedByPassword();
				String phrase = cryptographer.decrypt(codedPhrase, password);
				if ("Valid Password".equals(phrase)) {
					Session.initialize();
					return true;
				}
			}
			return false;
		}
	}

  副作用就是Session.initialize()的调用,checkPassword函数可以被理解为用来检查密码的。该名称未暗示它会初始化该次会话。所有调用者只是为了检查用户有效性,而误操作抹除会话数据的风险。

* 输出参数
  应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态。
8. 分隔指令与询问
  函数要么做什么事,要么回答什么事,不可兼得。函数应该修改某对象的状态,或是返回该对象的有关信息。解决方案:将指令与询问分隔开来。
9. 使用异常替代返回错误码

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

if/else导致了更深层次的嵌套结构。当返回错误码时,就是要求调用者立刻处理错误。使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化。

	try {
		deletePage(page); 
		registry.deleteReference(page.name); 			
                       configKeys.deleteKey(page.name.makeKey());
	}catch (Exception e) {
		logger.log(e.getMessage()); 
	}
* 抽离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());
		}
这样delete函数只与错误处理有关,deletePageAndAllReferences函数只与删除page有关,错误处理可以忽略掉。
* 错误处理就是一件事。
* 返回错误码就会通过一个类或者枚举来定义所有错误码,很多类都会产生依赖,当这些类产生修改时,其他类都需要重新编译。使用异常代替错误码,新异常就可以从异常类派生出来。
  1. 不可重复自己——DRY原则
      面向对象过程中将代码集中到基类,从而避免冗余。
posted @ 2015-09-21 02:59  涣涣虚心  阅读(360)  评论(0编辑  收藏  举报