从代码维护的角度闲话代码质量 您所在的位置:网站首页 佛祖保佑没有bug代码 从代码维护的角度闲话代码质量

从代码维护的角度闲话代码质量

#从代码维护的角度闲话代码质量| 来源: 网络整理| 查看: 265

前言

提升代码质量是程序员永恒的话题。数不清的文章书籍珠玉在前:《代码整洁之道》、《Effective Java》等各种指南,指导着程序员们前赴后继,奔向效率恐怖、代码风骚的彼岸....

除了翻阅书籍查看资料,从在学校随心所欲敲代码到在公司编写符合规范的代码并上线,自己感受最深的迅速提升代码质量有两个方式: ①CodeReview->被喷->修改->以后不敢这么写了 ②维护前人老代码,排查一些稀奇古怪的问题->改不出来->改不出来(循环n次)->改出来了->回顾问题出在哪

本文就从代码维护的角度,从实例出发,说通俗易懂的话(发肆无忌惮的表情包),轻松闲聊下代码质量的那些事儿...

正文 我看不懂,但我大受震撼

维护中最容易产生的疑问是:看不懂,这个函数在干啥啊?这个值是啥啊?为什么要这么写?这真的不是个bug吗?....

reedit论坛上有过一个很🔥的被大家戏称为“程序员酒后真言”的帖子里有这么一则:

Good code is code that can be understood by a junior engineer. Great code can be understood by a first year CS freshman. The best code is no code at all.

这是一个很有趣的论调,甚至最后这句有点儿“大音希声,大象无形”的韵味了。不管它是否完全正确,不可否认的是,代码的可读性确实很重要。代码只有便于理解,才有可能被除了作者以外的人维护。

1.明确的语义

一个类、方法、变量等都应该有明确的语义,达到的效果应该是: 搂一眼名字,至少大致明白它是干啥的。

①不要在代码里使用语义不明的硬编码

甚至像ui尺寸这种数据,如果在代码里多处用到,也请定义成常量 尽管这些值只是经验值/脑袋一拍得出来的 举个🌰:

private static final int TITLE_TEXT_SIZE_SP = 18; private static final int MAX_VIDEO_DURATION_MILLISECONDS = 24 * 1000; private static final int STYLE_IMG_WIDTH_PX = CommonUtil.dip2px(42); 复制代码

并且在常量定义的后缀上明确单位,这样做的好处是,需要修改时可以避免遗漏,并且常量命名自带解释。

②如果可以更具象,就不要用handle、process这类抽象化的表达

类似handleEvent()、processTask() 甚至于 doSomeThing() 这种抽象化的表达尽量不要使用。

一旦行数较多,理解成本变高,阅读者就不得不完整阅读才能知道到底做了什么处理。很容易打断思路,使人不得不点进去,否则不知道会发生些什么,会不会对下文有影响,这就会影响代码阅读与维护的效率。

细粒度的函数应该是自解释的,这一点后面会提到。

2.清晰的结构

臃肿的类/过长的函数总是被集中吐槽的对象,臃肿的类/过长的函数意味着难以阅读、难以寻找关键逻辑、难以维护。

相关改进的方法google有一大堆准则和实例,这里就不一一列出赘述了,谈谈觉得重要的两点。

①提炼函数 一个函数应该只干一件事情,是下载就不要搞解压,是获取就不要搞处理。

日常代码编写中,要注意对长函数进行提炼,使其尽可能变成变成一个个自带解释的细粒度函数的组合/衔接。

我认为的自带解释的细粒度函数:

只干一件事 尽量纯,只依赖入参,不依赖外部状态、变量,不在函数内部修改外部全局变量。 如果真的做到了以上两点,再配上一个有清晰语义的名字,那么看这些链接起来的函数,就会像是在看注释,如水银泄地。

如果要举个🌰,那rxjava的流式处理就是最常见的🌰:

xxxApiService.getXXX()//网络请求 .map(new ResponseFunction());//数据解包 .filter(xxxCondition)//数据过滤 .flatMap(xxx1)//业务逻辑,处理1 .flatMap(xxx2)//业务逻辑,处理2... .subscribe({},{})//UI展示 复制代码

