在前文( (32条消息) 陈旧语法密度之六——用Map消灭if-else if-else,“上帝代码”_陈勇的博客 - Scrum 敏捷开发培训咨询,绩效管理,团队管理,《火星人敏捷开发手册》-CSDN博客)中,笔者曾经提到一些实现每功能点所需代码行数的数据。时过境迁,现在重新修订一下当年的数据。

修订的主要信息源是2016年Capers Johns的《软件工程通史》发布的国际数据,以及笔者近年来咨询过的企业的真实团队数据。值得欣喜的是,所有数据均有大幅度改善。

功能点定义

这里所说的功能点是一种国际标准,它大致定义是:

功能点FP = 接口数 * 5.4 * 应用类型调整因子。

接口数大致等同于swagger ui中的接口数量,故可以自动获取。

5.4是历史原因造成的一个常数。

“应用类型调整因子”是众多调整因子中起决定性作用的一个。大致可以取值为:

OA类=1.0,多个应用集成=1.2,图表=1.3,一般金融=1.8,通信软件=1.9,嵌入式=2.0

其他未提及的应用可通过对比找到大致数值。

同一类型的应用尽管会有所不同,但从大数定理的角度看,其软件的复杂程度相当。实际上上述调整因子的数值本身就来自业界统计数据,也会随着业界的变化而变化。

编码消耗率CCR

编码消耗率表明了完成相同功能(包括正常和异常功能)所需的逻辑代码行数,其定义为:

编码消耗率CCR = 逻辑代码行数LLOC / 功能点数 FP。

CCR是Code Consuming Rate的缩写,此概念在国内几乎无人提及,但在国外的研究较多。究其原因,很大程度上是国外要通过CCR来研究不同语言语法、框架的设计与演进,对代码简洁性的影响,从而设计出下一版本的语言语法(甚至一种新语言)。

注意CCR不是一个通用的名词,国外的众多研究者并未为其命名(或虽命名但未流传),这个名字是笔者自己起的。

编码消耗率越低,则代码的精美程度越高。一半代码几乎意味着:一半的打字时间,一半的工作量,一半的缺陷,远低于一半的维护工作量。

此外,逻辑行而非物理行的定义中包括了对自然语言的尊重,所以将两行代码合并到一行,或将所有变量名都改成一个字母这些奇葩方法,并无助于降低CCR。删除错误处理代码以降低CCR也是不可行的,因为CCR是实现“相同功能”所需的代码行数。

上帝代码

指理想的,多一个字符则多,少一个字符则少的代码。

多数人都不相信存在上帝代码,这是因为多数人的代码距离上帝代码非常遥远。如果不长途旅行,人类就意识不到地球是圆的(东西方向旅行足够远会发现时差,南北方向则会发现气温甚季节变化,实际上最早计算出地球直径的就是古罗马时期一位去过埃及南部的旅行者);不发明望远镜,人类也意识不到地球乃至太阳不是宇宙中心。

在充分使用面向对象、设计模式等方法时,高手的代码与上帝代码“神似”,即两者的思维、原则、技法等大致相同。但由于“神似”很难被量化和度量,因此我们需要一个“形似”的指标来度量人类与上帝,以及人类之间的差距。

这个指标的一种简单直观的备选方案就是编码消耗率CCR。

编码消耗率的实际和预测数值

主流面向对象语言的编程风格大致相同,基本可以认为C# = Java,而C++在去除头文件后,估计比高版本的C#/Java多出10%~20%的代码,但也可以认为大致相同。

在2015年,Java的CCR中值是27LLOC/FP(来自Capers Johns的《软件工程通史》)。以此作为参考,可以很好地理解下面的数据。

以下是从高到低(从差到好)排列的各种数据。

可存活的最差项目:CCR <= 400

笔者参与过的一个较大型的重构产品则是190000LOC重构为13000LOC(大约剩下7%的代码,2004年一个电信项目),可惜只参加了代码评审,不是我本人重构的。根据当年主观感受推测,重构后的水平在每功能点30行左右,也就是说重构前在420行水平。

