编写可读代码的艺术读书整理

简介: 好代码的各种衡量 之前看过一些**编码规范,作者一般上来都是这样子的,先说明自己来自某大厂,职位什么的,然后下面就开始逐条规定,对于什么样的情况,我该怎么写代码。其实,这过程中是有些内容被忽视掉了的,那就是什么是好代码?在整理编码规范前,我觉得有必要去想想这个问题的。

好代码的各种衡量

之前看过一些**编码规范,作者一般上来都是这样子的,先说明自己来自某大厂,职位什么的,然后下面就开始逐条规定,对于什么样的情况,我该怎么写代码。其实,这过程中是有些内容被忽视掉了的,那就是什么是好代码?在整理编码规范前,我觉得有必要去想想这个问题的。

运行效率高的?比如:a/2写成a<<1这种,把每句代码的取址-译码-执行的时间降低到最低?nono,这是个标准,但是不是这个时代的标准,这个标准放到纸带打孔机那个时代还行,在这个时代,那百万分之一毫秒的差别,几乎不能引起关注。
还有N多假设在此省略。。。自行脑补吧。。

代码应当”让人“易于理解

这里特别强调是“人”易于理解,而不是让机器易于理解;因为,只要你语法正确,机器都能理解,相反,可能你周围的小伙伴理解起来,就未必能那么顺畅了。
说明原因之后,我们一下的内容,都会围绕这一原则进行具体描述。

可读性基本定理

代码的写法应当使别人理解它所需时间最小化
你可能遇到过这种情况,一个项目,来来回回好几拨人,可能当时的管理各方面也比较混乱吧,项目中的code style都乱的不成样子了,这时候,你忽然被告知,要在这个摇摇欲坠的项目里面,做点儿新需求,请问你什么感觉。

理解代码所需时间是否与其他标准有冲突

遇到这种问题时候,这里给出最简单的方法,先问自己,这个代码别人好理解吗?如果回答是肯定的,那么,就这样吧;否则,fix it.
例如,两个变量互换:

        A=A^B;
        B=B^A;
        A=A^B;

偶尔猛一看,略微蒙,倒不如这种傻瓜式的写法:

    int x = 10;
    int y = 20;
    int temp = x;
    x = y;
    y = temp;

表面层次上的改进

首先我们对代码的改进,先从最简单的开始:选择好的名字;写好的注释;以及把代码写成更好的格式。

关于命名

key words:把信息装进名字中

  • 选择专业的词
    先来看个bad name:
    public void statisticOrderOfDay();

    public void statisticOrderOfDay(String str);

    public void statisticStationOrderOfDay();

这个是我从一个service接口里面找到的,丝毫没改动,然后拿了过来,猛一看,这三个方法连在一起,感觉萌萌的,再仔细读,会发现更蒙,因为从名字里面,看不出有什么特别大的区别。
好点儿的的方法命名:

int updateByExampleSelective(@Param("record") BizRescueWorkOrder record, @Param("example") BizRescueWorkOrderExample example);

int updateByExample(@Param("record") BizRescueWorkOrder record, @Param("example") BizRescueWorkOrderExample example);

int updateByPrimaryKeySelective(BizRescueWorkOrder record);

int updateByPrimaryKey(BizRescueWorkOrder record);

key words:尽量使用精确的单词描述你要表达的事情!
- 避免使用空泛的名字
常见单词,temp,retVal,foo,bar,baz,qux,i,j这种。一个个来说,temp只能读出这个是个临时变量,retVal看出了这个东西是个返回值,至于foo,已经后面连这的这几个词,能用这些的,你站出来说说你是有多无聊。
好的名字应该尽可能的去描述变量的目的和它所承载的值。
例如:

    /* What will be run. */
    private Runnable target;

不恰当的用法这里也举几个例子,比如,在两个变量交换值的时候,此时使用temp是合适的,说明此时正在使用的是个临时变量,但是,比如去拼接一个很长的字符串时候,如果一直是temp+=*,就不大合适了;
再比如,嵌套循环中:

