如果有这么一款游戏,你操作的角色平均每 20 秒就会死亡一次,正常通关一次,总共需要死掉超过 2000 次。你猜这是一款神作还是垃圾?

《Celeste》(译名:“蔚蓝山”)就是这么一款游戏。在游戏里,你扮演一个名为 Madeline 的女孩,通过跳跃、抓墙、冲刺等动作,去努力登顶一座名为 “Celeste” 的高山。

图:《蔚蓝山》游戏画面,它是一款点阵画风 2D 平台动作游戏

正如我在开头说的,这款游戏的难度高到令人发指,玩家平均得死上千次才能通关。但奇怪的是,这款游戏获得的成就似乎和它的难度一样高。在 2018 发售那年,它获得了 TGA “年度游戏”提名并成功拿下了“最佳独立游戏”奖项。截止到 2018 年底,它总共卖出了超过 50 万份。

极低的犯错成本

让《蔚蓝山》大获成功的原因有很多。精妙的关卡设计、出色的动作手感、令人惊艳的游戏配乐,以及剧情里流露出的真诚人文关怀,都是非常关键的因素。但除开这些,我在玩游戏时,还注意到了一个有意思的细节:在游戏里,玩家的犯错成本非常低。

假如你操作跳跃的时机不对,角色掉入坑里死掉了。然后,在 不到 3 秒钟 内, Madeline 就会在房间入口处复活。你可以对自己的打法稍作调整,马上进行下一次尝试。

并非所有游戏都给予了玩家这种快速试错能力。比如在 PS4 游戏《血源诅咒》里,一次死亡可能代表你过去一小时获得的资源全都化为乌有。

注解:
在《血源诅咒》中,玩家死亡后会丧失当前拥有的所有血之回响(一种游戏内资源)。如果要找回它们,你需要从一个又一个怪物堆里穿过,回到你的死亡地点。如果你在路上再次死掉,那么那些血之回响就会全部消失。

所以,在《蔚蓝山》里,游戏设计者给了玩家一种可以 “低成本犯错” 的能力。有了它,我们可以快速从错误中学习,更好的完成挑战。那么,如果用编程来类比,我们在写代码时的犯错成本又如何呢?

编程时的“犯错成本”

假设我在开发一个新闻稿管理系统,系统里目前只有一种用户:“管理员”。但因为需求变更,我现在得给系统加上两个新角色:“编辑”和“主编”。

每类角色能做的事是有区别的:

  • 编辑:可以提交稿件、修改自己的稿件
  • 主编:在编辑的权限上,增加刊登稿件的功能
  • 管理员:可以做任何事以及管理所有人的权限

为了支持不同的角色,我需要改进现有的用户权限体系。首先,我得把和权限控制相关的所有功能点整理出来,然后开始写权限控制相关的代码。

没人能一次写出不出错的代码,所以写代码,其实就是一个在不断重复 “开发” -> “试错” -> “修改” 的过程:

  • 修改后端代码,增加新角色:“主编”
  • 在“主编”相关的功能点,增加权限保护代码片段
  • 保存代码,等待本地服务器重启加载改动 (5-10 秒)
  • 打开浏览器,点击各个功能页面,确认我的改动是否生效 (10 秒以上)
  • 如果测出问题,回到步骤 2,重复整个过程

在很长一段时间里,我在工作时的开发流程就是上面这样。我总是在接到需求后就马上对代码修修改改,然后打开浏览器,点点这里、点点那里,用肉眼观察一切是否正常。

使用这种开发方式,假如我某次写的卖QQ靓号代码有问题,那么从我每次改完代码,到一直走完步骤 3、4、5,整个过程至少得花费超过 30 秒。

如果你不觉得 30 秒很多,请你想想《蔚蓝山》吧。在《蔚蓝山》里,角色每次死亡到下次重试的时间间隔是不到 3 秒钟,二者相差 10 倍。所以,上面这种开发模式的“犯错成本”太高了。

如何降低“犯错成本”

其实,在开发这类 web API 时,我们完全没有必要傻乎乎的手工用浏览器点来点去。作为功能的开发者,我们可以(而且有义务)利用自动化测试来加速整个试错过程。

很多 web 框架都为这类测试提供了帮助。拿 Django 为例,你可以使用 django.test.Client 来轻松编写这类测试:

  1. # 以下代码片段来自 Django 官方文档
  2. import unittest
  3. from django.test import Client
  4. class SimpleTest(unittest.TestCase):
  5. def test_details(self):
  6. client = Client()
  7. response = client.get('/customer/details/')
  8. # 测试某次请求是否返回了 200 状态码
  9. self.assertEqual(response.status_code, 200)