这个项目的第一版本投入超过2000万(100人年左右),而重构只花费了30万(1.5人年,由团队中的一个个人完成),两者相差60倍。假设需求摸索与变更贡献了3倍,人员自我学习与成长2倍,剩下的10倍这一巨大差异可以认为是纯管理问题了。若项目在初始状态就建立了对CCR的监控,则10倍差距肯定可以大大缩短。

在瀑布开发中,CCR超过400,则产品极有可能连存活的机会也没有,无法等到其价值被认可后再进行重构。上述项目重构的动力,就是每卖出一套系统,团队需要派出两人到现场维护,很快就处于无人可派的地步。

在敏捷时代,这个数字可能更低,原因是敏捷时代需求变更的速度更快,十倍的冗余代码带来的维护成本更加明显。

QSM网站最差:134

QSM(QSM.com)是少数研究并发布CCR的组织之一,但其数据较为陈旧。

需要重构的项目:CCR > 100

这类产品相当于使用了正常代码的4倍编写,因此维护的工作量极大。原因是这4倍代码并不是顺序排列的,而是相互交叉甚至存在于不同的文件中。这就意味着本来只需要维护一行代码的变动,现在首先要找到要维护哪4行,甚至是“哪N行”代码。

笔者所知的一个刚刚被重构的产品,其CCR仍然高达42。但这也说明,这个数字勉强可以接受。但为了保证重构质量,重构后的CCR应该低于业界中值27。因为正常代码都有50%的概率低于此数值,重构的代码若更高则付出的两轮开发有点得不偿失。

QSM网站中值:53

这也是笔者之前引述较多的一个数字,直到出现Johns的27为止。两者的差距,可以认为是QSM网站统计的是历史中值,包含了众多陈旧代码,而Johns的数据则只包含2015年的数据。

Capers Johns 2015年中值:27

Capers Johns是国际公认的无出其右的度量大师,美国生产力研究所(SPR)的联合创始人。SPR的功能点相关数据量多达40000,相当于所有功能点标准组织(如IFPUG、NESMA)之和(笔者火星人陈勇也是SPR授权的标准功能点讲师),因此此数据具有一定的权威性。

注意能提供CCR的企业,一般都是较为优秀的企业。因此业界的实际中值应高于(差于)此数据。

国内正常团队的中值:30~50

来自于笔者咨询过的多个企业的数值,进行合理推测后的结果。

国内优秀团队的中值:22~25

来自于笔者咨询过的多个企业的数值,团队级样本数在30个左右(这也是笔者知道的所有数据,足见国内研究之少)。

但能发起编码咨询的企业的团队,一般也是较为优秀的团队,因此业界普通团队的实际数据不容乐观。若读者置身于优秀企业中又从事代码质量管理工作,应该要求企业中值向22以内靠拢。

最优秀团队的大致极限:13~16

此数据的来源较少,大致包括:QSM网站的最佳值为14,笔者自己的一个实际团队13.7(笔者亲自参与开发),咨询客户中存在的3个13~16的团队。

最优秀个体的大致极限:3~4

此数据的来源于2019年笔者自己单人开发的一个产品(需求与估算、量化管理工具,asp.net mvc web框架,瘦web方案,web中无js代码,故仅计算后端代码),具体数值为3.8LLOC/FP。这个数字相当于,业界正常的软件实际只需要1/7的代码即可实现。

此数值颠覆了之前笔者认为上帝代码CCR=10~12的认知,以至于多次核算无误后方才确认。

最优秀个体和最优秀团队的巨大差异,表明代码管理仍有巨大空间。同时也指出,团队的最优秀个体的作用应该是树立标杆和传递技能,而绝非独善其身。

上帝代码:2~3

依据最优秀个体做出的大致推算。

反射与“编码即信息”

鉴于仅通过面向对象、设计模式,辅以各种优秀框架如SpringBoot等,企业的优秀团队即可达成最优秀团队“极限”14附近的CCR取值,此处仅讨论在极限以下的改进。