这里写图片描述

这里写图片描述

使用这种没有意义的参数进行循环,很容易把下标写错,倒不如换用有意义的迭代索引:
这里写图片描述

tips:多去给自己的变量重命名,会发现命名能力会逐渐提升。
- 用具体的名字代替抽象的名字
这里写图片描述

- 名字的长度

System.Windows.Media.TextFormatting.GetTextEffectCharacterIndexFromTextSourceCharacterIndex()

上面的方法来自 .net framework via MSDN(够长吧,放出来吓吓你!)
到底名字长度多少合适,其实也并没标准,但是还是提供了一些指导性的原则:

  • 在小的作用域里面,尽量使用简短名字;想反,大的作用域内,你可能就需要更多的单词去描述这个东西了。
  • 缩写不要太过分啊:之前跟朋友聊天,说他最近在做的 项目,一个类名怎么也看不懂,然后就去问原作者,之后原作者告诉他,这个缩写是自己女儿名字的缩写。之后我就呵呵了,这个是真事儿。想说的是,缩写可以用,但是尽量让大家都能看懂,比如doc,str这种,还是可以接受的。
  • 丢掉没用的单词:比如,convertToString这个方法,完全可以这样子:toString().尽量简化。
  • 利用名字的格式来传递含义:
    例如,常见的常量值:
 /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

使用“_”区分类成员和局部变量。
- 使用不容易被误解的名字:表示上限时使用min_和max_作为前缀;对于包含范围的选择,使用firset和last;对于包含排除的范围,使用begin和end;对于布尔值,尽量使用has,is,来表示,避免使用否定的词比如disable_ssl;

  • 其他格式规范:对于不同的语言,都会有自己的一些特色性的东西,比如,jquery对象,命名加上$标记。

段落

跟我们小学时候学写作文一样,学了词汇,我们就要学习一些段落的知识了。
首先来说关于段落的几条原则:
- 使用一致的布局,让读者很快习惯这种style.
- 让相似的代码看上去更相似。
- 把相关代码进行分组,形成代码块。
一些小策略:
- 1,重新安排换行来保持一致跟紧凑

这里写图片描述

这样看起来三个方法是不是很相似了,如果觉得空间占用太大,还可以调整下:
这里写图片描述

- 2,用方法来整理不规则的东西

这里写图片描述

感觉assert测试除了参数不一样,其他都是一个意思,但是有的都换行了,不太一致,调整下:

这里写图片描述

  • 3,在需要时候使用列对齐

这里写图片描述
这样写,我们很容易一眼看出,第二个参数,第三个参数的位置。
- 4,选择一个有意义的顺序,始终使用它
比如,在接收表单参数的时候:
这里写图片描述

跟填写的顺序保持一致;从重要到不重要排序;
- 把声明按照块组织起来
如果类的声明是这样纸的:
这里写图片描述

是不是略微茫然,如果按照用途调整下:
这里写图片描述

个人风格:之前有个笑话,说两个大括号的位置就能让两个程序猿打起来。其实风格这个东西,就是个规范,就像是剪草坪,政府怎么规划,你就怎么剪,一致的风格比你自己 的style更重要。

注释

注释的原则:注释的目的是尽量帮助读者了解的跟原作者一样多

  • 什么时候不需要注释
    有时候,知道不做什么跟知道要做什么是一样有意义的。
//this is a class for Resource class
class Resource{
        //this is a constructor method
        Resource(){}
}

上面这个注释,其实是没有意义的,反而看上去有点儿可笑。
key words:不要用能从代码中[ 快速 ]推断出来的事实去注释,这样有点儿stupid!
- 不要为了注释去注释
之前有个理论说是注释比例跟代码比例是什么3:2这种,我就郁闷了。感觉其实没有这个必要。
- 不要去为名字起得不好的东西加上好注释
如果为了弥补名字不好,而去加上更多的注释去解释这个东西,那么还是请你先跳回去读,怎么给一个东西起个好名字。
- 加入comments
我们有时候在读word文档的时候,会加入审阅,这个就是这个意思。