这就挺符合上面提到的思想,从网络请求->数据处理->业务逻辑->UI展示环环相扣,真出了bug,也能很快知道在哪个环节去找问题。

而相反的,程序员一定见过类似的代码(伪):

funcXXX{ if{ 读取全局变量a,xxx if{ 写入全局变量b,创建局部变量c,xxx if{ 跑一个任务,xxx,在回调里修改xxxxx, if{ 修改全局变量a,操作一下ui,xxx if{ 再干点别的 } } } } } } 复制代码

现编了一点儿伪代码,但是在业务中确实挺容易碰到。 嵌套深,操作多,轻易不敢改,上下文影响大。维护起来就非常头疼。

②单一职责 上面提到了臃肿的类/过长函数让人头疼,以及函数的提炼/细粒度化,紧接我们就聊下类/模块的单一职责问题。

单一职责的维基百科定义:

在[面向对象编程]领域中,单一职责原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

定义很简单,但日常开发中很容易碰到以下几种情况:

一个类行数过多 一个类各种依赖 依赖多,互相影响容易改坏,也没法写单元测试。 一个类什么操作都有。 是xxxManager却在内部定义各种定义工具方法,甚至搞成static为外部调用。是xxxUtils却还持有着不断变化的成员变量。这就导致,类的命名和实际职责不符,或者说很难有一个合适、简短的命名来命名这个类。

其实就是违背了大家初学语言时,老师第一节课都会讲“松耦合,高内聚”的原则。如果在日常代码开发中,发现有上面说的几种情况,就应该考虑代码优化问题。

3.合理的复用 cv真的很爽,可是出了问题还是火葬场。 有共性应该先试图把公有逻辑剥离出来复用,不要轻易cv。

否则造成: 由A copy出来的了B、C... 有一天发现A其实有bug,但B、C却很容易被遗忘.. 改不全、改不完。

4.简练的注释

有一个古老的段子是

程序员最讨厌的两件事:1.自己写注释2.别人不写注释

前面提过,合理的命名+细粒度的代码本身自带解释。但也不是所有场景都cover的住。 🌰:

解释型注释 略过。 技术型注释 简单介绍技术实现的核心思想、原理。 防坑型注释 光靠代码没人能解释的清为什么这么写,于是写一些话粘上当时的team、链接。包括但不限于: 1.之所以这么写是因为... 产品要求这里要... 2.这里会导致.. 但.. 因此.. 3.!!!这里不要动!!! 4.除非你知道你在干什么,否则不要修改这里的代码。 复制代码

短短的几句话,是前人用血和泪凝练而成的经验教训...

祈祷型注释 佛祖保佑,永无bug 复制代码

诚然注释能辅助阅读者理解代码,但是冗余的注释也不会受到欢迎。一段代码如果需要写大量的注释,那么应该先检查代码是否有问题。

5.精炼的commit

前面提到的是代码编写的问题,代码提交也有一些需要注意的地方。这里也简单聊聊。

commit msg : 工作时commit msg没有强制要求,见过不少msg诸如”一些修改/几个bug“这样式儿的,就很迷惑。

一句话精炼描述 + 代码提交类型(feature/bug/refator) + 文档链接 + 修改背景 + 修改思路 + 影响范围 +xxx其他诸如版本号之类。

避免重复:刚参加工作时就犯过类似的错误,几个commit的msg都一致,被大佬提醒后就赶快改了(逃)。 如果几个提交都是为了解决同一个问题,且确实不存在分开提交的必要,可使用rebase -i 合并commit,或者干脆reset重提吧。

commit msg很重要,因为

cr同学在review前先看一眼commits的msg,就能大致了解整体代码编写的思路是否连贯。

维护的同学在阅读代码时,查看/过滤commit msg也能了解当时代码编写的背景。

想象一个场景,你碰到了一个棘手的问题很难解决,不明白为啥这里代码要这么写但又觉得一定是有特殊的原因,打开commit msg发现是"一些修改/几个bug"这样式儿的。想找当初提交的同学问问,又发现已经查无此人。此时还有勇气改代码吗/手动狗头

不过说起来,commit msg/Merge request的编写团队合作都可配置模板,其实完形填空就行。 但是每个commit的内容,就不好约束。