首先“极限”一词并非真正意义上的极限,而是统计学上,正常样本数下最优个体的结果。可以认为是在采用“广泛认知”的技术时可能取得的最好成绩。而某些全新的、尚未被广泛认知的技术的采用,可能导致极限被突破。

在2016年笔者所参与开发的项目中,CCR只有13.7,属于正常的优秀团队水平。但在短短3年后的2019年项目中快速降低到3.8,除了个体与团队的统计口径差异外,还有两个已被识别的因素。

反射

狭义的反射如百度百科的定义:“反射是一种计算机处理方式。有程序可以访问、检测和修改它本身状态或行为的这种能力。”比如一个类可以询问自己有哪些成员,成员方法有哪些输入输出参数,变量或方法的类型,其上有哪些注解等等。

但这个定义,无法解释为何被广泛称为“反射”,而非“自省”(的确有此称呼但不流行)等更容易望文生义的名字。以下是笔者在使用反射过程中,渐悟并最终采纳的新定义:

“反射指在软件中,同一信息仅存在一处本体;其他地方则使用类似光线反射的方式,静态或动态地引用此本体。这些引用称之为镜像。”

要很好地理解这一定义,需要理解下面的编码终极奥义第二条。

编码终极奥义

编码终极奥义是笔者在讲授编码规范类课程时创建的“公理体系”。它在纷繁芜杂的编码规范之上,建立了一个简单的价值观和思维起点,使编码规范不再是一个“天生如此的规定”,而是特定价值观追求和实现的产物。认可“如此规定”容易引发“各有各的习惯和规定”的不可知论,成为抵制编码规范的借口。

终极奥义共分为三条,数字越小约终极。

终极奥义第三条“编码即自然语言”,包括代码语法上对自然语言的符合(如方法应为动词,类应为名词,布尔值应为类似is...,单个变量和实例应为单数,集合名称应为复数等),也包括框架上对自然语言的符合(如人类在陈述事情时,喜欢先大致描述框架,再描述细节:“大象进冰箱分三步:第一步……,第二步……,第三步……;第一步具体又分为四个环节:……”,因此代码的编写和阅读顺序应该是与之匹配的,即先写出三个顺序调用的方法,再编写三个方法本身,并补充方法细节)。

终极奥义第二条“编码即信息”,后有详述。

终极奥义第一条“编码是美的”,描述了当第三、二条被违反,或受到限制时的处理。最常见的是自然语言会存在缺陷,比如英语中有些词汇不可数,可能导致单个变量和集合变量重名。此奥义允许在遵循自然审美观的情况下,突破或规避对人类现有语言、信息的限制。自然美包括简单性、对称性、统一性等基本审美观。比如var women = new List<Woman>()符合人类语法,但却不简单、对称,其他语言的阅读者必须理解women是woman的复数形式,才能理解此命名的合理性。而var females = new List<Female>()则是符合简单、对称性的,甚至能被拥有视觉能力的外星人快速理解。

编码终极奥义第二条:编码即信息

此终极奥义可以在两侧追加形容词,从而衍生出特定的版本。比如“最简编码即最简信息”,也就是之前提到的降低CCR的关键。

要做到最简洁的信息,首先一条就是信息不能有重复,这就引出了反射的广义定义:

“反射指在软件中,同一信息仅存在一处本体;其他地方则使用类似光线反射的方式,静态或动态地引用此本体。这些引用称之为镜像。”也可简化为“本体唯一,镜像引用。”

从逻辑上推测,反射应该是实现编码终极奥义第二条的核心方法。

静态与动态反射

静态反射,指本体信息仅存在一处,但可通过引用本体的信息,形成镜像。

动态反射,指在本体发生变化时,镜像会自动随之动态变化,无需额外处理。

狭义的反射

