• 《构建执法》阅读笔记之四


    1. 代码规范

    每个人对于什么是“好”的代码规范未必认同,这时我们很有必要给出一个基准线—什么是好的代码规范和设计规范

    计算机只关心编译生成的机器码,你的程序采用哪种缩进风格,变量名有无统一的规范等,与机器码的执行无关。但是,做一个有商业价值的项目,或者在团队里工作,代码规范相当重要。“代码规范”可以分成两个部分:

    1. 代码风格规范——主要是文字上的规定,看似表面文章,实际上非常重要

    2. 代码设计规范——牵涉到程序设计、模块之间的关系、设计模式等方方面面的通用原则


     

    2. 代码风格规范

    代码风格的原则是:简明易读无二义性
    提示:这里谈的风格是一家之言,如遇争执,关键是要本着“保持简明,让代码更容易读”的原则,看看争执中的代码规范能否让程序员们更好地理解和维护程序

    2.1 缩进

    是用Tab键好,还是2、4、8个空格?
    结论4个空格,在Visual Studio和其他的一些编辑工具中都可以定义Tab键扩展成为几个空格键。不用Tab键的理由是,Tab键在不同的情况下会显示不同的长度,严重干扰阅读体验。4个空格的距离从可读性来说,正好

    2.2 行宽

    行宽必须限制,但是以前有些文档规定的80字符行宽太小了(以前的计算机/打字机显示行宽为80字符),现在时代不同了,可以限定为100字符

    2.3 括号

    在复杂的条件表达式中,用括号清楚地表示逻辑优先级

    2.4 断行与空白的{ }行

    1. 最精简的格式A:

    if (condition)  DoSomething(); 
    else    DoSomethingElse();

    优点:因为可以节省几行
    缺点:不同的语句(Statement)放在一行中,程序调试(Debug)起来非常不方便,如果要一步一步观察condition中各个变量(condition可能是包含函数调用的复杂表达式)的变化情况,单步执行就很难了

    2. 有断行的格式B:

    if (condition)
        DoSomething(); 
    else 
        DoSomethingElse();

    缺点:由于没有明确的“{”和“}”来判断程序的结构,在有多层控制嵌套时,这样的格式就不容易看清结构和对应关系

    3. 改进的格式C

    if (condition) {
        DoSomething(); 
    } else {
        DoSomethingElse();
    }

    缺点:不够清晰

    4. 每个“{”和“}”都独占一行,即格式D

    if (condition) 
    { 
        DoSomething(); 
    } 
    else 
    { 
        DoSomethingElse(); 
    }

    2.5 分行

    不要把多条语句放在一行上

    a =1; b =2;      // bogus
    if (fFoo) Bar();   // bogus

    更严格地说,不要把多个变量定义在一行上

    Foo foo1, foo2;    // bogus

    2.6 命名

    用单个字母给有复杂语义的实体命名并不可取,也是经过了实践检验的方法叫“匈牙利命名法”。例如:

    fFileExist  // 表明是一个bool值,表示文件是否存在;
    szPath  // 表明是一个以0结束的字符串,表示一个路径

    2.7 下划线

    下划线用来分隔变量名字中的作用域标注和变量的语义,如:一个类型的成员变量通常用m来表示,或者简单地用一个下划线“”来做前缀。移山公司规定下划线一般不用在其他方面

    2.8 大小写

    由多个单词组成的变量名,如果全部都是小写,很不易读,一个简单的解决方案就是用大小写区分它们。

    • Pascal——所有单词的第一个字母都大写
    • Camel——第一个单词全部小写,随后单词随Pas-cal形式,这种方式也叫lowerCamel

    一个通用的做法是:

    • 所有的类型/类/函数名都用Pascal形式,所有的变量都用Camel形式
    • 类/类型/变量:名词或组合名词,如Member、ProductInfo等
    • 函数则用动词或动宾组合词来表示,如get/set、RenderPage()

    2.9 注释

    需要注释什么?不要注释程序是怎么工作的(How),程序本身就应该能说明这一问题

    //this loop starts the i from0 to len, in each step, it
    // does SomeThing
    for (i =0; i < len; i++)
    {
        DoSomeThing();
    }

    以上的注释是多余的。注释是为了解释程序做什么(What),为什么这样做(Why),以及要特别注意的地方,如下

    /go thru the array, note the last element is at [len-1]
    for (i =0; i < len; i++)
    {
        DoSomeThing();
    }

    复杂的注释应该放在函数头,很多函数头的注释都用来解释参数的类型等,如果程序正文已经能够说明参数的类型in/out,就不要重复!
    注释也要随着程序的修改而不断更新,一个误导的(Misleading)注释往往比没有注释更糟糕
    另外,注释(包括所有源代码)应该只用ASCII字符,不要用中文或其他特殊字符,否则会极大地影响程序的可移植性。
    在现代编程环境中,程序编辑器可以设置各种美观得体的字体,我们可以使用不同的显示风格来表示程序的不同部分。

    注意:有些程序设计语言的教科书对于基本的语法有详细的注释,那是为了教学的目的,不宜在正式项目中也这么做


     

    3. 代码设计规范

    代码设计规范不光是程序书写的格式问题,而且牵涉到程序设计、模块之间的关系、设计模式等方方面面,这里又有不少内容与具体程序设计语言息息相关(如C、C++、Java、C#),但是也有通用的原则,这里主要讨论通用的原则。如果你只想为了“爽”而写程序,那么可以忽略下面的原则;如果你写的程序会被很多人使用,并且你得加班调试自己的程序,那最好还是遵守下面的规定

    3.1 函数

    现代程序设计语言中的绝大部分功能,都在程序的函数(Function、Method)中实现。关于函数,最重要的原则是:只做一件事,并且要做好

    3.2 goto

    函数最好有单一的出口,为了达到这一目的,可以使用goto。只要有助于程序逻辑的清晰体现,什么方法都可以使用,包括goto

    3.3 错误处理

    当程序的主要功能实现后,一些程序员会乐观地估计只需要另外20%的时间,给代码加一些错误处理就大功告成了,但是这20%的工作往往需要全部项目80%的时间

    1. 参数处理
    在Debug版本中,所有的参数都要验证其正确性。在正式版本中,对从外部(用户或别的模块)传递过来的参数,要验证其正确性

    2. 断言
    如何验证正确性?那就要用断言(Assert)。断言和错误处理是什么关系?当你觉得某事肯定如何时,就可以用断言。

    3.4 如何处理C++中的类

    注意,除了关于异常(Exception)的部分,大部分其他原则对C#也适用

    1. 类

    • 使用类来封装面向对象的概念和多态(Poly-morphism)
    • 避免传递类型实体的值,应该用指针传递。换句话说,对于简单的数据类型,没有必要用类来实现
    • 对于有显式的构造和析构函数的类,不要建立全局的实体,因为你不知道它们在何时创建和消除
    • 仅在必要时,才使用“类”

    2. class vs. struct

    • 如果只是数据的封装,用struct即可

    3. 公共/保护/私有成员(public、protected和private)

    • 按照这样的次序来说明类中的成员:public、protected、private

    4. 数据成员

    • 数据类型的成员用m_name说明
    • 不要使用公共的数据成员,要用inline访问函数,这样可兼顾封装和效率

    5. 虚函数(Virtual Function)

    • 使用虚函数来实现多态(Polymorphism)

    • 仅在很有必要时,才使用虚函数

    • 如果一个类型要实现多态,在基类(Base Class)中的析构函数应该是虚函数

    6. 构造函数(Constructors)

    • 不要在构造函数中做复杂的操作,简单初始化所有数据成员即可
    • 构造函数不应该返回错误(事实上也无法返回)。把可能出错的操作放到HrInit()或FInit()中。

    7. 析构函数(Destructor)

    • 把所有的清理工作都放在析构函数中。如果有些资源在析构函数之前就释放了,记住要重置这些成员为0或NULL
    • 析构函数也不应该出错

    8. new和delete

    • 如果可能,实现自己的new/delete,这样可以方便地加上自己的跟踪和管理机制。自己的new/delete可以包装系统提供的new/delete

    • 检查new的返回值。new不一定都成功

    • 释放指针时不用检查NULL

    9. 运算符(Operators)

    • 在理想状态下,我们定义的类不需要自定义操作符。确有必要时,才会自定义操作符

    • 运算符不要做标准语义之外的任何动作。例如,“==”的判断不能改变被比较实体的状态

    • 运算符的实现必须非常有效率,如果有复杂的操作,应定义一个单独的函数

    • 当你拿不定主意的时候,用成员函数,不要用运算符

    10. 异常(Exceptions)

    • 异常是在“异乎寻常”的情况下出现的,它的设置和处理都要花费“异乎寻常”的开销,所以不要用异常作为逻辑控制来处理程序的主要流程

    • 了解异常及处理异常的花销,在C++语言中,这是不可忽视的开销

    • 当使用异常时,要注意在什么地方清理数据

    • 异常不能跨过DLL或进程的边界来传递信息,所以异常不是万能的

    11. 类型继承(Class Inheritance)

    • 仅在必要时,才使用类型继承

    • 用const标注只读的参数(参数指向的数据是只读的,而不是参数本身)

    • 用const标注不改变数据的函数


     

    代码复审

    代码复审的正确定义:看代码是否在“代码规范”的框架内正确地解决了问题

    名称形式目的
    自我复审 自己 vs 自己 用同伴复审的标准来要求自己。不一定最有效,因为开发者对自己总是过于自信。如果能持之以恒,则对个人有很大好处
    同伴复审 复审者 vs 开发者 简便易行
    团队复审 团队 vs 开发者 有比较严格的规定和流程,适用于关键的代码,以及复审后不再更新的代码覆盖率高——很多双眼睛盯着程序,但效率可能不高(全体人员都要到会)


    软件工程中最基本的复审手段,就是同伴复审


    1. 代码复审的目的在于

    • 找出代码的错误,比如:

      • 编码错误,比如一些碰巧骗过了编译器的错误

      • 不符合团队代码规范的地方

    • 发现逻辑错误,程序可以编译通过,但是代码的逻辑是错的

    • 发现算法错误,比如使用的算法不够优化,边界条件没有处理好等

    • 发现潜在的错误和回归性错误—当前的修改导致以前修复的缺陷又重新出现

    • 发现可能需要改进的地方

    • 教育(互相教育)开发人员,传授经验,让更多的成员熟悉项目各部分的代码,同时熟悉和应用领域相关的实际知识


    在代码复审后要做什么?

    喜欢在程序中加一些特定的标记,来跟踪各种“要做的事情”,这些标记最好是加上人名,以示负责,

    在代码复审过程中,$review 标记的问题要一一讨论,在代码复审过后,所有的 $review 标记要清除。在一个里程碑或正式版本发布之前,所有的 $todo$bug 标记都要清除


    代码的审核复查表

    1. 概要部分

    • 代码符合需求和规格说明么?

    • 代码设计是否考虑周全?

    • 代码可读性如何?

    • 代码容易维护么?

    • 代码的每一行都执行并检查过了吗?

    2. 设计规范部分

    • 设计是否遵从已知的设计模式或项目中常用的模式?

    • 有没有硬编码或字符串/数字等存在?

    • 代码有没有依赖于某一平台,是否会影响将来的移植(如Win32到Win64)?

    • 开发者新写的代码能否用已有的Library/SDK/Framework中的功能实现?在本项目中是否存在类似的功能可以调用而不用全部重新实现?

    • 有没有无用的代码可以清除?
      (很多人想保留尽可能多的代码,因为以后可能会用上,这样导致程序文件中有很多注释掉的代码,这些代码都可以删除,因为源代码控制已经保存了原来的老代码。)

    3. 代码规范部分

    • 修改的部分符合代码标准和风格么(详细条文略)?

    4. 具体代码部分

    • 有没有对错误进行处理?对于调用的外部函数,是否检查了返回值或处理了异常?

    • 参数传递有无错误,字符串的长度是字节的长度还是字符(可能是单/双字节)的长度,是以0开始计数还是以1开始计数?

    • 边界条件是如何处理的?switch语句的default分支是如何处理的?循环有没有可能出现死循环?

    • 有没有使用断言(Assert)来保证我们认为不变的条件真的得到满足?

    • 对资源的利用,是在哪里申请,在哪里释放的?有无可能存在资源泄漏(内存、文件、各种GUI资源、数据库访问的连接,等等)?有没有优化的空间?

    • 数据结构中有没有用不到的元素?

    5. 效能

    • 代码的效能(Performance)如何?最坏的情况是怎样的?

    • 代码中,特别是循环中是否有明显可优化的部分(C++中反复创建类,C#中 string 的操作是否能用StringBuilder来优化)?

    • 对于系统和网络的调用是否会超时?如何处理?

    6. 可读性

    代码可读性如何?有没有足够的注释?

    7. 可测试

    • 代码是否需要更新或创建新的单元测试?

    针对特定领域的开发(如数据库、网页、多线程等),可以整理专门的核查表


     

    结对编程

    结对编程的优势

    1. 在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作解决问题的能力更强

    2. 对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感

    3. 在企业管理层次上,结对能更有效地交流,相互学习和传递经验,分享知识,能更好地应对人员流动

    总之,如果运用得当,结对编程可以取得更高的投入产出比(Return of Investment)


    结对编程步骤

      1. 驾驶员:写设计文档,进行编码和单元测试等XP开发流程

      2. 领航员:审阅驾驶员的文档、驾驶员对编码等开发流程的执行;考虑单元测试的覆盖率;思考是否需要和如何重构;帮助驾驶员解决具体的技术问题

      3. 驾驶员和领航员不断轮换角色,不要连续工作超过一小时,每工作一小时休息15分钟。领航员要控制时间

      4. 主动参与。任何一个任务都首先是两个人的责任,也是所有人的责任。没有“我的代码”、“你的代码”或“他/她的代码”,只有“我们的代码”

      5. 只有水平上的差距,没有级别上的差异。两人结对,尽管可能大家的级别资历不同,但不管在分析、设计或编码上,双方都拥有平等的决策权利

      6. 设置好结对编程的环境,座位、显示器、桌面等都要能允许两个人舒适地讨论和工作。如果是通过远程结对编程,那么网络、语音通讯和屏幕共享程序要设置好

  • 相关阅读:
    进入MFC讲坛的前言(四)
    进入MFC讲坛的前言(二)
    进入MFC讲坛的前言(一)
    进入MFC讲坛的前言(三)
    jar命令+7z:创建,替换,修改,删除Jar, war, ear包中的文件
    java架构之项目结构(entity / DTO / VO)
    面试无忧之Zookeeper总结心得
    BigDecimal.setScale 处理java小数点
    最详细的 paypal 支付接口开发--Java版
    社会化登录分享-Android SDK的二次封装和使用
  • 原文地址:https://www.cnblogs.com/ziyixuedie/p/6398065.html
Copyright © 2020-2023  润新知