这一篇炸鸡要讲一讲 代码注释 的可读性优化。
关于注释的炸鸡的行文思路如下图所示。
一般你加入了一个项目组,领你进来的人通常会帮助你熟悉环境,而你可能记得最清楚的便是
xxx 是不能做的,你要注意。
所以在写注释前,先要注意一下什么时候 不需要注释。
我举个最极端的例子:
-- 这是生成一个英雄实例
local hero = Hero.new()
很明显,这样的代码阅读者能 快速 判断出其功能和意图,注释就是多此一举,为了注释而注释。
所以这时候应该能理解 上一篇炸鸡 抛出的问题了吧?
当然如果遇到这样的一行代码,那么注释是有必要的。
# 删除第二个 ? 之后的所有内容
name = '?'.join(line.split('?')[:2])
因为虽然阅读者可以通过一定时间的阅读可以得出结果,但是这种 一行嵌套多个方法 的代码可读性已经下降,所以加上注释能帮助阅读者快速理解代码。
当然,这样的代码,也可以利用临时变量将嵌套的函数结果暂存,同时对临时变量恰当命名,也能达到一定的效果,而这个方面的内容在之后的炸鸡会涉及,本篇就不再赘述。
如果只是对一个函数这样的声明注释,那么大可以删去。
-- 结束采集,在采集部队结束时调用
function endGather()
...
end
亦或者提供更多的信息:
-- 结束采集,在采集部队结束时调用
-- 返回采集初始时间,经 buff 调整时间,奖励列表等。
function endGather()
...
end
-- 结束采集,在采集部队结束时调用
-- 利用快排将采集时间与收益按照顺序一一映射,可快速索引最终收益
function endGather()
...
end
当然,这个已经是下文的内容了。
如果你有稍微了解过我之前的几篇关于代码命名的炸鸡,可以理解好的命名已经能提供不少信息。
如下代码所示,myFind
是个不明白其含义的函数命名。
-- 查找字符中第二个指定字符。
funciton myFind(char)
end
看了注释才明白这个函数的作用,可是如果直接将函数名换成 find2ndAppointChar
,也许会更恰当,而且还省去了注释。
我们可以得到一个不等式:
好的命名 > 坏命名 + 注释
那么我们已经明确了什么时候不需要注释,那么接下来就需要知道确定要写注释的时候,该怎么写。
当我们写注释的时候,可以把当时写这段代码的动机意图,所思所想写下来。大致可以分成两点:
我们利用注释可以记录一段代码的 内在逻辑算法,我们拿前文的例子来说明即可。
-- 结束采集,在采集部队结束时调用
-- 利用快排将采集时间与收益按照顺序一一映射,可快速索引最终收益
function endGather()
...
end
当然,我们的代码总会因为一些客观原因并不能尽善尽美,所以完全可以利用注释 提出这段代码的瑕疵。
-- todo: 本函数存在死循环可能,需要优化。
function randomChooseCity(cantChooseCity)
while true do
local cityId = math.random(1, 20)
if cityId ~= cantChooseCity then
return cityId
end
end
end
上述代码我们可以注意到,todo
这样的标签,利用这样的标签可以告诉阅读者关于这段代码的瑕疵。
标签 | 含义 |
---|---|
todo | 要做的,未完成的事 |
fixme | 已知的不能运行的代码 |
hack | 不得不使用了较为粗糙的办法解决 |
xxx | 很危险的逻辑 |
所以我们可以看到,利用注释,阅读者便能很快地了解你当时的想法和企图,而且这个阅读者很有可能就是未来几个月后的你。
这里插一嘴,如果你用 vscode 编写代码,推荐一个还不错的注释插件 Better Commments
。
利用如图方式提供较为好辨认的提示
你的代码不一定永远都是你来维护,就好像我之前一段时间接手了好几个别人的写了一半的代码。所以为了方便后来者的维护,我们可以利用注释提供修改思路,减轻后来者的负担。
(当然,你要直接找作者也可以,毕竟算个活注释。)
在上一点,我们知道了可以通过注释承认代码的瑕疵来记录当时的思想,而承认代码的瑕疵便是为 后来者提供了改进思路。
想必各位写代码的时候或多或少的会用到常量。其实很多情况下常量的命名就已经提供了足够的信息了:
ss.const.MAX_ITEM_AMOUNT = 10
那么如果为常量写上注释,那一定是要提供额外的信息,这便是 解释常量值的计算公式 或者 常量值由来与调整范围。
-- 最大 item 数可接受 5 - 20 之间调整。
-- MAX_ITEM_AMOUNT = log(MAX_SHOP_AMOUNT) / STD_KINGDOM_AMOUNT (4 - 7)
ss.const.MAX_ITEM_AMOUNT = 10
其实写注释就是为了让阅读者方便理解代码,所以写注释一定要站在阅读者的角度。
主要可以分成三个情况:
前两个情况,其实就是 记录思想 和 提供维护和修改指导思路 两个点涉及的内容。这里主要讲一下第三个情况。
阅读者通过阅读代码能明白代码在 代码层面的功能,例如循环、查找指定字符等。但是不一定能很快了解这些代码 背后的意图,也就是 高级别的抽象。
如下代码所示,代码层面的含义是 打乱 citys
数组,然后再循环这个数组,其中的元素不等于 lastCityId
则进入逻辑。
local cityIds = random.randomArray(citys)
for _, cityId in ipairs(cityIds) do
if cityId ~= lastCityId then
...
end
end
但更高级别的抽象其实就是 从已知 citys
中随机选择一个与上次选择不一样的 cityId
。
所以利用注释只解释代码层面,往往是不够的。当阅读者了解代码层面的功能时,不一定能确认这段代码它的原作者意图到底是什么。
如果多了意图的解释,阅读者其实也能通过阅读来判断这段代码是否事与愿违,这其实也为代码多了一层 检查。
很多时候,我个人也很抵触写注释,写代码累个半死还要再打字写注释,想下手的时候又不知从何开始。
所以写注释前,调整心态还是挺重要的。我们可以尝试以下三步走,来写自己的注释:
我举个栗子,我写了如下的函数和注释。只是为了记录所想。虽然长的要命,但还是写出来了。
-- * 这是在每天 0 点的时候执行的函数
-- ! 为离线玩家补偿, 应该拿 logoutTime 来计算,不应该再多一个 lastDayPassTime
-- ! 但是,在线跨好几天的玩家不能走同一套逻辑, 通过 isTimerTrigger 来区分是手动调用还是 daypass 服务调用
function onDayPassMakeUp(isTimerTrigger)
...
end
然后可以考虑能不能提炼一些内容,因为命名已经是叫做 onDayPassMakeUp
, 命名已经告诉我们一些信息,所以可以删去第一行注释。同时解释内在补偿逻辑时,也可以省一些字句。
-- ! 针对离线玩家,用 logoutTime 计算未参加活动的天数
-- ! isTimerTrigger 区分在线跨天和离线跨天。
function onDayPassMakeUp(isTimerTrigger)
...
end
这样算是一次提炼了。
不难发现,本篇炸鸡其实主要侧重让编写者能够开始写注释同时确定该怎么写,但是在这一小节我们看到,写出的注释不一定是最恰当的,是存在 优化方向的。
所以下一篇炸鸡就稍微说一说关于 注释的优化方向和优化方法,希望对你有所帮助。
todo
、fixme
等标签。