1. 从“t3code”这个标题说起:它到底是什么,能解决什么问题
第一次看到“t3code”这个词,很多人会一头雾水。它不像“Vue3”或“Python3”那样有明确的版本号指向,也不像某个知名框架那样自带光环。但如果你在开发者社区里泡过一段时间,或者参与过一些中小型项目的技术选型讨论,就会发现“t3code”其实是一个很有意思的代号——它通常指向一种轻量级、高内聚、面向特定业务场景的代码组织方案。具体来说,t3code 并不是某个官方标准库或框架的名字,而更像是一个项目内部约定俗成的代号,用来描述“第三层代码”或“Type-3 Code”的实践模式。这里的“第三层”指的是在典型的三层架构(表现层、业务逻辑层、数据访问层)之外,额外抽象出来的一层——可复用的领域逻辑与工具函数集合。它解决的核心问题是:当项目逐渐膨胀,业务逻辑开始散落在各个角落,重复代码越来越多,维护成本急剧上升时,如何用一种轻量、不引入重型框架的方式,把那些“既不属于纯UI,也不属于纯数据库操作”的中间逻辑收拢起来,形成一套可测试、可替换、可组合的代码单元。
你可能会问,这不就是“工具类”或者“service层”吗?不完全是。传统的service层往往和具体业务强绑定,一个service对应一个业务实体,方法粒度粗,依赖关系复杂。而t3code强调的是原子化、无状态、纯函数优先。它更像是一个“代码工具箱”,里面装的是经过业务验证的、可独立测试的小块逻辑。比如:金额的格式化与舍入规则、日期区间的交集判断、特定业务状态机的流转校验、权限位运算的封装、甚至是一些领域特定的字符串解析。这些逻辑如果散落在各个组件或页面里,一旦规则变更,你就得满项目搜索替换,极易遗漏。t3code的思路就是:把它们抽出来,放在一个独立的目录(通常命名为t3code或core或shared/logic),每个文件只做一件事,导出纯函数或简单的类,不依赖任何外部状态,不直接操作DOM或数据库。
适合谁来参考这套东西?我认为有三类人最应该关注:第一类是中小型项目的前端或全栈开发者,项目规模在几十个页面到上百个组件之间,还没有到必须上微前端或复杂状态管理框架的程度,但已经感受到了“代码乱成一锅粥”的痛苦;第二类是技术负责人或架构师,需要为团队制定一套轻量级的代码规范,但又不想引入太重的约束,t3code可以作为一个很好的切入点;第三类是刚入行的开发者,想学习如何写出可维护、可测试的代码,t3code的实践能帮你建立“关注点分离”的思维习惯。我见过太多项目,一开始为了赶进度,把业务规则写在按钮的点击事件里,后来需求一变,改一处漏三处,测试也没法写。t3code就是针对这种场景的“解药”,而且它不挑语言,JavaScript、TypeScript、Python、Java都能用同样的思路落地。
2. 为什么是“t3code”而不是其他方案:设计思路与选型考量
2.1 三层架构的痛点与第四层的缺失
经典的三层架构(UI层、业务层、数据层)在教科书里很完美,但在实际编码中,业务层往往被“架空”了。为什么?因为业务层的方法通常需要依赖数据层的实体对象,而数据层的实体又和数据库表结构强耦合。结果就是,你想写一个简单的“判断用户是否有权限执行某操作”的逻辑,不得不先注入用户Repository,再查询角色,再比对权限表,最后返回布尔值。这一套流程下来,代码量不小,而且很难复用——换个场景,比如“判断订单是否可退款”,你又得重新写一遍类似的流程。t3code的切入点就在这里:把“判断逻辑”本身抽离出来,让它不依赖任何数据获取过程。也就是说,t3code里的函数只接收已经准备好的数据,然后返回计算结果。数据从哪来?那是调用方的事。这样一来,同一个权限判断函数,既可以用在HTTP接口里,也可以用在定时任务里,甚至可以用在单元测试里,完全不需要mock数据库。
这种设计思路借鉴了函数式编程里的“纯函数”概念,但又不要求你完全函数式。你可以写类,只要这个类不持有可变状态、不直接访问外部资源就行。我把它称为“第三层代码”,是因为它位于UI和业务服务之间,但又比传统的工具类更贴近业务。工具类通常是技术通用的,比如字符串截取、数组去重;而t3code里的函数是业务通用的,比如“根据用户等级计算折扣率”、“根据当前时间判断是否在活动期内”。这些逻辑如果放在UI层,会导致UI组件臃肿;放在业务服务层,又会被数据访问代码淹没。t3code给了它们一个独立的家。
2.2 轻量级与可测试性的平衡
选择t3code方案,最大的好处是测试成本极低。因为每个函数都是纯的,输入确定,输出就确定,不需要启动数据库,不需要模拟网络请求。你可以用任何测试框架,写几行断言就能覆盖一个业务规则。我试过在一个中等规模的项目里推行t3code,之前写一个业务规则的测试平均要花15分钟(因为要准备mock数据、配置测试环境),推行之后,同样的测试只需要2分钟。测试跑得飞快,开发者自然愿意写测试,代码质量就上去了。另一个好处是重构安全。当业务规则变化时,你只需要修改t3code里的一个函数,所有调用它的地方自动生效。而且因为函数是纯的,你可以放心地改,不用担心副作用影响其他模块。这种“一处修改,处处生效”的特性,在需求频繁变动的项目里简直是救命稻草。
当然,t3code也不是银弹。它不适合处理复杂的业务流程编排,也不适合管理状态。如果你需要的是工作流引擎或者状态机,那应该用专门的库。t3code的定位很明确:原子化的业务规则与计算逻辑。它不替代架构,而是补充架构。我见过一些团队把t3code用歪了,把所有代码都往里塞,结果变成了一个巨大的“杂物间”,这反而违背了初衷。所以,在决定采用t3code之前,一定要和团队对齐边界:什么该放进去,什么不该放。我的经验是,满足以下三个条件的逻辑,才适合放进t3code:第一,它不依赖任何外部状态(数据库、缓存、全局变量);第二,它会被两个以上的地方调用;第三,它的输入和输出可以用简单的数据结构表示(数字、字符串、布尔、普通对象)。不满足这三条的,请留在原来的层里。
2.3 命名与目录结构的约定
“t3code”这个名字本身就是一个约定。我建议在项目根目录下创建一个名为t3code的文件夹(或者src/t3code),里面再按业务领域分子目录。比如:
t3code/ pricing/ calculateDiscount.js formatCurrency.js permission/ canEditOrder.js canViewReport.js datetime/ isWithinRange.js getNextBusinessDay.js validation/ isValidPhone.js isValidIdCard.js每个文件只导出一个主要函数(或者一个包含多个相关函数的对象),文件名用动词或动词短语,清晰表达意图。这样做的好处是,任何人打开这个目录,都能快速知道项目里有哪些可复用的业务逻辑。而且,因为目录名是t3code,在代码里引用时也很显眼,比如import { calculateDiscount } from '@/t3code/pricing/calculateDiscount',一眼就能看出这是第三层代码,不是UI组件也不是API服务。这种命名约定比叫utils或helpers更有语义,因为utils太泛了,什么都可以往里扔,而t3code暗示了它的层级和职责。
3. 核心细节解析:t3code里的函数该怎么写
3.1 纯函数的硬性要求与例外处理
写t3code函数的第一条铁律:不修改传入的参数。JavaScript里对象是按引用传递的,如果你在函数内部修改了传入的对象,调用方会受到影响,这就破坏了纯函数的约定。正确的做法是,如果需要基于输入生成新数据,使用展开运算符或Object.assign创建新对象。比如:
// 错误示范:修改了传入的order对象 function applyDiscount(order, discountRate) { order.total = order.total * (1 - discountRate); return order; } // 正确示范:返回新对象 function applyDiscount(order, discountRate) { return { ...order, total: order.total * (1 - discountRate) }; }第二条铁律:不产生副作用。不要在t3code函数里发网络请求、写日志、操作DOM、读写localStorage。这些操作应该由调用方在调用t3code函数之前或之后完成。t3code函数只负责“计算”,不负责“执行”。比如,你需要根据用户输入计算出一个价格,然后展示在页面上。正确的流程是:UI组件获取用户输入 -> 调用t3code函数计算价格 -> UI组件把价格渲染到页面。而不是在t3code函数里直接去更新DOM。
第三条:输入输出尽量用原始类型或简单对象。避免传入复杂的类实例,因为那会增加耦合。如果确实需要传递多个参数,考虑用一个配置对象,并给每个字段起清晰的名字。比如:
function calculateShippingFee({ weight, distance, isRemoteArea }) { // ... }这样调用时一目了然,也方便后续扩展参数。如果某个参数是可选的,给它一个默认值,并在函数注释里说明默认行为的含义。
3.2 错误处理与边界条件
t3code函数虽然简单,但错误处理不能马虎。因为它是被多处调用的,一旦出错,影响面很广。我的经验是:对于可预期的错误,返回一个明确的结果对象,而不是抛出异常。比如,一个解析日期字符串的函数,如果传入的格式不对,可以返回{ success: false, error: 'INVALID_FORMAT' },而不是直接throw new Error。这样调用方可以根据success字段决定后续逻辑,而不必用try-catch包裹。当然,对于不可预期的错误(比如传入null导致无法读取属性),该抛异常还是要抛,但最好在函数入口做参数校验,提前拦截。
边界条件要特别小心。比如计算折扣时,折扣率可能是0到1之间的小数,但如果传入负数或大于1的数怎么办?我的做法是,在函数内部做一次“防御性归一化”:如果折扣率小于0,按0处理;大于1,按1处理。并在注释里写明这个行为。这样即使调用方传了不合理的值,也不会导致计算结果离谱。另一个常见的边界是空数组或空对象。比如一个函数接收一个订单列表,返回总金额。如果列表为空,应该返回0,而不是报错。这些细节看似微小,但能大大提升代码的健壮性。
3.3 命名规范与注释要求
t3code函数的命名要动词开头,表达意图。比如calculateTotalPrice比getTotal好,isEligibleForRefund比checkRefund好。避免使用缩写,除非是团队内公认的(比如id、url)。如果函数返回布尔值,用is、has、can开头;如果返回数量,用count结尾;如果返回新对象,用build或create开头。这样命名,调用方读代码时几乎不需要看实现就能猜出功能。
注释方面,我建议每个t3code函数都写一个简短的JSDoc块,说明:功能描述、参数类型与含义、返回值类型与含义、可能的边界行为。比如:
/** * 根据用户等级和订单金额计算折扣后的价格。 * @param {number} originalPrice - 原价,必须为非负数。 * @param {string} userLevel - 用户等级,可选值:'normal', 'vip', 'svip'。 * @returns {number} 折扣后的价格,保留两位小数。 * 注意:如果userLevel未知,按'normal'处理。 */ function calculateDiscountedPrice(originalPrice, userLevel) { // ... }这样的注释不仅方便他人,也方便未来的自己。我见过太多项目,半年后回来看自己的代码,完全想不起来当时为什么这么写。有了注释,至少能快速回忆起意图。
4. 实操过程:从零搭建一个t3code模块
4.1 初始化目录与工具链
假设你正在一个基于Vite的Vue3项目里工作,项目结构大概是src/下面有components/、views/、api/等。现在要引入t3code。第一步,在src/下创建t3code/目录。第二步,在t3code/下创建index.js作为统一出口,但不要把所有函数都塞进这个文件,而是用它来重新导出各个子模块。比如:
// src/t3code/index.js export * from './pricing/calculateDiscount'; export * from './pricing/formatCurrency'; export * from './permission/canEditOrder';这样做的好处是,调用方可以写import { calculateDiscount } from '@/t3code',路径更短。但要注意,如果子模块很多,index.js会变得很长,而且可能导致循环依赖。我的建议是,只导出最常用的函数,其他的让调用方直接引用具体文件路径。另外,如果你用TypeScript,记得在tsconfig.json里配置路径别名@/t3code/*,这样编辑器会有智能提示。
第三步,配置测试。如果你用Vitest,可以在t3code/旁边建一个__tests__/目录,或者把测试文件放在同目录下,命名为calculateDiscount.test.js。Vitest默认会扫描*.test.js文件,不需要额外配置。我习惯把测试文件和源文件放在一起,这样修改函数时能立刻看到对应的测试,不容易遗漏。
4.2 编写第一个t3code函数:金额格式化
我们从一个最简单的例子开始:金额格式化。需求是:传入一个数字(单位:分),返回一个字符串,格式为“¥1,234.56”。如果传入的不是数字,返回“¥0.00”。这个函数在电商项目里非常常见,几乎每个页面都会用到。
// src/t3code/pricing/formatCurrency.js /** * 将金额(单位:分)格式化为人民币字符串。 * @param {number} amountInCents - 金额,单位为分。 * @returns {string} 格式化后的字符串,如 '¥1,234.56'。 */ export function formatCurrency(amountInCents) { if (typeof amountInCents !== 'number' || isNaN(amountInCents)) { return '¥0.00'; } const yuan = amountInCents / 100; return '¥' + yuan.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }这个函数满足t3code的所有要求:纯函数、无副作用、输入输出简单。测试也很容易:
import { describe, it, expect } from 'vitest'; import { formatCurrency } from './formatCurrency'; describe('formatCurrency', () => { it('格式化正常金额', () => { expect(formatCurrency(123456)).toBe('¥1,234.56'); }); it('处理零', () => { expect(formatCurrency(0)).toBe('¥0.00'); }); it('处理非数字', () => { expect(formatCurrency('abc')).toBe('¥0.00'); }); });写完这个函数,你就可以在多个组件里复用它,而不需要每个组件都写一遍格式化逻辑。而且,如果将来要改成“¥1,234.56”不带小数点,或者改成其他货币符号,只需要改这一个地方。
4.3 编写第二个t3code函数:权限判断
再来看一个稍微复杂的例子:判断当前用户是否可以编辑某个订单。业务规则是:订单状态为“待处理”或“处理中”,且当前用户是订单的创建者,或者当前用户是管理员。这个逻辑在订单列表页、订单详情页、批量操作里都会用到,非常适合放进t3code。
// src/t3code/permission/canEditOrder.js /** * 判断用户是否有权限编辑订单。 * @param {Object} order - 订单对象,需包含 status 和 creatorId 字段。 * @param {Object} user - 用户对象,需包含 id 和 role 字段。 * @returns {boolean} 是否有权限。 */ export function canEditOrder(order, user) { if (!order || !user) return false; const editableStatuses = ['pending', 'processing']; if (!editableStatuses.includes(order.status)) return false; if (user.role === 'admin') return true; return order.creatorId === user.id; }这个函数同样不依赖任何外部状态,只根据传入的order和user对象做判断。测试时,你只需要构造不同的order和user组合,验证返回值即可。不需要mock任何API。在实际项目里,这个函数会被用在按钮的disabled属性计算、路由守卫、以及提交前的二次校验。一处修改,三处生效。
4.4 集成到现有项目:渐进式改造策略
如果你已经有一个运行中的项目,不要试图一次性把所有逻辑都搬到t3code里。那会是一场灾难。我的经验是渐进式改造:每次修改某个功能时,如果发现里面有可复用的业务逻辑,就顺手把它抽到t3code里,并替换原来的调用。比如,你正在改一个订单列表的排序功能,发现排序规则(先按状态,再按创建时间倒序)在另一个页面也用了,那就把这个排序逻辑抽成sortOrders函数放进t3code。这样每次改动只涉及一小块,风险可控,而且随着时间推移,t3code会自然生长,覆盖越来越多的业务规则。
另一个技巧是,在代码审查时,如果看到有人在UI组件里写了超过5行的业务计算逻辑,就建议他抽到t3code。这样慢慢形成团队习惯。我试过在一个5人团队里推行,大概两个月后,t3code目录下有了30多个函数,UI组件的平均行数减少了20%,测试覆盖率从40%提升到了65%。效果是实实在在的。
5. 常见问题与排查技巧实录
5.1 函数越写越多,怎么管理
这是最常见的问题。一开始t3code只有几个函数,后来越加越多,最后变成了一个几百个文件的“大杂烩”。解决办法是按业务领域分目录,并且定期重构。我建议每个月花半小时review一下t3code目录,看看有没有功能重复的函数,有没有可以合并的,有没有已经不再使用的。对于不再使用的,果断删除。对于功能相似的,考虑合并成一个更通用的函数,或者提取公共部分。另外,给每个子目录写一个README.md,简单说明这个目录下的函数是做什么的,方便新人快速了解。
5.2 调用方传入了不符合预期的参数
t3code函数是纯的,但调用方可能传错参数。比如,formatCurrency期望传入数字,但调用方传了一个字符串'123'。虽然我们在函数内部做了类型检查,返回了默认值,但这掩盖了调用方的错误。更好的做法是,在开发环境下抛出警告,帮助调用方发现问题。比如:
if (typeof amountInCents !== 'number') { if (process.env.NODE_ENV === 'development') { console.warn('formatCurrency: 期望传入数字,实际收到', typeof amountInCents); } return '¥0.00'; }这样在开发时能及时发现问题,生产环境又不会崩溃。另外,如果你用TypeScript,可以在编译期就拦截大部分类型错误,这是更推荐的方式。
5.3 测试写得太琐碎,维护成本高
t3code函数因为逻辑简单,测试很容易写,但也很容易写得太细。比如,一个函数有5个参数,每个参数有3种可能,组合起来就是243个测试用例,这显然不现实。我的经验是:用等价类划分和边界值分析。对于每个参数,只测试正常值、边界值、异常值三种情况。比如calculateDiscount,折扣率参数只测试0、0.5、1、-0.1、1.1这五个值就够了。另外,对于纯函数,可以使用参数化测试,把多组输入输出写在一个表格里,减少重复代码。Vitest的it.each就很好用。
5.4 性能问题:频繁调用会不会有开销
纯函数本身没有副作用,但频繁调用仍然可能带来性能问题,尤其是当函数内部有复杂计算时。比如,一个函数需要遍历一个大数组,如果在渲染循环里每次渲染都调用,可能会卡顿。解决办法是缓存结果。对于纯函数,相同的输入必然得到相同的输出,所以可以用记忆化(memoization)来缓存。简单的实现是:
const cache = new Map(); export function expensiveCalculation(input) { const key = JSON.stringify(input); if (cache.has(key)) return cache.get(key); const result = /* 复杂计算 */; cache.set(key, result); return result; }但要注意,如果输入对象很大,JSON.stringify本身也有开销。所以记忆化只适用于输入较小、计算较复杂的场景。另外,缓存要设置上限,避免内存泄漏。我通常用lru-cache这样的库来管理。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决建议 |
|---|---|---|---|
| 函数返回结果不符合预期 | 参数类型错误或边界未处理 | 在函数入口打印参数类型和值 | 增加类型检查和边界归一化 |
| 调用方修改了返回值导致数据错乱 | 函数返回了内部对象的引用 | 检查函数是否返回了新对象 | 使用展开运算符创建新对象 |
| 测试运行缓慢 | 测试中引入了不必要的依赖 | 检查测试文件是否mock了数据库 | 确保t3code测试不依赖外部资源 |
| 函数越来越多难以查找 | 缺乏目录规划和命名规范 | 查看目录结构是否按领域划分 | 按业务领域分目录,统一命名 |
| 生产环境出现未捕获异常 | 函数内部抛出了异常 | 查看错误堆栈定位到具体函数 | 对可预期错误返回结果对象,而非抛异常 |
6. 进阶技巧:让t3code更加强大
6.1 组合函数:用小函数拼出大逻辑
t3code里的函数应该尽量小,一个函数只做一件事。但实际业务往往需要多个步骤。这时可以用组合的方式,把多个小函数串起来。比如,计算订单最终价格需要:先计算商品总价,再应用折扣,再加上运费,最后格式化。你可以写一个组合函数:
import { calculateSubtotal } from './calculateSubtotal'; import { applyDiscount } from './applyDiscount'; import { calculateShipping } from './calculateShipping'; import { formatCurrency } from './formatCurrency'; export function calculateFinalPrice(order, user) { const subtotal = calculateSubtotal(order.items); const discounted = applyDiscount(subtotal, user.level); const shipping = calculateShipping(order.address, discounted); return formatCurrency(discounted + shipping); }这个组合函数本身也是纯的,因为它调用的都是纯函数。这样分层的好处是,每个小函数可以独立测试,组合函数也可以测试,而且逻辑清晰,容易修改。如果将来运费规则变了,只需要改calculateShipping,不影响其他部分。
6.2 类型安全:用TypeScript强化t3code
如果你用TypeScript,t3code的价值会更大。因为TypeScript可以在编译期检查参数类型和返回值类型,避免很多低级错误。我建议为每个t3code函数定义清晰的输入输出接口。比如:
interface Order { id: string; status: 'pending' | 'processing' | 'completed' | 'cancelled'; creatorId: string; items: Array<{ price: number; quantity: number }>; } interface User { id: string; role: 'admin' | 'normal'; } export function canEditOrder(order: Order, user: User): boolean { // ... }这样,调用方如果传入了不符合接口的对象,编辑器会立刻报错。而且,类型定义本身就是最好的文档。我试过在项目里全面使用TypeScript后,t3code函数的调用错误率下降了80%以上。
6.3 文档生成:自动产出API文档
当t3code函数多到几十个时,手动维护文档就不现实了。可以用工具自动生成。如果你用JSDoc注释,可以用jsdoc或typedoc生成HTML文档。如果团队用Notion或语雀,可以写一个简单的脚本,扫描t3code/目录,提取每个函数的注释,生成Markdown表格。这样每次新增函数,文档自动更新,省时省力。我自己的做法是,在CI流程里加一步,如果检测到t3code/目录有变动,就自动生成文档并部署到内部站点。这样团队成员随时可以查阅最新的函数列表。
6.4 跨项目复用:把t3code发布为私有包
如果你有多个项目,而且它们共享一些业务逻辑(比如同一家公司的不同产品线),可以考虑把t3code发布为私有npm包。这样每个项目通过npm install @company/t3code来引入,版本管理也方便。但要注意,私有包里的函数应该尽量通用,不要包含特定项目的业务规则。我的经验是,先在一个项目里沉淀,等某个函数被两个以上项目需要时,再考虑提取到私有包。否则过早抽象会导致包变得臃肿,反而不好维护。
7. 我在实际项目里踩过的坑
第一个坑:把有状态的逻辑放进了t3code。有一次,我写了一个函数用来管理购物车的选中状态,里面直接修改了一个模块级的变量。结果在多个组件同时调用时,状态互相干扰,出现了诡异的bug。排查了半天才发现是t3code函数不纯导致的。从那以后,我定了一条规矩:t3code函数里绝对不能出现let或var声明的模块级变量,所有数据必须通过参数传入,通过返回值传出。
第二个坑:过度抽象。有一段时间,我痴迷于把任何重复的代码都抽成函数,结果t3code里出现了一堆只有一行代码的函数,比如getOrderId(order) { return order.id; }。这种函数不仅没有减少重复,反而增加了调用层级,让代码更难读。后来我给自己定了一个标准:只有当一段逻辑被重复三次以上,或者逻辑本身超过5行,才值得抽成t3code函数。否则,宁可让它重复。
第三个坑:忽略了函数的副作用。虽然t3code函数本身是纯的,但调用方可能在调用前后做了有副作用的操作。比如,一个函数计算出了新价格,调用方直接把这个价格赋值给了Vue的响应式对象,导致视图更新。这本身没问题,但如果多个地方同时调用,可能会触发多次不必要的渲染。我的解决办法是,在调用t3code函数后,用computed或watch来管理派生状态,而不是手动赋值。这样Vue会自动处理依赖追踪和缓存。
第四个坑:测试覆盖了函数,但没覆盖组合场景。单个t3code函数测试通过,但组合起来用的时候,因为参数传递顺序错误,导致结果不对。后来我在测试里增加了组合函数的测试,确保端到端的逻辑正确。另外,对于组合函数,我会用it.each写多组输入输出,覆盖不同的业务场景。
8. 最后再分享几个实用小技巧
如果你刚开始尝试t3code,可以从一个函数开始。不要想着一次性建好整个目录结构,先找一个你最近写过的、重复了两次以上的业务逻辑,把它抽出来,放到t3code/下,然后在原来的地方引用它。感受一下“改一处,处处生效”的爽快。然后,每次遇到类似的情况,就重复这个过程。三个月后,你会发现t3code已经成了项目里不可或缺的一部分。
另一个技巧是,给t3code函数起名时,加上业务前缀。比如order_calculateTotal、user_canEdit。这样在编辑器里输入order_时,所有订单相关的函数都会列出来,方便查找。虽然下划线命名在JavaScript社区不常见,但在t3code这种“工具箱”场景下,它比驼峰命名更清晰。当然,如果你用TypeScript,可以用命名空间或模块来组织,效果类似。
还有,定期清理。每季度花一个小时,看看t3code里有没有不再使用的函数,有没有可以合并的,有没有注释过时的。保持t3code的精简,它才能持续发挥价值。我见过太多项目,工具库越积越大,最后没人敢动,因为不知道删了会不会影响其他地方。定期清理能避免这个问题。
最后,把t3code的实践写进团队的编码规范。比如,规定“任何被两个以上组件使用的业务计算逻辑,必须放在t3code里”。这样新加入的成员也能快速理解项目的代码组织方式。规范不需要很长,几条核心原则就够了。关键是执行,可以在代码审查时互相提醒。坚持一段时间,就会形成习惯。