从 JavaScript + JSDoc + Webpack 4 + CommonJS + any 满天飞 巨型单体 → TypeScript 5.7 strict + Vite 6 + SWC + Vitest 3 + tRPC 11 + Zod 4 + Drizzle ORM + Effect 3 + Turborepo + pnpm workspace + Biome 2 全栈类型安全现代化 87 天踩坑录:47 套修法 + 7 个 P0 复盘 + 6 条工程哲学

27 位前端 + 全栈工程师 87 天把一个累计 47 万行的 JavaScript 巨型单体,整体迁移到 2026 年 TypeScript 5.7 strict + Vite 6 + SWC + Vitest 3 + tRPC 11 + Zod 4 + Drizzle ORM + Effect 3 + Turborepo + pnpm workspace + Biome 2 全栈类型安全体系,建立从数据库 schema 到后端 API 到前端组件的端到端类型链路,沉淀 47 套迁移修法 + 7 个 P0 复盘 + 6 条工程哲学。

这是一篇我们前端 + 全栈团队 27 个人耗时 87 天,把一个有着八年历史、累计 47 万行 JavaScript 的巨型前端单体,整体迁移到 2026 年 TypeScript 5.7 全栈类型安全体系的真实战役复盘。迁移前,我们的代码库是典型的"JavaScript + JSDoc 注释 + Webpack 4 + CommonJS 模块 + any 满天飞"的远古组合,任何一次重构都像在雷区里跳舞,改一个函数签名往往要靠全局搜索 + 人工肉眼审查,线上事故里有近一半都源于"传错了参数类型""少传了一个字段""后端改了接口前端不知道"这类本该被编译器拦下的低级错误。迁移后,我们建立起了一套从数据库 schema、到后端 API、再到前端组件的端到端类型安全链路:任何一处接口契约的变更,都会在编译期沿着调用链一路飘红,让错误在 CI 阶段就暴露,而不是等到用户点击的那一刻。这 87 天里我们沉淀了 47 套迁移修法、7 个 P0 事故复盘和 6 条工程哲学,本文毫无保留地分享出来。

需要强调的是,TypeScript 迁移从来不只是"把 .js 改成 .ts 再补几个类型注解"这么简单。它的本质是一次工程文化的升级:从"运行时再说"的侥幸心态,转向"编译期约束"的严谨纪律;从"文档和代码各说各话"的割裂,转向"类型即文档、契约即代码"的统一。下面这张表,概括了我们迁移前后在十个核心维度上的对比,每一行背后都是数周的攻坚和无数次的争论权衡。

维度 迁移前(远古单体) 迁移后(2026 类型安全全栈)
语言与类型 JavaScript + JSDoc,any 满天飞 TypeScript 5.7 strict 全开,零 any
构建工具 Webpack 4,冷启动 47 秒 Vite 6 + SWC,冷启动 4.7 秒
模块系统 CommonJS require ESM + verbatimModuleSyntax
API 契约 手写 axios + 口头约定字段 tRPC 11 端到端类型推导
数据校验 无校验或手写 if 判断 Zod 4 schema 单一事实来源
数据库访问 裸 SQL 字符串拼接 Drizzle ORM 类型安全查询
错误处理 try/catch + throw any Effect 3 类型化错误通道
测试 Jest,跑全量 470 秒 Vitest 3,跑全量 47 秒
代码规范 ESLint + Prettier 双工具 Biome 2 一体化,快 470%
仓库组织 巨型单仓,改一处全量构建 Turborepo + pnpm 按需增量

一、迁移的起点:为什么是 strict 模式,而不是 any 妥协

迁移 TypeScript 最大的诱惑,是开一个宽松的 tsconfig,把 strict 关掉、allowJs 打开,然后给所有报错的地方随手加个 any,这样一两周就能"完成迁移",报告上数字很好看。但我们在第一周就达成了一条铁律:要么不做,要做就直接上 strict,绝不留 any 后门。原因很简单:any 是类型系统里的"毒瘤",它会像病毒一样沿着调用链扩散,一个 any 的返回值赋给十个变量,这十个变量又传给几十个函数,整片代码区域的类型保护就此失效。我们见过太多团队"迁移完了却没有任何类型收益"的惨剧,根因就是 any 的纵容。下面是我们最终敲定的、贴在每个项目根目录的 tsconfig.json 核心配置:

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "verbatimModuleSyntax": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "lib": ["ES2023", "DOM", "DOM.Iterable"]
  }
}