复制代码

对于前面的需求,我们可以直接编写下面这样的单元测试代码。

  1. # 针对不同的角色定义不同的单元测试类
  2. class RoleEditorTestCases(TestCase):
  3. """编辑角色的测试类
  4. """
  5. def test_create_post(self):
  6. # 编辑角色可以正常调用创建帖子接口
  7. response = self.request_post('/posts/', {'title': 'foo'}, current_user=self.user)
  8. assert response.status_code == 201
  9. assert isinstance(response.data, dict)
  10. def test_create_admin(self):
  11. # 编辑应该无权调用创建管理员接口
  12. response = self.request_post('/admins/', {'user_id': 100}, current_user=self.user)
  13. assert response.status_code == 403
  14. class RoleAdminTestCases(TestCase):
  15. """管理员角色的测试类
  16. """
  17. def test_create_admin(self):
  18. # 管理员可以调用创建管理员接口
  19. response = self.request_post('/admins/', {'user_id': 100}, current_user=self.user)
  20. assert response.status_code == 201

复制代码

有了这些单元测试后,整个试错流程可以得到极大改进。每当我改完代码后,只要运行 pytest 命令跑一遍相关的单元测试,就能知道改动是否奏效了。

  1. ❯ pytest
  2. ======== test session starts ========
  3. platform darwin -- Python 3.8.1, pytest-5.3.5
  4. collected 5 items
  5. tests/api/test_permissions.py .....
  6. ======== 5 passed in 0.72s ========

复制代码

不需要等待开发服务器加载变更、不需要打开浏览器点这点那。一切试错任务都可以在几秒钟之内完成。

编写测试其实也是 DRY

我在前面说过,在游戏《蔚蓝山》里,如果角色死掉了,那么她马上会从当前这个 房间入口处 重生。让我们设想一下,假如游戏没有采用这种设计:在新机制下,角色每次死亡后,玩家都得回到本章开始的地方,重新挑战一遍好几十个已经通过的房间。那会怎么样?估计很多人会气的把手柄摔地上。

但是,依赖人工测试的开发流程,其实就非常接近于让人摔手柄的设计。

拿用户权限功能来说,因为这个功能非常关键,所以我每次做出大改动后,都需要重复验证一下每个功能点在各角色下的表现是否正常。假如系统里一共有 20 个功能点需要和权限挂钩,那么 20 * 3 个角色,就是 60 个需要测试的点。

即便我有三头六臂,每个功能点只花 20 秒测试,整套东西测下来也需要 20 分钟。

但是,如果你已经为这些场景写好了单元测试,那么事情就变得简单多了。每次做了改动之后,你只需要重新执行一遍单元测试,就能把所有场景都验证一次。

Django 框架有一条设计哲学叫 “Don't repeat yourself (DRY)” - “不要重复你自己”。多数情况下,我们说 DRY 是指不要写重复代码。但我认为“不要重复手工测试已经测过的东西”其实也可以算是 DRY 的一种。

所以,每当你手动测试一次功能时,其实就是在重复你自己。既然如此,何不将它写成一个单元测试呢?

“所以,就是在劝我写单元测试?”

是的,我就是在劝你写单元测试。作为对比,让我们看看利用单元测试的开发流程是什么样的:

  • 修改后端代码,增加新角色:“主编”
  • 在“主编”相关的功能点,增加权限保护代码片段
  • 编写与功能代码相关的单元测试代码,与 2 同步进行
  • 执行单元测试,如果失败,从 2 开始调整代码,重复整个过程 (几秒钟)

通过把测试行为自动化,我们可以大大减少整个开发过程的试错成本。事实上,自从若干年前养成了写单元测试的习惯,我就一直坚持至今。那么,我到底是因为什么在写单元测试呢?

  • 单元测试让我的代码 Bug 更少?
  • 单元测试帮助我写出扩展性更强的代码?
  • 单元测试让我在重构时更不容易出错?

以上可能都是。但现在,我可以往上面的列表里再加上一点:使用单元测试来开发的过程,有一种流畅感,失败后就马上重试,一切就犹如在操作 Madeline 登顶那座蔚蓝色的山。