**    
 * When I wrote this, only God and I understood what I was doing
 * Now, God only knows
 */
/**    
* 写这段代码的时候,只有上帝和我知道它是干嘛的
* 现在,只有上帝知道
*/
  • 为代码中的瑕疵写注释
// I am not sure why this works but it fixes the problem. 
// 虽然我不知道为什么这样管用,但它却是修复了问题
  • 给常量加上注释
  • 公布可能的陷阱
  • 全局性的总结性的注释

写好注释的how-to-do and not to do

  • 让注释保持紧凑
 //
        // 摘要:
        //     Initializes a new instance of the System.IO.Ports.SerialPort class using the
        //     specified port name and baud rate.
        //
        // 参数:
        //   portName:
        //     The port to use (for example, COM1).
        //
        //   baudRate:
        //     The baud rate.
        //
        // 异常:
        //   T:System.IO.IOException:
        //     The specified port could not be found or opened.
        public SerialPort(string portName, int baudRate);
  • 避免使用不必要的动词
    比如,this,it这类词,鬼才知道你指的是什么。
  • 润色粗糙的句子
    这里写图片描述

改为下面的,是不是更好:
这里写图片描述

- 精确的描述函数的行为
这里写图片描述

因为并没有描述出,怎么才算是一行,改为下面的:
这里写图片描述

  • 使用输入输出来说明特别情况

这里写图片描述

  • 声明代码的意图
    这里写图片描述

  • 使用信息含量高的词
    这里写图片描述

改为下面一行:
这里写图片描述

简化循环逻辑

在顺序,选择,循环中,尽量将选择和循环逻辑处理成顺序逻辑,这个比较符合我们大脑的处理方式的。

这里写图片描述

把控制流变得容易读

  • 条件语句
    这里写图片描述
if(age>18){

 }

关于尤达表示法:

if(null==obj){}

对于上述写法,首先来描述下为什么会有这么写的,这个是从上古流传下来的写法,当时编译器对于if(obj=null)这种if条件判断是可以通过,此时的obj=null会被处理成赋值运算,而不是条件判断,为了避免出现这种错误,所以倒过来写成null==obj,但是今天的编译器会对obj=null这种写法给出warn,所以尽量少用这种可读性不强的写法。
- if/else语句块顺序
这里写图片描述

