代码审计
代码审查可以帮助提高代码质量,避免由于开发人员不好的代码习惯而造成的 bug。下面列出的这些要点应该可以作为大部分代码审查的指导,如果是 Java 应用的话,这些建议应该被视作最佳实践。
代码审查(Code Review)是软件开发中常用的手段,它容易发现和架构以及时序相关等较难发现的问题,还可以帮助团队成员提高编程技能,统一编程风格等。
一、代码审计需要注意的点
- 代码审查要求团队有良好的文化
团队需要认识到代码审查是为了提高整个团队的能力,而不是针对个体设置的检查“关卡”。“A的代码有个bug被B发现,所以A能力不行,B能力更好”,这一类的陷阱很容易被扩散从而影响团队内部的协作,因此需要避免。代码审查本身可以提高开发者的能力,让其从自身犯过的错误中学习,从他人的思路中学习。如果开发者对这个流程有抵触或者反感,这个目的就达不到。 - 谨慎的使用审查中问题的发现率作为考评标准
码审查中如果发现问题,对于问题的发现者来说这是好事,应该予以鼓励。但对于被发现者,我们不主张使用这个方式予以惩罚。软件开发中bug在所难免,过度苛求本身有悖常理。更糟的是,如果造成参与者怕承担责任,不愿意在审查中指出问题,代码审查就没有任何的价值和意义。 - 控制每次审查的代码数量
根据smartbear在思科所作的调查,每次审查200行-400行的代码效果最好。每次试图审查的代码过多,发现问题的能力就会下降,我们在实践中发现,随着开发平台和开发语言的不同,最优的代码审查量有所不同。但是限制每次审查的数量确实非常必要,因为这个过程是高强度的脑力密集型活动。时间一长,代码在审查者眼里只是字母,无任何逻辑联系,自然不会有太多的产出。 - 带着问题去进行审查
我们在每次代码审查中,要求审查者利用自身的经验先思考可能会碰到的问题,然后通过审查工作验证这些问题是否已经解决。从用户可见的功能出发,假设一个比较复杂的使用场景,在代码阅读中验证这个使用场景是否能够正确工作。使用这个技巧,可以让审查者有代入感,真正的沉浸入代码中,提高效率。有的研究建议每次树立目标,控制单位时间内审核的代码数量。 - 所有的问题和修改,必须由原作者进行确认
如果在审查中发现问题,务必由原作者进行确认。
这样做有两个目的:
(1)确认问题确实存在,保证问题被解决
(2)让原作者了解问题和不足,帮助其成长
有些时候为了追求效率,有经验的审查者更倾向于直接修改代码乃至重构所有代码,但这样不利于提高团队效率,并且会增加因为重构引入新bug的几率。
6.利用代码审查激活个体“能动性"
即使项目进度比较紧张,无法完全的进行代码审查,至少也要进行部分代码的审查,此时随即抽取一些关键部分是个不错的办法。背后的逻辑是,软件开发是非常有创造性的工作,开发者都有强烈的自我驱动性和自我实现的要求。让开发者知道他写的任何代码都可能被其他人阅读和审察,可以促使开发者集中注意力,尤其是避免将质量糟糕,乃至有低级错误的代码提交给同伴审查。开源软件也很好的利用了这种心态来提高代码质量。
7.在非正式,轻松的环境下进行代码审查
如前所述,代码审查是一个脑力密集型的工作。参与者需要在比较轻松的环境下进行该工作。因此,我们认为像某些实践中建议的那样,以会议的形式进行代码审查效果并不好,不仅因为长时间的会议容易让效率低下,更因为会议上可能出现的争议和思考不利于进行如此复杂的工作。
8.提交代码前自我审查,添加对代码的说明
所有团队成员在提交代码给其他成员审查前,必须先进行一次审查。这次自我修正形式的审查除了检查代码的正确性以外,还可以完成如下的工作:
(1)对代码添加注释,说明本次修改背后的原因,方便其他人进行审查。
(2)修正编码风格,尤其是一些关键数据结构和方法的命名,提高代码的可读性。
(3)从全局审视设计,是否完整的考虑了所有情景。在实现之前做的设计如果存在考虑不周的情况,这个阶段可以很好的进行补救。
在实践中发现,即使只有原作者进行代码审查,仍然可以很好的提高代码质量。
9.实现中记录笔记可以很好的提高问题发现率
成员在编码的时候应做随手记录,包括在代码中用注释的方式表示,或者记录简单的个人文档,这样做有几个好处:
(1)避免遗漏。在编码时将考虑到的任何问题都记录下来,在审查阶段再次检查这些问题都确认解决。
(2)根据研究,每个人都习惯犯一些重复性的错误。这类问题在编码是记录下来,可以在审查的时候用作检查的依据。
(3)在反复记录笔记并在审查中发现类似的问题后,该类问题出现率会显著下降。
二、为什么需要代码审查
代码审查这是大家都需要做的事情,Google的代码之所以优秀原因其实很简单:他们非常重视代码审查。代码审查并不是Google独有的,它被公认为是一个很好的(提高代码质量的)手段,很多人已经在日常开发中采用代码审查。在 Google,任何的产品或者项目代码在检入(代码仓库)之前都需要进行有效的审查。
每个人都要参与代码审查,而且这里指的不是非正式的审查:它是软件开发环节中非常重要而且通用的规则。不仅是产品代码,所有的代码都需要进行审查。审查代码不需要投入很多的精力,但是(与不做审查相比)产生的效果却是天壤之别。
关于代码审查(code review),Jonathan Danylko(乔纳森·丹尼可) 的看法是“代码要经常检查(包括自查和其他同事检查)。不要把别人的检查,看成是对代码风格的苛求。应该把它们看作是有建设性的批评。对个人来说,经常检查你的代码并且自问,“我怎样才能写得更好呢?” 这会加速你的成长,让你成为一个更优秀的程序员。”
你能从代码审查中收获什么?
如果有另外一个人检查即将提交的代码,能够帮助找到bug。这是代码审查众所周知且经常被提及的好处。但这是最没有价值的一个好处。人们确实可以在代码审查中找到bug。然而,在代码审查中找到的bug绝大多数都是一些代码作者花上几分钟就能找到的小bug。那些真正需要花时间才能找到的bug在代码审查中是检查不到的。
代码审查最大的好处在于它是一种社交的途径。如果你编程的时候就知道会有同事检查你的代码,那么你的程序会有所不同。你写的代码会更加整洁,有着较好的注释,结构也组织的不错——因为你知道会有人来检查你的代码,而且你很在意他们的意见。
代码审查还有一个更大的好处,就是可以分享知识。在很多的开发团队中,每个人都会负责并且专注于一个核心模块。除非别的同事负责的模块出现问题导致自己的代码不能运行,否则他们是不会去关注别人的工作。这样产生的结果是,每一个模块的代码只有一个人比较熟悉。假如事不凑巧,那位程序员正好休假或者离开了公司,那么没有人了解那些代码了。如果有代码审查的环节,那么至少会有两个人熟悉代码——代码的作者和审阅者。审阅者虽然没有作者对代码那么了解——但是他同样熟悉代码的设计和结构,这些信息是无价之宝。
要做好代码审查需要一段时间练习。经验不足的审阅者通常会落入一些代码审查的陷阱,这些陷阱往往会造成很多的麻烦,给那些希望尝试代码审查的人们留下了坏印象,成为了他们采纳代码审查的一个主要障碍。
代码审查最重要规则是对即将提交的代码中查找问题——你需要做的就是确认代码是正确的。而通常会犯的一个错误,也是刚刚接触代码审查的新手容易犯的一个错误,即审阅者会判断这段代码是否按照自己思路来实现。
当有一个问题需要解决时,通常会有几十种的办法。当选定一个解决方法时,会有百万种代码实现。因此,作为一个审阅者,你的工作不是确保代码是按照你的方式来编写的——因为这是不可能的事情。审阅者的工作是确保原作者编写的代码是正确的。如果你没有遵守这个规则,你可能会到处碰壁,审查结束时你的心情很糟糕,对你来说肯定不是一件好事情。
问题在于这是不自觉就会犯的一个错误。假定你在程序员的视角去看一个问题的时候,你会得到一个自己的解决方案——并且你认为你看到的就是这个问题(应该采用的)解决办法。如果想要成为一名好的审查者,你需要知道这是不对的。
第二个误区就是人们感觉一定要说点什么(才算是做了代码审查)。代码的作者花了很多的时间和精力来编写代码——你难道不应该说点什么吗?
答案是:你不应该。
如果只是说“代码没问题”,这永远没错。反之,如果你不断地去查找一些“问题”并加以指责,或者说你不断地去制造一些事情来说些什么,那么代码的作者会认为,当你的言论只是为了避免冷场。从此,你的意见不会受到重视。
第三个误区就是速度。你不应该匆忙完成一次代码审查——但是也不要拖延。
三、21世纪的代码审查
在软件工程领域里代码审查可以结束程序员之间无谓的争执。开发者常常会因为一些愚蠢的小事斗嘴,冒犯对方,甚至抓住Bug而喋喋不休,但是我们有理由相信代码审查绝对是个不错的好方法:
- 越早发现bug也就意味着可降低项目成本。无须释放一个修复补丁,因为它正处在开发阶段。
- 代码变得越来越重要。
- 知识贯穿于你的团队中,不再像以前那样一大块代码只有某一个人知道。
- 开发者需要加倍的努力。如果开发者知道别人要对他的工作进行评估时,就会采取额外的努力做好工作,同时他还喜欢用文档注释标出异议。
如今,在21世纪的今天很多项目都没有使用代码审查。下面将提供8条准则供大家学习与参考。 - 永远别忘了TDD(Test Drive Development)
TDD的主要目标是使代码更加清晰、简单、无bug。测试驱动开发从设计和开发应用的每个小功能的测试开始。在TDD方法中,首先,开发测试来指定和验证代码将做什么。在确认测试代码前,先找别人帮你检查下是否无误。在别人做之前尽量检查出bug并且将其处理好。代码审查最重要规则是对即将提交的代码中查找问题——你需要做的就是确认代码是正确的。
2.尽可能的自动化
这里有几个非常好的Java工具比如:PMD, Checkstyle, Findbugs等等。使用这些工具前,为这些工具制定一套细则是非常重要的。这能够确保你使用同一个代码审核标准从而区别于那些常被用于20世纪老式的代码审查规范。在理想的状态下,这些工具可运行在各种版本控制系统上通过hook审查每个代码。如果该代码不好将被阻止在外。
3.尊重设计
在我开始从事Java项目早期时,用代码审查的方式已为时已晚。因为当你检查代码问题时实际上给你的设计造成了缺陷。设计模式被误解,一些繁杂的附属物质混入进来或者开发者脱离了主题。代码审查会牵连到很多面,无论是设计还是设计审查。设计需要更高的质量和灵感,我们应该避免一些复杂的思维。 - 统一的风格指南
即使是使用自动化工具(诸如Checkstyle,Findbugs等)也应避免不必要的风格冲突,你的项目应该具备有风格指南。(在尽可能的情况下)坚持Java协议的规范标准。尝试着为你的项目介绍制定一个“词典”,这就意味着,当涉及这个代码时,查看该代码的用法和环境是否适宜,这些都很容易被检测出。 - 挑选适宜的工具
如果开发者都在使用Eclipse或者idea开发工具,你可以通过你的方式来查看代码、调试代码甚至可使用IDE上的一切东西当来帮助你在审查代码时更加的便捷。
6.记住每个项目都不同
也许你在采用以前的项目方法工作,但是,请记住每个项目之间是不同的。每一个项目都有特定的架构(高并发或是高分散),有特定的文化(或许很多人喜欢使用Eclipse或者idea),并使用特定的工具(maven or ant)。不同的项目有不同的工作方法。
7.懂得取舍
代码审查需要积极和细致而不是卖弄学问。理清头绪,换个角度想想,改变自己的心态而不是想着着去改变别人。 - Be buddies
buddy review是指与其他团队成员每隔一到两天以非正式的形式讨论,并且快速的浏览(5-10分钟)对方的代码。这种方法的好处: - 及早的发现问题
- 总是很快的知道该干什么
- 代码审查无须过长,因为你只需要查看新的代码,旧的代码会很快赶上
- 这种非正式的场合——没有紧张感,很有趣!
- 可以定期的交换想法
buddy reviewing在团队中是一种很好的工作方式,当某人在团队中出现问题时可以及早的发现。这不仅可以帮助大家,还可以交换彼此的进度和想法。
总之,如果你的项目正在进行代码审查,应该做到快速、有效、不浪费别人的时间。正如文章所说的,这几点非常重要。代码审查用意是在代码提交前找到其中的问题。
四、代码审查所用到的一些工具和一些规范
下面就文档注释、功能实现、安全性、性能以及编码习惯五个方面来阐述一下代码审查所用到的一些工具和一些规范。首先是java代码从编写出源代码到可执行文件的一个运行过程:
Java源代码 ——(编译器 )——> jvm可执行的Java字节码 ——(jvm解释器) ——> 机器可执行的二进制机器码 ——>程序运行(采用字节码的好处:高效、可移植性高)
一、需要增加必要的文档说明
1、Javadoc应该在每一个类和方法中添加。
很多程序对Javadoc都不重视,认识不到Javadoc的作用,很多人都是这样认为的:“我只要写好功能就够了,写Javadoc太浪费时间,也没啥作用,还不如用写Javadoc的时间再多些个功能呢!”,我们知道注释是为了解释代码的作用的,是为了将来给自己或者别人快速了解代码的,在方法内一般用行注释//的比较多,是针对一小块代码做出解释的,而Javadoc的作用是针对整个方法或者整个类做一个简要的概述的,使得别人不通过看具体方法代码就能知道某个方法或者某个类的作用和功能。写了Javadoc的在别人使用到类时,将鼠标悬停到类上或者方法上,javadoc会以提示信息显示出来,这样开发者在跳进源代码中就能知道类或者方法的作用。说到这里可能还是有很多人不能认同这种观点,还是认识不到Javadoc的作用。
2、如果是修复某个bug,应该添加 bug ID。
3、走捷径的方法或者复杂的逻辑需要进行解释说明。
4、如果代码会被公开,每个文件头都要标注版权信息。
5、复杂的 HTML,JavaScript,CSS 应该包含文字说明。
二、功能实现方面
1、如果类似的逻辑被使用了多次,应该把它写成一个帮助类,然后再在多处调用。
2、鼓励使用 API 而不是重复编写代码解决相同的问题。
3、要强调代码的单元测试。
4、任何新加的代码不应该破坏已有的代码。
三、安全方面
1、任何代码都不能执行用户的输入,除非转义过了。这个常常包含 JavaScript 的 eval 函数和 SQL 语句。
2、禁止那些在短时间内提交非常多请求的 IP。
3、任何类,变量,还有方法都应该有正确的访问域。
4、尽量避免使用 iframe。
四、性能方面
1、所有数据库和文件操句柄在不需要的时候都应该被关闭。
2、SQL 语句的写法会导致性能千差万别。
3、鼓励创建不可变(immutable)的类。
4、类似的逻辑代码,尽量通过 if else 语句来实现更多的重用。
5、如果是 Web 项目,请检查是否使用了合适的图片尺寸,CSS sprites 和浏览器缓存等技术。
6、全局都需要的信息保存在 application context 中。
五、良好的编码习惯
1、没有被使用的变量要删除。
2、针对不同的 Exception 要用不同的 catch 语句,而不是一个 Exception 解决所有问题。
3、针对变量,方法和类,都应该遵循各自的命名规则命名。
4、常量应该被写在独立的常量类中。
5、每行代码的尾部不要有多余的空格。
6、一个单独的语句不应该超过编辑器的可视区域,它可以被拆分成几行。
7、检查 String 对象既不是null也不是空的,最好方法是 if(“”.equals(str))
8、给方法添加适当的访问控制,而不是所有都是 public。
9、遵守项目中使用的框架的最佳实践建议,例如 Spring,Struts,Hibernate,jQuery。
六、代码格式检验及相关工具
1、 PMD是一个源代码分析器。它寻找常见的编程缺陷,例如未使用的变量、空的catch块、多余的对象创建等等。
2、 重复代码会导致程序过长,错误更多,进而难以维护,对重复代码进行重构能够缩短编译时间,降低认知负载,减少人为错误。
3、 findbugs静态分析工具做到无需开发人员费劲就能找出代码中已有的缺陷。
4、checkstyle它能够自动化代码规范检查过程。
1、 在idea安装checkstyle的插件
Setting中找到plugin,搜索checkstyle
2、 如果这里没有安装会显示install按钮,我这边是因为已经安装了,所以没有显示
3、 安装完需要重启idea,然后在othersetting里面配置checkstyle
选中本地的checkstyle文件,填写描述
4、 checkStyle文件中有个变量需要配置上一步骤suppressions.xml的文件路径,也可以直接写死路径
5、 然后使用为程序进行检查,选择对应的checkstyle执行
七、常见的代码编写过程中出现的格式规范
1、一条赋值表达式语句中同时声明或赋值多个变量,大大降低了程序的可读性与可测试性。
2、如果一个类没有被继承,没有被覆盖,那么可以用final修饰,在执行速度方面比一般类快。
3、一些if语句由于结构简单,只有一行,有些程序员为了方便,不加花括号,这样不便于阅读,和后续维护。
4、Java寻找类的顺序:在显示导入的类中,在包中,最后再寻找通配符相关的包
使用通配符*,会影响编译性能,,阅读代码更加困难,不会对运行时的性能产生任何影响。
4、 代码一个主类中若有内部类,那么内部类中有可能会用到主类中的属性和方法,为了避免无法识别,就是找到内部类,放置到主类的最后面就可以解决。
5、 switch用在编程中是一个判断选择逻辑结构。其功能就是控制流程流转。没有default可能会导致复杂的逻辑问题并由此产生安全隐患,这种错误就是字面意思,在default中加入响应的break语句。
6、 不同的系统里,tab占用的字符数不同,会造成代码阅读困难,对于所有制表符的位置全部替换成四个空格,尽量不要使用tab键。
7、 为了便于代码易于阅读,所有的,后面加上空格,运算符前后都加。对于括号,循环,if语句等等要用统一的格式。
8、 单个java文件不要超过1500行,单个方法不应该超过100行。
9、 尽量不要导入不使用的类,直接删除import相关语句。
10、 if和for循环层数过多(if语句最多5层,for循环最多2层),可以单独提取出超过的代码块,作为成员函数来调用即可。对于参数超过标准的问题,目前直接屏蔽,没有找到解决方案。
11、 建议使用log.info或者log.error代替System.out和System.err,标准错误输出流(System.err)和标准输出流(System.out)使用的是不同的流对象。二者是独立执行,并且JVM 为了高效的执行输出流信息,二者在执行时是并行执行的,因此我们看到的结果是打印顺序总是随机的。
12、 在方法声明上,可以抛出指定的异常,但是不要简单抛出Throwable这个类,这样的设计方法不好,因为不知道抛出的类型到底是哪种具体问题,无法针对性的处理。
Error通常是一些底层的和硬件有关的错误,与程序本身无关,不应该被捕获,因为捕获了无能为力。
八、业务安全,理解该系统的逻辑
1、用户登陆、用户注册、找回密码等功能中密码信息未采用加密算法。
2、用户登陆、用户注册、找回密码等功能中未采用验证码或验证码未做安全刷新(未刷新Session中验证码的值)导致的撞库、密码爆破漏洞。
3、找回密码逻辑问题(如:可直接跳过验证逻辑直接发包修改)。
4、手机、邮箱验证、找回密码等涉及到动态验证码未限制验证码失败次数、验证码有效期、验证码长度过短导致的验证码爆破问题。
5、充值、付款等功能调用了第三方支付系统未正确校验接口(与第三方的交互、与客户的交互,主要查看逻辑问题)。
6、后端服务过于信任前端,重要的参数和业务逻辑只做了前端验证(如:文件上传功能的文件类型只在JS中验证、后端不从Session中获取用户ID、用户名而是直接接收客户端请求的参数导致的越权问题)。
7、用户身份信息认证逻辑问题(如:后台系统自动登陆时直接读取Cookie中的用户名、用户权限不做验证)。
8、条件竞争问题,某些关键业务(如:用户转账)不支持并发、分布式部署时不支持锁的操作等。
9、重要接口未限制请求频率,导致短信、邮件、电话、私信等信息轰炸。
10、敏感信息未保护,如Cookie中直接存储用户密码等重要信息,跟踪cookie中的变量最终到了哪。
11、弱加密算法、弱密钥,如勿把Base64当成数据加密方式、重要算法密钥采用弱口令如123456。
九、反编译
反编译是将可执行的(准备运行的)程序代码(也称为目标代码)转换为某种形式的高级编程语言,使其具有更易读的格式。反编译是一种逆向工程,它的作用与编译器的作用相反。它与编译相反。完成此任务的工具称为反编译器。反编译有许多不同的原因,例如理解程序、恢复源代码以进行存档或更新、查找病毒、调试程序和翻译过时的代码。完全自动化的反编译是不可能的。没有反编译器可以获得开发人员编写的确切源代码。
反编译有时被不道德地用于复制源代码以在未经版权所有者许可的情况下重复使用或改编。反编译 APK 文件并不是一项非常艰巨的任务。它需要将 dex 文件转换为 jar 文件,然后将这些 jar 文件转换为 java 源代码,从而获取应用程序源代码。可以通过保护性手段(例如代码复杂性和混淆)将程序设计为抗反编译。这包括反调试技术、限制调试器、跟踪检查、优化和剥离二进制文件。
反编译工具: idea插件在线安装jclasslib、fernflower、jad、jd-gui、
Jprofiler是一个全功能的 Java 剖析工具( profiler ),专用于分析 J2SE 和 J2EE 应用程序。它把 CPU 、执行绪和内存的剖析组合在一个强大的 应用中。 JProfiler 可提供许多 IDE 整合和应用服务器整合用途。 JProfiler 直觉式的 GUI 让你可以找到效能瓶颈、抓出内存漏失 (memory leaks) 、并解决执行绪的问题。它让你得以对 heap walker 作资源回收器的 root analysis ,可以轻易找出内存溢出; heap 快照( snapshot )模式让未被参照( reference )的对象、稍微被参照的对象、或在终结( finalization )队列的对象 都会被移除;整合精灵以便剖析浏览器的 Java 外挂功能。