游戏《蔚蓝山》教我的编程道理相关推荐

  1. 某程序员吐槽:免费教妹子Java编程,妹子却不让自己找她闲聊!

    许多程序员教妹子编程.带妹子打游戏,都是醉翁之意不在酒,名为教学,实为追求,但有一个程序员小哥哥却比较悲催,他答应一个妹子当她师傅,教她Java编程,结果妹子却说,学习是学习,平时是平时,让小哥哥平时 ...

  2. 《趣学JavaScript——教孩子学编程》——第1章 认识JavaScript1.1 认识JavaScript

    本节书摘来自异步社区<趣学JavaScript--教孩子学编程>一书中的第1章,第1.1节,作者: [美]Nick Morgan(摩根)译者: 李强,更多章节内容可以访问云栖社区" ...

  3. 《趣学JavaScript——教孩子学编程》——2.4 Boolean

    本节书摘来自异步社区<趣学JavaScript--教孩子学编程>一书中的第2章,第2.4节, 作者: [美]Nick Morgan(摩根)译者: 李强,更多章节内容可以访问云栖社区&quo ...

  4. 教孩子学编程 python语言版_教孩子学编程 PYTHON语言版 PDF_IT教程网

    资源名称:教孩子学编程 PYTHON语言版 PDF 资源目录: 第1章Python基础--认识环境1 1.1认识Python3 1.2用Python编写程序5 1.3运行Python程序5 1.4本章 ...

  5. 《趣学Python——教孩子学编程》——第1部分 学习编程 第1章 Python不是大蟒蛇 1.1 关于计算机语言...

    本节书摘来自异步社区<趣学Python--教孩子学编程>一书中的第1章,第1.1节,作者[美]Jason R. Briggs,尹哲 译,更多章节内容可以访问云栖社区"异步社区&q ...

  6. 小学生也能教你学编程了!8岁“程序猿”编程教学走红网络

    小学生也能教你学编程了! 可以前往百度查看 最近, 一位网名为Vita君的8岁上海小学生成了"网红",他在B站做"UP主",发布的"小学生教你学编程& ...

  7. 教孩子学编程 python 下载_趣学python pdf 中文下载

    趣学python教孩子学编程pdf是一本专为想要学习python不错的朋友准备的入门教程,可以轻松的帮之你由浅入深,由难到易的学习python编程,感兴趣欢迎下载学习! 趣学python教孩子学编程p ...

  8. java写3d单机游戏可以吗_3D单机游戏《天鹰教》源码发布(二)

    3D单机游戏<天鹰教>源码发布 作者 作者: 游蓝海 博客: http://blog.csdn.net/you_lan_hai mail:     you_lan_hai@foxmail. ...

  9. 程序员编程艺术第一 二十二章集锦与总结(教你如何编程)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 程序员编 ...

最新文章

  1. java方便适配器,JAXB简单自定义适配器以及简单使用
  2. JS 进制转换的理解
  3. java中删除sqlite数据库语句_sqlite数据库的介绍与java操作sqlite的实例讲解
  4. java如何将数据保存为xml6_用Java实现可保存状态的数据库生成XML树,源码来了(9)...
  5. C语言中输入输出格式控制
  6. arraylist remove() java_执行ArrayList的remove(object)方法抛异常?
  7. java 编码实现内存拷贝_java提高篇(六)-----使用序列化实现对象的拷贝
  8. ajax post 没有返回_Ajax异步技术之三:jQuery中的ajax学习
  9. 数据结构入门之链表(C语言实现)
  10. java理论之java基础
  11. vue实现多行数据提交_Vue+Mockjs,模拟接口数据,实现前后端独立开发
  12. maven生成jar包
  13. 怎么在php分页上结束分页,很经典的分页程序,该如何处理
  14. python基于高德地图坐标拾取系统获取地址坐标
  15. AST介绍:解析html生成语法树
  16. 安川机器人报错_安川机器人伺服驱动器常见的报警代码
  17. matlab单回路和串级控制回路,串级控制回路PID参数如何整定?
  18. 三菱PLC程序,汽车厂流水线输送控制系统
  19. 如何打印计算机文档目录,word怎么把目录显示出来
  20. Python实用案例,Python脚本,Python实现每日更换“必应图片”为“桌面壁纸”

热门文章

  1. else if mybatis 嵌套_新手如何书写C++代码,远离深度嵌套的if-else
  2. java ltpa_SystemOut频繁输出SECJ0369E: 使用 LTPA 时认证失败
  3. 深度学习-线性回归基础-02
  4. Java操作oracle数据库
  5. mysql数据库、表、索引、触发器
  6. python学习之路day1
  7. 20155330 实验四 Android程序设计
  8. 基本Socket通信流程
  9. 转 Fragment 和 FragmentActivity的使用
  10. 简单的C++程序题总结