- 谨慎使用三目运算符

 var data = new[]
            {
                new []{"v1",  db.V1.ToString("0.000"),db.V1>singleV_min && db.V1<singleV_max  ? "√" : "×"},
                new []{"v2",  db.V2.ToString("0.000"),db.V2>singleV_min && db.V2<singleV_max ? "√" : "×"},
                new []{"v3",  db.V3.ToString("0.000"),db.V3>singleV_min && db.V3<singleV_max ? "√" : "×"},
                new []{"v4",  db.V4.ToString("0.000"),db.V4>singleV_min && db.V4<singleV_max ? "√" : "×"},
                new []{"v5",  db.V5.ToString("0.000"),db.V5>singleV_min && db.V5<singleV_max ? "√" : "×"},
                new []{"v6",  db.V6.ToString("0.000"),db.V6>singleV_min && db.V6<singleV_max ? "√" : "×"},
                new []{"v7",  db.V7.ToString("0.000"),db.V7>singleV_min && db.V7<singleV_max ? "√" : "×"},
                new []{"v8",  db.V8.ToString("0.000"),db.V8>singleV_min && db.V8<singleV_max ? "√" : "×"},
                new []{"v9",  db.V9.ToString("0.000"),db.V9>singleV_min && db.V9<singleV_max ? "√" : "×"},
                new []{"v10",  db.V10.ToString("0.000"),db.V10>singleV_min && db.V10<singleV_max ? "√" : "×"},
                new []{"v11",  db.V11.ToString("0.000"),db.V11>singleV_min && db.V11<singleV_max ? "√" : "×"},
                new []{"v12",  db.V12.ToString("0.000"),db.V12>singleV_min && db.V12<singleV_max ? "√" : "×"},
                new []{"v13",  db.V13.ToString("0.000"),db.V13>singleV_min && db.V13<singleV_max ? "√" : "×"},
                new []{"v14",  db.V14.ToString("0.000"),db.V14>singleV_min && db.V14<singleV_max ? "√" : "×"},
                new []{"v15",  db.V15.ToString("0.000"),db.V15>singleV_min && db.V15<singleV_max ? "√" : "×"},
                new []{"v16",  db.V16.ToString("0.000"),db.V16>singleV_min && db.V16<singleV_max ? "√" : "×"},
                new []{"v17",  db.V17.ToString("0.000"),db.V17>singleV_min && db.V17<singleV_max ? "√" : "×"},
                new []{"v18",  db.V18.ToString("0.000"),db.V18>singleV_min && db.V18<singleV_max ? "√" : "×"},
                new []{"v19",  db.V19.ToString("0.000"),db.V19>singleV_min && db.V19<singleV_max ? "√" : "×"},
                new []{"温度1", db.Temprature1 == 215 ? "null" : db.Temprature1.ToString(), db.Temprature1 == 215 ? "--" : db.Temprature1 > temprature_min && db.Temprature1 < temprature_max ? "√" : "×"},
                new []{"温度2", db.Temprature2 == 215 ? "null" : db.Temprature2.ToString(), db.Temprature2 == 215 ? "--" : db.Temprature2 > temprature_min && db.Temprature2 < temprature_max ? "√" : "×"},
                new []{"温度3", db.Temprature3 == 215 ? "null" : db.Temprature3.ToString(), db.Temprature3 == 215 ? "--" : db.Temprature3 > temprature_min && db.Temprature3 < temprature_max ? "√" : "×"},
            };
  • 避免do/while循环
  • 从函数中提前返回
  • 别用goto,不然揍你呦
  • 最小化嵌套
