精华内容
下载资源
问答
  • 好的程序员从来不靠格子衫或者颜值吃饭,就像你...本文从 产品,接口,指标,日志,代码清晰度,代码复杂度 等方面,谈谈如何提高代码质量。产品和接口好的产品经理未必是个好的程序员,但好的程序员一定是个好的产...

    好的程序员从来不靠格子衫或者颜值吃饭,就像你家 C 罗明明可以靠脸,却非要用不断精进的身体和技术迷倒你。

    fb865f964b41f4bd1607432897cbb786.png

    对伟大前锋来说,进球,以及一个能够迸发出进球能力的身体非常重要。

    对靠谱程序员来说,代码质量,以及一颗能够洞悉高质量软件编写之道的大脑弥足珍贵。

    本文从 产品,接口,指标,日志,代码清晰度,代码复杂度 等方面,谈谈如何提高代码质量。

    产品和接口

    好的产品经理未必是个好的程序员,但好的程序员一定是个好的产品经理。

    产品经理的工作是什么?是把复杂的逻辑用清晰的,易用的方式(接口)展现给用户。

    程序员的产品是代码,代码的用户是其它程序员 —— 所以高质量的代码是让别的程序员容易理解,容易使用的代码。注意,这个层次的容易理解,是指结构,原理和接口上容易理解,而并非代码的细节容易理解。

    细节在产品这个层次,一定要隐藏起来。用户在打开浏览器,访问 arcblock.io 的时候,并不需要关心 DNS 是怎么工作的,PKI 体系是怎么运作的,HTTP / TLS / TCP / IP 协议是什么,报文是怎么从 user space 交付到 kernel space,再怎么 DMA 到网口发送出去 —— 这还没完,接下来出场的,还有负责 l2 protocol 的 switch,保护你安全的 firewall,邮递员 router,以及明明概念上是网络技术,却整个青春都错付给了安全的 NAT。。。

    如果产品经理做的产品展示给用户是这样巴拉巴拉的细节,那么丫一定会被扯烂暂住证,大耳光从天黑抽到天亮,然后早班绿皮车送到清河去挖沙;如果程序员的 main() 如此啰嗦,不管人家受得了受不了,那么他这辈子笃定找不到同性朋友,更别说异性了。

    所以程序员在写代码之前,先要想想如果这是一篇演讲稿,我该如何说起?我能在三五分钟讲清楚这代码要干什么?有没有生活中或者同行会心一笑立刻 get 到的例子可以类比?

    90% 以上的情况,程序员是在写 parser。换句话说,我们写的绝大部分代码就是把一系列的输入,经过若干转换(transformation),变成一系列输出。

    a60e8d9b372bcb4ac8aad3b6efff0ea3.png

    举些具体的例子。

    前端工程师是把用户的 url 请求,parse 成浏览器 DOM 上的一系列 component,把用户的行为,parse 成某种内部的事件 {event_type, event_data},并且进一步由 event_type parse 成某个 event_handler —— 然后这个 handler 继续 parse event_data,直到其转化成新的 DOM,或者对后端的某个 API 的某个请求。

    对于 API 来说,它 parse request,生成 response。request 可能被 parse 成一个 sql,交付给 database;也可能被 parse 成满足另一个服务接口的 request(比如 grpc),交给另一个服务。这样周而复始,直到 API 收集完七颗龙珠召唤神龙各个服务的所有数据,再 parse 成一个合规的 response,交还给 client。

    所以程序员看待自己的代码产品,要像庖丁看待肥牛一样 ——「未见全牛」,「神遇而不以目视」,「以无厚入有间」—— 满眼望去,就是一个个 parser,大的 parser 挂小的 parser,再挂更小的 parser。每层,甚至每个 parser,都是个 pipeline —— 它们一般由 validator,serializer,transformer 等接口组织起来,辅以各种 builder,decorator,factory,commander,再加上为之而生的 tools,utility,helper 等搭建而成。

    这样一层层组织下来,该粗的地方粗,该细的地方细,遇人说人话,遇猿说猿语,代码可伶可俐,可萝可御。

    接下来,是很重要却最让人挠头的事情,给你的大大小小的模块 取名。名字倾注着感情,就像寒夜里小女孩划下的火柴,酣战一宿的圣盔谷外甘道夫挥起的魔杖,给人以光明,温暖,希望,以及读到时触电般的「我懂你」。

    肖申克的救赎里有段,午餐时 Andy 问大伙那个前夜里被打死的可怜的胖子叫什么?大伙一脸懵逼,说我 TM 为什么要关心一个死胖子的名字。这一幕看着很痛,就像华安在成为华安之前,只有一个如蝼蚁般微渺的代号。如果你想让你的代码不是一个让人漠视的死胖子,而是人们愿意谈论,那么,取个容易让人理解,甚至让人刻骨铭心的名字吧。

    不好的名字除了让人不解,漠视,甚至宛如与人世间幽隔的恶鬼,望上一眼,大家便想逃离;好的名字,嗯,随便说一个,聂小倩,同样是与人世间幽隔的孤鬼,你我却念念不忘。

    在 Juniper,我最忘不了的两台服务器是 gretel 和 hansel,取自格林童话;在途客圈,让我心心念之的项目是 atlantis 和以及其上 viking (code name) —— 这不难理解,要追寻 atlantis,你需要远征 (viking);在 tubi,cms service 是个糟糕的取名,merlin 算是回归了正途,虽然作为一个 build service,它的魔力并不太强,还时不时失灵;而在 arcblock,我在上篇文章里谈到的 AADL,被正式取名 AODL —— 这不重要,估计你也记不住,不过,她有了一个对外的名字:goldorin —— 托尔金为中土大陆精灵族发明的精灵语。

    在 代码命名:僧敲月下门 那篇文章里,我提到晦涩的 IKE 代码里 pitcher / catcher 让协议的 negotiation 读来犹如欣赏棒球比赛。好的名字,和好的接口几乎成对出现,它让程序员的产品 —— 代码,变得鲜活,读来如沐春风,如饮醇酒,如赏佳人。

    指标和日志

    好的产品是在改进中不断提升的,就像凤凰,经历烈火不断煎熬,得以涅槃。而要想改进,离不来测量 (measure),它是构建 (build) 和学习改进 (learn) 中间最重要的一环。

    2fe2c7c6760634dc3d5ec83ac87db42b.png

    热力学第二定律是最让人讨厌也最让人无奈的定律。它直接导致了「不运动肚子上的赘肉必然增加」,「不收拾房子房子会越来越乱」,「不持续改进代码,代码的质量会越来越低」这些让人烦心的事情。

    而这个破定律的祖师爷 Lord Kelvin 说:

    024bc040a9409d3af299c03190a540a1.png

    嗯,测量很重要,非常重要。如果构建和改进是两根枝杈,测量就像蜘蛛在两者间挂下的网,这网越密,两根树枝间的路就越多,就越容易从一端走到另一端,循环往复。

    对于测量的途径主要是指标 - metrics 和日志 - logs。metrics 像是心电图或者 CT,让身体的状况一览无余。所以 metrics 用来了解现状,指明方向;logs 则是细密的日记,什么都有,唯独没重点,所以常常在现状和问题的方向确定后,用来归因。比如说 CT 报告说,这周和上周相比,肝不那么好了,需要小心肝。那么肝为什么不好?把一周的日志调出来一看,哎呀,夜夜酒吧里纵情于世界杯,难怪。于是得出改进方案:世界杯结束后,别又喝酒又熬夜又赌球这病就好了,没事。

    metrics 和 logs 大部分时候是给自己和别的程序员看的,所以从上文的角度看,它也是个产品,符合产品和接口定义的一切准则。

    先说 metrics。

    定义 metrics 的时候,你要先搞明白你要改进些什么,这是所谓的 begin with the end in mind。代码的运行效率?那么,究竟那里效率不高?怎么定义效率,怎么计算效率(latency? throughput? 还是什么)。代码的容错性?那么,什么样的 error 要收集,如何分门别类?哪里是潜在的错误大本营?

    知道要改进什么后,接下来脑袋里要有幅图 —— 不是富春山居图 —— 是自己或者别人使用这些 metrics 的场景预现图,就像至尊宝给山贼展示他和白晶晶的旷古奇恋的画面一样。

    比如说要提高效率,并且确定是降低 latency,所以打算收集服务的 response time,那么,response time 是看 line chart 还是 bar chart,知道了 latency 突然升高这件事之后,下一步呢?怎么知道再看什么?要和其它 metrics / event 关联么?关联哪些,怎么关联?想想意外事件发生之后,作为唯一可以背锅的程序员,身后一堆产品运营盯着你的屏幕,丧着个个脸,表情比出殡还悲壮,好像你一秒钟给公司损失几十万上下似的。在紧张的汗水打湿了你的格子衫时,你能看些什么,你该看些什么?

    这样从解决什么问题,收集什么 metrics,怎么关联使用 metrics,一层层定义下来之后,我们可以确保两件事情:1. 当坏事发生的时候,我第一个知道。比如:对外的 API 的 95 percentile 的 response time 过去 5 分钟突然增加了 30%。2. 我能快速锁定问题的大致范围。比如:从其它 metrics 上看,是因为 diagon alley 服务的 latency 突然升高,进一步地,diagon alley 的 disk write IOPS 显著提高。那么这个问题,我就看为什么 diagon alley 的 disk write 不正常。

    接下来是 logs。

    logs 是不出问题不必太在意,但一旦出问题一定要能够方便定位具体的位置的奇葩重要 数据。所以 logs 求充足具体,要像辞海一样广而全 —— 比如当 metrics 告诉我们,问题出在我们并不清楚茴香豆的「茴」字时有几种写法,logs 能够帮助我们快速翻出来有用的那段,然后找出「茴」的四种写法。

    logs 兼具给人看,和给机器分析两种效用,因而,最好要固定格式,以方便机器分析;但又不要用类似 JSON 的供机器阅读的方式,如果不配合一个好用的 parser,当人阅读时像是韩式整容过的足球宝贝,或者被抽干了形容词的句子,每个都长得一个模样,需要摘了眼镜用放大镜仔细找不同。

    通过合理的 metrics 和 logs,测量变得唾手可得。这便释放出来我们不断迭代不断改进的能力。同样起点的代码,同样水准的程序员,一个一周迭代一次,一个一天迭代一次,其累进的质量在若干周期之后,会有质的变化。

    代码清晰度和代码复杂度

    如果上面几个方面都做好了,代码的质量再差也是有下限的。这个下限可以通过严格使用 linter 和不断提升对所用语言的掌握来提高。就好比一个会独立思考并勤于思考的人,他的文章值得一读,也许从遣词造句,从修辞手法,从原起承提来说,他还稚嫩,但那是下限,并且是很容易提升的下限。

    在 elixir 的 linter 里,我把 ABC complexity size 设置为 70,Cyclomatic complexity 设置为 15。所谓 ABC complexity,是代码里的 assignments(A),branches(B),conditionals(C) 的平方之和开方根的结果,它代表了一段代码有多冗长。Cyclomatic complexity,或者说循环复杂度,是指由程序的源代码中量测线性独立路径的个数,它代表了一段代码有多难懂(我们的小脑仁最不擅长同时记几件事情,比如情人节和结婚纪念日)。还有一些其他的设置,比如 nesting(嵌套层数)不超过 3, arity(函数的秩,或者说参数个数)不超过 6 个等等。这些 lint 的约束,会强迫你在函数的实现细节层面,考虑地更好。大部分情况下,同一个功能的代码可以有不同的表述方式,linter 的目的就是建立约束,强迫你用更合理的方式去表达一个功能点。

    比如我常常不经意写出的代码:

    c514c4571b386dc4511e5fd68f044213.png

    这样降低了代码的 complexity,提高了代码的 clarity,同时,还使得代码的 extensibility 大大提升 —— 以后要加一个 “type 3” 的处理,仅仅是加一个简单的函数而已,非常符合 open/close 原则。

    这样的小技巧有赖于对语言的精进,和对 linter 规则的恪守。虽然例外偶有发生 —— 比如一个复杂的 sql query 用 Ecto 表述很容易超过 ABC,但绝大多数情况,守着规则,会让你受益 —— 每次 commit,过 linter 就像灵魂在桑拿房里给蒸气熏碾,痛苦难耐。勉力熬过去后,推门出去一下子无比清爽,有种拨云见日,level up 的感觉。

    展开全文
  • 如何写出优雅整洁易懂的代码是一门学问,也是软件工程实践里重要的一环。笔者推荐三本经典的书籍《代码整洁之道 》、《编写可读代码的艺术》、《重构:改善既有代码的设计》,下文重点将从注释、命名、方法、异常、...

    28c726d501584143b45912929fa62235.png

    普通的工程师堆砌代码,优秀的工程师优雅代码,卓越的工程师简化代码。如何写出优雅整洁易懂的代码是一门学问,也是软件工程实践里重要的一环。笔者推荐三本经典的书籍《代码整洁之道 》、《编写可读代码的艺术》、《重构:改善既有代码的设计》,下文重点将从注释、命名、方法、异常、单元测试等多个方面总结了一些代码整洁最佳实践,大部分是笔者总结于以上三本书中的精华,也有部分是笔者工程实践的总结。篇幅有限,本文将总结性给出一些实践建议,后续会有文章来给出一些代码整洁之道的事例。

    注释

    • 不要给不好的名字加注释,一个好的名字比好的注释更重要;
    • 不要“拐杖注释”,好代码 > 坏代码 + 好注释;
    • 在文件/类级别使用全局注释来解释所有部分如何工作;
    • 一定要给常量加注释;
    • 团队统一定义标记:
      • TODO 待处理的问题;
      • FIXME 已知有问题的代码;
      • HACK 不得不采用的粗糙的解决方案;
    • 在注释中用精心挑选的输入输出例子进行说明;
    • 注释应该声明代码的高层次意图,而非明显的细节;
    • 不要在代码中加入代码的著作信息,git可以干的事情不要交给代码;
    • 源代码中的html注释是一种厌物, 增加阅读难度;
    • 注释一定要描述离它最近的代码;
    • 注释一定要与代码对应;
    • 公共api需要添加注释,其它代码谨慎使用注释;
    • 典型的烂注释:
      • 不恰当的信息;
      • 废弃的注释;
      • 冗余注释;
      • 糟糕的注释;
      • 注释掉的代码;
    • 唯一真正好的注释是你想办法不去写的注释:
      • 不要有循规式注释,比如setter/getter注释;
      • 不要添加日志式注释,比如修改时间等信息(git可以做的事情);
      • 注释一定是表达代码之外的东西,代码可以包含的内容,注释中一定不要出现;
      • 如果有必要注释,请注释意图(why),而不要去注释实现(how),大家都会看代码;
      • 适当添加警示注释;

    命名

    • 尽可能使用标准命名方法,比如设计模式,通用学术名词等;
    • 命名要找更有表现力的词:
      • 使用更专业的词,比如不用get而使用fetch或者download;
      • 避免空泛的名字,像tmp;
      • 使用具体的名字来细致的描述事物;
      • 给变量名带上重要的细节,比如加上单位ms等;
      • 为作用域大的名字采用更长的名字,作用域小的使用短名字;
      • 变量类型为布尔值表达加上is,has,can,should这样的词会更明确;
    • 变量名称长短应该与其作用域对应;
    • 别害怕长名称,长而具有描述性的名称比短而令人费解的名称好;
    • 函数名称应该说明副作用,名称应该表达函数,变量或类的一切信息,请不要掩盖副作用,比如CreateAndReturnXXX;

    方法

    • 函数不应该有100行那么长,20行封顶最好:
      • if else while等控制语句其中代码块应该只有一行,也就是一个函数调用语句;
      • 函数的锁进层次不应该多于两层;
      • 一个函数只做一件事,一个函数不应该能抽象出另外一个函数;
    • 某个公共函数调用的私有函数紧随其后;
    • 最理想的参数是零参数,最长不要超过三个入参,尽量不要输出参数:
      • 如果函数传入三个及以上参数最好将其抽象为类;
      • 标识参数十分丑陋,向函数传入布尔值用于区分不同业务的做法很丑陋,应该拆分为多个函数;
    • 别返回null值,抛出异常或者返回特殊对象,尽量避免NPE;
    • 别传入null值;

    异常与错误

    • 抽离try catch包含的代码块,其中代码块抽象为一个函数;
    • 抛出的每个异常,都应当提供足够的环境说明,已便判断错误的来源与处所;
    • 不要将系统错误归咎于偶然事件;

    并发

    • 分离并发相关代码与其它代码;
    • 严格限制对可能被共享的数据的访问;
    • 避免使用一个共享对象的多个同步方法;
    • 保持同步区域微小,尽可能少设计临界区;

    单元测试

    • 不要怕单元测试的方法名字太长或者繁琐,测试函数的名称就像注释;
    • 不要追求太高的测试覆盖率,测试代码前面90%通常比后面10%花的时间少;
    • 使用最简单的并且能够完整运用代码的测试输入;;
    • 给测试函数取一个完整性的描述性名字,比如 Test _;
    • 测试代码与生产代码一样重要;
    • 如果测试代码不能保证整洁,你就会很快失去他们;
    • 每个测试一个断言,单个测试中断言数量应该最小化也就是一个断言;
    • FIRST原则:
      • 快速 Fast;
      • 独立 Independent 测试应该相互独立;
      • 可重复 Repeatable 测试应当在任何环境中重复通过;
      • 自足验证 Self-Validating 测试应该有布尔值输出;
      • 及时 Timely 最好的方式是TDD;

    代码结构

    • 代码行长度控制在100-120个字符;
    • 可能用大多数为200行,最长500行的单个文件构造出色的系统;
    • 关系密切的代码应该相互靠近:
      • 变量声明应该靠近其使用位置;
      • 若某个函数调用了另外一个,应该把他们放在一起,而且调用者应该放在被调用者上面;
      • 自上向下展示函数调用依赖顺序;
    • 应该把解释条件意图的函数抽离出来,尽可能将条件表达为肯定形式;
    • 不要继承常量,比如接口中定义常量,不要使用继承欺骗编程语言的作用范围规则;
    • 模块不应了解它所操作对象的内部情况;
    • DTO(Data Transfer Objects)是一个只有公共变量没有函数的类;
    • 对象暴露行为,隐藏数据;
    • 不要使用“尤达表示法” 如 if(null == obj),现代编译器对if(obj = null)这样的代码会给出警告;
    • 一般情况使用if else,简单语句使用三目运算符;
    • 通常来讲提早返回可以减少嵌套并让代码整洁;

    设计

    • 类应该足够短小:
      • 类应该满足单一权责原则(SRP),类和模块只有一个修改理由;
      • 类应该只有少量的实体变量;
      • 类应该遵循依赖倒置原则 DIP(Dependency Inversion Principle),类应该依赖于抽象而不是依赖于具体细节;
      • 类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好;
    • 通过减少变量的数量和让他们尽量“轻量级”来让代码更有可读性:
      • 减少变量;
      • 缩小变量的作用域;
      • 只写一次的变量更好,如常量;
    • 最好读的代码就是没有代码:
      • 从项目中消除不必要的功能,不要过度设计;
      • 从新考虑需求,解决版本最简单的问题,只要能完成工作就行;
      • 经常性地通读标准库的整个API,保持对他们的熟悉程度;
    • 简单设计:
      • 运行所有测试;
      • 不可重复;
      • 表达了程序员的意图;
      • 尽可能减少类和方法的数量;
      • 以上规则按重要程度排列;
    • 无论是设计系统或者单独模块,别忘了使用大概可工作的最简单方案;
    • 整洁的代码只提供一种而非多种做一件事的途径,他只有尽量少的依赖。明确定义并提供尽量少的API;
    • 减少重复代码,提高表达力,提早构建,简单抽象;

    小结

    本文从注释、命名、方法,单元测试,并发等视角简单给出了一些最佳实践,相信每一个优秀的工程师都有一颗追求卓越代码的心。

    原文:http://yq.aliyun.com/articles/598076?utm_content=m_51055

    展开全文
  • 奇技指南本文的主题是如何代码提高产品质量,目前基本的方法是通过静态代码扫描和二进制文件扫描,获取产品代码的漏洞,本文在此基础上,对获取的产品代码漏洞进行了深挖探索。导读文章主要从四个方面说明 产品...

    奇技指南

    本文的主题是如何从代码层提高产品质量,目前基本的方法是通过静态代码扫描和二进制文件扫描,获取产品代码的漏洞,本文在此基础上,对获取的产品代码漏洞进行了深挖探索。

    791ef85640a208934576dd4224307a4f.png

    导读

    文章主要从四个方面说明

    1. 产品代码漏洞检查的背景和方法
    2. 代码漏洞的搜索深挖技术
    3. 提高产品质量的方法
    4. 总结与展望

    产品代码漏洞检查的背景和方法

    • why-为什么要检查产品代码的漏洞

    一般情况下,产品质量的问题多数与程序代码相关。比如银行软件出现漏洞,导致十几个客户信用卡被盗刷。2003年阿丽亚娜5型火箭升空爆炸造成5亿美元的损失。由于电控系统的软件问题导致大面积停电事故,给交通,通信,居民生活造成严重影响等等,都是和产品代码相关。代码的漏洞检查与分析可以帮助用户从根源上减少70%-80%的产品崩溃和安全性问题。只有代码中的崩溃和安全缺陷得以及时消除,最终形成的产品才能具备较高的质量,有效降低整个产品风险。

    9aa5e6adfdaf25d408eb674faf2eb9d4.png
    4c6bf67685783da82ec8cc5189aaaf69.png
    8b97e03d03a6aae940bca03936caaf40.png
    • when-什么时候检查产品代码的漏洞

    在产品开发测试发布过程中,流程越往后,漏洞造成的影响越大。漏洞发现的越早,修复成本越低。

    e54dc3c571b30f6de221f04311032cdd.png

    如下图中红色的曲线,横坐标是产品发布流程,纵坐标是修复缺陷成本,可以看出,在测试阶段,修复成本比较低,在产品发布之后,修复成本是成指数增长的。

    3862a9f120ca75d2a5daab2611cfb836.png

    所以在产品测试阶段,最好把产品代码中的漏洞都检查出来。那怎么对产品代码的漏洞进行检查。

    • how-检查产品代码漏洞的方法

    现阶段一般有2种方法,而且这2种方法我们已经实现了。

    一种是源代码的漏洞扫描与检查,主要方法是对编码规范的检查,常见的编码规范有4类,分别是错误类,安全类,禁用类和建议类,具体内容详见下图。自定义代码规范的制定与实时更新,根据具体业务场景的代码规范的制定等等方法都能很好的检查出产品代码的漏洞。

    13f02b890d616363d0235932304fb1b5.png

    另外一种是对二进制文件的漏洞扫描与检查,比如google提供的veridex工具,可以扫描非法API调用,该工具将非法API分了3类。

    04077fc3b9839779f9e67a8dd1829f5f.png
    • 深度挖掘产品代码漏洞的方法

    通过上面介绍的2种方法,只能对特定代码或二进制进行检查,但是对产品,乃至整个公司的代码仓库,隐藏的bug却是惊人的。

    由此,在上述2种基础的方法上,我们引入了另外一个技术,代码漏洞的搜索深挖技术,简而言之,即代码搜索。

    978c42266266c9ff2bdb1253a9fa1ca8.png

    此外,经过调研发现,国外也有类似的研究,NASA,microsoft等机构已经利用代码搜索技术,发现了多个零日漏洞。

    代码漏洞的搜索深挖技术

    • 代码搜索的问题和挑战

    主要是6个困难点,如下图所示。

    1. 代码特征的确定
    2. 搜索速度慢
    3. 代码信息量太少,不好定位漏洞。
    4. 代码入库非常缓慢
    5. 过滤条件不好兼容
    6. 数据量大,搜索数据量高达千万级代码文件。
    63c677939ce6c70696f4b7185865cd47.png

    针对这些问题我们做了一序列的优化和改进

    • 代码搜索的技术架构

    主要是5部分,如下图所示。

    1. python后台部分用于增量更新数据源信息和实时更新索引。
    2. 正排数据源,主要采用mysql数据库,包括表结构的设计,索引和分表设计等。
    3. Sphinx实时分布式索引,用于提供索引创建服务和搜索索引服务等。
    4. Php+nginx服务端部分,为前端提供接口服务。
    5. 前端部分,用于展示搜索结果和后台管理等。
    6751d9393e0cfa6b0fd833371655b63a.png
    • 代码搜索的服务端

    代码搜索的服务端为前端或其他系统提供API接口,一共有6大模块,包括搜索模型,登录模型,校验模型,用户模块,日志模块,代码review模块。数据库为上述6大模块提供数据支持。

    0f8ed3bcde2d3c0a817a9f7d3c8539ad.png
    • 代码搜索的后台

    大致分为3层

    最下面一层数据来源层,支持svn和git的代码仓库,来源包括qbuild系统和授权系统,获取代码日志,增量下载文件,最后存储在数据库中。

    索引层主要是从数据源中获取文档信息,然后经过分词模型,倒排索引算法,将索引存储在文件系统中。

    服务层主要是sphinx索引工具提供的索引服务,通过排序,获取索引文档信息后,从正排数据库中拿到文档全部信息,返回结果数据。

    db38baaf8c0d8ace49dc79109d2852f6.png
    • 数据源增量入库方案

    代码搜索的困难点之一是数据源入库非常慢,针对这个问题,我们有如下的优化方案,数据源的增量入库方案。

    df56196e953e7f026c8b8b507054e9d3.png

    主要有8个步骤

    1. 分别是从qbuild或授权系统获取代码地址
    2. 获取当前代码地址的提交日期
    3. 根据提交日期获取代码提交日志
    4. 通过解析日志,获取增量文件列表,然后每个文件进行下面的处理,先进行去重判断
    5. 然后下载该文件,再进行去重判断
    6. 存储在数据源中
    7. 经过分词工具
    8. 最终实时存储索引

    这个过程比较长,但是分解到每一步,却比较容易实现,比如获取代码提交日志和代码文件下载,svn对应的命令可以参考如下。

    svn log -r {0} --xml -v "{1}" --username "{2}" --password "{3}" --non-interactive --no-auth-cache --trust-server-cert> {4}svn export -r {0} "{1}" "{2}" --force --username {3} --password "{4}" --non-interactive --no-auth-cache --trust-server-cert

    在数据源增量入库方案中,有一个很大的问题需要解决,就是重复的问题。可以看一下,对于svn有路径包含重复的问题,下面那个路径是包含上面那个路径的,上面那个路径将会被入库2次。

    http://svn.example.com/svn/testxxx/111/222/333http://svn.example.com/svn/testxxx/111

    Git也有相似的问题,分支重复,不同分支代码会有大量重复提交的记录。

    http://git.example.com/root/11 分支:masterhttp://git.example.com/root/11 分支:v1.1

    我们的去重方法是,针对svn,利用模块id+revision的方式,对于svn,同一个模块id下的revison是递增的,不会有重复问题。相应的,git是通过仓库id+提交sha1值去重的,对于同一个仓库,提交的sha1值是唯一的。

    • 实时分布式索引技术

    代码搜索系统遇到的另外一个困难是搜索太慢,为此我们引入了sphinx索引工具,为什么选择sphinx索引工具呢。该工具支持高达数十亿个文档,数TB的数据和每秒数千个查询。支持各种数据源,包括xml,sql,python等。支持结果的各种过滤聚合功能,快速高效的索引,应用场合广泛,比如维基百科,优酷土豆,github等。下图是今年的索引工具的排行榜,可以看到sphinx排在第5,受众范围广。

    31a21fc6b95d16a77436a2c1fa58452d.png
    • sphinx工具使用

    Sphinx主要包括3个可用的工具

    分别是index实时索引工具,主要是对数据源的数据进行倒排索引,并存储,使用命令如下,sphinx.conf是sphinx的配置文件。

    eg: /usr/local/sphinx/bin/indexer -c sphinx.conf code

    Searchd搜索服务工具,php可以通过sphinx扩展,访问该服务,使用命令如下。

    eg: /usr/local/sphinx/bin/searchd -c sphinx.conf &

    Search搜索工具,客户端搜索工具,可以用该工具测试索引的正确性,一般只是测试使用。

    eg: /usr/local/sphinx/bin/search -c sphinx.conf mykeyword

    可以看到这3个命令都用到了sphinx的配置文件,那么这个文件怎么配置。

    sphinx实时分布式的配置详情

    一般情况下,最初会采用主索引和增量索引的方式,但是随着数据的增加,服务和运维都有压力,通过优化,我们最终采用实时分布式的方式。实时索引的好处有,代码索引无延时,没有额外的定时程序更新和合并索引服务,降低运维成本,提高搜索精确性和可靠性。分布式的好处有,资源利用率提高,搜索效率提高,搜索并发性提高等

    实时分布式的配置如下

    第1个实时索引的配置,type是rt,也就是realtime,path表示该索引存储的位置,下面几行是字段的定义,rt_field就是需要索引的字段,rt_attr_uint和rt_attr_timestamp是索引字段的属性,一个是int类型,一个是时间戳类型。

    第2个配置是分布式配置,type是distributed,下面几行是分布式位置。

    第3个配置是索引服务配置,9312接口是提供索引服务的,9306是接收实时索引服务的,下面2行是日志位置。

    index coderealtim{    type = rt  path = user/local/sphinx/indexer/files/coderealtime  rt_field = content  rt_field = filename  rt_attr_uint = rpid  rt_attr_timestamp = cdate}index codedistributed{  type = distributed  local = coderealtime  agent = localhost:9312:crt1  agent = localhost:9312:crt2}searchd{  listen      = 9312  listen      = 9306:mysql41  log        = /user/local/sphinx/indexer/logs/searchd.log  query_log    = /user/local/sphinx/indexer/logs/query.log}

    代码搜索排序方法

    代码搜索最重要的一个指标就是排序方法,本方案,主要从3个方面对代码结果进行排序,分别是词组评分,代码提交时间,和BM25算法。这3个指标中最重要的是BM25算法,下面简单的介绍该算法的实现方法,公式如下:

    ccfe27d2cde7e89ca944f45b812dd2d1.png
    54a9bd636cd8c7b7f95ad8a07ee65a59.png
    0ff182ffe71f857ca2e567489fe7b226.png
    2e41bf8be4b86b9c7ab32a8a34311b03.png
    48952b8f17bfe7d4ffd6b88703db3050.png

    Score(Q,d)是衡量某次query查询和文档的相关性计算公式,d表示当前文档,Q是query中所有的关键字集合,qi是其中的某个关键词,n是Q的长度,Wi是这个词的权重,R(q,d)是这个词和文档的权重。Wi默认是IDF值,N表示所有文档数,n(qi)表示包含该关键词的文档数,0.5是避免n(qi)为0的情况。大致的意思是关键词在所有文档中出现频率越多说明越普遍,就越不重要,权重越低。R(q,d)是这个词和文档的权重,大致的意思是某个关键词在该篇文档出现的次数越多,说明越重要。

    Wi突出的全局的权重,R(q,d)表示的局部权重。举个通俗的例子,在图书查找过程中,比如[作者]这个词,几乎在所有书中都会出现,所以[作者]这个词的权重很低,[人工智能]这个词不常见,如果某个图书中经常提到人工智能这个词,大概率这本书在讲人工智能。BM25算法通过统计的方法,就能对代码进行合理的排序。

    提高产品质量的方法

    如何利用代码搜索技术提高产品质量,主要是2种方法

    第1种方法是结合业务督促开发修复代码漏洞,一方面根据前面介绍的检查产品代码漏洞的2种方法,根据这些检查出来的漏洞进行深度搜索,将产品和公司代码库中隐藏的漏洞都修复了,去除产品隐患,另一方面结合业务,比如某个函数实现有漏洞,可以根据函数名进行搜索,查看函数调用的模块,避免代码漏洞的扩散。

    第2种方法是对产品代码的敏感词的检查,比如代码审计系统的敏感词和禁用api的检查,文件签名系统的敏感签名信息的检查等。

    下面这个图是代码搜索的一个demo,主要有3部分构成,最上面是搜索输入,中间是过滤条件,包括时间,代码语言,归属人,代码仓库。最下面是搜索的结果,主要包括文件名,仓库名,文件位置,版本号,提交日期和归属人,测试人员可以根据仓库和归属人信息找到对应的开发负责人,进而督促修复漏洞。

    ddf8a9f18de1fd513662f441b7ae0779.png

    总结与展望

    本文主要从3个部分阐述了如何从代码层提高产品质量

    第一部分是产品代码漏洞检查的背景和方法,主要讲了检查产品代码漏洞的2种方法,即源代码漏洞扫描与检查、二进制文件漏洞扫描与检查,但是这2种方法只能对特定项目的代码进行检查,隐藏的bug量是巨大的,从而引出第二部分,代码漏洞的搜索深挖技术。

    第二部分是本文的重点,展开讲了代码搜索的技术方案及实现细节

    第三部分从2个方面说明了如何利用代码搜索技术提高产品质量。

    代码搜索系统能够快速定位问题,通过对细节的不断探索,搜索速度显著提升,搜索排序质量提高了,本系统辅助优化了产品代码质量。接下来,我们将从2方面进一步优化,分别是代码推荐结合代码语义上下文和AI的方法,进一步提升代码推荐的精确度,以及函数式的代码推荐。

    想了解更多张娜讲师的分享内容,后台回复“8”即可领取360技术嘉年华——测试之美第八季的视频和PPT啦

    关于360技术:360技术是360技术团队打造的技术分享公众号,每天推送技术干货内容,更多技术信息欢迎关注“360技术”微信公众号

    展开全文
  • 覆盖率技术基础截止到Go1.15.2以前,关于覆盖率技术底层实现,以下知识点您应该知道:go语言采用的是插桩源码的形式,而不是待二进制执行时再去设置breakpoints。这就导致了当前go的测试覆盖率收集技术,一定是侵入...

    覆盖率技术基础

    截止到Go1.15.2以前,关于覆盖率技术底层实现,以下知识点您应该知道:

    • go语言采用的是插桩源码的形式,而不是待二进制执行时再去设置breakpoints。这就导致了当前go的测试覆盖率收集技术,一定是侵入式的,会修改目标程序源码。曾经有同学会问,插过桩的二进制能不能放到线上,所以建议最好不要。
    • 到底什么是"插桩"?这个问题很关键。大家可以任意找一个go文件,试试命令go tool cover -mode=count -var=CoverageVariableName xxxx.go,看看输出的文件是什么?
      • 笔者以这个文件为例https://github.com/qiniu/goc/blob/master/goc.go, 得到以下结果:
        package main
    
        import "github.com/qiniu/goc/cmd"
    
        func main() {CoverageVariableName.Count[0]++;
            cmd.Execute()
        }
    
        var CoverageVariableName = struct {
            Count     [1]uint32
            Pos       [3 * 1]uint32
            NumStmt   [1]uint16
        } {
            Pos: [3 * 1]uint32{
                21, 23, 0x2000d, // [0]
            },
            NumStmt: [1]uint16{
                1, // 0
            },
        }
    

    可以看到,执行完之后,源码里多了个CoverageVariableName变量,其有三个比较关键的属性:

      • Count uint32数组,数组中每个元素代表相应基本块(basic block)被执行到的次数
      • Pos 代表的各个基本块在源码文件中的位置,三个为一组。比如这里的21代表该基本块的起始行数,23代表结束行数,0x2000d比较有趣,其前16位代表结束列数,后16位代表起始列数。通过行和列能唯一确定一个点,而通过起始点和结束点,就能精确表达某基本块在源码文件中的物理范围
      • NumStmt 代表相应基本块范围内有多少语句(statement)

    CoverageVariableName变量会在每个执行逻辑单元设置个计数器,比如CoverageVariableName.Count[0]++, 而这就是所谓插桩了。通过这个计数器能很方便的计算出这块代码是否被执行到,以及执行了多少次。相信大家一定见过表示go覆盖率结果的coverprofile数据,类似下面:github.com/qiniu/goc/goc.go:21.13,23.2 1 1
    这里的内容就是通过类似上面的变量CoverageVariableName得到。其基本语义为
    "文件:起始行.起始列,结束行.结束列 该基本块中的语句数量 该基本块被执行到的次数"

    依托于go语言官方强大的工具链,大家可以非常方便的做单测覆盖率收集与统计。但是集测/E2E就不是那么方便了。不过好在我们现在有了 https://github.com/qiniu/goc

    集测覆盖率收集利器 - Goc原理

    关于单测这块,深入go源码,我们会发现go test -cover命令会自动生成一个_testmain.go 文件。这个文件会Import各个插过桩的包,这样就可以直接读取插桩变量,从而计算测试覆盖率。实际上goc也是类似的原理(PS: 关于为何不直接用go test -c -cover 方案,可以参考这里 大卡尔:我们是如何做go语言系统测试覆盖率收集的?

    不过集测时,被测对象通常是完整产品,涉及到多个long running的后端服务。所以goc在设计上会自动化会给每个服务注入HTTP API,同时通过服务注册中心goc server来管理所有被测服务。如此的话,就可以在运行时,通过命令goc profile实时获取整个集群的覆盖率结果,当真非常方便。

    整体架构参见:

    d16b079f0a065626ea59421d8c1676d8.png

    代码覆盖率的最佳实践

    技术需要为企业价值服务,不然就是在耍流氓。可以看到,目前玩覆盖率的,主要有以下几个方向:

    • 度量 - 深度度量,各种包,文件,方法度量,都属于该体系。其背后的价值在于反馈与发现。反馈测试水平如何,发现不足或风险并予以提高。比如常见的作为流水线准入标准,发布门禁等等。度量是基础,但不能止步于数据。覆盖率的终极目标,是提高测试覆盖率,尤其是自动化场景的覆盖率,并一以贯之。所以基于此,业界我们看到,做的比较有价值的落地形态是增量覆盖率的度量。goc diff 结合Prow平台也落地了类似的能力,如果您内部也使用Kubernetes,不妨尝试一下。当然同类型的比较知名的商业化服务,也有CodeCov/Coveralls等,不过目前她们多数是局限在单测领域。
    • 精准测试方向 - 这是个很大的方向,其背后的价值逻辑比较清晰,就是建立业务到代码的双向反馈,用于提升测试行为的精准高效。但这里其实含有悖论,懂代码的同学,大概率不需要无脑反馈;不能深入到代码的同学,你给代码级别的反馈,也效果不大。所以这里落地姿势很重要。目前业界没还看到有比较好的实践例子,大部分都是解决特定场景下的问题,有一定的局限。

    而相较于落地方向,作为广大研发同学,下面这些最佳实践可能对您更有价值:

    • 高代码覆盖率并不能保证高产品质量,但低代码覆盖率一定说明大部分逻辑没有被自动化测到。后者通常会增加问题遗留到线上的风险,当引起注意。
    • 没有普适的针对所有产品的严格覆盖率标准。实际上这更应该是业务或技术负责人基于自己的领域知识,代码模块的重要程度,修改频率等等因素,自行在团队中确定标准,并推动成为团队共识。
    • 低代码覆盖率并不可怕,能够主动去分析未被覆盖到的部分,并评估风险是否可接受,会更加有意义。实际上笔者认为,只要这一次的提交比上一次要好,都是值得鼓励的。

    谷歌有篇博客(见参考资料)提到,其经验表明,重视代码覆盖率的团队通常会更加容易培养卓越工程师文化,因为这些团队在设计产品之初就会考虑可测性问题,以便能更轻松的实现测试目标。而这些措施反过来会促使工程师编写更高质量的代码,更注重模块化。

    最后,欢迎加入七牛云Goc交流群,我们一起聊聊goc,聊聊研发效能那些事。

    更多工程效能、测开技术讨论可以关注公账号: 大卡尔

    参考资料

    • 谷歌管理覆盖率的经验: https://testing.googleblog.com/2020/08/code-coverage-best-practices.html
    展开全文
  • 导语 研发都知道代码 Review 的重要性,在腾讯代码 Review 也越来越受大家重视,作为腾讯专有云平台研发的一员,我参与了大量的代码 Review,明显地感受到有效的代码 Review 不但能提高代码的质量,更能促进团队沟通...
  • " test user: 18") } 这样编写,虽然可以完成对UserController的渲染逻辑的测试,但因为我们mock掉了UserService,那么UserService调用后端Web服务并处理的业务逻辑并不会被覆盖: jacoco测试代码覆盖率报告 ...
  • 程序静态分析(Program Static Analysis)是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码分析...
  • 程序静态分析(Program Static Analysis)是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标的一种代码...
  • 她有丰富的标准库、性能强劲、代码简洁...当他选择和她在一起,很多人都不看好;当她成就了他,他骄傲地跟所有人说:Go是他一生的至爱。我发现我花了四年时间锤炼自己用 C 语言构建系统的能力,试图找到一个规范,...
  • 最近,在作者就职的 HFT 公司里,一个团队成功地把一些对速度不太敏感的基础设施代码从 Python 移植到了 Go,这也促使他们决定尝试用 Go 对复杂冗余的 C++ 服务端程序进行重构,这些代码有 5W 行之多,并且对吞吐量...
  • 以下几点个人实用小技巧希望能帮到你教会你如何大幅度提高fps增加游戏体验!1.降低画质特效这是最重要的一点,cs这游戏的高画质最主要就是体现在光影细节方面,影子反光细节贴图等等,降低画质只会让贴图不够细节,...
  • 最近,在作者就职的 HFT 公司里,一个团队成功地把一些对速度不太敏感的基础设施代码从 Python 移植到了 Go,这也促使他们决定尝试用 Go 对复杂冗余的 C++ 服务端程序进行重构,这些代码有 5W 行之多,并且对吞吐量...
  • lcov是建立在gcov之上的一个可以生成html代码覆盖率报告的工具,最近公司开始尝试引入代码覆盖来提高产品质量,lcov很好地满足了我们的需求,虽然lcov本身支持生成代码覆盖率的diff报告,但是跟我们的需求不太符合。...
  • 本文将结合敏捷开发周期短,变化快等特点,介绍如何通过在开发过程中采取一系列步骤来保证和提高整个开发团队的代码质量,并阐述了每一步可以利用的工具和最佳实践,从而使开发过程更加规范化,成就高质量的代码,...
  • 作者 | Skura编辑 | Camel本文是对...文章转载AI科技评论,这篇论文针对最常用的损耗(softmax 交叉熵、focal loss 等)提出了一种按类重新加权的方案,以快速提高精度,特别是在处理类高度不平衡的数据时尤其有用。本...
  • eclipse 代码覆盖率 俗话说傻瓜还是工具 。 但是,对我来说,如何最有效地使用工具并不总是显而易见的。 因此,我通常会花一些时间检查新的游乐场1 ,这些游乐场有望在不降低质量的情况下提高我的工作速度。 这样,...
  • 一. 前言单元测试并不只是为了验证你当前所写的代码...通过优先编写测试用例,可以很好的从用户角度来对功能的分解、使用过程和接口等进行设计,从而提高代码结构的高内聚、低耦合特性。使得对日后的需求变更或代...
  • 提高被测代码数量或降低代码总数,均可达到提高代码覆盖率的效果。在本文中,您将看到如何通过使用反射机制,在外部直接对目标类中的不可访问成员进行测试,以提高被测代码数量;以及通过修改 Cober
  • 同时通过一个实际例子来讲解取得代码覆盖率的基本流程,其间贯穿一些基本概念,并且在得到代码覆盖率的基础之上分析如何改进测试用例,在反复迭代的过程中来提高测试用例的代码覆盖率。您可以在短时间内了解代码覆盖...
  • 代码覆盖率分析

    万次阅读 2011-07-03 10:06:25
    无论是单元测试、API测试还是功能性测试,最终都是调用了产品的代码;...增加新的测试代码,以提高代码覆盖率;分析测试代码的效率,以便设计出更有效的测试代码或测试用例代码覆盖率常用的指标语句
  • 过有效的单元测试相信对于单元测试的作用,大家都不会陌生,对于能提高效率的事情,何乐而不为呢?对于Java项目来说,junit是公认的测试框架,熟练使用就可以了,另外,你一定会用到mockito这个工具,具体见文末的...
  • 这样,我遇到了EclEmma ,这是Eclipse IDE的代码覆盖工具,对于实现全面的测试用例非常有用。 覆盖范围 通常,“ 测试覆盖率是查找代码库中未经测试的部分的有用工具 ”,因为“ 测试驱动开发是...
  • lcov是建立在gcov之上的一个可以生成html代码覆盖率报告的工具,最近公司开始尝试引入代码覆盖来提高产品质量,lcov很好地满足了我们的需求,虽然lcov本身支持生成代码覆盖率的diff报告,但是跟我们的需求不太符合。...
  • 本文旨在介绍jacoco单元测试覆盖率工具的使用,jacoco覆盖率测试报告是一款评估junit单元测试质量的有效依据。 在开发业务代码时,往往需要对接口方法进行单元测试,但测试过程中很难全面的覆盖程序的每一分支,...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 165
精华内容 66
关键字:

如何提高代码覆盖率