首先,正在进行的,说明下本文的可读性。
1.背景 根据今年形势996icu,加班加点的情况比较多。与其抱怨,不如改变。
从内因去改变:主题,编写可读代码,大大工作效率。
2.观点:代码是写给人看的,推荐一本书《编写可读代码的艺术》。
3.方法:以《编写可读代码的艺术》内容,结合实际项目代码,给出13条建议。
4.个性化建议。
5.项目代码举例。
6.总结,可读性的好处,解决的问题。
本次分享中提到的想法实践,纯属个人见解,仅供参考,多多海涵。
职业生涯难题,996icu,加班加点。
与其抱怨,不如改变。
外因,不可控。
内因,可控。
想想能为一起coding的同事做点什么?
《编写可读代码,提高工作效率》。
需求又变了?加班
工作量有点大,进度催的紧,先加班为敬
系统bug多,顾此失彼,心情忧伤。
接手同事代码,看不懂。没文档、没流程图,产品经理和测试,不停追问业务细节。
可测试。测试经常打听实现细节。
联调真费劲,队友真...
代码可读性差,不好维护,容易修改出问题。
抓住1个根本性问题:写出的代码,可读性强,能让人快速理解、轻松维护、容易扩展。
为什么说能?且听下文分解...
2013年读过一本书《编写可读代码的艺术》,推荐阅读。
程序员之间的互相尊重体现在他所写的代码中,他们对工作的尊重也体现在那里。
代码最重要的读者不是编译器,解释器或电脑,而是人。
写出的代码能让人快速理解、轻松维护、容易扩展的程序员才是专业的程序员。
可读性基本定理:代码的写法,应当使别人理解它所需要的时间最小化。
"别人"应当指所有阅读你的代码的人,包括同事,也包括6个月后的你自己!
增加变量让代码更可读
if(name != null && name != ""){
}
if(StringUtils.isNotEmpty(name)){
}
boolean isNotEmpty = StringUtils.isNotEmpty(name);
if(isNotEmpty){
}
boolean isNotEmpty = StringUtils.isNotEmpty(name);
if(isNotEmpty && name != "admin"){
}
需求变动:开始只有1个规则,后来变为2个规则
boolean ruleOne =calcRuleOne();
boolean roleTwo = calcRuleTwo();
if(ruleOne && ruleTwo){
}else if(){
}else if(){
}else{
}
如果不认真写代码,出了bug,让你怀疑人生。
@RestController
@RequestMapping("manage/dictionary")
public class DictionaryController extends BaseController{
//获得数据字典
@RequestMapping("/getDictionary")
private String getDictionary(Dictionary dictionary){
// code
}
//收款人列表
@RequestMapping("/payeeList")
public String payeeList(){
// code
}
}
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");
getPage(url); 是从缓存获取页面,还是实时从互联网上获取呢?
根据url,获得1页内容?获得1个变量。
更专业的词:fetchPageFromCache,downloadPage,getPage。
String tmp =user.name();
tmp += " "+user.email();
用userInfo这样的名字更具有描述性。
建议:tmp这个名字只应用于短期存在且临时性为其主要存在因素的变量。
serverCanStart:检测服务是否可以监听某个给定的TCP/IP端口。
更好的名字:
canLinstenOnPort:这个名字直接地描述了这个方法要做什么事情。
var start = new Date().getTime();
//do sth
var end = new Date().getTime();
var costTime = (start-end)/1000;
(时间的单位是秒s,还是毫秒ms?)
costTimeMs?
int d;
int days;
int daysSinceLastUpdate;
在小的作用域可以使用短的名字,大的作用域使用长的名字。
看看当前上下文是否有足够的信息。
//常量名和类名的取名方式不一样
private String userBtn;
public static final int MAX_NUMBER= 100;
public class Number{
}
//挑出?减掉?
allPersons.filter(“age>100”);
推荐用begin和end来表示包含/排除范围
String str ="abcd";
str.substring1(int first,int last);
str.substring2(int bigin,int end);
public boolean addUser(){
boolean flag= true;
return flag;
}
把flag换成addSucceed
private String name;
//很多程序都习惯了把以get开始的方法当作“轻量级访问器”这样的用法
//它只是简单地返回一个内部成员变量。
private String getName(){
return name;
}
//bad
private String getName(){
return "My name is:"+Name+" !";
}
命名一致
userName
username
name
类型一致
varchar(32) comp_id, String compId
int(11) comp_id , Integer compId;
//get/query/find/select 查询类方法 (高频方法)
//add 增加类方法
//update 修改类方法
//delete 删除类方法
String name;
updateName="";
String email;
sendEmail();
String address;
saveAddress();
class Name{
}
class Name
{
}
一致的风格比“正确”的风格更重要。
//用户模块
public class UserService{
}
建议:不要为那些能从代码本身快速推断的事实写注释。
boolean isEmpty = StringUtils.isEmpty(name);
if(isEmpty){
}
没用的注释
/**
* 导出列表excel
*
* @param params
* @param resp
* @return
*/
@Override
public Result export(Map params, HttpServletResponse resp) {
}
/**
* 导出列表excel
*/
@Override
public Result export(Map params, HttpServletResponse resp) {
}
4.2.1 加入“导演评论”
//准确率可以达到99%,没有必要达到100%
getValue();
4.2.2 为代码中的瑕疵写注释
//冒泡排序不够快
bubbleSort();
4.2.3 给常量加注释
//人的最大年龄
public static final int MAX_AGE=150;
4.3.1公布可能的陷阱
//调用外部服务来发送邮件。(1分钟之后超时)
sendEmail();
4.3.2 总结性注释
//求和
int[] array = {1,2,3};
for(int index=0;index
//返回文件的行数
//计算换行符(\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 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 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);
}
}
}
```
变量作用域过大
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);
关键思想:把条件、循环以及其它对控制流的改变做得越“自然”越好。
运用一种方式使读者不用停下来重读你的代码。
if(age >20){
}
比
if(20 < age){
}
更易读。
if(name == null){
}
比
if(null == name){
}
在中文和英文等自然语言中(“如果你的年龄大于20”)更常见,更符合一般用法。
即比较的左侧,它的值倾向于不断变化,比较的右侧,它的值倾向于稳定。
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
}
它对于可读性的影响是富有争议的。
拥护者认为这种方式可以只写一行而不用写成多行,
反对者则说这可能会造成阅读的混乱而且很难用调试器来调试。
关键思想:相对于追求最小化代码行数,一个更好的度量方法是最小化人们理解它所需的时间。
建议:默认情况下都用if/else。
三目运算符?:只有在最简单的情况下使用。
do{
}while(condition);
do/while循环的奇怪之处是一个代码块是否会执行,是由其后的一个条件决定的。
通常来讲,逻辑条件应该出现在它们“保护”的代码之前,这是if,while和for语句的工作方式。
因为你通常会从前向后来读代码,这使得do/while循环有点不自然了。
public boolean contains(String str,String substr){
if(str==null || substr==null){
return false;
}
if(substr.equals("")){
return true;
}
...
}
if(userResult==SUCCESS){
if(permissionResult != SUCCESS){
reply.writeErrors("error reading permission");
reply.done();
return;
}
reply.writeErrors("");
}else{
reply.writeErrors(userResult);
}
reply.done();
可以通过提前返回,来减少嵌套。
if(line.split(",")[0].name=="root"){
}
增加一个解释变量
String username = line.split(",")[0].name;
if(name=="root"){
}
即使一个表达式不需要变量(因为你可以看出它的含义),把它装入一个新变量中仍然有用。
我们把它叫做总结变量,因为它的目的是用一个短很多的名字来代替一大块代码,
这个名字会更容易思管理和思考。
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){
...
}
关于变量的3个问题
a.变量越多,就越难全部跟踪它们的动向。
b.变量的作用域越大,就需要跟踪它的动向更久。
c.变量改变得越频繁,就越难以跟踪它的当前值。
没有价值的临时变量
now = datetime.time();
rootMessage.lastVisitTime=now;
减少控制流变量
boolean done=false;
if(condition && !done){
if(...){
done=true;
continue;
}
}
可以改为
if(condition){
if(...){
break;
}
}
把定义向下移
int a=0;
int b=0;
int c=0;
//handle a
//handle b
//handle c
改为
int a=0;
//handle a
int b=0
//handle b
全局变量改为局部变量。
"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;
}
}
a.抽取出那些与程序主要目的“不相关的子问题”。
b.重新组织代码,使它一次只做一件事情。
c.先用自然语言描述代码,然后用这个描述来帮助你找到更整洁的解决方案。
本章的建议是“积极地发现并抽取不相关的自逻辑”,我们是指:
a.看看某个函数或代码块,问问你自己,这段代码高层次的目标是什么?
b.对于每一行代码,问一下:它是直接未来目标而工作吗?这段代码高层次的目标是什么呢?
c.如果足够的行数在解决不相关的子问题,抽象代码到独立的函数中。
介绍性的例子
int[] array = {2,4,1,3};
求最大值和最小值。
void method(){
//排序函数,这就是1个子问题
//取第1个和最后1个
}
通用代码,它完全地从项目的其它部分解耦出来。这样的代码容易开发,容易测试,并且容易理解。SQL数据库、JavaScript库、XML库等。
把名字转换成1个URL,这类项目特有的功能,也是可以提取出来的。
同时在做几件事的代码很难理解。
一个代码块可能初始化对象,清除数据,解析输入,然后应用业务逻辑,所有这些都同时进行。
如果所有这些代码都纠缠在一起,对于每个"任务"都很难靠其自身来帮你理解它从哪里开始,到哪里结束。
当你把一件复杂的事向别人解释时,那些小细节很容易就会让他们迷惑。
把一个想法用“自然语言”解释是个很有价值的能力,因为这样其它知识没有你这么渊博的人才可以理解它。 这需要把一个想法精炼成最重要的概念。
这样做,不仅帮助他人理解,而且也帮助你自己把这个想法想得更清楚。
用户输入网站地址:如“http://article.cn”;
浏览器解析网址到IP,如122.96.184.84;
浏览器建立和该IP的Socket;
浏览器与该主机通信,取得网页;
显示网页内容。
知道什么时候不写代码,可能对于一个程序员来讲是他所要学习的最重要的技巧。
你所写的每一行代码都是需要测试和维护的。
通过重用库或者减少功能,你可以节省时间并且让你的代码保持精简节约。
最好读的代码就是没有代码。
创建“工具”代码减少重复代码;减少无用代码或者没有用的功能; 在一个成熟的库中,每一行代码都代表大量的设计、调试、重构、文档、优化和调试。
Collections,Lang,BeanUtils,Compress
很多功能没有完成,或者没有用,也可能是让程序更复杂。
一个功能,不是只有开发,还有测试,最后还有维护和升级。
不是所有的程序都需要运行的快,100%准确,并且能处理所有的输入。 如果你真的仔细检查你的需求,有时你可以把它削减成一个简单的问题。
user_name,userName,UserMapper,UserService,UserController
@Component
@Slf4j
public class TaskScheduler {
@Resource
private 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(){
}
CrmApiRpc
CrmInfoDelegate
PaymentSplitService
AccConfigInfoController
可借鉴的点:流程清晰 生成器入口
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);
命名精确,望文知义、单一职责、及时重构、流程清晰、可测试
降低复杂度-读懂代码花费的时间少、方便修改和维护、方便交接(代码)、bug少、方便测试、方便复用和重构、与产品经理测试等非写代码的人交流。
编写可读代码,提高工作效率。
https://fansunion.blog.csdn.net/article/details/12159019
https://fansunion.blog.csdn.net/article/details/12159345
https://fansunion.blog.csdn.net/article/details/12159431
个人观点:一次书写,人人阅读。 Write once,Read anyone。 Coding for fun, coding for my life。