//TODO:检查电池组压差
            if (!isDisplay)
            {
                if (isBefore == 0)
                {
                    if (double.Parse(DataAnalysis.getBatteryGroupPress(batteryDataList)) * 1000 >= double.Parse(config.getStringValueByKey(IniConfig.BEFORE_BATTERY_GROUP_VOLUMN_DIFF,"")))
                    {
                        db.IsPass = false;
                        checkMsg += " 电池组压差告警,应小于:" + config.getStringValueByKey(IniConfig.BEFORE_BATTERY_GROUP_VOLUMN_DIFF,"") + " mv";
                    }

                }
                else if (isBefore == 1)
                {
                    if (double.Parse(DataAnalysis.getBatteryGroupPress(batteryDataList)) * 1000 >= double.Parse(config.getStringValueByKey(IniConfig.AFTER_BATTERY_GROUP_VOLUMN_DIFF,"")))
                    {
                        db.IsPass = false;

                        checkMsg += " 电池组压差告警,应小于:" + config.getStringValueByKey(IniConfig.AFTER_BATTERY_GROUP_VOLUMN_DIFF,"") + " mv";

                    }
                }

对于这种嵌套情况,可以使用在内层中,提前返回来减少嵌套,让代码尽量顺序可读。

拆分超长表达式

keywords:把你的超长表达式拆分成更容易理解的小块。
- 用作解释的变量

这里写图片描述

变量与可读性

变量的问题:
1,变量越多,越容易跟踪它的东西;
2,变量的作用域越大,越难跟踪它的问题;
3,变量的改动越频繁,越难跟踪它的问题;
总结起来就是:操作一个变量的地方越多,它越容易出问题。
- 减小变量
减少无用的中间变量;
减小控制流变量;
- 缩小变量的作用域
key workds:让你的变量对尽量少的代码行可见。
一些方法:
1,将成员变量降格成局部变量;
2,将方法变为静态的;
3,把大类拆分成小类;
4,大方法拆分成小方法;

重新组织代码

基本的重新组织代码的方式:

1,抽取出那些与程序主要目的“不相关的子问题”;
2,重新组织代码使它只做一件事情;
3,先用自然语言描述代码,然后用这个描述来帮助你找到

积极地发现并抽取出不相关的子逻辑:
- 1.看看某个函数或代码块,问问你自己,这段代码更高层次的目标是什么
- 2.对于每一行代码,问一下:它是直接为了目标而工作的吗?这段代码更高层次的目标是什么
- 3.如果足够的行数在解决不相关的子问题,抽取代码到独立的函数中。

具体策略:
- 创建纯工具代码
- 创建大量通用代码
- 简化已有方法
一次只做一件事:
同时在做几件事的代码很难理解,一个代码块可能初始化对象,清除数据,解析数据,然后应用业务逻辑,所有的这些同时进行。如果这些代码都纠缠到一起,对于每个任务都很难靠其自身理解它从哪里开始,到哪里结束。
关键思想:应把代码组织的一次只做一件事。
一次只做一件事所用到的流程:
1.列出代码中的所有任务。这里的任务没有很严格的定义——它可以小的如“确保这个对象有效”,或者含糊得“遍历树中所有结点”。
2.尽量把这件任务拆分到不同的函数中,或者至少是代码中不同的段落中。
把想法变成代码:
用自然语言描述问题和解决办法,然后用这些自然语言来帮助你写出更自然的代码。
少写代码:
你所写的每一行代码都是需要测试和维护的,通过common lib和减少功能,可以节约时间并让代码保持精简。
key words:最好读的代码就是没有代码。
- 别费神实现不需要的功能
- 保持小的代码库
- 熟悉周围的代码

目录
相关文章
|
1月前
|
存储 监控 NoSQL
编写可读代码的艺术
编写可读代码的艺术
63 0
|
6月前
|
程序员 开发工具 Docker
13个程序员常用开发工具用途推荐整理
13个程序员常用开发工具用途推荐整理
71 0
|
1月前
|
设计模式 算法 程序员
代码之禅:从功能实现到艺术表达的技术感悟
【2月更文挑战第15天】 在数字世界的无限画布上,每一行代码都承载着创造的力量。本文透过作者多年的技术探索与实践,探讨编程不仅仅是逻辑的堆砌和功能的实现,更是一种深度思考与艺术表达的过程。从最初的代码拼凑者到后来的架构设计者,再到如今追求代码艺术性的实践者,文章将带领读者一同走进程序员的内心世界,感受那些被键盘敲击声激发出的灵感火花。
|
6月前
|
设计模式 自然语言处理 JavaScript
设计模式第十三讲:编写可读代码的艺术
设计模式第十三讲:编写可读代码的艺术
|
Unix 编译器 程序员
如何写出高质量的代码 -- 给所有编程学习者的一个建议
如何写出高质量的代码 -- 给所有编程学习者的一个建议
136 0
如何写出高质量的代码 -- 给所有编程学习者的一个建议
|
存储 运维 供应链
一个以小说的叙述方式书写的项目
 最近在读一本名为《凤凰项目:一个IT运维的传奇故事》的书,读后颇有感触,从业这么多年,的确碰到过书中的很多场景,书中描绘的故事其实就是现实工作中的各类缩影。
|
程序员
都说代码注释是程序员必备技能,但是你这注释也太奇葩了吧!
都说代码注释是程序员必备技能,但是你这注释也太奇葩了吧!
130 0
都说代码注释是程序员必备技能,但是你这注释也太奇葩了吧!
|
存储 JavaScript 程序员
程序员如何像写程序一样写作?
我希望通过这个专栏来介绍一下个人对Markdown这种写作方式的看法和使用经验,以此来抛砖引玉,引起大家对Markdown更多的关注,进而将软件开源的精神推广至写作领域。毕竟,文字作品才是我们人类开发时间最长,数量最多的一种“软件”。
2125 0
|
SpringCloudAlibaba 架构师 程序员
为什么说程序员不能只会安静写代码?| 开发者必读(013期)
最炫的技术新知、最热门的大咖公开课、最有趣的开发者活动、最实用的工具干货,就在《开发者必读》!
779 0