首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >为什么你的代码越改越乱?不是不会写,是少了这个能力

为什么你的代码越改越乱?不是不会写,是少了这个能力

作者头像
前端达人
发布2026-06-24 20:42:24
发布2026-06-24 20:42:24
1180
举报
文章被收录于专栏:前端达人前端达人

先说一个让我后背发凉的周一

那天早上,刚坐下还没喝口咖啡,旁边工位的小李就转过椅子凑过来,一脸为难。

「哥,帮我看个东西。这是我写的商品列表,现在产品要加个『店铺专享』的标签。本来挺简单一个活儿……但我不敢动手。」

我问他为啥不敢。

他说:「这个组件被四个页面用着。我改一个地方,另外三个页面说不定就一起崩了。上次加个小功能,测试给我提了五个 bug,全是别的页面冒出来的。」

我把代码拉出来一看,心里咯噔一下——

1200 行。37 个 props。点开往下翻,满屏都是 if-else,跟乱麻一样。

我问他:「这组件咋变成这样的?」

小李挺委屈:「我一开始想得可好了,四个页面都是列表,长得也像,我寻思复用一下多省事啊,就抽了个『通用列表组件』出来。结果写着写着发现,这四个页面看着像,业务逻辑根本不是一回事。没办法,只能一个 if 一个 if 往上堆……堆着堆着就成这样了。」

那一刻我特别想拍拍他肩膀。因为这个坑,我自己当年也一模一样地踩过。

而小李缺的,恰恰不是技术。他 React 用得挺溜,hooks、Context 该会的都会。

他缺的,是一个很少有人专门教、但决定你三年后是被同事感谢还是被同事吐槽的能力——工程判断力。

今天咱们就掰开揉碎聊聊它。

工程判断力,到底是个啥

先别被这个词吓到,它一点都不玄。

工程判断力 = 你在敲下这行代码的时候,能停半秒问自己一句:「这么写,三个月后会不会回过头来咬我一口?」

就这么简单。

它不是让你去学新框架。也不是让你去背设计模式。更不是让你把代码写得花里胡哨、显得很高级。

它是一种「在好几个看起来都还行的方案里,挑出那个最不容易让你将来后悔的」的能力。

新手和老手的差距,很多时候不在「会不会写」,而在这儿。新手想的是「这功能怎么实现」,老手在实现的同时,脑子里还有另一根弦:「这玩意儿以后好不好改?」

光说概念太空。我给你拆四个真实场景,全是日常开发里天天能碰上的。看完你大概率会「卧槽这不就是我」。

场景一:「长得像,那就合并吧」——新手最爱踩的第一个坑

❌ 坏判断:表面看着像,就强行共用

假设你手上有两个列表页面,乍一看长得跟双胞胎似的:

代码语言:javascript
复制
   页面A(商品列表)              页面B(我的收藏)
┌─────────────────┐         ┌─────────────────┐
│ 商品名  |  ¥价格 │         │ 商品名  |  ¥价格 │
│ 库存:10 | [收藏] │         │ 收藏于:3天前|[删] │
└─────────────────┘         └─────────────────┘

新手的第一反应几乎都是:「这俩都是列表,都有商品名、有价格,我抽一个通用组件出来,两边一起用,多优雅!」

于是就有了这玩意儿:

代码语言:javascript
复制
// ❌ 一开始挺美好
<ProductList
  items={products}
  showPrice={true}
  showInventory={true}
  showFavoriteBtn={true}
  showRemoveBtn={false}
  showLastViewTime={false}
// ……三个月后变成了这样:
// showShopExclusive={...}
// showCompareBtn={...}
// highlightStyle={...}
// 数了数,props 整整 37 个
/>

刚开始你还挺得意,觉得自己写得真复用。

可现实是这样的:过了俩月,产品说要再上一个「推荐页」,样式得不太一样。你寻思「简单,加个 highlightStyle 不就行了」。结果你这边一改,收藏页的样式跟着崩了——因为它俩共用同一个组件啊。

然后你就开始打补丁:「那我加个判断,推荐页才用新样式。」一个 if。过几天又来个需求,又一个 if……

