本次分享是怎么做到“可读性”的

首先,正在进行的,说明下本文的可读性。
1.背景 根据今年形势996icu,加班加点的情况比较多。与其抱怨,不如改变。
从内因去改变:主题,编写可读代码,大大工作效率。
2.观点:代码是写给人看的,推荐一本书《编写可读代码的艺术》。
3.方法:以《编写可读代码的艺术》内容,结合实际项目代码,给出13条建议。
4.个性化建议。
5.项目代码举例。
6.总结,可读性的好处,解决的问题。

友情声明

本次分享中提到的想法实践,纯属个人见解,仅供参考,多多海涵。

一、主题:编写可读代码,提高工作效率

职业生涯难题,996icu,加班加点。
与其抱怨,不如改变。
外因,不可控。
内因,可控。
想想能为一起coding的同事做点什么?
《编写可读代码,提高工作效率》。

1.现状:加班加点

需求又变了?加班
工作量有点大,进度催的紧,先加班为敬
系统bug多,顾此失彼,心情忧伤。
接手同事代码,看不懂。没文档、没流程图,产品经理和测试,不停追问业务细节。
可测试。测试经常打听实现细节。
联调真费劲,队友真...

2.根本原因之一:代码可读性差

代码可读性差,不好维护,容易修改出问题。

3. 解决办法

抓住1个根本性问题:写出的代码,可读性强,能让人快速理解、轻松维护、容易扩展。

为什么说能?且听下文分解...

二、观点:代码是写给人看的

2013年读过一本书《编写可读代码的艺术》,推荐阅读。

1.观点

程序员之间的互相尊重体现在他所写的代码中,他们对工作的尊重也体现在那里。
代码最重要的读者不是编译器,解释器或电脑,而是人。
写出的代码能让人快速理解、轻松维护、容易扩展的程序员才是专业的程序员。

2 可读性怎么定义?

可读性基本定理:代码的写法,应当使别人理解它所需要的时间最小化。 
"别人"应当指所有阅读你的代码的人,包括同事,也包括6个月后的你自己!

2.1 示例1:

增加变量让代码更可读

if(name != null && name != ""){}if(StringUtils.isNotEmpty(name)){}boolean isNotEmpty = StringUtils.isNotEmpty(name);
if(isNotEmpty){}boolean isNotEmpty = StringUtils.isNotEmpty(name);
if(isNotEmpty && name != "admin"){}

2.2 示例2:

需求变动:开始只有1个规则,后来变为2个规则

 boolean ruleOne =calcRuleOne();boolean roleTwo = calcRuleTwo();if(ruleOne && ruleTwo){}else if(){}else if(){}else{}

2.3 示例3

如果不认真写代码,出了bug,让你怀疑人生。

@RestController
@RequestMapping("manage/dictionary")
public class DictionaryController extends BaseController{//获得数据字典@RequestMapping("/getDictionary")private String getDictionary(Dictionary dictionary){// code}//收款人列表@RequestMapping("/payeeList")public String payeeList(){// code}}

2.3 示例4,歧义的目录

src/main/resources/template/abc.xlsx
(Maven打包到 classes/template/abc.xlsx)
Generator.class.getClassLoader().getResourceAsStream("template/abc.xlsx");
src/main/template/abc.xlsx
Maven打包到 classes/abc.xlsx)
Generator.class.getClassLoader().getResourceAsStream("abc.xlsx");

三、方法:编写可读代码,13条建议

第一部分 表面层次的改进

1.把信息装到名字里

1.1选择专业的词

getPage(url); 是从缓存获取页面,还是实时从互联网上获取呢?
根据url,获得1页内容?获得1个变量。

更专业的词:fetchPageFromCache,downloadPage,getPage。

1.2避免象tmp这样泛泛的名字

String tmp =user.name();
tmp += " "+user.email();

用userInfo这样的名字更具有描述性。

建议:tmp这个名字只应用于短期存在且临时性为其主要存在因素的变量。

1.3 用具体的名字代替抽象的名字

serverCanStart:检测服务是否可以监听某个给定的TCP/IP端口。
更好的名字:
canLinstenOnPort:这个名字直接地描述了这个方法要做什么事情。

