变量名
原则一:名副其实
- 变量名和函数名或类名,一定要让人知道它是干什么用的。如果一个变量,需要注释来补充,则不是一个好的变量名。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public List<int[]> getThem(){ List<int[]> list1 = new ArrayList<>(); for(int[] x : theList){ if(x[0] == 4){ list1.add(x); } } return list1; }
|
上面这段代码存在几个问题,有很多东西,如果不结合上下文(其他代码)来看的话,我们很难快速知道该段代码到底想表达什么东西:
(1)theList 是什么东西?类型是什么
(2)4 表达什么意思
(3)X[0],有什么特殊的吗?
(4)List1,又是什么东西
(5)getThem又是什么意思
假设代码的场景,正在开发一款扫雷游戏,theList是所有单元格存储集合,[0]表示该单元格的状态,4表示已经标记。显然,list1是存储已经标记的所有单元格。因此,上述代码可以改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public List<int[]> getAllTagCell(){ List<int[]> tagCellList = new ArrayList<>(); for(int[] cell : gameboard){ if(cell[CELL_STATUS] == TAG_THE_CELL){ tagCellList.add(x); } } return tagCellList; }
public List<Cell> getAllTagCell(){ List<Cell> tagCellList = new ArrayList<>(); for(Cell cell : gameboard){ if(Cell.isTagged()){ tagCellList.add(x); } } return tagCellList; }
|
原则二:做有意义的区分
(1)变量名后加数字
因为同一个作用域里(通常指某个function内),不能够出现相同名字的变量。因此很常见的办法就是,在变量后面加数字。
1 2 3 4 5
| public arrsTransfer(int[] n1,int[] n2){ for(int i=0;i<n1.length;i++){ n1[i]=n2[i]; } }
|
很显然,这是把数组n1拷贝到n2。这里有个问题,如果不写注释的情况下,很难知道是 n1 —> n2,还是 n2 —> n1。
因此,我们把n1和n2,改为destination和source。代码的可读性就好很多了。
(2)没意义的区别
比如说,有一个Person类,你很难说出Person,PersonInfo,People,这几个变量有什么区别。如果不做特殊约定的话
同样,a和the也很难区分。
原则三:尽量避免缩写,除非这个缩写是大家的共识
1 2 3 4 5 6 7 8 9
| class Record{ private long genymdhms; private long modymdhms; }
class Record{ private long generationTimestamp; private long modifyTimestamp; }
|
如果不是大家的共识,没有人知道ymdhms
表示什么东西。
原则四:常量尽量起别名
不直接使用常量,而用别名有两个好处。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public List<int[]> getThem(){ List<int[]> list1 = new ArrayList<>(); for(int[] x : theList){ if(x[0] == 4){ list1.add(x); } } return list1; }
|
以这段代码为例子,如果直接使用0和4,我们不明白这代表什么意思。可读性很差。
并且在修改代码的时候,我们直接搜索4,必然会出现一大堆无关的东西,因此,我们改用别名来操作。
1 2 3 4
| const ( CELL_STATUTE = 0 CELL_BE_TAGGED = 4 )
|
原则五:每个概念一个词
- 同一个类中的方法,有getXXX,fetchXXX ;findXXX,selectXXX,这种会让人很困惑。我们统一使用get,find这样会更好
- 例如,manager,controller,driver也会让人困惑,统一manager好了
原则六:有意义的前缀
设想你有名为firstName
,lastName
,street
,houseNumber
,city
。把他们放一块,很显然形成一个地址,但是单独来看就不知道是什么了。
可以在变量前加个前缀,就很显而易见了,例如addressFirstName
函数
先看一段很糟糕的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static String testableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception{
WikiPage wikiPage = pageData.getWikiPage(); StringBuffer buffer = new StringBuffer();
if(pageData.hasAttribute("test")){ if(includeSuiteSetup){ WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(); if(suiteSetup != null){ WikiPagePath pagePath = suiteSetup.getFullPath(); } } } buffer.append(pageData.getContent());
if(pageData.hasAttribute("test")){ if(includeSuiteSetup){ } } }
return ""; }
|
这段代码不光长,而且很复杂(if判断很多,而且还是嵌套判断),有大量的字符串。导致整段代码理解起来非常费解。有太多不同层级的抽象,奇怪的字符串,以及if嵌套。
但是,我们可以做一点抽象和重构,就能在几行代码内就解决问题。我们看一下重构后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private static final string TEST_ATTRIBUTE_TAG = "Test"; public static String renderPageWithSetupsAndTeardowns(PageData pageData,boolean isSetUp) throw Exception(){ boolean isTestPage = pageData.hasAttribute(TEST_ATTRIBUTE_TAG); if(isTestPage){ WikiPage testPage = pageData.getWikiPage(); String newPageContent = new StringBuffer(); includeSetupPage(XXX); newPageContent.append(pageData.getContent()); includeTeardownPage(XXX); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); }
|
我们可以很清晰的看到这段代码到底干了什么事情,总的来说是两件事。处理setUpPage和TearDownPage,并且把内容塞入一个testPage中。
原则一:函数应该尽可能短
《clean code》提倡代码最好压缩到20行左右。我个人觉得这个标准过于严苛了,做到以下几点我个人觉得就可以了
- 如果某段中,对某个对象的几个(>=2)字段修改,最好把他进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class People{ String name; int age; int sex; }
public void testFunciton(RpcReq req,RpcRsp rsp){ People people = new People(); people.name = req.name; people.age = req.age; people.sex = req.sex; }
public People getPeopleItemByReq(RpcReq req){ People people = new People(); people.name = req.name; people.age = req.age; people.sex = req.sex; return people; }
public void testFunciton(RpcReq req,RpcRsp rsp){ People people = getPeopleItemByReq(req); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private static final string TEST_ATTRIBUTE_TAG = "Test"; public static String renderPageWithSetupsAndTeardowns(PageData pageData,boolean isSetUp) throw Exception(){ boolean isTestPage = pageData.hasAttribute(TEST_ATTRIBUTE_TAG); if(isTestPage){ WikiPage testPage = pageData.getWikiPage(); String newPageContent = new StringBuffer(); includeSetupPage(XXX); newPageContent.append(pageData.getContent()); includeTeardownPage(XXX); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); }
|
像这段代码,把setupPage和includeTeardown的处理逻辑封装了,然后封装之后因为他们是同层级的功能,又能封装一次,就让函数非常简洁
- 一个函数内,尽量少if。if内的条件,以及后续的处理逻辑,也可以进行封装
原则二:只做一件事情
一开始的那段代码,做了很多事情。(1)创建传冲去 (2)获取页面信息 (3)渲染路径 (4)添加字符串…但是重构之后,就变得简单了很多,制作了一件事,把setup和tearDown放入到测试页面中。其他事情,放到如何getSetUpPage和tearDownPage中。
如何判断该函数是否符合只做一件事的标准?我个人觉得符合下面条件就可以了
- 函数做的事情,跟函数名描述的一样
- 函数是否再继续拆分
- 但是,没必要过于追求函数只做一件事,个人觉得,函数里面所做的事情,都是同一层级的,那问题也不大,我们把该函数封装起来就好了,因为追求所有函数都只做一件事是很难的。应该追求的是,大部分的函数都只做一件事
原则三:每个函数一个抽象层级
自顶向下规则
- 该函数,要做设置和拆分
- 设置
- 如果这是套件,则套件设置步骤
- 如果不是套件,则普通步骤
- 拆分
原则四:switch 和 if else
写出短小的switch语句很难,因为switch这个语法就是定义为做同多件事情。我们看一下下面这段代码
1 2 3 4 5 6 7 8 9
| public Money caculatePay(Employee e){ switch(e.type){ case COMMISSIONED: caculateCommissionPay(e);break; case HOURLY: caculateHourlyPay(e);break; } }
|
这段代码有两个比较重大的问题:
- 如果Employee添加新的type,那么必需修改该函数 (我们期望的当然是改动越小越好)
- 显然类似结构的代码会在很多地方,如果新加类型,改动就会非常大了
这种问题的根本原因是几个不同type的Employee耦合在了一起,那么解决方案就是把switch隐藏在工厂方法的底层。那么只要创建Employee的时候,指定type就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public abstract class Employee{ public caculatedPay(); public payDate(); }
public class SalaryEmployee extend Employee{ }
public calss HourlyEmployee extend Employee{ }
public class EmployFactory(){ Employee makeEmployee(int emlpoyeeType){ switch(employeeType){ case SALARY: return new SalaryEmployee(); case HOURLY: return new HourlyEmployee(); } } }
|
把type隐藏在工厂方法里面,上述的问题大部分都能够解决。并且代码的可拓展性会变得更好。
原则五:起一个好的函数名
- 不要害怕长名称,长名称总比短而令人费解的名称好
- 命名方式保持一致
目前遇到起名很烦躁的情况是Dao层,给自己定下这些规范
(1)主键 / 唯一键查询
Select + XXX + By + PrimaryKey
(2)批量查询
Find + XXX + By + field1_field2…
原则六:尽量减少函数参数
对于函数的参数 0个最好,1个可以接受,2个还行,3个底线,超过3个不能接受。如果传递真的超过三个,考虑以下两个方面: