这个暑假出来实习,第一次体会到在一个团队中开发的体验,与网上的网站看到的大为不同,以前看网上说什么程序员写了屎山代码,写了一堆模糊的注释或者说垃圾代码不写注释。

但在我的实习体验中,代码虽然看起来很多,但大多都一目了然,第一天去的时候我问了我的实习导师,为什么公司代码没有注释?他说公司的编码全部都是按照代码整洁之道,只写整洁易懂的代码,让人一目了然。我在看了几天代码之后就很快就明白了部分代码含义,并且完成了几项任务。简洁的代码确实会让人如沐春风,公司的三本必读书籍之一就是《Clean Code》。

如果说你想写出优秀的程序,一定要去看《Clean Code》这本书!

1.有意义的命名

1.1 名副其实

变量选择要体现本意的名称,能让人更容易理解和修改代码。

例如:

int d ; //消逝的时间,以日计

int elapsedTimeInDays;
int daysSinceCreation;
int fileAgeInDays;

好的命名习惯可以让代码块变得易读,无关于简洁度,而是可以减少代码的模糊度。

比如下面这段代码。

public List<int []> getThem(){
List<int []> list1 = new ArrayList<>();
for(int[] x : theList){
if (x[0] == 4){
list1.add(x);
}
}
return list1 ;
}

在看这段代码时有很多问题不理解,如theList是什么类型的东西,theList下标0条目的意义是什么?值4的意义是什么?我怎么使用返回列表?

假设这是一款扫雷游戏,盘面是theList的单元格列表,那就将其名称改为gameBoard。盘面上每个单元格都用一个简单数组表示,0下标条目是状态栏,而状态值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;
}

也可以把cell封装为一个类,在判断FLAGGED时,调用判断方法来掩盖这个数字。

1.2 避免误导

我们必须避免留下掩盖代码本意的错误线索,避免使用与本意相悖的词。比如accountList来指称一组账号,除非它真的是List类型。如果说他包含的一组账号并不是List类型,就会引起错误的判断。所以,用accountGroup或bunchOfAccounts,甚至直接用accounts都会好一些。

另外需要提防使用不同之处较小的名称,如XYZControllerForEfficientHandlingOfStrings和另一处的XYZControllerForEfficientStorageOfStrings,辨别需要花费多少时间呢?

1.3 做有意义的区分

对于一些变量,只是在其后面添加数字用以区分并不规范,只是滥竽充数,这只能让编译器满意,以数字系列命名(a1,a2,a3,a4...)是依义命名的对立面,完全没有提供正确的信息:没有提供导向作者意图的线索。

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

如果参数名改为sourcedestination,这个函数就会像样很多。

1.4 使用读得出的名称

如果你定义的名称是一串你读出不来的字符,无疑在你阅读时会增加你大脑的负荷。

比如说本书作者曾经遇到的,一家公司在程序里面写了个genymdhms(生成日期、年、月、日、时、分、秒),他们读作gen why emm dee aich emm es,而作者却照读为gen-yah-mudda-hims,后来设计师和分析师也这样读。当我们给其他人解释这个名称时。他们总是读作自己的自造词。

1.5 使用可搜索的名称

当你使用单字母名称和数字常量时,就很难从一大篇文字中找到他。

但你找MAX_CLASSES_PER_STUDENT就很容易了,但数字7就很难找到了。

若变量或常量可能在代码中多处使用,则应赋其值便于搜索的名称。

比较

