游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态

编写此文的目的,并不是教大家如何写出没有Bug的代码。 没有Bug是不可能的。 话虽如此,但是我们完全可以在开发阶段,避免一些低级的Bug。笔者在游戏上线后维护服务器的时间较长,我发现线上所谓的严重问题,真正出问题的代码就是那么几行(一般不会超过10行)。也就是说,这些容易出问题的部分,并不是系统中复杂的部分。因此,我们一定有办法在事前对其进行规避。

我认为避免Bug需要我们有良好的开发习惯,和冷静耐心的心态。于此之上,再通过经验加以一些技巧,就可以避免很多大小Bug了。其中,我认为最重要的是习惯。好的习惯不会让你多花太多时间,但是能尽早发现问题。

习惯和技巧

自测

自测,即代码编写结束后,在本地或开发环境中运行,然后通过白盒或者黑盒的方式测试自己开发的代码。

在本地开发时,务必拥有一个开发环境,这个开发环境可以测试自己正在开发的、还未合并到主干的代码。有了这个环境,你就可以快速地将自己新开发的内容运行起来。如果可以Debug查看每一行的结果就更好了。

自测的意义在于,你可以成为自己代码最早的测试人员,验证自己的思路是否真的被正确开发。自测能给你一个机会,让你执行自己的代码,并且能让以下容易遗漏和犯错的部分得到快速验证:

如果你的环境不允许你打断点,或者一些情况比较复杂(多线程环境或有延时函数等),你可以在本地多打一些debug级别的日志,查看数据的处理过程。

Q&A

  1. 我等客户端同学开发完,联调的时候再一起测不行吗?

可以,但是在联调时你的工作重心已经到了其他地方,你会忽略或遗忘当时写这段代码的一些想法。而这些想法很有可能就是容易出错的地方。另外客户端同学一般只会测自己感兴趣或者担心出问题的部分,他们关注的部分不一定和你一致。

  1. 要是我都写正确了,自测不就是浪费时间吗?

不。我们人类一定会犯错,即使我们逻辑写对了,也有可能会把诸如道具产销日志等一些不影响功能,但是运营、策划需要的部分遗漏,也有可能会忘记打warning日志或者error日志,或者是发现一些变量名不合适,或者是重新回看的时候发现代码结构冗余臃肿。这些都是值得修改优化的点。如果我们不自测,那么可能未来就没有时间拿来安排自测了。

  1. 服务器开发逻辑的时候,没有现成可用的客户端包进行测试,自测很麻烦。

是的,很麻烦,但是并非毫无办法,接下来会介绍一些自测的技巧。

自测的技巧

变量及函数命名技巧

不要使用setXXX(),但是你可以针对这个业务逻辑,定义一个更贴合业务的逻辑,比如设置玩家积分时,同时更新排行榜上的玩家数据,则setScore()会让人误以为这个函数只是给对象里的score属性赋值,而如果是叫updateScore()或updateScoreAndRank(),则可以直观的知道,这个函数要更新积分,并且可能会更新排行榜。

代码分段

代码一定要分段写。印象中有一些书籍或者公司里的开发规范中约定,每个函数不要超过多少多少行。我想也许是担心大家把函数写得太长,可读性变差而设的规则。但是,我认为真正的问题在于可读性。在某些特定情况下,一些函数可能来不及优化,不得不写得很长,但我们至少可以通过分段和注释,对代码进行梳理。

一个好的代码分段结构,大概如下:

//单行注释A:介绍接下来一小段要执行什么内容
aaaaa;
bbbbb;
if (c) {
    ddd;
    .....
}

//单行注释B:介绍接下来一小段要执行什么内容
aaaaa;
bbbbb;
if (c) {
    ddd;
    .....
}

//...

注意,在两段代码之间,有一个空行。这个空行很重要。他代表着上面这段代码的含义是最上面单行注释A,而下一段代码的含义是单行注释B。如此,阅读者可以很轻松地理解这两段代码到底分别执行了什么逻辑。

分段的技巧

