
1. 这次重构我到底在改什么先说结论我这次说的“模板代码可读性提升”不是把变量名从a改成userName那种表面功夫也不是为了应付代码评审临时整理一下格式。它指的是当你接手一套别人写的、或者自己半年前写的模板代码时通过一系列有策略的重构手段让这套代码从“能跑但看不懂”变成“能跑而且愿意看”并且让后续维护成本明显降下来。我为什么突然要专门聊这个事因为最近我在帮某团队复盘一套跨平台消息推送的模板工程代码量不大大概两千多行但团队成员换了两轮已经没人能说清楚里面每个模板片段到底在渲染什么、为什么这么写。打开文件满屏都是div套div中间夹杂着一堆只在特定场景下才生效的条件判断。功能倒是没出过大错但每次需求变更都像拆炸弹。后来我们花了两个迭代的零碎时间做可读性提升效果立竿见影——新同学上手时间从两周缩到三天线上问题定位速度翻了一倍不止。这篇文章我就拿这套模板代码作为例子把我实际做过的拆解、改法、踩过的坑完整捋一遍。适合谁看适合那些手里捏着遗留模板代码、想重构又不敢大动的开发者也适合刚入行不久、想知道“好读的代码到底长什么样”的同学。我会讲清楚每一步的动机附带可以直接抄的实操方案。2. 可读性差的模板代码差在哪几个地方2.1 结构层面一眼望不到头的嵌套模板代码最典型的问题就是嵌套地狱。我接手的那套推送模板一个订单通知的场景HTML 结构大概长这样最外层是包裹容器里面套了 header、body、footerbody 又分左中右三栏其中中间栏再套一个列表列表项里又嵌了一段活动文案。视觉上确实没毛病但代码结构上就是一层套一层缩进稍微乱一点你根本分不清哪个闭合标签对应哪个开标签。这种结构的问题不只是“看着累”。一旦产品说要调整某个区块的顺序你就要开始数 div数错了就崩样式。我见过不止一次有人为了调整一个按钮的位置最后把整个页面的布局都带偏了。更麻烦的是这种深层嵌套结构里中间层的模板变量往往依赖上一层的数据解析结果你不动结构还好一动就牵出一串连锁反应。我当时的做法是先给整套模板画“结构树”不写代码就用缩进把每个区块的父子关系列出来。画完之后才发现有一层包裹容器是完全多余的纯属历史遗留——早期为了兼容某个老客户端的渲染引擎加的后来那个客户端早就下线了但包裹层一直没人敢删。像这样的冗余层就是可读性提升的第一批目标。2.2 命名层面看过即忘的变量与片段名模板里的变量命名大概率是历史包袱的重灾区。我见过data、info、list、tmp这种毫无信息量的名字也见过a111、b2这种纯编号的变量。最夸张的是一条推送模板里同一个用户昵称出现了三种写法name、user_name、nick而且它们在不同区块里各自生效想全局替换都无从下手。变量命名对模板代码的可读性影响比普通业务代码更严重。因为模板通常是数据源和展示层的交界阅读者需要同时理解“这个数据从哪来”和“这个数据渲染到哪”。如果变量名只写一半比如叫data你就得顺着数据源的赋值逻辑往回找找半天才发现它其实是用户最近一笔订单的金额。这种理解成本积累起来看十行代码就要花十分钟。片段partial命名也是重灾区。我那套模板里有个片段叫item.wxml你光看名字根本不知道它是商品卡片还是优惠券入口。后来我统一改成product-card、coupon-entry这种语义化命名并且把片段文件按业务模块分目录存放。改完之后团队成员光凭文件名就能判断该去哪里找代码这部分投入的回报率极高。2.3 逻辑层面模板里塞满了“聪明”写法模板引擎通常都支持一定的逻辑表达式于是很多人就忍不住在模板里写复杂判断。比如我见过这样的写法{{ item.status 1 || (item.status 2 item.level 3) ? A : B }}。单看这一行你勉强能读懂但它嵌在一个循环列表里外面还有一层if判断整体逻辑就完全不可读了。更要命的是有些状态流转逻辑直接写在模板里。比如“当订单处于待支付状态且超时时间小于30分钟时展示催付按钮超过30分钟则展示取消按钮”这种带时间条件的逻辑明明应该放在预处理阶段算好一个showPayRemind的布尔值传给模板结果却写在模板里导致每次要看懂这个页面的行为都得把整个渲染上下文翻一遍。模板里不是不能写逻辑而是要写“一眼能看懂”的逻辑。复杂的业务判断放到数据预处理层模板只负责根据已经算好的标记做简单分支。这样模板的阅读者不用关心背后的计算过程只需要关心“这个标记为真时显示什么”。这是我从这次重构里收获最大的一条原则。3. 提升可读性的整体思路与设计原则3.1 先定目标可读性提升不是代码重写动手之前必须想清楚一件事你做的是可读性提升不是重构业务逻辑更不是推翻重来。业务行为不能变——用户看到的推送内容、样式、交互全部保持一致变化的是代码的呈现方式让阅读它的人能更快理解它。我给自己定了三个目标第一任意一个模板片段打开文件的前30秒内能说出它的职责第二任何一次需求变更定位到对应代码的时间不超过5分钟第三新人阅读整套模板代码不需要前任开发者的口头讲解仅凭代码注释和命名就能理解设计意图。这三个目标听起来很朴素实际操作起来没那么简单。因为可读性是主观的你觉得好读别人不一定觉得。所以我在动手前先拉了一份团队的代码规范清单看看哪些规则已经被约定但没被执行。比如变量命名要用驼峰、片段文件要以业务模块命名这些都是团队早有规定的只是历史代码没遵守。那次可读性提升本质上就是一次迟到的规范落地。3.2 核心手段分级重构小步快跑我用的策略是“分级重构”第一级结构调整把每个模板文件的区块划分清楚删掉冗余包裹层保证每个文件的结构在缩进上就有清晰的视觉层次。第二级命名统一所有变量、片段、文件的命名过一遍按统一规则修改并且建立新旧命名对照表防止改漏。第三级逻辑上移把模板里的复杂表达式、多条件判断抽出去放到数据预处理层模板里只留简单的if/else和each。第四级注释补全文件头部写清楚“这个模板渲染什么场景”“数据从哪个接口来”“依赖哪些字段”关键分支处写一句注释说明为什么这么做。每一步都单独提交、单独验证确保不影响线上功能。千万不要想着一个晚上全改完那种改法出问题之后根本没法回滚。3.3 一个重要原则让模板“哑”一点我特别想强调一个观点模板引擎的职责是渲染不是计算。把逻辑放到模板里看似省事实际上是把计算职责和展示职责混在一起了。一个好的模板应该是“哑”的——它只根据传入的数据做最基本的展示决策不关心数据是怎么来的也不关心数据的深层含义。为了贯彻这个原则我甚至把一些“三元表达式”都从模板里清掉了。因为三目运算符虽然简洁但阅读时大脑需要做一次条件解析不如一个提前算好的布尔变量直观。比如原来模板里写{{ user.vipLevel 2 ? 尊享用户 : 普通用户 }}我改成在数据层算一个userLevelLabel字段模板里直接输出这个字段。从性能角度说几乎没差别但从阅读角度说前者要理解“vipLevel大于2意味着什么”后者直接告诉你“这里显示什么”。4. 实操过程把一套订单通知模板彻底捋顺4.1 准备阶段先把代码读透再动手动手改代码之前我先做了一件特别笨但特别有用的事通读。我把整套模板代码从头到尾读了一遍不是浏览是逐行读。读的过程中做了三份记录第一份记录是“结构笔记”画出每个文件的区块树标注每个区块的职责比如“这是头部宣传语、这是商品列表、这是底部引导关注”。第二份记录是“数据流笔记”记下每个模板变量从哪个数据字段来、经过什么加工、最终渲染到什么位置。第三份记录是“逻辑清单”把所有模板里的条件表达式、循环嵌套、过滤器单独列出来标注它们的触发场景。这份通读花了大概半天时间但省下了后面无数的返工。因为我发现有些代码片段看着很冗余实际上是为了兼容某个老版本的客户端有些变量名看着混乱其实是历史迭代中从另一个系统迁移过来时保留的原始命名。不了解这些背景就贸然重命名很可能把上下游的数据关系搞断。4.2 结构调整从“长蛇阵”变成“积木块”通读之后我做的第一件事是结构调整。拿一个商品推荐区块举例原始代码大概是这样的div classrecommend-wrapper div classrecommend-header span{{title}}/span /div div classrecommend-body div classproduct-list {{each products}} div classproduct-item img src{{$value.image}} / div classproduct-info p classproduct-name{{$value.name}}/p p classproduct-price{{$value.price}}/p /div /div {{/each}} /div /div /div这段代码的问题在于区块职责不够分明“recommend-header”和“recommend-body”只是样式类名没有在模板结构上体现“标题区”和“内容区”的区分商品条目本身应该是一个独立的片段而不是内嵌在这个区块里。我的改法是把商品条目抽成一个单独的片段文件product-card原先的区块只保留列表容器同时给整个区块加上清晰的注释分隔线!-- 商品推荐区块数据源recommendApi -- div classrecommend-wrapper !-- 区块标题 -- {{if showTitle}} div classrecommend-header span{{title}}/span /div {{/if}} !-- 商品列表 -- div classrecommend-body {{if products.length 0}} div classproduct-list {{each products}} {{ product-card product$value}} {{/each}} /div {{else}} p classempty-tip暂无推荐商品/p {{/if}} /div /div这样做的好处是阅读者一眼就能看出“这个区块分为标题和列表两部分列表为空时有兜底提示”商品卡片的内部细节被封装进独立片段想了解就点进去看不想了解就直接跳过。结构上层次分明像搭积木而不是像一根长绳从头串到尾。4.3 命名统一建立新旧对照表手动替换命名统一这一步我踩过一个坑一开始我直接用全局替换把name全部替换成userName。结果某个片段里的name其实是“商品名称”的字段被我误改成userName上线后商品名称全部显示空白还好灰度阶段就发现了。那次事故之后我学乖了。所有重命名操作必须先在通读笔记里建一张“新旧名称对照表”比如旧名称新名称使用位置语义说明nameuserName订单头部用户下单时填写的昵称nameproductName商品卡片商品标题infoorderSummary订单金额区块订单汇总文案listproductList商品推荐区块推荐商品数组对照表建好之后我按“一个名称、一个位置”的方式逐步替换每替换完一处就搜一遍有没有遗漏再跑一次本地渲染测试。整个过程像做外科手术慢但安全。新的命名规则我定得很简单变量名必须能回答“这个数据是什么”方法名必须能回答“这个操作做了什么”片段文件必须能回答“这个片段展示什么”。颜色、字体、间距这类样式相关的变量统一以color-、font-、spacing-开头不在这次讨论范围内但原则一致。4.4 逻辑上移把“聪明”留给后端前面提到的那个复杂状态判断我把它改造成这样。原来模板里是{{if item.status 1 || (item.status 2 item.level 3)}} button classbtn-primary立即购买/button {{else if item.status 3 item.expireTime - now 1800}} button classbtn-warning即将失效快去支付/button {{/if}}这段逻辑放在模板里代码阅读者必须知道“status等于1代表什么”“level大于3代表什么”“expireTime和now怎么比较”才能看懂。我改成在数据预处理阶段算好两个字段canBuy和showExpireWarn模板变成{{if canBuy}} button classbtn-primary立即购买/button {{else if showExpireWarn}} button classbtn-warning即将失效快去支付/button {{/if}}模板一下就清爽了。阅读者不需要关心业务规则本身只需要关注“这两个标记控制两个按钮的显隐”。而且业务规则变化时改数据层即可模板完全不用动后续维护成本大幅下降。这里有一个度的问题不是说模板里完全不能有逻辑而是说逻辑的复杂度要控制在“一行表达式能说清”的范围内。比如{{if user.isVip}}这种就完全没问题它不需要额外解释。真正的复杂逻辑交给数据层去算。4.5 注释的艺术注释解释“为什么”不解释“是什么”很多开发者写注释的习惯是描述代码行为——“这里判断订单状态是否为1如果为1则显示按钮”。这种注释毫无价值因为代码本身已经告诉你它做了什么注释应该告诉你它为什么这么做。我在那次重构里给关键注释定了三个类别。第一类是“背景类注释”写在文件头部说明这套模板的服务场景、数据来源、历史兼容性约束。第二类是“决策类注释”写在有业务判断的地方解释为什么做这个判断——比如“老版本客户端不支持空数组渲染所以需要兜底提示”。第三类是“警示类注释”写在容易踩坑的位置比如“此字段已废弃但删除会导致低版本客户端样式错乱请勿移除”。注释不是写给自己看的是写给三个月后的自己和其他协作者看的。所以我在重构里把那些“描述代码行为”的注释全部删掉只保留了“说明决策原因”的注释。效果很直接代码的视觉噪音少了重点反而更突出了。5. 常见问题与排查技巧实录5.1 改了命名之后数据渲染不出来了这个是我自己踩过的最典型的坑前面也提了一句。原因就是对模板引擎的变量解析机制不够敬畏。有些模板引擎是直接在字符串里替换变量名如果你把两处不同含义的name一起改了就会导致数据源里根本没有userName这个字段渲染自然失败。排查方法也简单。第一步看控制台报错一般会提示“undefined variable”。第二步检查数据源确认接口返回的字段名是name而不是userName。第三步检查片段之间的参数传递有些模板引擎的片段作用域是隔离的父级传参名和子级接收名不一致也会导致渲染为空。我那次的教训是改命名之前先搜索这个变量在数据源、预处理逻辑、模板三处的所有出现位置画一条“变量生命周期线”确认改名不会破坏任何一个环节再动手。宁可慢一点也不要靠“替换完试一下”的侥幸心理。5.2 抽片段之后样式突然变乱了把内嵌结构抽成独立片段最常见的问题就是样式丢失。因为很多模板的样式选择器依赖特定的 DOM 层级比如.recommend-wrapper .product-list .product-item你把product-item抽到独立片段后DOM 层级理论上没变但某些模板引擎在渲染片段时会插入额外的包裹节点导致选择器失效。我遇到的实际情况是片段渲染默认多包了一层template标签所有依赖直接子元素的选择器全部命中失败。排查时我先是怀疑样式文件没引入后来用浏览器开发者工具查看渲染后的 DOM 结构才发现多了一层节点。解决方案有两种。一种是在模板引擎配置里关闭片段外层包裹另一种是改样式选择器让它们只依赖 class 本身不依赖层级。我推荐后者因为层级选择器本身就是可读性的敌人——你看到.wrapper .body .list .item时根本不知道这个 class 到底长在哪个节点上远不如.product-item来得直接。5.3 改完模板之后线上数据和本地渲染对不上这是模板开发里最让人头疼的问题。本地渲染测试一切正常上线后就发现某些用户看到的推送内容缺字段。后来排查发现问题不在模板改动而在数据源——线上某些订单的状态字段与本地测试数据不一致比如本地测试用的订单都是status1线上却有大量status4的历史订单而模板里对status4的情况完全没有兜底。这个问题的本质是测试数据覆盖不全。我在那次重构的后期建立了一份“模板数据测试清单”把线上可能出现的数据形态都列出来正常态、空数据、残缺字段、异常状态值、超长字符串等等。每次模板改动跑一遍这份清单能避免绝大多数线上渲染事故。5.4 团队协作时有人不按新规范来可读性提升最大的难点往往不是技术而是习惯。我重构完一个模块之后过了两周团队里新来的同学在新增需求时又在模板里写了复杂的三元表达式还用了旧的变量名。这很让人崩溃但也是常态。我的应对方式是把规范“工具化”而不是靠口头强调。比如配置模板引擎的代码检查器发现模板里有超过一定复杂度的表达式就报错配置命名后缀规则不符合规则的片段文件根本没法提交。技术手段的约束力远大于代码评审时的一两句提醒。6. 从一次重构里沉淀下来的几条实操心得这次模板代码可读性提升做完之后我复盘了很久。技术层面的事情前面已经聊了不少这里分享几条更偏“方法论”的心得。第一条心得是可读性提升要“以终为始”。动手之前先想清楚“这套代码接下来会怎么被使用、被谁使用”。如果这套模板接下来半年不会再动那优化的优先级可以往后退如果它正在高频迭代期那么每省下的一分钟阅读时间都会在后续的每一次变更里放大收益。第二条心得是模板代码的可读性提升本质上是在“降低团队的理解成本”。我见过很多团队宁可忍受烂代码也不愿意花一个迭代的时间去整理理由永远是“业务太忙没时间”。但实际上烂代码带来的隐性成本——理解偏差、低级故障、新人上手慢——往往比一个迭代的整理时间要高得多。这不只是代码问题这是效率问题。第三条心得是改模板和改函数不同模板的可读性对视觉层级的要求更高。函数代码的可读性主要靠抽象和命名模板代码的可读性还要靠物理结构——缩进、注释分隔线、片段封装。你要让阅读者像看一张海报一样一眼扫过去就能分清主次区域而不是像读一篇没有段落的文章那样用眼睛一行一行去扒。最后再分享一个小技巧。每完成一个模板文件的可读性提升我都会做一次“盲读测试”把文件打开遮住业务背景说明只靠代码本身和注释尝试说出这个文件的职责和关键流程。如果说不出来就说明还有没读到位的部分继续改如果三分钟内能说清楚就算通过。这套方法我后来也用在团队新人培训上效果出奇地好。如果你手里也有一套怎么看都不顺眼的模板代码不用急着推倒重来先按我说的思路——通读一遍建好结构笔记和命名对照表从最不影响业务的区块开始一层一层把结构理顺、命名统一、逻辑上提、注释补全。改完一个文件就验证一个文件不用追求一步到位但求每一步都往前推进。等整套模板都捋完你再看那些代码大概率会有点成就感——那种“打开文件就心里有数”的踏实感是写新代码给不了的。