for(int j=0;j<34;j++){
s+=(t[j]*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;
}

上述代码中,sum并非有特别用意的名称,但他可以搜索到。采用能表达意图的名称虽然拉长了函数代码,但WORK_DAYS_PER_WEEK确实比数字5要好找。

1.6 避免使用编码

编码已经太多了,不需要把作用域和类型编码进名称中,这徒然增加了解码的负担,所以不要使用变量名增加前缀的方式,如m_这种前缀。

public class Part{
private String m_dsc ;
public Part(String name){
m_dsc = name ;
}
}
public class Part{
String description;
public Part(String descripation){
this.description = description ;
}
}

学会无前缀的方式,人们就不用去看到没有意义的废料前缀了。

1.7 避免思维映射

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

单字母变量就是这个问题,在没有冲突的情况下,循环计数器可能会命名为i或者j,这种情况下,k这个变量可能就会在读者脑中有其他的意义,它是不是也是用于计数的?

明确才是王道。

1.8 类名

类名和对象名应该是名词或名词短语,如CustomerWikiPageAccountAddressParser,避免使用ManagerProcesserDataInfo这样的类名。类名不应该是动词。

1.9 方法名

方法名应该是动词或动词短语。如postPaymentdeletePagesave。属性访问器、修改器和断言应该根据其值命名,并依据JavaBean标准加上getsetis前缀。

String name = employee.getName();
Customer.setName("mike");
if(paycheck.isPosted())...

重载构造器时,使用描述了参数的静态工厂方法名。如

Complex fulcrumPoint = Complex.FromRealNumber(23.0);

通常好于

Complex fulcrumPoint = new Complex(23.0);

可以考虑将构造器设置为private,强制使用这种命名手段。

1.10 别扮可爱

命名不要太皮,不要耍大宝,如HolyHandGrenade(神圣手雷)这个函数是干什么的谁知道呢,不如使用DeleteItems(删除条目)这个名称。还有如whack(劈砍)这种名称,不如使用kill(杀死)

1.11 每个概念对应一个词

给每一个抽象概念选择一个词,比如在一堆代码中有Controller,又有Manager,还有Driver,就会让人很困惑,DeviceManager和Protocol-Controller有什么区别? 为什么不全用Controllers或者Managers?

1.12 别用双关语

避免将同一单词用于不同目的。同一术语用于不同概念,基本上就是双关了,比如add方法,许多类中都有add方法,该方法用来增加或连接现存值来获得新值。但对于把单个参数放到集群中,这个方法叫做add吗?这样虽然看似和其他add保持一致了,但语义却是不同,应该使用insert或append之类的词来命名才对。

2. 函数

2.1 短小,只做一件事

让人明了的函数应该简短,函数体只做一件事,并且通过函数名称就可以看出所做。比如以丑长闻名的Swing程序,把它写成每个函数只有3-4行无疑可以让每一步都一目了然。

应该如何短小。应该例如下面的例子:

public static String readerPageWithSetupsAndTeardowns(PageData pageData,boolean isSuite) throws Exception{
if(isTestPage(pageData)){
includeSetupAndTeardownPages(pageData,isSuite);
}
return pageData.getHtml();
}

函数应该只做一件事。做好这件事,只做这件事。

2.2 每个函数一个抽象层级

每个函数一个抽象层级的目的就是为了确保一个函数只做一件事。

这一条可以理解为不同抽象集的函数一层层连接,每一级只去负责自己的任务。

2.3 switch语句

switch天生要做N件事,而且我们很难写出简短的switch语句。但是我们还是可以利用多态将每个switch都放在较低的抽象层级,而且永不重复。

switch (e.type){
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
...
}

这种写法太长了,而且出现新的雇员在添加时会变得更长,其次它做了不止一件事,违反了单一权责原则,对雇员添加又违反了开放封闭原则,而且最麻烦的是可能到处都有类似结构的函数。

该问题的解决方法是将switch埋到抽象工厂底下,不被任何人看到,工厂使用Employee的派生物创建适当的实体,而不同的函数如calculatePay、isPayday和deliverPay等,则由Employee接口多态地接收。

public abstract class Employee{
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
} public interface EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r) throw InvalidEmployeeType;
} public class EmployeeFactoryImpl implements EmployeeFactory{
public Employee makeEmployee(EmployeeRecord r){
switch(r.type){
case COMMISSIONED:
return new CommissionedEmployee(r);
case :
return new CommissionedEmployee(r);
}
}
}
2.4 函数参数

最理想的参数数量是零,其次是一,再其次是二,应当避免三参数函数。

参数是阅读函数时最关键的部分,所以应当做到精简!