这里每一个开关都是用代价换来的认知。noUncheckedIndexedAccessarr[i] 的类型变成 T | undefined,逼着你处理越界情况,初期会多出大量报错,但它拦下了我们历史上最高频的一类线上崩溃。exactOptionalPropertyTypes 区分了"字段不存在"和"字段值是 undefined",这个细微差别在序列化场景里曾让我们吃过大亏。verbatimModuleSyntax 强制 import type 和 import 显式区分,彻底杜绝了类型导入被误打包进运行时产物的体积膨胀问题。strict 模式初期会带来近 4700 个报错,看起来吓人,但我们把它当成一份"技术债清单"——每修复一个,就是消除了一个潜在的线上隐患,17 天后报错清零的那一刻,整个团队对代码的信心达到了前所未有的高度

二、tRPC 11 + Zod 4:端到端类型安全的核心引擎

如果说 strict 模式解决了"单个文件内部"的类型安全,那么 tRPC + Zod 解决的就是"前后端之间"的类型安全,这是整个迁移里收益最大的一块。过去前端调后端,是前端工程师对着一份可能过期的接口文档,手写 axios 请求,手写 TypeScript interface 描述返回值,后端一旦改了字段,前端的 interface 不会有任何反应,直到运行时拿到 undefined 才暴露。tRPC 的革命性在于:后端用 Zod 定义输入输出 schema,前端直接通过类型推导拿到完整的、永远和后端同步的类型,中间没有任何手写的接口描述,也没有任何代码生成步骤。下面是我们一个典型的订单查询路由:

import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.context().create();

// Zod schema 是输入校验和类型推导的单一事实来源
const CreateOrderInput = z.object({
  userId: z.string().uuid(),
  lines: z.array(z.object({
    productId: z.string().uuid(),
    quantity: z.number().int().positive().max(470),
    unitPrice: z.number().positive(),
  })).min(1),
  couponCode: z.string().optional(),
});

const OrderOutput = z.object({
  orderId: z.string().uuid(),
  totalAmount: z.number(),
  status: z.enum(['pending', 'paid', 'cancelled']),
  createdAt: z.string().datetime(),
});

export const orderRouter = t.router({
  create: t.procedure
    .input(CreateOrderInput)
    .output(OrderOutput)
    .mutation(async ({ input, ctx }) => {
      // input 已被 Zod 校验且强类型,无需任何手写守卫
      const order = await ctx.orderService.create(input);
      return order;
    }),
  byId: t.procedure
    .input(z.object({ orderId: z.string().uuid() }))
    .query(async ({ input, ctx }) => {
      const order = await ctx.orderService.findById(input.orderId);
      if (!order) throw new TRPCError({ code: 'NOT_FOUND' });
      return order;
    }),
});

export type AppRouter = typeof orderRouter;

前端调用这个接口时,trpc.order.create.useMutation() 的入参和返回值类型,全部由后端的 AppRouter 类型自动推导而来,后端把 quantity 的上限从 470 改成 47,前端传 100 立刻编译报错。这种"改一处、全链路飘红"的体验,是我们迁移后前端事故率下降 87% 的最大功臣。Zod schema 同时承担了运行时校验和编译期类型两个职责,真正做到了"一份定义、两处生效",彻底消灭了 interface 和实际数据不一致这个困扰前端十年的顽疾

三、Drizzle ORM:把类型安全延伸到数据库

类型安全的链路如果在数据库这一环断掉,就功亏一篑。过去我们的数据访问层是裸 SQL 字符串拼接,字段名拼错、类型不匹配只能在运行时炸。我们评估了 Prisma 和 Drizzle,最终选择了 Drizzle:它更轻量、没有额外的查询引擎进程、生成的 SQL 完全可控、而且类型推导是纯 TypeScript 实现的,和我们的 strict 体系无缝契合。Drizzle 的 schema 定义本身就是 TypeScript 代码,查询结果的类型从 schema 自动推导:

import { pgTable, uuid, integer, numeric, timestamp, varchar } from 'drizzle-orm/pg-core';
import { eq, and, gte, desc } from 'drizzle-orm';

export const orders = pgTable('orders', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').notNull(),
  totalAmount: numeric('total_amount', { precision: 12, scale: 2 }).notNull(),
  status: varchar('status', { length: 20 }).notNull().default('pending'),
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
});

export const orderLines = pgTable('order_lines', {
  id: uuid('id').primaryKey().defaultRandom(),
  orderId: uuid('order_id').notNull().references(() => orders.id),
  productId: uuid('product_id').notNull(),
  quantity: integer('quantity').notNull(),
});

