“能运行”只是最早的阶段

个人项目最容易获得满足感的时刻,通常是第一个页面成功打开、第一个接口返回数据,或者第一次把应用部署到公网。那个阶段的目标非常明确:让想法成为可以操作的东西。为了尽快到达结果,我们会接受临时变量、重复代码和只在本机成立的配置。

这些做法本身并不可耻。原型阶段需要速度,过早抽象也可能让项目在第一周就失去动力。问题出现在项目已经被持续使用,却仍然沿用原型期的规则。功能越来越多,任何修改都要同时担心前台、后台、数据库和配置文件,最后“还能运行”变成了不敢触碰的理由。

我逐渐意识到,一个个人项目是否成熟,不取决于它有多少页面,而取决于我隔一段时间回来后,能否快速理解它,安全地修改它,并且知道哪里出了问题。

先明确系统边界

这个站点被拆成三个部分:公开前台、管理后台和后端服务。这样的拆分看起来会增加目录数量,却减少了职责上的模糊。

公开前台只读取公开接口,不应该知道管理功能如何认证;管理后台负责内容编辑、状态切换和上传;后端负责权限、数据库和对外响应。一个页面属于哪里,应该在开始编码前就有答案,而不是因为某个目录写起来方便,就把管理路由重新塞进公开站点。

边界的价值在项目变大后才会明显。当前台视觉重构时,我不需要担心误改管理表单;调整后台认证时,也不应该影响公开文章读取。代码不一定因此变少,但每次修改需要同时装进脑中的东西会变少。

不用“看起来成功”的降级

个人项目中常见一种温柔的错误处理:接口失败就返回空数组,配置缺失就使用默认地址,数据库不可用就暂时读取本地文件。页面看起来没有报错,却也没有告诉开发者真实发生了什么。

这种降级在演示中很方便,在长期维护中却非常危险。空列表究竟代表没有文章,还是数据库连接失败?默认头像究竟是用户没有设置,还是上传地址配置错误?如果所有失败都被包装成正常状态,排查问题只能依赖猜测。

现在我更倾向于让必要配置尽早失败,让接口使用真实 HTTP 状态码,让前台展示明确错误状态。错误信息不需要吓人,但必须诚实。一个清楚失败的页面,比一个安静展示假数据的页面更容易维护。

把内容状态当成正式业务

博客系统看似简单,但只要同时存在草稿、发布和隐藏状态,内容流转就已经是一项正式业务。公开页面只能读取已发布内容,后台则需要看到全部状态;文章第一次变为发布时必须记录发布时间,从发布状态撤回时又要明确是否保留历史时间。

如果这些规则分散在前端按钮、后端控制器和数据库默认值里,很快就会出现页面显示与后台状态不一致的情况。因此状态规则应该集中在服务层,并通过数据库查询条件明确执行。前台不能为了“有内容可看”而自行展示草稿,后台也不能默认让用户猜测新建内容最终会出现在哪里。

这类规则不炫目,却决定了系统是否可信。

配置必须属于环境

另一个容易积累的问题是地址和密钥。开发时写死一个 localhost 很快,复制一段临时 JWT 密钥也很快,但这些值一旦进入业务代码,就会在部署环境、测试环境和另一台电脑上不断制造差异。

我把数据库地址、会话密钥、邮件服务、前台来源和公开 API 地址都视为必需配置。缺少它们时,应用应该给出明确提示并停止启动,而不是带着一个“change-me”继续运行。配置文件负责描述环境,代码只负责读取经过验证的值。

这样做并不会让第一次启动更轻松,却会让每一次部署更可预测。

UI 也需要可维护的规则

可维护性不只存在于后端。前台如果每个页面都有自己的颜色、圆角、阴影和间距,同样会让小修改变成全站搜寻。

这次整理中央内容区时,我开始把窗口材质、导航、正文宽度和响应式规则放到共享组件中。动态、文章和友链可以使用不同结构,但它们共享相同的窗口边界和顶部导航。调整内容框透明度时,只需要修改一个位置。

共享并不意味着所有页面必须使用同一张卡片。真正值得共享的是稳定规则,而不是偶然长得相似的 DOM 结构。

验证是完成的一部分

“我改完了”与“它可以交付”之间,还隔着验证。类型检查能发现组件属性和数据结构错误,生产构建能发现服务端渲染与打包问题,直接请求接口可以确认前后端是否真的连通。

验证不需要无限扩大。文字样式调整没有必要编写复杂端到端测试,但认证、发布状态和数据写入值得更严格检查。验证强度应该与风险成比例,而不是所有改动都只依赖刷新页面看一眼。

愿意继续修改,才算真正完成

整理一个全栈项目并不会带来一个永远整洁的终点。新的需求仍然会打破旧结构,某些抽象也会随着认识变化而被删除。维护性的意义不是防止变化,而是让变化发生时有清晰路径。

当我能够知道配置在哪里、状态由谁管理、失败如何暴露、页面边界如何划分,并且可以通过明确命令验证结果时,这个项目才从“曾经成功运行”变成了“我愿意继续维护”。