【笔记】Clean Code(持续更新)的更多相关文章

  1. R笔记整理(持续更新中)

    1. 安装R包 install.packages("ggplot2") #注意留意在包的名称外有引号!!! library(ggplot2) #在加载包的时候,则不需要在包的名称外 ...

  2. NXP LPC系列学习笔记汇总(持续更新中)

    1. LPC11E68循环冗余校验CRC学习笔记 文章主要介绍了如何使用LPC11E68的CRC外设功能,并介绍了与CRC引擎相关的寄存器,然后以生成CRC-CCITT多项式校验为例进行了介绍. 2. ...

  3. Android周笔记(9.8-14)(持续更新)

    本笔记记录一周内的小知识点和一些心学习的Demo. 1.PopupWindow: new 一个activity_pop_window:id为popwindow的Button,id为hello123的T ...

  4. C++ 编程技巧笔记记录(持续更新)

    C++是博大精深的语言,特性复杂得跟北京二环一样,继承乱得跟乱伦似的. 不过它仍然是我最熟悉且必须用在游戏开发上的语言,这篇文章用于挑选出一些个人觉得重要的条款/经验/技巧进行记录总结. 文章最后列出 ...

  5. workbench使用小笔记(不定期持续更新)

    1. 删除不使用的工作空间 在使用workbench时,之前可能建了好几个工作空间,现在有一些不使用了,每次打开都能还能看到它们,对于强迫症来说多少有一些不爽.如下图: 现在,就把那些不使用的工作空间 ...

  6. 虚拟机centos笔记整理,持续更新~~

    远程拷贝文件:scp -r 文件名 主机名:完整路径名(冒号不能少)拷贝当前windows系统的文件到当前目录:rz -y 查找文件:updatedb 修改数据库locate 文件名 即可查找文件

  7. markdown常用语法使用笔记+使用技巧(持续更新......)

    参考引用内容: 简书教程 一 基本语法 1. 标题 语法: 在想要设置为标题的文字前面加#来表示,一个#是一级标题,二个#是二级标题,以此类推.支持六级标题. 注:标准语法一般在#后跟个空格再写文字 ...

  8. BLE资料应用笔记 -- 持续更新

    BLE资料应用笔记 -- 持续更新 BLE 应用笔记 小书匠 简而言之,蓝牙无处不在,易于使用,低耗能和低使用成本.'让我们'更深入地探索这些方面吧. 蓝牙无处不在-,您可以在几乎每一台电话.笔记本电 ...

  9. “Clean Code” 读书笔记序

    最近开始研读 Robert C.Martin 的 “Clean Code”,为了巩固学习,会把每一章的笔记整理到博客中.而这篇博文作为一个索引和总结,会陆续加入各章的笔记链接,以及全部读完后的心得体会 ...

随机推荐

  1. promise 极简版封装

    Promise 优点: 解决回调地狱, 对异步任务写法更标准化与简洁化 缺点: 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消; 其次,如果不设置回调函数,Promise内部抛出的 ...

  2. Java连载48-final关键字

    一.final关键字 1.注意点: (1)final是一个关键字,表示最终的,不可变的. (2)final修饰的类无法被继承 (3)final修饰的方法无法被覆盖 (4)final修饰的变量一旦被赋值 ...

  3. window10下pytorch和torchvision CPU版本安装

    1.环境 python3.5 Anaconda 4.2.0 2.pytorch安装 pip3 install https://download.pytorch.org/whl/cpu/torch-1. ...

  4. LeetCode 387: 字符串中的第一个唯一字符 First Unique Character in a String

    题目: 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引.如果不存在,则返回 -1. Given a string, find the first non-repeating charact ...

  5. 实现简易JDBC框架

    1 准备 JDBC 基本知识 JDBC元数据知识 反射基本知识 2:  两个问题 业务背景:系统中所有实体对象都涉及到基本的CRUD操作.所有实体的CUD操作代码基本相同,仅仅是发送给数据库的sql语 ...

  6. 为什么Redis 单线程却能支撑高并发?

    作者:Draveness 原文:draveness.me/redis-io-multiplexing 推荐阅读 1. Java 性能优化:教你提高代码运行的效率 2. 基于token的多平台身份认证架 ...

  7. 如何真正实现由文档驱动的API设计?

    前言 本文主要介绍了一种新的开发思路:通过反转开发顺序,直接从API文档中阅读代码.作者认为通过这种开发方式,你可以更清楚地知道文档表达出什么以及它应该如何实现. 如果单从API文档出发,由于信息量不 ...

  8. 解决java导入project出现红叉

    1.右击,import,选择需要导入的文件组. D:\softwar\seeyon\A8\ApacheJetspeed\webapps\seeyon\WEB-INF\lib  全选,打开,finish ...

  9. C# 校验并转换 16 进制字符串到字节数组

    问题 最近在进行硬件上位机开发的时候,经常会遇到将 16 进制字符串转换为 byte[] 的情况,除了这种需求以外,还需要判定一个字符串是否是有效的 16 进制数据. 解决 字符串转 byte[] 的 ...

  10. Java内功心法,Set集合的详解

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...