news 2026/9/14 5:53:24

WeKan 的 Less Code 计划:用可测量方式削减 1,880 行维护代码的六阶段实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WeKan 的 Less Code 计划:用可测量方式削减 1,880 行维护代码的六阶段实践

WeKan 的 Less Code 计划:用可测量方式削减 1,880 行维护代码的六阶段实践

【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan

Wekan 团队在 docs/Design/LessCode.md 中记录了一套完整的“减码”工程实践:在不删除受支持行为、翻译、无障碍特性与测试的前提下,通过六个阶段将维护源码从 192,952 行削减到 191,220 行,并额外清除了 148 行已废弃的跟踪代码。本文将完整继承该文档的基线数据、工作规则与各阶段验收标准,并结合仓库中的实际源码(语言注册表、分页原语、板级读取策略、导入管线),讲解每一阶段“减少独立实现”而非“压缩代码”的落地方式。

基线:先量清楚,再谈减少

Less Code 计划的第一步是建立可重复的度量基线。文档于 2026-09-03 对client/server/imports/models/下的 JavaScript、Jade、CSS 与 MJS 文件进行了统计:

来源文件数行数
JavaScript 与 MJS798150,724
CSS7328,941
Jade11113,287
合计982192,952

生成的 bundle、依赖、翻译 JSON 与测试均不计入。文档特别强调,基线是“导航工具”而非“刷分目标”:把代码挪进生成文件、压缩格式,都不算减少。这一约束贯穿全部六个阶段的验收标准。

初始调研找出的最大机会点如下:

区域现有证据机会
看板主题boardColors.css 达 6,339 行主题值只声明一次,结构规则共享
语言注册表languages.js 达 1,724 行用紧凑元数据生成重复的注册表条目
大型 UI 控制器多个文件 1,400–2,900 行抽取真正重复的分页、表单与动作逻辑
授权同一规则散布在 models、methods、publications 与 REST 路由每个操作用一个策略函数
导入器各 Creator 模块重复实体创建管线归一化输入后走同一条持久化管线
废弃路径动态 Blaze 引用掩盖部分无用代码仅在运行时与测试证据下删除

同时文档明确排除了几类“伪减码”:拆分大文件、把 Jade 换成 Svelte、压缩源码、把逻辑搬进依赖包,这些本身都不满足计划目标。

工作规则:小步迁移,证据先行

计划的工作规则是每阶段完成并验证后才开始下一阶段,并在文档中记录前后测量值:

  • 行为变化需要正向与负向测试;可见变化还需要相应的 UI 或截图测试;
  • 保留特例,而不是把它们硬塞进让代码更难理解的抽象;
  • 优先小迁移——当测量显示某个抽象带来的复杂度超过它消除的复杂度时,该阶段可以中止。

这套规则直接决定了后续每阶段的“验收标准”写法:不是“删了多少行”,而是“测试是否通过、行为是否保持、维护面是否变小”。

阶段一:看板主题声明——用 CSS 变量收敛重复规则

问题出在 client/components/boards/boardColors.css:该文件为每个命名主题重复写选择器与声明,膨胀到 6,339 行。计划的做法是引入由 CSS 自定义属性支撑的共享结构规则,同时保留渐变、图片主题、Apple Glass Pastel 等异常设计的显式覆盖。

执行步骤:

  1. 清点普通颜色主题用到的属性角色,并归类异常主题;
  2. 为普通主题添加一套共享规则与一小段变量声明;
  3. 改造现有主题测试,让它验证计算结果而不是旧的重复选择器布局;
  4. 分批迁移普通主题,每批之后运行主题、页头、公共板、All Boards 与复选框测试;
  5. 对比 CSS 体积与桌面/移动宽度下代表性渲染的看板;特例主题若显式 CSS 更清晰则保持显式。

验收标准:所有既有视觉行为与主题选择保持可用;主题测试与相关 Playwright 测试通过;boardColors.css的声明数与字节数实质性下降;新增一个普通纯色主题主要靠声明变量即可完成。