在理清楚需求的业务逻辑后,你可以先写一个空函数,然后再函数内只写注释,不写逻辑。

public void reward() {
  //判断功能开启
  //判断活动开启
  //判断玩家传参
  //判断玩家领取状态
  //扣除玩家消耗
  //修改状态
  //发放奖励
  //推送奖励弹窗
  //返回成功给客户端
}

当你把这个函数需要做的事情梳理清除,简单写出注释,分段也是水到渠成的事情了。

代码注释

代码注释不是越多越好,也不是越长越好,而是越契合团队的理解越好。每个组都有自己的术语表(他们可能没有明确列出,但是他们一定有一套约定俗成的用词),一定要遵守他们的术语,用团队中常用术语进行注释的书写。

写注释不是必须的,写注释是帮助他人和未来的自己理解代码的含义,加快理解代码的速度。因此,如果团队里的其他成员有足够的能力,能直接阅读代码来理解逻辑,那么也可以不用注释。

注释的形式

在需要注释的情况下,可以在以下位置进行注释:

不用注释的情况

需要注释的情况

注释不是越多越好

注释写得多也会带来其他的问题,比如后续需求变更时,我们会因为工作排期紧张,优先修改代码,而忽略了注释。在之后的维护中,可能会使后来的维护者感到疑惑。

注意点

改动已有的类时要当心

改动到底层功能的代码时,一定要争取时间自测

在面向对象的设计思想下,必然有一些相似的类,他们有着类似的功能业务逻辑。我们会将通用逻辑抽取到父类,然后子类只针对自身业务,新增一些自己的属性和函数,或是重写一些父类函数。如果修改到了父类的代码,则一定要在代码层面检查一遍,这次修改可能影响到的子类有哪些。如果有足够的条件,最好能自己将影响的子类相关的业务逻辑自测一遍,并且向管理者反馈本次修改的影响范围。

定义枚举或者常量,拒绝使用魔法数字

除了0以外,在代码里的业务逻辑,类型、状态一般是用数字区分,比如道具类型,领奖状态等。一定要定义常量或者枚举来进行业务逻辑的编写,并在常量或枚举定义时介绍其含义。

即使是定义了常量或者枚举,也不要用诸如”TYPE_1”,“TYPE_2”这样简单的区分,最好是用有含义并且相对简短的单词来进行编写枚举,比如”TYPE_PLAYER”,“TYPE_ROBOT”,这样阅读代码的人可以一眼知道1对应的是玩家,2对应的是机器人。

和功能相关的所有同事积极沟通,认真听取他们的需求

这里的需求并不是策划案文档里的需求。事实上,在我参与的几个项目组中,每一个功能的策划案都和最终的效果不完全一样。因为需求是不断变化的,而策划案是死的。尤其是游戏这样的复杂逻辑,在管理不规范的团队中,只看着策划案做,一定会丢失一些隐藏的需求,或者误解策划的真正目的。

所以,在研究策划案之后,我们最好能准备一些问题,反过来向文档编写者进行提问,最好能帮他们把逻辑理顺。这一切的目的都是为了我们后续的开发更顺畅,提前将关键的问题找到并且抛出来。

而除了策划,我们还需要和客户端同学进行沟通。设计协议的时候一定要对着UX交互图或者UI渲染图进行设计,定完协议后需要向客户端同学进行介绍,而不是让他们自己理解;有一些需求是客户端处理的需求,但是可能需要服务器在特定条件下发送一些数据,这种情况也需要提前沟通,我们就可以提前做一些准备。

不要认为需求简单就懒得沟通。新做的功能必然有新的要解决的问题。一定要思考这个需求是否解决了他们想解决的问题,而不是只做需求的实现者。

有空的话,看看同事提交的代码

不是只有负责人才需要Review代码。相反,我认为每个人都可以Review代码。不论你的同事能力如何,你总能从别人的代码中获得一些新的理解。如果同事写得好,我们可以学习;同事写得不够好,我们可以提意见。顺便,我们可以知道其他人在这个版本里做了什么功能。如果你是一个团队里的新成员,那么看看别人的提交,阅读他人的提交变更,可以很快学习到这个团队里开发功能的一些惯例。

