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/wekanWekan 团队在 docs/Design/LessCode.md 中记录了一套完整的“减码”工程实践在不删除受支持行为、翻译、无障碍特性与测试的前提下通过六个阶段将维护源码从 192,952 行削减到 191,220 行并额外清除了 148 行已废弃的跟踪代码。本文将完整继承该文档的基线数据、工作规则与各阶段验收标准并结合仓库中的实际源码语言注册表、分页原语、板级读取策略、导入管线讲解每一阶段“减少独立实现”而非“压缩代码”的落地方式。基线先量清楚再谈减少Less Code 计划的第一步是建立可重复的度量基线。文档于 2026-09-03 对client/、server/、imports/、models/下的 JavaScript、Jade、CSS 与 MJS 文件进行了统计来源文件数行数JavaScript 与 MJS798150,724CSS7328,941Jade11113,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 等异常设计的显式覆盖。执行步骤清点普通颜色主题用到的属性角色并归类异常主题为普通主题添加一套共享规则与一小段变量声明改造现有主题测试让它验证计算结果而不是旧的重复选择器布局分批迁移普通主题每批之后运行主题、页头、公共板、All Boards 与复选框测试对比 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 成功解析结果样式表themeAccents、boardTileTheme、allBoardsPage、publicBoardsPage、headerBars、checkboxesAreSquare与appleGlassPastelTheme全部通过共 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、急切导入以及未被注册表认领的文件i18nLazyLoading、i18nLazyLoaded、newLanguageWiring、rtl与changeLanguageColumns测试通过共 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 closedexport 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.disabled与models/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,7321. 看板主题6,339 行 / 196,012 字节5,895 行 / 178,134 字节110 断言通过浏览器不可用完成cd10392302. 语言元数据1,724 行 / 34,873 字节510 行 / 24,362 字节注册表对等与 31 断言通过完成a3bc8155d3. UI 机制191,284 维护行191,279 维护行原语与 96 个使用方断言通过完成eb0e347864. 授权191,279 维护行191,248 维护行6 个策略测试与传输回归通过完成e8c867b335. 导入器191,248 维护行191,220 维护行6 个管线与 95 个导入断言通过完成8b71153356. 删除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),仅供参考