在反射的广义定义下理解狭义定义:类的名称、成员变量、成员方法、变量和方法的类型、名称乃至排列顺序、其上所加的注解的类型和内容……等,只存在于类的定义中,其他地方均使用反射技术对其访问,而不是额外存储一份。

在“本体唯一,镜像引用”的广义定义下,以下也是反射的概念:

广义的数据反射

1. 在一处为常量赋值,其他地方使用此常量而非复制其数值。

广义的过程反射

将本体从数据(类与变量)扩展到过程(方法和运算符等),以下也是反射:

1. 将公用代码提取成公用方法和共用类;

2. 将主流程封装在基类中,分支流程通过多态调用;

3. 泛型类与方法(C++中的模板);

4. 在前端使用class触发js运行,而非为控件单独写js

甚至一些看似细微的改进也属于反射:

1. 用三元表达式替换分支语句(变量和赋值操作只出现一次);

2. 使用map(或dictionary)实现数值转换而不是分支语句(分支中的代码只出现一次);

广义的宏观模型反射

模型这个词可能不准确,泛指更大范围的反射。

1. 代码框架(如asp.net mvc所提供的web框架)

2. 基于微服务的设计。

为何需要编码终极奥义第二条?

既然有这么多具体的方法,为何需要这个抽象的编码终极奥义第二条?

原因是具体方法五花八门,因语言的差异而不同,甚至因语言的版本而不同(比如C++即将推出反射)。而且编程时人们脑海中也不可能随时存储着这些方法,极易导致“在大学都学过,但工作中从来没用过”。

编码终极奥义更容易理解、记忆,不会随语言、时间、空间的变化而变化。只要发现信息即将重复,就会自然地思考要用某种方法来解决。只要有了需求,即使具体方法从未听过,也可以从搜索引擎、论坛上找到答案。

从2016年到2019年,笔者实际上只编写了大约1000多行代码,编码消耗率CCR却发生了质的变化,与发现编码终极奥义第二条有决定性关系。早在2011年初学C#时就开始使用其注解(C#中叫[Attribute]),但也仅限于其框架内教学中举例的地方(字段校验和权限控制),其他地方感觉并没有显著需求。但在2017年发现编码终极奥义第二条后,在2019年和现在的项目中,甚至已经需要为类和方法动态增加、修改、去除注解。由于这已经超出了C#的语法能力,因此不得不写了一个额外的Helper来实现(Stack Overflow上有很多类似的需求和讨论)。

因此掌握了编码终极奥义,就可以不再局限于自己所知的知识,甚至会突破现有知识的边界。“比解决问题更重要的是发现问题”。编码终极奥义第二条将所有信息重复都视为要解决的问题,再思考用哪种技术来解决重复,有效防止了什么都学过但从来没用过的现象。

----------------------------------

点击 QAD量化敏捷开发的个人空间_哔哩哔哩_Bilibili 或在B站搜索“QAD量化敏捷开发”查看博主海量免费视频。

----------------------------------