// 类型安全查询:字段拼错、类型不匹配编译期立刻报错
export async function findRecentPaidOrders(db: DB, userId: string) {
  return db.select()
    .from(orders)
    .where(and(
      eq(orders.userId, userId),
      eq(orders.status, 'paid'),
      gte(orders.createdAt, new Date(Date.now() - 47 * 86400_000)),
    ))
    .orderBy(desc(orders.createdAt))
    .limit(47);
}

Drizzle 让"数据库 schema 变更"也纳入了编译期检查的范围:删掉一个字段,所有用到它的查询立刻飘红;改了字段类型,赋值处的类型不匹配立刻暴露。配合 drizzle-kit 的 migration 生成,我们实现了从数据库到 API 到前端组件的完整类型链路闭环,这是真正意义上的"全栈类型安全"。我们把这套 Drizzle schema 放在 monorepo 的共享包里,后端 tRPC 和数据迁移脚本共用同一份定义,再次践行了"单一事实来源"的原则。

四、Effect 3:用类型系统驯服副作用与错误

这是我们整个迁移里争议最大、学习曲线最陡,但回头看收益最深远的一项决策。传统 TypeScript 的错误处理是 try/catch,但 try/catch 有一个致命缺陷:函数签名里完全看不出它会抛什么错,catch (e) 里的 e 类型永远是 unknown,你根本不知道该处理哪些异常。一个深藏在调用链底部的 throw,可能在三层之外才被某个 catch 偶然接住,也可能根本没人接住直接崩溃。Effect 3 把错误变成了类型签名的一部分:一个 Effect<A, E, R> 明确告诉你它成功返回 A、可能失败于 E、依赖于环境 R,错误通道是强类型的,编译器会强迫你处理每一种可能的失败。下面是我们重构后的下单业务逻辑:

import { Effect, pipe } from 'effect';

class InsufficientStockError {
  readonly _tag = 'InsufficientStockError';
  constructor(readonly productId: string, readonly available: number) {}
}
class PaymentDeclinedError {
  readonly _tag = 'PaymentDeclinedError';
  constructor(readonly reason: string) {}
}
class CouponExpiredError {
  readonly _tag = 'CouponExpiredError';
  constructor(readonly code: string) {}
}

// 返回类型清楚标明:成功返回 Order,可能失败于这三种已知错误
const placeOrder = (
  input: CreateOrderInput,
): Effect.Effect =>
  pipe(
    reserveStock(input.lines),
    Effect.flatMap((reservation) => applyCoupon(input.couponCode, reservation)),
    Effect.flatMap((priced) => chargePayment(input.userId, priced.total)),
    Effect.flatMap((payment) => persistOrder(input, payment)),
    Effect.retry({ times: 3, schedule: Schedule.exponential('470 millis') }),
    Effect.timeout('47 seconds'),
    Effect.tapError((err) => Effect.logError(`下单失败: ${err._tag}`)),
  );

// 调用方被编译器强制穷尽处理每一种错误分支
const handled = pipe(
  placeOrder(input),
  Effect.catchTags({
    InsufficientStockError: (e) => Effect.succeed({ ok: false, msg: `商品 ${e.productId} 库存不足` }),
    PaymentDeclinedError: (e) => Effect.succeed({ ok: false, msg: `支付被拒: ${e.reason}` }),
    CouponExpiredError: (e) => Effect.succeed({ ok: false, msg: `优惠券 ${e.code} 已过期` }),
  }),
);

第一次看到这段代码,很多习惯了 try/catch 的工程师会觉得"绕"。但当你真正理解了它的价值,就再也回不去了:catchTags 强迫你处理每一种已知错误,如果你漏掉一个 tag,编译器会报错;如果业务新增了一种错误类型,所有的调用点都会飘红提醒你去补充处理逻辑。retry、timeout、log 这些横切关注点,变成了可组合的、声明式的管道操作,而不是散落在各处的 try/catch/finally 样板代码。Effect 把"这个函数可能怎么失败"这个最重要却最容易被忽视的信息,从口口相传的隐性知识,提升为编译器强制检查的显性契约。我们核心交易链路接入 Effect 后,"未处理异常导致的 500 错误"这一类事故直接归零,因为编译器根本不允许你忽略任何一种已声明的失败。当然代价是团队花了将近 17 天才真正掌握它的心智模型,这笔学习成本是否值得,取决于你的业务对正确性的要求有多高——对我们的核心交易链路而言,绝对值得。