不知不觉,你就变成了文章开头那个小李。

✅ 好判断:业务不一样,就大大方方让它们各过各的

老手看到这两个页面,不会先想「它俩像不像」,而是先问一句:

它俩长得像,但「关心的东西」是一回事吗?

掰开看:

  • 商品列表页关心的是:还有没有货、卖多少钱、为啥推荐给你
  • 收藏页关心的是:你啥时候收藏的、你自己记的小笔记、这东西还在不在架上

你看,皮肤像,骨头完全不一样。既然业务上根本是两码事,为啥要硬塞进一个组件里互相添堵?

所以老手会很「不优雅」地直接写两个:

代码语言:javascript
复制
// ✅ 各写各的,反而清清爽爽
// HomepageList.jsx —— 首页商品列表
function HomepageList({ products }) {
return (
    <div>
      {products.map((p) => (
        <div key={p.id}>
          <h3>{p.name}</h3>
          <span>¥{p.price}</span>
          <span>库存:{p.stock}</span>
          <button>收藏</button>
        </div>
      ))}
    </div>
  );
}

// FavoritesList.jsx —— 我的收藏
function FavoritesList({ products }) {
return (
    <div>
      {products.map((p) => (
        <div key={p.id}>
          <h3>{p.name}</h3>
          <span>收藏于:{p.addedDate}</span>
          <span>我的笔记:{p.myNote}</span>
          <button>删除</button>
        </div>
      ))}
    </div>
  );
}

三个月后又要加推荐页?太好了,再写一个 RecommendationList 就完事,跟前面俩谁也不挨着。各自演进,互不打扰。