编码消耗率CCR,“上帝代码”,反射,简洁代码相关推荐

  1. php asp.net 代码量少,.NET_asp.net 反射减少代码书写量, 复制代码 代码如下:public b - phpStudy...

    asp.net 反射减少代码书写量 public bool Add(Liuyan refmodel) { string sql = "insert into liuyan(name,phon ...

  2. google python代码规范_如何用好python编码规范,写一手漂亮的代码

    前一段时间在编写python 代码的时候编辑器中一直在提示规范问题,因为强迫症的原因,我决定遵循python 的编码规范去编码,然后把需要注意的点记录下来, 帮助自己和大家一起成长. 这是我的main ...

  3. 一名Android程序员如何减少代码中该死的-if-else-嵌套,怎么让代码更简洁?

    减少代码中该死的-if-else-嵌套,让代码更简洁! 写在前面 不知大家有没遇到过像"横放着的金字塔"一样的if else嵌套: if (true) {if (true) {if ...

  4. pep8 python 编码规范_如何用好python编码规范,写一手漂亮的代码

    前一段时间在编写python 代码的时候编辑器中一直在提示规范问题,因为强迫症的原因,我决定遵循python 的编码规范去编码,然后把需要注意的点记录下来, 帮助自己和大家一起成长. 这是我的main ...

  5. 还在手写 Getter/Setter 方法吗?Lombok 让你的代码更简洁!

    以下是文章大纲: Lombok 是什么? 官网:https://projectlombok.org/ Lombok 是一个 Java 库,使用 Lombok 可以通过简单的注解帮助我们消除 Java ...

  6. ES6技巧和窍门,使您的代码更简洁,更短且更易于阅读!

    by Sam Williams 通过山姆·威廉姆斯 ES6技巧和窍门,使您的代码更简洁,更短且更易于阅读! (ES6 tips and tricks to make your code cleaner ...

  7. 二分查找算法+代码(通俗易懂简洁扼要)

    知乎原文出处二分查找算法+代码(通俗易懂简洁扼要) - 知乎 欢迎关注我的知乎账号:进击的steve - 知乎 二分查找是一个可以把单值查找时间复杂度从O(n)降到O(logn)的算法. 二分查找的前 ...

  8. 为什么说 Compose 的声明式代码最简洁 ?Compose/React/Flutter/SwiftUI 语法对比

    前言 Comopse 与 React.Flutter.SwiftUI 同属声明式 UI 框架,有着相同的设计理念和相似的实现原理,但是 Compose 的 API 设计要更加简洁.本文就这几个框架在代 ...

  9. Java14发布,16大新特性,代码更加简洁明快

    2020/3/17日JDK14正式发版,生产环境用不用再说,赶紧下载下来体验一番.不过据说该版本并不是长期版本,但即使再发布新版本也是在此基础上的. 另外,想体验Java14还是需要一些准备工作的,除 ...

  10. 低代码、零代码、免编码平台—对企业有什么优势?

    随着社会的发展,支持数字化转型的新软件面临着越来越大的压力.进而会出现开发人员供不应求,众多公司缺乏开发应用程序所需技术人才的现象.这时开发公司就会逐渐倾向低代码或零代码工具平台.这些平台的特点是允许 ...

最新文章

  1. wordpress ajax 调用wpdb_莫名其妙的调用Roboto字体又莫名其妙的恢复的过程
  2. 【Linux】10.安装和开启ftp服务
  3. apache_php_tomcat基于主机名的多虚拟主机整合笔记
  4. 多线程—线程池Executor框架及四种常用线程池
  5. 【JDBC】Eclipse连接Mysql
  6. sqlserver 如何把一列分为一行显示_SqlServer数据库如何快速修改表的一列值
  7. Linux内存管理:memblock
  8. 搜狗云输入法对外提供调用体验
  9. Mimics 21.0软件学习笔记(一)基本操作
  10. ArcGIS学习总结(二)——空间数据处理
  11. Lab3:自行车码表
  12. android 蓝牙信息提醒,Android蓝牙与BLE通信相关的读、写、通知、指示操作
  13. ORAN C平面 Section Extension 5
  14. cout 输出 uint8_t 整形值
  15. mac终端Login Incorrect问题
  16. 递归算法时间复杂度和空间复杂度分析与举例
  17. 2010年Oracle大事记
  18. kali安装dockers和docker-compose
  19. 在Ubuntu中获取奥比中光的深度值 Python代码
  20. pdf怎么分割成一页一页的文件?

热门文章

  1. linux 跨网段ping,Linux中跨网段ping问题
  2. Ubuntu界面美化
  3. 解决:uni-app 图片加载不出来
  4. date设置时间提示:Local time zone must be set--see zic manual page 2018
  5. 论文笔记:多标签学习——ACkEL算法
  6. U3D教程多摄像机协同运行
  7. oracle基础|什么是数据库操作语言|什么是DDL、DML、DCL
  8. 大鱼吃小鱼java源代码_大鱼吃小鱼 JAVA游戏源码
  9. Polarized 3D翻译
  10. svchost.exe占网速的解决办法