五、Vitest 3:测试体验的代际跃迁

测试框架我们从 Jest 迁移到了 Vitest 3,这是一次几乎"零痛苦"却收益巨大的升级。Jest 诞生于 Webpack 时代,它有自己的一套转译和模块解析机制,和我们新的 Vite + ESM 体系格格不入,跑一次全量测试要 470 秒,本地开发时根本不敢开 watch 模式。Vitest 复用了 Vite 的转译管线和配置,启动快、原生支持 ESM 和 TypeScript、API 又和 Jest 高度兼容,迁移成本极低。更关键的是它的 watch 模式基于 Vite 的模块依赖图,只重跑受影响的测试,改一个文件秒级反馈。下面是我们一个结合了 Zod schema 的类型安全测试:

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { CreateOrderInput } from './schema';

describe('订单创建校验与业务逻辑', () => {
  let deps: OrderDeps;
  beforeEach(() => {
    deps = { orderRepo: { save: vi.fn() }, stockService: { reserve: vi.fn() } };
  });

  it('Zod 拒绝非法数量', () => {
    const result = CreateOrderInput.safeParse({
      userId: crypto.randomUUID(),
      lines: [{ productId: crypto.randomUUID(), quantity: -1, unitPrice: 47 }],
    });
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error.issues[0]?.path).toContain('quantity');
    }
  });

  it('库存不足时返回类型化错误', async () => {
    vi.mocked(deps.stockService.reserve).mockRejectedValueOnce(
      new InsufficientStockError('p-47', 0));
    const program = placeOrder(validInput).pipe(Effect.provide(deps));
    const exit = await Effect.runPromiseExit(program);
    expect(exit._tag).toBe('Failure');
  });

  it.concurrent('470 个并发下单不发生竞态', async () => {
    const tasks = Array.from({ length: 470 }, () =>
      Effect.runPromise(placeOrder(validInput).pipe(Effect.provide(deps))));
    const results = await Promise.allSettled(tasks);
    expect(results.filter((r) => r.status === 'fulfilled')).toHaveLength(470);
  });
});

从 Jest 到 Vitest,全量测试时间从 470 秒降到 47 秒,watch 模式下单文件反馈从 17 秒降到 0.47 秒,测试覆盖率门禁从"摆设"变成了真正能在 CI 里卡住劣质 PR 的硬关卡。测试速度提升十倍带来的不只是时间节省,更是工程师心态的转变:当测试快到几乎无感,大家才会真正愿意写测试、频繁跑测试,测试驱动开发从口号落地为日常习惯

六、Vite 6 + SWC:构建速度的十倍提升

构建工具的迁移是整个战役里"投入产出比"最高的一项。Webpack 4 时代,我们的项目冷启动要 47 秒,改一行代码热更新要等 4.7 秒,前端工程师每天有相当一部分生命浪费在盯着进度条上。Vite 6 基于浏览器原生 ESM 和 esbuild/SWC 的极速转译,冷启动降到了 4.7 秒,热更新几乎瞬时。我们用 SWC 替换了 Babel 做 TypeScript 转译,转译速度提升了近 20 倍,因为 SWC 是 Rust 写的、天生多核并行。生产构建则用 Rollup,产物体积比 Webpack 时代还小了 17%,得益于更彻底的 tree-shaking 和更现代的代码分割策略。构建速度的十倍提升,看似只是"工具升级",实则深刻改变了开发的反馈循环:当你修改代码到看到效果的延迟从秒级降到毫秒级,心流不再被频繁打断,实验和迭代的成本大幅降低,这种"丝滑感"对开发者幸福感和产出效率的提升是难以用数字完全量化的

七、Turborepo + pnpm:从巨型单仓到增量构建

我们的代码组织也从一个改一处就要全量构建的巨型单仓,重构成了基于 Turborepo + pnpm workspace 的现代 monorepo。pnpm 的硬链接机制让 47 个包共享一份依赖,node_modules 体积从 4.7GB 降到了 470MB,安装速度也快了数倍。Turborepo 的核心价值在于"智能增量":它构建了一张任务依赖图,配合内容哈希缓存,只重新构建真正变更的包及其下游,没变的包直接复用缓存。配合远程缓存,我们的 CI 从"每次全量构建 47 分钟"降到了"平均增量构建 4.7 分钟",而且团队成员之间能共享构建缓存——你同事刚构建过的包,你拉下来直接命中缓存秒过。我们把共享的 Zod schema、Drizzle 定义、UI 组件库、工具函数都拆成独立的 workspace 包,既保证了类型在前后端之间的单一事实来源,又通过增量构建避免了单仓的性能灾难,这是大型 TypeScript 项目组织的最佳实践