我知道你心里可能有个声音:「这不就是重复代码吗?老师不是说 DRY(Don't Repeat Yourself)吗?」

这恰恰是新手最容易误解的一点:DRY 说的是「不要重复同一段业务逻辑」,不是「不要写长得像的代码」。 长得像但业务不同,那叫巧合,不叫重复。强行合并巧合,才是真正的灾难。

一张表看懂差别

坏判断

好判断

「看着像」就合并

「业务不同」就分开

props 越加越多,最后 37 个

每个组件都清清爽爽

改一处,崩三处

各改各的,互不影响

一年后:没人敢动

一年后:随便扩展

所以下次手痒想抽组件之前,先问自己一句:这俩的「像」,是会一直像下去,还是只是现在碰巧像?

场景二:一个字段散落在五个地方——改个需求,能改到你想辞职

❌ 坏判断:哪儿要用就哪儿写

后端新返回了一个字段 lastPurchaseTime(用户最后一次下单的时间)。

产品说,要根据这个字段做一堆「区分新老用户」的逻辑。你二话不说,哪儿要用就在哪儿写:

代码语言:javascript
复制
// 首页
if (user.lastPurchaseTime) {
  showPromotion();        // 买过的用户才给看优惠
}

// 商品详情页
if (Date.now() - user.lastPurchaseTime > 90 * 天) {
  showBadge('热门');       // 90 天没买的,给看"热门"标签
}

// 搜索结果页
if (user.lastPurchaseTime) {
  showPersonalizedRecs(); // 买过的才推个性化
}

// 邮件通知
if (Date.now() - user.lastPurchaseTime > 180 * 天) {
  sendRecallEmail();      // 180 天没买的,发召回邮件
}

// 活动页
const discount = user.lastPurchaseTime ? 0.9 : 0.95;

写的时候特别顺,一个需求一个 if,五分钟搞定,下班!

然后……半年后的某个下午,产品端着咖啡过来了:

「咱们以后不用『最后购买时间』判断新老用户了,改成『最近一个月有没有活跃登录』。」

你嘴上说「行没问题」,心里已经开始冒冷汗——因为这个判断,你散在了五个文件里

你得一个一个去翻、一个一个去改。改完首页、详情页、搜索页、邮件……眼看就改完了,结果活动页那个 discount 你给忘了

上线,用户投诉:「我明明是老用户,怎么折扣比新人还少?」

排查、复盘、写事故报告。一个下午没了。

✅ 好判断:同一个「概念」,只在一个地方定义

老手看到「区分新老用户」这种需求,脑子里会亮一盏灯:

这个『判断用户活不活跃』的定义,明显会在一堆地方用到。它不该是散装的,它该有个『老家』。

于是先建个文件,把这个「概念」供起来:

代码语言:javascript
复制
// userStatus.js —— 「用户状态」的唯一定义处
// 整个项目,只在这里说清楚"什么叫活跃用户"

exportfunction isActiveUser(user) {
if (!user.lastPurchaseTime) returnfalse;
returnDate.now() - user.lastPurchaseTime < 90 * 24 * 60 * 60 * 1000;
}

exportfunction isInactiveUser(user) {
return !isActiveUser(user) && user.lastPurchaseTime;
}

然后各个页面,都不自己判断了,统一来这儿「借」:

代码语言:javascript
复制
// 首页
if (isActiveUser(user)) showPromotion();

// 商品详情页
if (isInactiveUser(user)) showBadge('热门');

// 邮件通知
if (isInactiveUser(user)) sendRecallEmail();

现在产品再来改需求?你只需要打开 userStatus.js,改 isActiveUser 这一个函数。 改完,全项目自动生效,一处不漏。

整个过程从「改一下午还漏一个」变成「改一分钟,下班」。

差别在哪

代码语言:javascript
复制
坏判断:逻辑散落五处 → 改五个地方 → 一定漏一个 → 线上事故
好判断:逻辑收归一处 → 改一个地方 → 全自动同步 → 安心下班

判断的关键就一句话:当你发现一段逻辑要在第二个地方写的时候,就该警觉了——它是不是该有个『老家』?

场景三:赶时间上线没问题,但「会变的东西」得拎出来

❌ 坏判断:图快,全糊在一起

这个场景特别常见,尤其是小公司、创业团队。三周要上线 MVP,时间紧任务重,于是代码就写成了这样:

代码语言:javascript
复制
// HomePage.jsx —— 啥都往里塞
function HomePage() {
  useEffect(() => {
    fetch('/api/products')           // API 调用在这
      .then((r) => r.json())
      .then((data) => {
        // 业务逻辑也糊在这
        let result = data.filter((p) => {
          if (currentUser.vip) {
            return p.price < 1000;   // VIP 看贵的
          } else {
            return p.price < 500;    // 普通用户看便宜的
          }
        });

        // 价格处理还糊在这
        result = result.map((p) => ({
          ...p,
          showPrice: currentUser.vip ? p.price * 0.9 : p.price,
        }));

        setProducts(result);
      });
  }, []);

return<div>...</div>;
}

能跑吗?能跑。上线了吗?上线了。当下看,一点毛病没有。

可问题是,三个月后需求一定会变,而这时候你会发现:

  • API 路径要换 → 糟了,五个页面都这么写的,得改五个地方
  • VIP 折扣规则调整 → 这逻辑散在三个组件里,又得满世界找
  • 想加个缓存 → 都糊成一团了,根本不知道往哪儿下手

✅ 好判断:照样赶时间,但把「容易变的」单独拎出来

老手赶起时间来一点不含糊,但他有个习惯:写之前先想一秒,这堆东西里,哪些以后可能会变?把它们拎出来单独放。

代码语言:javascript
复制
// 第一步:API 调用,拎出来
asyncfunction getProducts() {
return fetch('/api/products').then((r) => r.json());
}

// 第二步:筛选逻辑,拎出来
function filterByUserType(products, user) {
const limit = user.vip ? 1000 : 500;
return products.filter((p) => p.price < limit);
}

// 第三步:价格逻辑,拎出来
function applyDiscount(product, user) {
return {
    ...product,
    showPrice: user.vip ? product.price * 0.9 : product.price,
  };
}

// 组件本身,瞬间清爽
function HomePage() {
  useEffect(() => {
    getProducts()
      .then((data) => filterByUserType(data, currentUser))
      .then((data) => data.map((p) => applyDiscount(p, currentUser)))
      .then(setProducts);
  }, []);

return<div>...</div>;
}

注意,这个版本写起来并不比上面那个慢,就是多拆了三个小函数而已。但三个月后的待遇天差地别:

代码语言:javascript
复制
API 路径变了?   → 改 getProducts() 一个函数        ✅
VIP 折扣调了?   → 改 applyDiscount() 一个函数      ✅
想加缓存?       → 在 getProducts() 里加几行        ✅

差别在哪

代码语言:javascript
复制
坏判断:全糊一起  →  写得快,但一碰就碎
好判断:各归各位  →  写得一样快,但稳如老狗

快没有错,糊在一起才有错。下次赶时间之前,花一秒问问自己:这里头,哪些是『以后大概率要动』的?把它们先隔开。

场景四:「为了将来」提前造轮子——最隐蔽、最烧钱的坑

❌ 坏判断:脑补一堆需求,提前造个大而全的引擎

这个坑,越是有点经验、有点追求的工程师越容易踩。因为它看起来特别「有远见」。

某个团队发现项目里表单到处都是,长得还都差不多,于是拍板:「咱们做一个『通用表单引擎』吧,以后所有表单一个配置就生成,多牛!」

吭哧吭哧搞了俩月,功能确实强大:JSON schema 自动生成表单、字段条件显示、动态校验、字段联动……应有尽有。

代码语言:javascript
复制
// 表单引擎 —— 3000+ 行,条件分支密密麻麻
const formConfig = {
fields: [
    {
      name: 'type',
      type: 'select',
      showIf: (data) => data.step === 1,            // 条件 1
    },
    {
      name: 'price',
      type: 'number',
      showIf: (data) => data.type === 'electronics', // 条件 2
      validate: (val, data) => {
        if (data.type === 'electronics') {
          // 又是一堆条件……
        }
      },
    },
    // ……还有几十个字段,每个都带一串条件
  ],
};

第一个表单,用着挺爽。第二、第三个,也能凑合。

然后画风开始崩了:

  • 第四个表单要「实时搜索某个字段」→ 引擎不支持,改引擎
  • 第七个表单要「跨字段联合校验」→ 引擎又不支持,再改引擎
  • 到第十个表单的时候,这个引擎已经被各种特例改得面目全非,条件分支多到全组没人敢碰,碰了就出事

当初为了「省事」造的轮子,最后成了全组的噩梦。

✅ 好判断:别脑补,用「真实出现过的需求」反推

老手不会上来就造引擎。他会很务实地说:

我现在手上就三个表单,先看看它们『真正共同』的部分是啥。

一看,三个表单真正一样的,其实就那么点:文本输入框、下拉选择框、错误提示

那就只抽这几个最稳的小积木:

代码语言:javascript
复制
// 最基础的小积木,几乎永远不用改
function TextField({ label, value, onChange }) {
  return (
    <div>
      <label>{label}</label>
      <input value={value} onChange={(e) => onChange(e.target.value)} />
    </div>
  );
}

function SelectField({ label, options, value, onChange }) {
  return (
    <div>
      <label>{label}</label>
      <select value={value} onChange={(e) => onChange(e.target.value)}>
        {options.map((opt) => (
          <option key={opt}>{opt}</option>
        ))}
      </select>
    </div>
  );
}

然后每个表单,用这些小积木自己拼,各拼各的:

代码语言:javascript
复制
// 电子产品表单 —— 自己的逻辑自己管
function ElectronicsForm({ onSubmit }) {
  const [form, setForm] = useState({ type: 'electronics', price: '' });

  return (
    <form onSubmit={onSubmit}>
      <SelectField
        label="类型"
        options={['electronics', 'clothing']}
        value={form.type}
        onChange={(val) => setForm({ ...form, type: val })}
      />
      <TextField
        label="价格"
        value={form.price}
        onChange={(val) => setForm({ ...form, price: val })}
      />
      <button type="submit">提交</button>
    </form>
  );
}

function ClothingForm({ onSubmit }) {
  // 服装表单,自己的逻辑,自己写
}

每个表单也就 50~100 行,谁来看都一目了然。

第四个表单要搜索功能?加在它自己组件里就行,不影响别人。第七个要跨字段校验?同样加在自己家里。

那什么时候才该把某个功能抽出来共用?等到你发现『有五个表单都在写一模一样的搜索逻辑』的那一刻——这时候抽,才是真需求,不是脑补。

差别在哪

代码语言:javascript
复制
坏判断:为想象中的需求造轮子 → 过度设计 → 越改越难
好判断:等真实需求出现了再抽 → 刚刚好 → 随时能扩

有句话送给你,写代码时贴在屏幕上都不为过:

「等它疼了再吃药,别没病先吃一柜子保健品。」


等等,为啥判断力这么难学?

读到这你可能有个疑问:既然判断力这么重要,为啥学校不教、培训班不讲、网上也很少有系统教程?

原因很扎心,就一句话:判断力没有标准答案。

你看那些「确定的东西」,都有地方学:

  • 框架不会用?→ 有官方文档
  • 设计模式不懂?→ 有教科书
  • 代码规范不清楚?→ 团队 wiki 写得明明白白

可判断力呢?

  • 有时候,重复代码是对的;有时候,是错的。
  • 有时候,提前抽象是聪明;有时候,是画蛇添足。
  • 有时候,快速上线是负责;有时候,是埋雷。

答案永远是「看情况」——看这是什么项目、什么团队、处在什么阶段。

没有非黑即白,全是灰度。

这也是为啥很多人宁可一个接一个地刷新框架——因为框架有标准答案,学了就是学了,有成就感。而判断力,得在模糊里一点点摸,短期内还看不出效果。

但恰恰是这种「短期看不见、长期拉开差距」的东西,才是新手和高手真正的分水岭。

好消息:判断力是能练的,给你三个笨办法

别灰心。判断力虽然没有教科书,但它不是天赋,是可以刻意练的。给你三个我自己一直在用的笨办法。

办法一:给自己设个「三个月回头看」

写完一个功能,别急着扑向下一个需求。在日历上给自己标个三个月后的提醒,到点了,回来看一眼:

「我三个月前抽的那个共享组件,现在到底有没有用?」

  • 如果真有用 → 想想为什么有用,记下来,下次照着做
  • 如果没用、反而添乱 → 想想当初哪一步判断错了,下次警惕
  • 如果一般般 → 想想它在什么条件下才有用,把这个条件记牢

大部分人从来不回头看,写完就忘,所以踩一样的坑能踩好几年。就这么一个动作,你的判断力会比同龄人涨得快得多。

办法二:动手改代码前,先问自己三个问题

每次准备改一段代码,花十秒,过一遍这三问:

Q1:这是临时的改动,还是长期的?临时试验性的(比如新业务线试水),别太较真,能跑就行;核心功能、长期要维护的,那就值得多花点心思设计。

Q2:这个改动会牵连到几个地方?只影响这一处,随便改;影响五六处,那这逻辑就该收到一个统一的地方管。

Q3:万一这个决定是错的,改回来有多难?改回来很简单(比如临时加的样式),那就大胆试;改回来要命的(比如数据库表结构、对外的 API 格式),那就慎之又慎,多验证两遍再动手。

三个问题答完,你心里大概就有数了:这个决定到底是「随手就改」级别,还是「得开个会」级别。

办法三:跟老司机聊天,但别问「怎么做」

这是性价比最高的一招。

跟团队里的资深工程师聊,别问「这个该怎么写」——那种答案文档里都有。要问:

「你见过哪些当年觉得很聪明、后来后悔得拍大腿的设计?」

一个老司机随口就能给你讲:

「我见过一个通用表单引擎,俩人写了俩月,结果四年了就三个表单在用,投入根本没回本。后来有个新项目,我直接拍板每个表单自己写,反而快了三倍。」

这一个真实的『翻车故事』,比你刷十篇理论文章都有用。 因为它把判断的「分寸感」直接喂给了你。

把判断力浓缩成三个问题

懒得记那么多?没关系,下次写代码做决定时,脑子里过这三道关就够了:

代码语言:javascript
复制
1. 这个"像",是稳定的吗?
   ├─ 只是暂时碰巧像?  → 别合并,各写各的
   └─ 是长期的固定模式? → 可以考虑共用

2. 这段逻辑,会到处重复出现吗?
   ├─ 第一次写        → 先就地写,别急
   ├─ 第二次写        → 开始警觉,考虑收口
   └─ 第三次写        → 别犹豫了,抽出来统一管

3. 万一决定错了,代价多大?
   ├─ 巨大(改不动)   → 慎重,多验证
   ├─ 中等           → 留条"后悔药",方便回退
   └─ 很小(随手改)   → 大胆做,错了再说

把这张图截下来,存手机里。下次手悬在键盘上犹豫的时候,拿出来对一对。

一张总表,收藏备用

遇到的情况

新手的反应

老手的反应

看到代码重复

立刻抽象共用

等到第三次出现才抽

一个字段到处要用

哪儿要用哪儿写

收到一个地方统一定义

赶时间上线

全糊在一起

照样快,但拎出会变的部分

需求还不确定

做个"足够灵活"的大系统

只做"现在真需要"的

改一段代码

改哪算哪

先问"会不会牵连别处"

最后,送你一个「踩刹车」的习惯

下周,当你又要伸手改代码的时候,先停 30 秒。

就停半分钟,问自己三句话:

1. 我现在在共用什么?它会一直一样下去吗?2. 我现在在写的这段逻辑,会不会在别处重复出现?3. 我现在这个决定,三个月后的我,会想穿越回来揍我吗?

你回头看会发现,很多「当时觉得特别合理」的代码决定,其实都是在给几个月后的自己埋雷。

而所谓好的判断力,说穿了也不神秘——

就是在那个落笔的瞬间,比别人多踩了半脚刹车而已。

这半脚刹车,新手没有,老手有。它不靠天赋,靠的是一次次「三个月后回头看」攒出来的肌肉记忆。

从今天这篇开始练,不晚。

聊聊你的故事

你有没有过这样一个时刻:当时觉得自己写得特聪明,三个月后却后悔得想删库?

来评论区交作业,就说两点:

  • 当时你做了啥决定(比如「抽了个看着很通用的组件」)
  • 后来为啥后悔(比如「需求一分化,改起来差点把我逼疯」)

每一个「踩坑现场」,都是在帮后面的兄弟姐妹排雷。点赞最高的那个故事,我会放进下期文章里,配上『怎么避坑』的拆解。 👀

要是这篇让你想起了某段不堪回首的代码,转发给你那个还在疯狂抽组件的同事吧,救他于水火。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先说一个让我后背发凉的周一
  • 工程判断力,到底是个啥
  • 场景一:「长得像,那就合并吧」——新手最爱踩的第一个坑
    • ❌ 坏判断:表面看着像,就强行共用
    • ✅ 好判断:业务不一样,就大大方方让它们各过各的
    • 一张表看懂差别
  • 场景二:一个字段散落在五个地方——改个需求,能改到你想辞职
    • ❌ 坏判断:哪儿要用就哪儿写
    • ✅ 好判断:同一个「概念」,只在一个地方定义
    • 差别在哪
  • 场景三:赶时间上线没问题,但「会变的东西」得拎出来
    • ❌ 坏判断:图快,全糊在一起
    • ✅ 好判断:照样赶时间,但把「容易变的」单独拎出来
    • 差别在哪
  • 场景四:「为了将来」提前造轮子——最隐蔽、最烧钱的坑
    • ❌ 坏判断:脑补一堆需求,提前造个大而全的引擎
    • ✅ 好判断:别脑补,用「真实出现过的需求」反推
    • 差别在哪
    • 等等,为啥判断力这么难学?
  • 好消息:判断力是能练的,给你三个笨办法
    • 办法一:给自己设个「三个月回头看」
    • 办法二:动手改代码前,先问自己三个问题
    • 办法三:跟老司机聊天,但别问「怎么做」
  • 把判断力浓缩成三个问题
  • 一张总表,收藏备用
  • 最后,送你一个「踩刹车」的习惯
  • 聊聊你的故事
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档