commit 内容 限制一次commit的代码行数,有意识的拆分commit!!! 一个commit提交上千行代码,这些代码完全干了不止一件事儿(搭建基本框架、修改ui、请求网络...),相当于把一次feature的代码全糅在一个commit里去做。review同学会很痛苦,时间久了之后自己也无法了解当时代码的编写思路。出了错,定位也麻烦。这里就不多说,推荐下看到过的一篇文章。

如何写好提交,做一个有品味的开发者 help.aliyun.com/document_de…

谁能告诉我到底发生了什么?

前面主要从维护的角度闲聊了下代码的可读性问题,但一份代码要容易维护,可读性强还不够。

没有日志,没人能知道用户究竟使用了什么样的上古神机,以什么样奇怪的姿势在使用app。

1.用日志debug

要养成一个习惯,程序出了问题,先看日志,日志解决不了,再打断点。断点打完后,补充缺失的日志。

因为改bug就是在排查哪里状态出了异常,程序是状态的合集,而日志负责打印这些状态。

这种习惯养成以后,熟悉的业务排查起问题来会越来越快,上下文的日志一检索,就知道大概发生了什么,哪里有异常。

另外,如果线下debug就得必须用断点,日志看不出来问题的话,到了线上,等问题反馈回来,只能得到“先加一波日志,等后续排查”这样的结果。

但加日志也得等发版,一个因为日志缺失而无法排查的问题,一来二去时间就会拖得很长。

2.足够的日志

入参、出参、分叉路径等,都应该有足够的日志。

一段出了问题的日志回捞上来,要可以定位到用户在什么页面,有哪些关键操作步骤,业务代码走到了哪些分叉.....

但不要污染日志,包括不限于:

打印各种整个的网络请求数据 一些反复触发但又意义不大的ui变化,如ui滑动之类 3.日志的等级与处理

日志的等级与使用、处理应该统一。

i、d、w、e 错误等级要和日志等级匹配。 不要因为日志比较重要想要提醒自己,就使用不匹配的日志等级,从而打印一堆日志error,实际程序正常运行不受影响。无用的error会掩盖真正严重的影响用户体验的错误。

程序状态异常的严重问题除了要loge以外,应该上报错误,否则会出现意想不到的大大大大坑。 例如用户进不了页面,但又没崩溃,也没有单独错误上报上来,就只能等用户反馈。等反馈到手里,大锅就得背上身。

真正的精华与总结!

代码质量是永恒的话题,本文主要从代码维护角度闲聊了代码质量关于可读性、日志编写的问题,也只涉及到了冰山一角。

因为一时兴起,而不断码出这篇文章的过程中,一些疑问从灵魂中迸发:

我在这里巴拉巴拉这么多,我的代码质量高吗,可以保证不犯文中的那些错误吗,不成为构造🔟山的一员吗?

那些积重难返、难以维护的业务代码,真的都是因为程序员不注重代码质量,乱写一气吗?

我相信绝大部分程序员谈到代码质量都能侃侃而谈,聊上那么几句。也相信有追求的程序员都会注意代码质量问题。

那为啥代码的🔟山越垒越高?"抽象"的代码屡屡出现?

是人性的扭曲,还是道德的沦丧?

不!很多时候都是因为业务太复杂,而时间太紧迫!

小王,这个功能需要这么久吗? 这个不是很简单吗? 不就是....

到底什么时候可以上线?老板发话了,必须赶上这个版本!

为什么这么简单的一个需求需要这么排期这么久?

需求做到一半,产品/设计:虽然当时是这么设计的(设计了一辆二轮自行车),但是后来觉得...,所以我们现在要改成xxxxx(改成一辆插着翅膀,混合动力,背着两个轮胎,海陆空三用的小汽车)才行。

于是最后程序员的内心和写出来的代码就很容易变成:

能跑就行 不是没崩吗? 又不是不能用? 后话

从上学到工作,都有写博客的习惯。最开始在csdn,后来在简书,可惜一个已经自暴自弃,一个又全是吸引眼球的营销文。 恰好看到掘金有更文活动,最近有富裕的时间来写点东西,于是敲起键盘... 记录文字让人内心平静。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有