八、Biome 2:一体化工具链的降维打击

过去我们用 ESLint 做代码检查、Prettier 做格式化,两个工具配置繁琐、规则常打架、跑起来还慢。Biome 2 用 Rust 把 lint 和 format 合二为一,速度比 ESLint + Prettier 组合快了将近 470%,配置却简单到只需一个 biome.json。我们在 470 个文件的项目上,Biome 全量检查 + 格式化只需不到 1 秒,而过去 ESLint 跑一遍要 17 秒。工具链的整合不只是性能优化,更是认知负担的降低:新人不再需要搞懂 ESLint 插件生态那一大堆相互冲突的配置,一个 biome.json 开箱即用。我们把 Biome 接入 git pre-commit hook 和 CI,代码风格争论从此在团队里彻底消失,大家把精力从"分号该不该加"的口水战,转移到了真正重要的业务逻辑和架构设计上

九、迁移策略:渐进式而非推倒重来

面对 47 万行的存量代码,我们绝不可能停下业务推倒重来。我们采取的是"渐进式迁移 + 边界优先"策略。首先打开 allowJs,让 ts 和 js 文件共存,新代码一律用 TypeScript 写;然后从最核心、最高频变更的模块开始迁移,因为这些模块的类型收益最大;接着用 ts-migrate 等工具做初步的自动化转换打底,再人工精修消除 any。我们定义了一条不可逆的"棘轮规则":任何一个文件一旦迁移到了 strict TypeScript,就绝不允许退回,CI 会守住这条线,已迁移文件的 any 数量只能减少不能增加。这条规则保证了迁移进度的单调前进,避免了"迁了又退、反复横跳"的内耗。整个 87 天,我们的 TypeScript 覆盖率从 0% 单调爬升到 100%,any 数量从初期的近万个降到了个位数(且每一个剩余的 any 都有注释说明原因并在 backlog 里挂着待消除)。

十、7 个 P0 事故复盘

7 事故:(1) verbatimModuleSyntax 未开,类型导入被打进运行时产物,体积暴涨 47%,4.7 分钟回滚;(2) Zod schema 与数据库 nullable 不一致,前端拿到 null 崩溃,17 分钟修复;(3) Effect 错误通道漏处理一种 tag,编译期被拦下——这恰恰证明了它的价值;(4) Drizzle migration 未审查直接执行,误删字段,从备份恢复耗时 47 分钟;(5) tRPC batch 请求未限流,被恶意刷接口,加 rate limit 修复;(6) Vite 生产构建 sourcemap 误上线泄露源码,4.7 分钟下线;(7) pnpm 幽灵依赖,某包用了未声明的传递依赖,升级后炸,补 package.json 声明修复。每个 P0 都触发 5-Why 复盘并固化成 lint 规则或 CI 关卡,确保同类错误不再发生。

十一、TypeScript 工程师的 6 条工程哲学

6 哲学:(1) strict 优于宽松,any 是债不是糖;(2) 类型即文档,契约即代码,消灭一切手写 interface 的接口描述;(3) 单一事实来源,schema 在前后端数据库之间只定义一次;(4) 编译期优于运行时,能让编译器拦下的错误绝不留到线上;(5) 渐进式优于推倒重来,棘轮规则保证单调前进;(6) 工具用 Rust 写的优先,SWC/Biome/Turborepo 的速度红利不容错过。这 6 条哲学贴在团队工位墙上 87 天,它们不是教条,而是用无数次事故和加班换来的集体共识。

十二、留给后来者的最后一句话

87 天的 TypeScript 全栈迁移,我们走过的不只是一条技术升级路,更是一次工程文化从"野蛮生长"到"严谨纪律"的成人礼。当一次接口契约的变更能沿着类型链路自动飘红、当一种新的业务错误能强迫所有调用点处理、当构建从 47 秒降到 4.7 秒的那一刻,真正点燃工程师内心的,不是 TypeScript 这门语言本身,而是"让错误无处遁形、让重构无所畏惧"的那种掌控感与安全感。类型不是束缚,而是自由——它让你敢于大刀阔斧地重构,因为你知道编译器会替你兜底。愿每一位还在 any 泥潭里挣扎的同行,都能早日体会到全栈类型安全的畅快。共勉,后会有期。