1.4为名字附带更多信息

    var start  = new Date().getTime();//do sthvar end = new Date().getTime();var costTime = (start-end)/1000;(时间的单位是秒s,还是毫秒ms?)costTimeMs?

1.5名字应该有多长

    int d;int days;int daysSinceLastUpdate;
在小的作用域可以使用短的名字,大的作用域使用长的名字。
看看当前上下文是否有足够的信息。

1.6利用名字的格式来传递含义

//常量名和类名的取名方式不一样

 private String userBtn;public static final int MAX_NUMBER= 100;public class Number{}

2.不会误解的名字

2.1容易产生误解的例子

   //挑出?减掉?allPersons.filter(“age>100”);

2.2 推荐用first和last来表示包含的范围

推荐用begin和end来表示包含/排除范围

   String str ="abcd";str.substring1(int first,int last);str.substring2(int bigin,int end);

2.3 给布尔值命名

public boolean addUser(){boolean flag= true;return flag;}

把flag换成addSucceed

2.4与使用者的期望相匹配

  private String name;//很多程序都习惯了把以get开始的方法当作“轻量级访问器”这样的用法//它只是简单地返回一个内部成员变量。private String getName(){return name;}//badprivate String getName(){return "My name is:"+Name+" !";}

2.5 变量命名的一致性

命名一致

   userNameusernamename

类型一致

   varchar(32) comp_id, String compIdint(11) comp_id ,  Integer compId;

3.审美

3.1把声明按块组织起来

//get/query/find/select 查询类方法  (高频方法)//add  增加类方法//update 修改类方法//delete 删除类方法

3.2把代码分成“段落”

String name;
updateName="";String email;
sendEmail();String address;
saveAddress();

3.3个人风格与一致性

class Name{}class Name
{}

一致的风格比“正确”的风格更重要。

4.该写什么样的注释

4.1什么不需要注释

//用户模块
public class UserService{}

建议:不要为那些能从代码本身快速推断的事实写注释。

boolean isEmpty = StringUtils.isEmpty(name);
if(isEmpty){}
没用的注释/*** 导出列表excel** @param params* @param resp* @return*/@Overridepublic Result export(Map<String, Object> params, HttpServletResponse resp) {
}/*** 导出列表excel*/@Overridepublic Result export(Map<String, Object> params, HttpServletResponse resp) {
}

4.2记录你的思想

4.2.1 加入“导演评论”

//准确率可以达到99%,没有必要达到100%
getValue();

4.2.2 为代码中的瑕疵写注释

//冒泡排序不够快
bubbleSort();

4.2.3 给常量加注释

//人的最大年龄
public static final int MAX_AGE=150;

4.3 站在读者的角度

4.3.1公布可能的陷阱

//调用外部服务来发送邮件。(1分钟之后超时)
sendEmail();

4.3.2 总结性注释

//求和

int[] array = {1,2,3};
for(int index=0;index<array.length;index++){sum += array[index];
}

4.4 精确地描述函数的行为

//返回文件的行数
//计算换行符(\n)的个数
int countLines(String fileName);

项目代码举例