有空的话,看看服务器日志

功能到线上更新前,一般会在内网先进行更新和测试。此时需要关注服务器的日志,观察最近新发生的报错,或者一些罕见的、异常的日志。测试人员可能因为测试不畅,报了太多错,导致他们无法从游戏客户端界面获取到正确的报错信息,或者因为一些保护逻辑,而修正了某个错误逻辑,而服务器日志可以告诉你具体哪里报错。不要只听测试人员的反馈进行修复Bug,而是结合日志进行分析,找到真正的问题所在。

当服务器更新之后,更要抽出一点时间看服务器的日志。在内网环境下,不一定能完整测试出外网玩家的行为和数据环境。但是外网如果报了内网没有发现的错,则需要仔细分析,提前发现,提前修正错误的情况。如果我们干等客服反馈,有可能会错失修复Bug的最佳时机。因为玩家发现问题,反馈问题,客服记录,客服反馈,到项目组这边,再到项目组评估问题是否存在,这在工作繁忙的时候会浪费大量时间。而如果能提前从日志里发现端倪,提前通过日志排查,则会让影响面缩小很多。

抄写别人的功能时,多长一些心眼

有时候我们会接到某个需求,“做得和已有的XXX差不多就行了”。这时,一定要担心自己在临摹抄写代码时的一些细节,如传参是否正确,变量含义是否和之前代码一致。

如果有条件,最好请“已有的XXX”功能的开发人员帮你Review过一遍。你对代码的理解,大概率和他略有不同。

心态

代码永远会写错,我们能做的就是尽量避免错误

写程序的人或多或少有一种“造物主”的心态,因为在软件的世界里,所有的逻辑都是我们一行一行编写开发,将逻辑实现的。但是我们无法真正做到完美无暇,一次编译就通过,且没有Bug。原因是底层框架可能有所不兼容、自己能力不足或需求理解不清、策划改动需求导致推翻重写、甚至是线上环境出问题导致不可知的Bug等。

保持一个良好的心态,明白我们不管做了多少努力,该犯错还是要犯错。我们要勇敢的面对错误的发生,从错误中吸取教训,不断成长。

另外,并不是所有的Bug都如洪水猛兽。大多数情况下,有些Bug玩家可能忽略、不在意甚至“自适应”了,有些Bug在领导眼里并不重要,所以优先级放得很低。这些情况我们也可以通过一些渠道了解,然后我们也可以动态地调整自己的期待。

出Bug不可怕,逃避Bug才可怕

所有的问题都会解决的。

在经验匮乏的前几年,我时常会因为自己的开发经验不足,而担心更新到线上出问题担惊受怕。这更多是因为读书时遗留的一部分学生思维,认为线上出问题,是我“作业”没做对。这个逻辑在读书时期或许是正确的,但是在实战开发中则是需要割舍的。

因为游戏服务器是一个不断迭代开发的巨大项目,并且工期比传统行业更紧更急(当然出问题的代价也更低),这常常导致在少部分情况下,我们无法对每一次修改进行完整的测试。(是的,即使是我们做到了上述所有的习惯和技巧,也无法每一次都完整测试),所以服务器出问题是一个必然的情况。

我们能做的就是不要逃避Bug。是的,Bug来的时候,我们会不知所措,会很疑惑,甚至会有点沮丧——自己辛苦写的代码还是报错了,并且如果是严重的涉及奖励发放的问题,则可能需要挤占我们的休息时间。

但是,线上出问题,正是一个积攒经验的机会!我们已经尽人事,养成良好习惯,熟悉各种技巧,尽力提前核对并且预防需求变更和未知情况,如果还是报错,那么正是我们还存在未知情况,正是我们提升自我的机会。保持积极,勇敢面对。

风萧古道

© 2025 Aria

Instagram 𝕏 GitHub