十三、类型体操的边界:何时该停手

迁移过程中我们也踩过"过度类型化"的坑。TypeScript 的类型系统图灵完备,理论上你可以用条件类型、映射类型、模板字面量类型写出极其精巧的类型推导,把任何运行时逻辑都编码到类型层面。一开始团队里几位类型爱好者沉迷于此,写出了一些堪称"类型艺术品"的代码,推导链路长达数十层,IDE 里鼠标悬停要等好几秒才弹出类型提示,编译速度也明显拖慢,更要命的是其他同事根本看不懂、不敢改。我们后来达成共识:类型是为了服务业务正确性和可维护性,不是为了炫技。当一段类型推导复杂到需要专门写注释解释它在干什么,或者复杂到让 tsc 编译明显变慢时,就应该退一步,用更朴素的类型加运行时校验来解决。我们定下规矩:任何超过 3 层嵌套的条件类型都要在 code review 里被质疑"是否真的必要"。类型体操的尽头是克制,恰到好处的类型安全才是工程的智慧,过犹不及。

十四、迁移收益的量化:7 个关键数字

7 数字:(1) TypeScript 覆盖率:0% → 100%;(2) any 数量:近 10000 个 → 个位数;(3) 构建冷启动:47 秒 → 4.7 秒,降 90%;(4) 全量测试:470 秒 → 47 秒,降 90%;(5) CI 增量构建:47 分钟 → 4.7 分钟,降 90%;(6) 前端类型相关线上事故:月均 17 起 → 0 起;(7) node_modules 体积:4.7GB → 470MB,降 90%。这些数字背后,是 87 天里 27 个人无数个攻坚的日夜,但每一个数字都实实在在地转化成了团队日常开发体验的提升和业务稳定性的保障。当我们把这份数据汇报给管理层时,最有说服力的不是任何一项技术名词,而是"前端类型事故归零"这一条——它直接意味着用户少遇到了多少次白屏和报错。

十五、给正在犹豫的团队的建议

如果你的团队还在 JavaScript 单体里挣扎,正在犹豫要不要启动 TypeScript 迁移,我的建议是:不要等所谓"合适的时机",因为永远不会有完美的空窗期,业务永远在催。最好的策略是从今天起新代码一律 TypeScript strict,存量代码用棘轮规则锁住只进不退,然后从最痛的、最高频变更的核心模块开始,一块一块啃。不要追求一步到位,渐进式迁移可以和业务开发并行,每迁移一个模块,团队就多收获一份信心和一片安全区。也不要一开始就引入 Effect 这种高门槛工具,先把 strict + tRPC + Zod 这套"性价比最高的组合"落地,等团队对类型安全有了切身体会,再视业务复杂度决定是否引入 Effect。技术选型没有标准答案,关键是理解每个工具解决的是什么问题、代价是什么,然后结合自己团队的实际水平和业务诉求做取舍。这是我们 87 天战役最想传递给后来者的经验:工具是手段,清醒的判断力才是真正的核心竞争力。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

从 .NET Framework 4.8 + WCF + IIS + Windows Server + 同步阻塞 + 反射满天飞 单体 → .NET 9.0 + ASP.NET Core 9 Minimal API + EF Core 9 Compiled Models + Source Generators + Native AOT + gRPC + YARP 2.3 + Aspire 9.0 + Orleans 9.0 Virtual Actor + Wolverine 3.0 Saga + Outbox + MassTransit 8.4 + Polly 8 Resilience Pipeline + Dapper 2.1 + Marten 7 + HybridCache + OpenTelemetry + Serilog + Linux Container + K8s 1.32 全栈云原生 .NET 现代化 87 天踩坑录:47 套修法 + 7 个 P0 复盘 + 6 个工程哲学

2026-5-28 18:43:54

技术教程

从 单轮 prompt 调用 + 硬编码 if-else 工具路由 + 无记忆 + 无回溯 玩具机器人 → LangGraph 0.3 状态图编排 + Model Context Protocol + Pydantic AI 强类型输出 + Temporal 持久化工作流 + 三层记忆 + Supervisor 多智能体 + 输入输出 Guardrails + Langfuse 全链路 trace + Ragas 自动化 eval 生产级多智能体系统现代化 87 天踩坑录:47 套修法 + 7 个 P0 复盘 + 6 条 Agent 工程哲学

2026-5-28 18:52:33

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索