实际结果:

  • 十个普通纯色主题现在只声明十二个色板值,并共用一套结构规则;
  • 渐变、图片、Relax、Dark、Apple Glass Pastel、Modern 与 Clean 主题因结构或行为不同而保持显式;
  • boardColors.css从 6,339 行 / 196,012 字节 / 1,266 条规则 / 2,216 条声明,降到 5,895 行 / 178,134 字节 / 1,058 条规则 / 2,064 条声明;
  • PostCSS 成功解析结果样式表;
  • themeAccentsboardTileThemeallBoardsPagepublicBoardsPageheaderBarscheckboxesAreSquareappleGlassPastelTheme全部通过,共 110 个断言;
  • 文档还诚实记录了一个环境边界:在 1,280 px 与 390 px 下的 Chromium 计算样式检查未能完成(ARM64 沙箱只有 x86-64 的 Chromium 二进制,模拟启动未结束),因此运行时视觉验证被标注为环境限制而非已声称结果。

该阶段完成于提交cd1039230。值得注意的是,仓库中该文件当前约 5,885 行,与记录值同量级,可用作交叉印证。

阶段二:语言元数据——245 个语言记录压成一行一个

imports/i18n/languages.js 原本为每种语言手写一个重复结构的注册表对象,共 1,724 行。计划要求:用紧凑元数据与生成的懒加载导入替换手写对象,但公共注册表形状与语言顺序必须保持不变,新增语言仍须满足翻译策略规定的三个集成点。

验收标准:

  • 每个现有 locale 以相同的 code、tag、原生名称与 RTL 值注册;
  • 所有翻译保持懒加载;
  • 翻译、键顺序、占位符与语言选择器测试通过;
  • 权威元数据更短,且带重复 tag 检查。

当前仓库中的实现正是这一阶段的产物。imports/i18n/languages.js 现在是一组紧凑元数据行:

const languageMetadata = [ ["ace", "ace", "ace", "Bahsa Acèh", false], ["af", "af", "af", "Afrikaans", false], ["ar", "ar", "ar", "العربية", true], ["fa-IR", "fa", "fa-IR", "فارسی/پارسی (ایران‎)", true], // ... 每行: [语言键, 语言码, BCP-47 tag, 原生名称, RTL 标志] ];

文件头部注释点明了设计意图:“紧凑语言元数据与字面量动态导入分开存放。每个 import 都是一个静态分割点,浏览器只下载打开的那个语言”——这一点由 tests/i18nLazyLoading.test.cjs 钉住。

实际结果:

  • 245 条语言记录现在每条只占一行紧凑元数据;
  • 独立的 loader map 为每种语言保留一个字面量动态import(),维持 Meteor 按语言拆包的特性;
  • 运行时对等比较确认新旧模块的全部 245 个条目在键、code、tag、原生名称、RTL 标志与 loader 路径上完全一致;
  • 文件从 1,724 行 / 34,873 字节降到 510 行 / 24,362 字节(仓库当前 languages.js 正是 510 行);
  • 防护逻辑现在拒绝重复元数据键、重复语言 tag、重复 loader 键、缺失 loader、急切导入,以及未被注册表认领的文件;
  • i18nLazyLoadingi18nLazyLoadednewLanguageWiringrtlchangeLanguageColumns测试通过,共 31 个断言。

该阶段完成于提交a3bc8155d

阶段三:重复 UI 机制——一个 adjacentPage 原语收敛五处分页

这一阶段的铁律是“先测量重复,再抽取任何东西”,聚焦重复的分页、搜索、菜单数据、模态生命周期、表单取值与 Meteor method 结果处理,并且明确禁止为“看起来像但行为不同”的代码创建通用 helper。

验收标准:

  • 每个被抽取的抽象至少有两个真实使用方;
  • 用户可见的错误、键盘与无障碍行为保持;
  • 焦点单元测试覆盖共享原语,UI 测试覆盖使用方;
  • 计入新抽象后总维护代码仍然更低。

实际落地是一个adjacentPage原语,位于 models/lib/tablePage.js:

// One bounded step for pagers that do not use pageInfo directly. export function adjacentPage(total, page, direction, perPage = TABLE_PAGE_ROWS_PER_PAGE) { const info = pageInfo(total, page, perPage); const step = Math.sign(Number(direction)); if (!Number.isFinite(step) || step === 0) return info.page; return Math.min(info.totalPages, Math.max(1, info.page + step)); }

它复用了同文件内pageInfo的钳制逻辑(页码超出范围时解析到真实存在的页,而不是空视图),现在被 All Boards、Admin Panel 通用报表、事件流、office 报表,以及 People 面板中 organizations、teams、people 与登录地钻取共四个分页上下文共用;People 把活动面板一次性映射到页码/总数/页大小状态,替换了原来分散的上/下页分支。

测试覆盖普通翻页、两个边界、归一化方向与非法方向;现有使用方套件通过 96 个断言(tablePage55、allBoardsPage27、订阅生命周期 3、分组 offices 11)。全量维护源码从 191,284 行降到 191,279 行——净减 5 行已包含新共享原语本身,而测试(不计入该度量)则增长以钉住其行为。

该阶段完成于提交eb0e34786

阶段四:授权策略——canReadBoard 让 DDP 与 REST 同判

计划要求清点集合方法、Meteor method、publication 与 REST 端点之间的等价权限判断,把每个等价判断移入纯共享策略,传输层特有的错误格式化留在边缘。验收标准包括:同一 actor 与资源在 DDP 与 REST 下得到相同决定;策略有允许与拒绝(含缺失资源)测试;publication 不暴露被对应变更策略拒绝的记录;重复的权限条件是删除而不是包裹。

这一阶段的结论分两部分:

其一,审计确认现有的写能力策略已经同时服务集合权限、Meteor method、REST 变更与 publication 中的目标检查——于是保留它,没有再建竞争抽象。这是该计划“抽象只增不减就停手”原则的正面案例。

其二,新增了一个传输中立的canReadBoard策略,现在位于 models/lib/boardVisibility.js:

// Transport-neutral read policy for a board. Public boards accept anonymous // readers; every other board requires an active member. function canReadBoard(userId, board) { return !!(board && typeof board.isVisibleBy === 'function' && board.isVisibleBy(userId ? { _id: userId } : null)); }

它现在服务两个 DDP publication、两个 HTTP 附件路由与十四处 position-history 方法检查:公共板允许匿名与已认证读取,私有板要求活跃成员,缺失或畸形板 fail closed。HTTP 与 DDP 各自在边缘保留自己的响应/错误格式化;遗留附件 publication 现在与 HTTP 下载路由做出相同的公共板判断,而不是拒绝所有匿名订阅者。仓库中的 server/publications/legacyAttachments.js、server/publications/cardsWindow.js、server/routes/legacyAttachments.js、server/routes/universalFileServer.js 与 server/methods/positionHistory.js 都是这一策略的消费方,行为由 tests/boardVisibility.test.cjs 等六个焦点策略/接线测试覆盖。

维护源码从 191,279 行降到 191,248 行(含新策略模块)。该阶段完成于提交e8c867b33

阶段五:导入器管线——适配器归一化,写入器只有一份

计划为导入器定义了一个小型内部板表示:源特定模块负责解析与归一化数据,一个有测试的写入器统一创建用户、板、泳道、列表、卡片、评论、清单与附件。验收标准要求:现有受支持格式行为不变;源特定怪癖留在适配器中;畸形与部分导入有负向测试;实体创建与 ID 映射逻辑在可能处只有一份实现。

仓库中的落地是两个文件级别的共享实现。models/lib/importPipeline.js 中的writeImportedEntity独占“直接插入 + 可选时间戳刷新 + 旧 ID 到新 ID 记录”:

export async function writeImportedEntity(collection, document, options = {}) { const id = await collection.direct.insertAsync(document); if (options.touch) { await collection.direct.updateAsync(id, { $set: options.touch }); } if (options.ids && options.sourceId != null) { options.ids[options.sourceId] = id; } return id; }

runImportPipeline则按声明的顺序执行各阶段,把已创建的板 ID 带入后续写入器,并把缺失的可选集合归一化为空数组;畸形顶层输入直接抛TypeError,任何未创建板的管线以Import pipeline did not create a boardfail closed:

export async function runImportPipeline(creator, board, stages) { if (!board || typeof board !== 'object' || Array.isArray(board)) { throw new TypeError('Imported board must be an object'); } let boardId; for (const stage of stages) { const input = stage.source ? board[stage.source] || [] : board; const result = await creatorstage.method; if (stage.createsBoard) boardId = result; } if (!boardId) throw new Error('Import pipeline did not create a board'); return boardId; }

源特定归一化仍留在适配器里:models/wekanCreator.js 保留 WeKan 真实泳道与额外的依赖/规则阶段,models/trelloCreator.js 保留 Trello 的合成默认泳道与内联清单项。六个管线测试与 95 个既有导入器断言全部通过(tests/importPipeline.test.cjs),三个被改模块还通过了 Node 24 语法检查。维护源码从 191,248 行降到 191,220 行;同批清点中还删除了过时的注释掉的导入器实现与未使用的空 WeKan checker。

该阶段完成于提交8b7115335

阶段六:基于证据的删除——先静态分析,后运行时验证

计划把静态分析当“候选生成器”:候选必须对照 Blaze 模板名、动态 import、全局注册、服务器启动副作用与打包入口点逐一核实后才允许删除。验收标准:每次删除都有搜索证据与相关回归覆盖;应用启动、生产构建与已注册测试套件成功;任何受支持的部署模式或文档化功能不得被静默移除。

实际结果:

  • 静态搜索发现两个带.disabled后缀、因此不会被 Meteor 当作 JavaScript 处理的跟踪模型实现;没有任何 import、模板、启动注册、包入口或应用源码引用它们的文件名或AttachmentsOld/AvatarsOld符号;
  • 删除了models/attachments_old.js.disabledmodels/avatars_old.js.disabled,共 148 行跟踪代码,可从 git 历史恢复;
  • 受支持的遗留 CollectionFS 读取仍保留在 models/attachmentBackwardCompatibility.js(对应仓库文件 models/attachments.js 体系内的回读模块)、遗留附件 publication 与其 HTTP 路由中——迁移抽取与文件安全套件通过;
  • 一个回归测试验证被退役的文件与符号保持缺席,同时活跃的回读模块保持存在;
  • 全部 709 个已注册 Node 测试套件零失败通过(48 秒);meteor build .build-lesscode --directory成功,仅有的诊断是既有的资源大小与可选 MongoDB 依赖警告;
  • 带临时WRITABLE_PATH的开发启动达到Started your app并在http://127.0.0.1:3999提供服务后干净停止;
  • 维护源度量保持在 191,220 不变——因为.disabled文件本就被排除在该基线之外。删除的价值在于它删掉了 148 行可审查的跟踪代码,而不是移动或压缩它们。

该阶段完成于提交e37717d07

汇总结果与最终结论

各阶段的测量汇总(继承自原文档):

阶段之前之后测试结果
基线192,952 行191,220 行不适用维护行少 1,732
1. 看板主题6,339 行 / 196,012 字节5,895 行 / 178,134 字节110 断言通过;浏览器不可用完成(cd1039230
2. 语言元数据1,724 行 / 34,873 字节510 行 / 24,362 字节注册表对等与 31 断言通过完成(a3bc8155d
3. UI 机制191,284 维护行191,279 维护行原语与 96 个使用方断言通过完成(eb0e34786
4. 授权191,279 维护行191,248 维护行6 个策略测试与传输回归通过完成(e8c867b33
5. 导入器191,248 维护行191,220 维护行6 个管线与 95 个导入断言通过完成(8b7115335
6. 删除148 跟踪禁用行0 跟踪禁用行709 个 Node 套件、构建与启动通过完成(e37717d07

最终结论:六个阶段全部完成,维护的 JS/MJS、Jade 与 CSS 度量从 192,952 行降到 191,220 行,即净减 1,732 行;加上阶段六删除的 148 行基线外跟踪禁用行,本次实测工作的总量为1,880 行删除或规避

计划同样给出了明确的边界判断:这些减少来自“更少的独立实现”,而不是压缩或更换模板技术。把全部 Jade/Blaze 视图换成 Svelte 反而会临时增加代码,因为两套 UI 系统、适配器与迁移测试会共存,它本身不构成减码步骤。后续的进一步工作应当延续这种测量式方法:一次只处理一个重复行为,只有当测试与维护面都改善时才保留该变更。

对阅读此文的工程师而言,这份文档的可复用价值在于其方法论:先度量、排除刷分手段;每个阶段用“行为保持 + 正负向测试 + 维护面变窄”做验收;保留已存在且正确的抽象而非重复造轮子;删除必须留下可检索的证据链。这些约束比具体的 1,732 行数字本身更值得迁移到其他大型代码库的重构工作中。

【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 5:52:45

ADMM图像去噪实战:Plug-and-Play框架与MATLAB实现解析

简介:这是一份面向图像处理学习者和科研人员的ADMM图像去噪MATLAB源码包,围绕交替方向乘子方法在图像去噪与去模糊中的应用展开,适合希望掌握优化算法落地实践的读者。资源共19个文件,以15个m脚本为主,涵盖总变分去噪、…

作者头像 李华
网站建设 2026/9/14 5:51:19

5款专业演示工具评测与AI PPT替代方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 5:51:16

LangChain实现情感聊天机器人记忆优化方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华