@Slf4j
@RestController
@RequestMapping(value = "/api/bill")
public class BillController extends BaseController {/*** 获取账单列表和回款列表未处理的记录总数*/@RequestMapping(value = "/undealwithcount")public Result getUndealwithCount() {try {Integer billResultCount = 0;Integer refundResultCount = 0;Map<String, Integer> resutMap = new HashMap<>();BillVo vo = new BillVo();UserInfoDto userInfo = getUserInfo();vo = getAuthBillVo(vo, userInfo);vo.setBillInAccStatus("0,1");vo.setStoredBillStatus("1");vo.setStartBillMonth("201901");//g过滤掉2018的未核账数据PageBean<AccountStatement> billDataPage = billService.getBillDataPageByRoleId(vo);long totalRecord = billDataPage.getTotalRecord();billResultCount = (int) totalRecord;log.info("bicontroller getUndealwithCount query refund from bi param userId={}", userInfo.getUserId());String unSplitCountUrl = unSplitCounturl + "?casUserId=" + userInfo.getUserId();log.info("bicontroller getUndealwithCount query refund from bi param unSplitCountUrl={}", unSplitCountUrl);String resp = httpClientUtil.get(unSplitCountUrl);log.info("bicontroller getUndealwithCount query refund from bi result resp={}", resp);if (null != resp && StringUtils.isNotBlank(resp)) {JSONObject jsonObject = JSONObject.parseObject(resp);String code = jsonObject.getString("code");Integer data = jsonObject.getInteger("data");String message = jsonObject.getString("message");if ("000000".equals(code)) {refundResultCount = data;} else {log.info("查询bi系统回款列表未拆分记录数异常message={}", message);}}resutMap.put("billList", billResultCount);resutMap.put("refundList", refundResultCount);return ResultUtils.success(resutMap);} catch (Exception e) {log.error("获取账单未核算记录总数失败,异常信息={}.", e);e.printStackTrace();return ResultUtils.error(ResultEnums.QUERY_FAIL_ERROR);}}
}
```

一个方法,8处可改进

变量作用域过大
vo被改变了吗?
BillController里出现了bicontroller
billList和billResultCount
异常时,用error
log记录error有问题
如果正确,log打印了异常,还需要"e.printStackTrace()"

log.error("获取账单未核算记录总数失败,异常信息={}.", e);
e.printStackTrace();
代码重复 
null != resp && StringUtils.isNotBlank(resp)

提取子方法,1个方法解决1个问题 
String resp = httpClientUtil.get(unSplitCountUrl);

第2部分 简化循环和逻辑

5.把控制流变得易读

关键思想:把条件、循环以及其它对控制流的改变做得越“自然”越好。
运用一种方式使读者不用停下来重读你的代码。

5.1条件语句中参数的顺序

if(age >20){
}
比
if(20 < age){
}

更易读。

if(name == null){
}
比
if(null == name){
}

在中文和英文等自然语言中(“如果你的年龄大于20”)更常见,更符合一般用法。
即比较的左侧,它的值倾向于不断变化,比较的右侧,它的值倾向于稳定。

5.2 if/else语句块的顺序

if(a== b){//case one
}else{//case two
}

也可以写成

if(a != b){//case one
}else{//case two
}

之前你可能没想太多,但在有些情况下有理由相信其中一种顺序比另一种好:
a.首先处理正逻辑而不是负逻辑的情况。例如,if(debug)而不是if(!debug)。
b.先处理简单的情况。这种方式可能还会使得if和else在屏幕之内都可见,这很好。
c.先处理有趣的或者是可疑的情况。

下面所示是负逻辑更简单并且更有趣的一种情况,那么会先处理它

if (not the same  username){//case one
}else{//case two
}

5.3三目运算符

它对于可读性的影响是富有争议的。
拥护者认为这种方式可以只写一行而不用写成多行,
反对者则说这可能会造成阅读的混乱而且很难用调试器来调试。

关键思想:相对于追求最小化代码行数,一个更好的度量方法是最小化人们理解它所需的时间。
建议:默认情况下都用if/else。
三目运算符?:只有在最简单的情况下使用。

5.4避免do/while循环

do{}while(condition);

do/while循环的奇怪之处是一个代码块是否会执行,是由其后的一个条件决定的。
通常来讲,逻辑条件应该出现在它们“保护”的代码之前,这是if,while和for语句的工作方式。
因为你通常会从前向后来读代码,这使得do/while循环有点不自然了。

5.5从函数中提前返回

public boolean contains(String str,String substr){if(str==null || substr==null){return false;}if(substr.equals("")){return true;}...
}

5.6最小化嵌套

if(userResult==SUCCESS){if(permissionResult != SUCCESS){reply.writeErrors("error reading permission");reply.done();return;}reply.writeErrors("");
}else{reply.writeErrors(userResult);
}
reply.done();

可以通过提前返回,来减少嵌套。

6.拆分超长的表达式

6.1用做解释的变量

if(line.split(",")[0].name=="root"){}增加一个解释变量String username = line.split(",")[0].name;if(name=="root"){}

6.2总结变量

即使一个表达式不需要变量(因为你可以看出它的含义),把它装入一个新变量中仍然有用。
我们把它叫做总结变量,因为它的目的是用一个短很多的名字来代替一大块代码,
这个名字会更容易思管理和思考。

if(request.user.id == document.user.id){//user can edit this document
}if(request.user.id != document.user.id){//document is read only
}

这里的表达式“request.user.id==document.user.id”看上去可能并不长,
但它包含5个变量,所以需要多花点时间来想一想如何处理它。

这段代码中的主要概念是:“该用户拥有此文档吗?”
这个概念可以通过增加一个总结变量来表达得更清楚。

final boolean userOwnDocument = (request.user.id==document.user.id);
if(userOwnDocument){...
}
if(!userOwnDocument){...
}

7.变量与可读性

关于变量的3个问题
a.变量越多,就越难全部跟踪它们的动向。
b.变量的作用域越大,就需要跟踪它的动向更久。
c.变量改变得越频繁,就越难以跟踪它的当前值。

7.1减少变量

没有价值的临时变量

now = datetime.time();
rootMessage.lastVisitTime=now;

减少控制流变量

boolean done=false;
if(condition && !done){if(...){done=true;continue;}
}

可以改为

if(condition){if(...){break;}
}

7.2缩小变量的作用域

把定义向下移
int a=0;
int b=0;
int c=0;//handle a
//handle b
//handle c改为
int a=0;
//handle aint b=0
//handle b

全局变量改为局部变量。

7.3 只写一次的变量更好

"1"表示什么意思?

 vo.setStoredBillStatus("1");public static final int MAX_AGE=140;

常量、枚举,可能更能表达变量的含义

public enum CompLevelEnum {SME(4, "小客户"),GENERAL_CUSTOMER(3, "一般客户"),AREA_KEY_CUSTOMER(2, "区域级重点客户"),COMP_KEY_CUSTOMER(1, "公司级重点客户");private Integer code;private String message;CompLevelEnum(Integer code, String message) {this.code = code;this.message = message;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

第三部分 重新组织代码

8. 三种组织代码的方法

a.抽取出那些与程序主要目的“不相关的子问题”。

b.重新组织代码,使它一次只做一件事情。

c.先用自然语言描述代码,然后用这个描述来帮助你找到更整洁的解决方案。

9.抽取不相关的子问题

本章的建议是“积极地发现并抽取不相关的自逻辑”,我们是指:

a.看看某个函数或代码块,问问你自己,这段代码高层次的目标是什么?

b.对于每一行代码,问一下:它是直接未来目标而工作吗?这段代码高层次的目标是什么呢?

c.如果足够的行数在解决不相关的子问题,抽象代码到独立的函数中。

介绍性的例子

int[] array = {2,4,1,3}; 求最大值和最小值。 void method(){ //排序函数,这就是1个子问题 //取第1个和最后1个 }

10.纯工具代码

文件操作,邮件发送等。

创建大量通用代码

通用代码,它完全地从项目的其它部分解耦出来。这样的代码容易开发,容易测试,并且容易理解。SQL数据库、JavaScript库、XML库等。

项目专有的功能

把名字转换成1个URL,这类项目特有的功能,也是可以提取出来的。

11.一次只做一件事情

同时在做几件事的代码很难理解。
一个代码块可能初始化对象,清除数据,解析输入,然后应用业务逻辑,所有这些都同时进行。
如果所有这些代码都纠缠在一起,对于每个"任务"都很难靠其自身来帮你理解它从哪里开始,到哪里结束。

12. 把想法变成代码

当你把一件复杂的事向别人解释时,那些小细节很容易就会让他们迷惑。
把一个想法用“自然语言”解释是个很有价值的能力,因为这样其它知识没有你这么渊博的人才可以理解它。 这需要把一个想法精炼成最重要的概念。

这样做,不仅帮助他人理解,而且也帮助你自己把这个想法想得更清楚。

一个示例:用户在浏览器访问1篇文章

用户输入网站地址:如“http://article.cn”;

浏览器解析网址到IP,如122.96.184.84;

浏览器建立和该IP的Socket;

浏览器与该主机通信,取得网页;

显示网页内容。

13.少写代码

知道什么时候不写代码,可能对于一个程序员来讲是他所要学习的最重要的技巧。

你所写的每一行代码都是需要测试和维护的。

通过重用库或者减少功能,你可以节省时间并且让你的代码保持精简节约。

最好读的代码就是没有代码。

13.1保持小代码库。

创建“工具”代码减少重复代码;减少无用代码或者没有用的功能; 在一个成熟的库中,每一行代码都代表大量的设计、调试、重构、文档、优化和调试。

Collections,Lang,BeanUtils,Compress

13.2 别费神实现那个功能–你不会需要它。

很多功能没有完成,或者没有用,也可能是让程序更复杂。

一个功能,不是只有开发,还有测试,最后还有维护和升级。

13.3 质疑和拆分你的需求。

不是所有的程序都需要运行的快,100%准确,并且能处理所有的输入。 如果你真的仔细检查你的需求,有时你可以把它削减成一个简单的问题。

四、6点个性化建议

1、不错的建议

1.1约定优于配置

user_name,userName,UserMapper,UserService,UserController

1.2只写必要的注释,业务复杂的地方写注释

1.3削减代码行数

2、有争议的建议,个人特殊习惯

2.1 Service只要实现类,不要接口。

2.2 数据库字段,采用java驼峰命名,减少映射。

2.3 慎用设计模式。

五、代码举例:Talk is cheap, Show me the Code

1. 2019年,账务系统,Web项目

例子1,定时任务,可读性更强

@Component
@Slf4j
public class TaskScheduler {@Resourceprivate CrmApiRpc crmApiRpc;/*** 1小时1次*/@Scheduled(initialDelay = 30*60*1000, fixedRate = 1* 60 * 60 * 1000)public void task1() {log.info("-----task start-----");try {doTask();} catch (Exception e) {log.error("task error", e);}log.info("-----task end-----");}
}/*** 1小时1次*/@Scheduled(cron = "0 */1 * * *")public void task2(){}

例子2,相关代码,统一放在一起

 CrmApiRpcCrmInfoDelegate

例子3,核心业务,用1个类单独维护

 PaymentSplitService

例子4,用MybatisPlus框架,轻松coding少写代码

 AccConfigInfoController

2. 2017年,Mybatis代码生成器,工具项目

可借鉴的点:流程清晰 生成器入口

3.1初始化配置

3.2根据配置生成代码

3.2.1获得数据库连接

3.2.2得到所有表名

3.2.3循环生成每个表对应的模版

     1).根据数据库连接和数据库表名,构造模版的数据模型2).将Java模型转换成Map格式3).生成4个标准文件(读取模版,根据Map,渲染,保存)a. GeneratorTool.generateModel(generatorModel);b. GeneratorTool.generateBean(generatorModel);c. GeneratorTool.generateMapperJava(generatorModel);d. GeneratorTool.generateMapperXml(generatorModel);

3.3自动打开生成文件的目录

六、总结

1.可读代码的手段

命名精确,望文知义、单一职责、及时重构、流程清晰、可测试

2.可读代码的好处

降低复杂度-读懂代码花费的时间少、方便修改和维护、方便交接(代码)、bug少、方便测试、方便复用和重构、与产品经理测试等非写代码的人交流。

3.结论

编写可读代码,提高工作效率。

七、QA

八、参考资料

1.读书笔记-编写可读代码的艺术[上]

https://fansunion.blog.csdn.net/article/details/12159019

2.读书笔记-编写可读代码的艺术[中]

https://fansunion.blog.csdn.net/article/details/12159345

3.读书笔记-编写可读代码的艺术[下]

https://fansunion.blog.csdn.net/article/details/12159431

4.《编写可读代码的艺术》

5.《重构-改善既有代码的设计》

个人观点:一次书写,人人阅读。 Write once,Read anyone。 Coding for fun, coding for my life。

编写可读代码,提高工作效率相关推荐

  1. cpu java poi 导出_让 Java 开发更简单,提高工作效率 | Gitee 项目推荐

    1.项目名称:基于 Spring Boot 的权限管理系统 项目简介:Good 权限管理系统是作者学习 springBoot 时基于 springBoot 开发的一套轻量级的权限系统,其目的是形成一套 ...

  2. 8个提高工作效率的Web前端开发框架总结

    今天要跟大家分享的文章是关于8个提高工作效率的web前端开发框架总结.着互联网的推进我们可以寻找到各种高效资源,比如我们设计网站.小程序和公众号开发的时候,前端设计并不需要我们太多的自主设计,我们可以 ...

  3. 码农提高工作效率(转载)

    本文转自:http://www.cnblogs.com/huang0925/p/3612741.html 俗话说,天下武功,唯快不破.也就是说要练成天下高手的话,出招速度一定要快,这样才能在江湖上立足 ...

  4. 开发人员该选择什么大数据工具提高工作效率?

    开发人员该选择什么大数据工具提高工作效率? 海量数据使得数据分析工作变得繁重困难,开发人员选择合适的大数据工具来开发大数据系统成为新的挑战.因此开发人员要根据不同的数据处理方式对大数据工具进行分类. ...

  5. 使用EditPlus技巧,提高工作效率(附英文版、自动完成文件、语法文件下载)

    除了windows操作系统,EditPlus可以说是我最经常使用的软件了.无论是编写xhtml页面,还是css.js文件,甚至随笔记记这样的事情,我都会使用EditPlus(现在使用的是EditPlu ...

  6. 使用EditPlus技巧,提高工作效率(附英文版、自动完成文件、语法文件下载)(转载)...

    除了windows操作系统,EditPlus可以说是我最经常使用的软件了.无论是编写xhtml页面,还是css.js文件,甚至随笔记记这样的事情,我都会使用EditPlus(现在使用的是EditPlu ...

  7. 使用EditPlus技巧,提高工作效率(自动完成文件、语法文件下载)

    除了windows操作系统,EditPlus可以说是我最经常使用的软件了.无论是编写xhtml页面,还是css.js文件,甚至随笔记记这样的事情,我都会使用EditPlus(现在使用的是EditPlu ...

  8. 编写可读代码(二) 如何命名

    记得看到过一个调查,说程序员最头疼的事情是什么,最后票数最高的是Naming things(http://kb.cnblogs.com/page/192017/).从中不难看出,命名这件往往被初学者忽 ...

  9. 如何用 MacBook 提高工作效率 【配置篇】

    上一篇文章从应用(application)的选择上推荐了一些我常用的应用或工具,这篇主要从配置(config)和思路.原则上提高工作效率.总而言之,我们的目的只有一个,帮助尽快完成工作,能不加班就不加 ...

最新文章

  1. Action4D:人群和杂物中的在线动作识别:CVPR209论文阅读
  2. R语言多因素方差分析及评估假设检验
  3. 机智云5.0推出IoT套件GoKit4.0 可实现物联网应用协同开发
  4. 深入了解Brackets编辑器 [好东西啊]
  5. 穿越五年的时空,重回三元湖畔
  6. freemark 时间比较
  7. 【OpenCV学习】矩阵基本操作
  8. 【渝粤教育】电大中专职业生涯规划 (2)_1作业 题库
  9. es6新增的html标签,javascript – 如何导入已在html中的标签中定义的es6模块?
  10. MiningZhiDaoQACorpus,580万百度知道问题,980万问答对数据挖掘项目
  11. 小米集团2021未来星专项招聘计划!
  12. linux下内存调试工具——valgrind
  13. OpenGL超级宝典(第7版)之清单的初始环境配置VS2019
  14. 第八届ACM程序设计大赛总结
  15. 郑州学python哪个机构好_郑太高铁线路图_郑太高铁站点_【高铁网】_郑太高铁时刻表_郑太高铁通车时间...
  16. sqli-labs(50-53)
  17. esp32上传文件方法
  18. php 短信验证码过期时间,php下发短信验证码60秒简单验证
  19. Liunx userdel删除用户时提示userdel: user *** is currently used by process 12910
  20. 使用LCN框架解决分布式事物

热门文章

  1. android8 Camera2 从 CameraService 到 HAL Service
  2. KMP算法解决病毒检测问题
  3. AD20/Altium designer——如何生成Gerber打板文件
  4. Retrofit网络请求数据的使用
  5. KMP算法前后缀原理
  6. JavaSE_笔试题_判断题1
  7. WIN10 Redis安装与使用
  8. 手撕线程池 ThreadPool
  9. 【新业务搭建】竞争情报业务规划及体系构建的思考——By Team
  10. Go十大常见错误第7篇:不使用-race选项做并发竞争检测