大概两年前,我第一次在朋友群里看到91行代码创意赛的征集帖,第一反应是:这不就是变相的代码减肥大赛吗?后来真正动手投稿才发现,限制行数这件事和“能不能写出来”完全是两码事。你哪怕不加空行、硬凑91行也不难,真正难的是让这91行里同时有创意、有完成度、有技术含量,还经得起评委一行一行去读。这篇文章我打算把这类比赛从构思、编码到写技术文章的整套思路拆开来讲,既是给自己留一份复盘,也是给后来参赛的人一份可以直接照着走的借鉴。
1. 为什么偏偏是91行?先读懂规则背后的意图再动手
1.1 行数限制不是门槛,是方向
91行这个数字乍看有点奇怪。市面上大多数极客挑战要么是“100行写一个编译器”、要么是“50行实现一个3D引擎”,91行不在这些主流数字里。如果你去看比赛公告的措辞,基本都会提到“创意优先、以小见大”这类说法,有些还会把“91”解读成谐音“就要”,意思是“你离一个完整作品只差就要动手”。
理解了这层意图,再看规则就完全不一样了。它不是在要求你写一个极其精密的“微型系统”,而是在引导你做一个恰好能讲完一个故事的作品。91行既不像10行那样只够写玩具,也不像300行那样有足够空间铺陈完整业务逻辑。它卡在一个很有意思的区间:刚好能塞下一个核心玩法和一层简单交互,但塞不下多余的系统设计。这种“带一点点挤”的体感,才是这个比赛真正的趣味所在。
1.2 评委端到端看作品的三层视角
我参加过几次类似的评审交流,也和不少评委聊过他们真实打分时的心理过程。一个91行的作品摆在那里,评委通常不会先看代码,而是先跑一遍效果,然后才会回头看代码。他们的关注点基本落在三个层面:
- 创意层:这个东西有没有让人眼前一亮?是不是以前没见过?
- 技术层:代码结构是否合理?有没有人在刻意压缩后还能保持条理?
- 叙事层:技术文章是否讲清楚了“为什么这么做”和“卡在哪了”?
如果三个层面都不出错,作品分数就不会低。但很多参赛者最容易犯的错,是把90%的精力都花在技术压缩上,创意和文章草草带过。评委看完效果觉得普通,看完代码觉得厉害,看完文章觉得什么都没说,最后总分就被拉下来了。
1.3 行数松紧的潜规则
这里要说一个规则外的经验:91行的统计口径,不同比赛差异很大。有的比赛只算代码行,不含空行和注释;有的比赛会把注释也算进去;有的甚至要求提交的文件直接以执行入口为起点,连package声明都算。这直接决定了你后续的编码策略。
我的建议是,动手之前先把“一行”的定义看清楚,尤其是换行符算不算一行这类细节。遇到最严格的情况,一个很长的条件表达式如果被自动格式化工具折成了三行,那三行就是三行。这会极大影响你写代码时的换行习惯。
2. 从创意倒推91行:真正该先做的事情是画思维导图
2.1 一句话说清楚你的内核
不管你脑子里冒出来的是炫酷的粒子动画、复古小游戏,还是一个藏在浏览器地址栏里的彩蛋,先别急着打开编辑器。我习惯的流程是拿一张便利贴,把作品的内核用一句话写在上面,比如:
- “让文字变成不断分裂又重组的气泡”
- “一个人在沙漠里走,每走一步身后就长出一棵树”
- “输入公式,自动生成一幅函数画”
这句话必须足够具体,具体到别人听完就能脑补出画面。如果这个阶段你要想很久,说明创意还没成型。91行代码的承载能力有限,凡是连你自己都无法一句话讲清的东西,代码里多半也讲不清。
2.2 拆出一个“必须功能”清单
一句话定稿之后,把它拆成三个清单:必须有、可以有、纯属加戏。以“输入公式生成函数画”为例:
- 必须有:公式解析、坐标映射、画布渲染。
- 可以有:预设几个常用公式、颜色渐变、渐变背景。
- 纯属加戏:多语言切换、导出图片、公式历史记录。
拆完之后你会发现,真正的主角只有三个功能模块。91行的空间足够把这三个模块写得明明白白,前提是你愿意砍掉“可以有”里那些凑热闹的项。很多参赛作品死掉,不是因为想法不够好,而是想塞的东西太多,最后每个模块都在凑合,整体就像一盘糊掉的菜。
2.3 从技术栈角度反推复杂度
同一个创意,用不同语言和运行时实现,行数可以是天壤之别。比如你要做粒子动画,用CSS配合少量JavaScript,和用全量实现一个Canvas粒子引擎,体感完全不同。我个人的倾向是:能靠解释器或浏览器内置能力解决的事,就不要自己重复造轮子。
参与这类创意赛,其实也是一次技术选型博弈。选了处理图片方便的库,你的代码重点可以放在创意逻辑上;选了必须手动管理内存的语言,你的精力就会被底层细节吃掉一大半。在91行这个体量里,没有什么比“把生态用起来”更划算的事了。这个原则适用于绝大多数参赛者,除非你参加的就是“不允许依赖任何库”的硬核赛道。
2.4 先画代码骨架,再填血肉
拆完功能后,我建议你用伪代码或者简单的括号图,把代码骨架画出来。通常9行以内的代码就能勾勒出一个程序的骨架:
- 数据初始化。
- 主循环/主流程。
- 渲染/输出。
- 事件响应(如果有交互)。
骨架一旦清晰,后面填代码就是一个“对号入座”的过程。很多人在写91行代码时感到混乱,就是跳过了这个骨架步骤,想到什么写什么,最后代码顺序跟大脑一样乱。
3. 把创意压进91行的工程化技巧:先写正常代码,再做减法
3.1 先定行数统计口径,再谈压缩
我在前面提过,不同比赛对“行”的定义不一样。在你动手写第一行代码之前,先把口径固定下来:
- 空行算吗?
- 注释算吗?
- 库导入算吗?
- 打包配置算吗?
口径直接决定你后续的压缩空间。比如注释也算行的话,你就要考虑用更简短的命名来代替注释,或者在文末统一放注释说明。如果只有纯代码算行的话,偶尔用一两个注释反而能提高评审阅读效率,是性价比很高的“隐藏加分项”。
3.2 先写一个“未压缩版”,别跟自己较劲
这是我最想强调的一点:不要一开始就写“91行版本”。先无视行数,把一个能跑、功能完整、逻辑清楚、稍微臃肿的版本写出来。这个版本可能是150行,也可能是190行,都不重要。
因为压缩这件事,本质上是在删“冗余”,而不是在一张写满字的纸上抠缝。先写完整版,你才能看到哪些逻辑其实是重复的、哪些分支其实可以合并、哪些变量其实可以抽象成数组索引。你在原始版本里做重构,比在纠结的91行里边写边改要容易十倍。
而且这个“完整版”还有一个隐藏用途:它是你调试时候的救生圈。等压缩版出了问题,翻回去看一眼完整版,问题往往一眼就能看出来。
3.3 压缩三板斧:函数折叠、数据驱动、状态联动
做完未压缩版,就可以开始真正的手艺活了。我总结出三个最常用、也最不容易破坏代码可读性的压缩手段。
第一板斧:函数折叠
把只在某个流程里出现一次的逻辑,拆成一个独立函数。听起来像在反压缩,但实际操作里,把一段10行的循环体挪进函数,再用一行调用它,只要函数在代码里出现了两次以上,总行数反而会下降。这个操作尤其适用于处理重复的绘制、重复的数据转换。函数折叠的另一大好处是:评审能在脑子里建立“入口函数+若干功能性函数”的模块感,代码不会看起来像一坨意义不明的线性流水账。
第二板斧:数据驱动
用数据表替代条件分支。比如你要根据不同的状态码生成不同颜色的点,不需要写五个if,只需要准备一个数组:
const colors = ['#ff004c', '#00a1ff', '#8d00ff', '#ffb300', '#00c853']; // 后续直接用 colors[state] 取色类似地,如果不同按键要触发不同动作,也可以用一个对象把按键名映射到方法名。把逻辑分支变成数据索引之后,代码行数会肉眼可见地下降,而且逻辑反而更清晰。唯一的代价是你需要一点抽象思维,把“一堆if”翻译成“一张表”。
第三板斧:状态联动
很多程序里存在“一个变量变,其他好几个变量跟着变”的连锁反应。如果你不留神,就会为每个连锁反应写一行更新逻辑,白白浪费好几行代码。更好的做法是把这些状态收敛到一个对象里,通过一个函数统一重新计算。比如一个粒子系统里的速度、位置、透明度,其实可以一次性从更新函数里推出,不必各自单独维护。
3.4 压缩的边界:不要为了行数牺牲可读性
这里必须泼一盆冷水。压缩有一个很明确的边界,越过去就是灾难。比如把变量名全部改成a、b、c,把逻辑全部写成一行嵌套三元表达式,确实能压掉不少行数,但你自己过一个星期都未必看得懂。而评审是要一行一行读你代码的,他们看到的不是聪明,而是一堆自找麻烦。
我在实际操作中的习惯是:压缩幅度限制在略低于未压缩版60%左右。也就是190行的完整版,目标压到110到120行,再从里面优雅地扣掉20行。这个幅度既能体现出“我很懂行数控制”,又不至于让代码变成不可读的密码本。
4. 极限行数下的调试与自测:如何避免交出一个“看起来很美”的残次品
4.1 压缩代码后的第一件事:跑一遍完整流程回放
代码压缩到91行以后,第一件要做的事,不是再减几行,而是把核心流程从头到尾跑一遍。很多压缩手法会带来变量作用域和初始化的顺序问题。比如你为了省代码,把声明和初始化合并到表达式里了,结果变量在渲染时才被定义,执行时直接报错。
我见过太多参赛作品,演示动图拍得挺好看,但真正运行起来,只在某个特定窗口大小下不出错,换个环境就白屏。这种问题在普通项目里不算致命,但在比赛里是致命的印象分减分项。
4.2 几个最适合极限代码项目的“自检套路”
- 变量清零测试:刷新页面后不进行任何额外操作,程序能否自动进入正常状态?
- 数据边界测试:如果输入是空数组、0、负数、极大数,程序会不会崩溃?
- 窗口缩放测试:画布类作品请重点测试窗口尺寸变化,特别是从非常小拉到非常大。
- 连续交互测试:按钮连点十次以上、鼠标快速移动,会不会出现状态卡死?
这四个测试不需要写测试框架,肉眼手动跑几遍就够了。它们能拦下90%的“演示时没问题、交卷就翻车”的惨案。
4.3 给代码留“呼吸位”,顺便给自己留后路
再强调一遍:91行限制不代表你要写满91行。很多参赛者以为越接近91行越好,其实并不是。只要你的核心功能完整、代码干净,写87行、88行完全没问题。那些“留白”的行数,在你发现一个bug但想不出优雅修法时,就是你的补丁空间。
我自己甚至会刻意在压缩完之后,至少保留3到5行的余量,专门用来应急修复。这比费尽心机压到刚好91行要健康得多。
5. 代码之外的半壁江山:技术文章到底该怎么组织
5.1 把技术文章当成作品的一部分,而不是事后总结
比赛既然叫“创意赛”,那技术文章就不该只是“代码说明书”。你身后的文字,某种程度上是评委理解你创意意图的第二个窗口。代码再漂亮,文章写得乱七八糟,分数也很难高。
我建议把技术文章当作作品的活动说明书来写:读者没有打开代码之前,光看你的文章,就已经能在脑海里运行一遍你的作品。要达到这个效果,文章里至少要有三层内容:
- 灵感入口:为什么想到做这个?最初的触发点是什么?
- 结构拆解:91行代码是如何分布的?每段代码负责什么职责?
- 过程叙述:从想法到成品,你经历了哪些取舍和失败?
5.2 用“一个主流程+三个关键片段”的方式讲解代码
把全部91行代码贴出来,然后从头到尾逐行注释,这是效率最低的写法。评委并不需要你把每一行都讲一遍,他们需要的是你帮他们抓住重点。我推荐的结构是:
- 贴一个完整的程序运行效果图或动图。
- 用一个“入口到出口”的整体流程图,讲清楚数据从哪来、经过哪几步、最后输出到什么。
- 挑三个最“见功力”的代码片段,分别讲清楚它们为什么这么写、有没有更常规的做法、你为什么要选择这种更精简的写法。
这样讲,文章既不会太啰嗦,又能把技术深度展示到位。很多人担心自己写不出深度,其实深度不在于代码有多绕,而在于你能不能让读者感受到:你不是只会写代码,你在认真思考怎么写代码。
5.3 把失败尝试写进文章,反而更容易拿“技术分”
我参与过的评审里,对技术文章的评分往往有个神奇的口味:有失败尝试的文章,分数通常更高。因为失败尝试能让评委看到你的思考过程,知道你遇到过真实的问题,并且通过调整方案解决了它。这比一帆风顺的完美叙事可信得多。
比如你可以写:“一开始我试图用循环生成背景网格,但这样会让代码多出8行,后来我改用Canvas的填充样式配合透明度,直接省掉了这8行。”这种叙述非常短,却能立刻展现你的工程判断力。
5.4 文章标题、开头和结尾的小技巧
标题不要直接叫“基于××的××系统”,太像毕业论文了。创意赛的文章标题更适合走“悬念+功能”的路线,比如“我用91行代码让字母在屏幕上学跳舞”。开头直接用场景切入,别铺背景知识,你要让读者在30秒内知道:这个作品是什么、好玩在哪。结尾也别写“综上”,把你在调试中最意外的一次发现写出来,反而会留下更深刻的印象。
6. 我实际参赛和评审后的几个经验细节
6.1 展示效果图和动图,一定要用真实运行的截图
很多参赛者的文章里,效果图是从设计稿里截的,或者修过色。“实物与图片不符”在评委这里是硬伤。代码类比赛的评审不需要多专业,只需要把代码跑一下,立刻就知道你的图真不真。与其花时间美化截图,不如把运行效果录成一段几秒钟的GIF或短视频,这反而最能体现作品的真实程度。
6.2 注意“本地能跑”和“别人能跑”之间的差距
这可能是最容易被忽视的一点。你本地装的依赖、环境变量、浏览器版本,和评委的评审环境大概率不完全一致。能缩小这个差距的手段,就是保证你在提交说明里写清楚运行方式:是直接双击HTML,还是需要先执行哪条命令。写得越清楚,评委跑通你的作品的概率越大,第一印象自然越好。如果你还能顺手把依赖文件一起提交,那就更稳妥了。
6.3 文件名、入口文件和提交格式,别让细节拖后腿
我见过有作品因为入口文件叫main2.js、代码里却引用app.js而直接跑不起来的。也见过有些作品压缩包解压后有一堆临时文件,评委根本不知道该打开哪个。正确的做法是:压缩包里只保留必要文件,入口文件命名为index.html或main.py,再写一个几行的README,说明运行环境和操作步骤。这些细节不花多少时间,却能让评委对你的好感度直接拉满。
6.4 最后再分享一个小技巧
我个人在提交前一定会做一个“30秒陌生人测试”:找一位朋友,把压缩包发过去,只发一句话“你打开跑一下”,然后看他的操作。如果他看完这句话就能顺利跑起来,说明你的提交没问题;如果他卡住了,卡住的地方就是你需要改进的交接点。这个测试虽然土,但能测出你在自己脑子里想当然忽略的所有问题,比你自己反复自测十遍都有效。
如果你正准备参加类似的创意赛,我的建议是:别把91行当成一座必须爬过去的山,把它当成一张刚好够画完一张小画的纸。创意决定这张画值不值得看,代码决定它能不能真正呈现出来,技术文章决定观众离开后记不记得住它。三样东西都照顾到,成绩自然不差。