首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >初级和高级程序员的差距,不在语法,而在这7个习惯

初级和高级程序员的差距,不在语法,而在这7个习惯

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

大多数人写代码越写越好,不是因为他们背下了更多语法,而是因为他们终于不再把代码搞得比它本该有的样子更复杂。

咱们先聊个有点扎心的事实。

刚入行那会儿,我一直以为:高级工程师之所以厉害,是因为他们脑子里装了更多"骚操作"——更冷门的 API、更花哨的写法、更高级的算法。我拼命刷这些,以为刷够了就能升级。

后来我跟一群真正的老兵共事过:扛过线上事故、半夜被叫起来回滚、数据库迁移迁到一半卡住、需求被产品改了八遍……我盯着他们的代码看,整个人是懵的。

他们的代码,一点都不炫。

没有花哨的抽象,没有那种"专门写来发朋友圈截图"的炫技,平平无奇,甚至有点无聊。

可奇怪的是——需求一变再变,别人的代码改崩了,他们的代码还活着。

我后来才明白:初级和高级真正的差距,不在"会多少语法",而在 7 个判断习惯。这些习惯跟你用 React 还是 Vue 没关系,它们决定的是——bug 好不好找、代码好不好改、系统会不会被下一个人误解。

这篇文章,我尽量讲得让刚入行的同学也能看懂。每一条都配一个真实业务场景,咱们一个一个来。

习惯 1:先把"坏情况"挡在门外,而不是把逻辑越埋越深

📌 先看个场景

假设你在做一个"修改用户资料"的功能。需求很简单:用户改个昵称、邮箱就行。

小白通常这么写——像挖隧道一样,每个判断都往里钻一层:

代码语言:javascript
复制
async function updateUserProfile(userId, input) {
const user = await getUser(userId);
if (user) {                       // 用户存在吗?
    if (input.email) {              // 邮箱填了吗?
      if (user.canEditProfile) {    // 有权限改吗?
        return saveProfile(user.id, input);  // ← 真正干活的代码在这
      }
    }
  }
thrownewError("更新失败");
}

刚写完看着没问题,对吧?功能也能跑。

但问题来了:过两周,产品说"得加个账号状态检查";再过一周,"还要验证邮箱是否激活";又过几天,"加上操作日志"。每加一条,你就得往里再套一层 if。最后这函数缩进到第六层,真正干活的那行代码被埋在最深处,谁来读都头疼。

高级工程师怎么写? 反过来——先把所有"坏情况"挡在门外,专业叫法是「提前返回」或「卫语句(guard clause)」:

代码语言:javascript
复制
async function updateUserProfile(userId: string, input: ProfileInput) {
const user = await getUser(userId);

if (!user) thrownew NotFoundError("用户不存在");       // 不满足条件,直接打回
if (!input.email) thrownew ValidationError("邮箱必填");
if (!user.canEditProfile) thrownew ForbiddenError("无权编辑资料");

return saveProfile(user.id, input);  // ← 走到这,说明前面全过了,放心干活
}

打个比方:这就像进小区。保安在大门口就把没带门禁卡的、不是本小区的人拦下来,进了门的都是合法住户,里面就不用再反复盘问了。而嵌套写法是——人都进电梯了、进家门了,还在一道道盘问"你是谁、你能进吗"。

代码语言:javascript
复制
嵌套写法(地窖式)                提前返回(保安式)
─────────                        ──────────
if (用户存在) {                   if (!用户存在) 打回
  if (邮箱填了) {                 if (!邮箱填了) 打回
    if (有权限) {                 if (!有权限) 打回
      ...真正逻辑...               ...真正逻辑...(顶格,一眼看到)
    }
  }
}
读者要在脑子里记住:3层状态      读者要记住:0层(看完即忘)

为什么这事重要? 因为读代码比写代码更费劲。尤其线上出 bug 时,你瞪着满屏代码找问题,提前返回能让你一眼看清"什么情况下会失败",而不是在六层缩进里迷路。

💡 小白记住一句话:进门先验票,验过票的就别再怀疑了。

习惯 2:给变量起"业务名字",而不是"技术名字"

📌 先看个场景

你在做电商后台,要给"还在生效的订阅用户"批量发续费提醒。小白随手写:

代码语言:javascript
复制
const data = await getData(id);
if (data.status === "active") {
  await process(data);
}

datastatusprocess——全是技术词,代码能跑,没毛病。

但半年后呢? 别人接手你的代码,看到 data 一脸懵:这是用户?是订单?是订阅?status === "active" 又是啥意思——账号激活?订阅生效?还是邮箱验证通过?他得把整个函数翻一遍才敢动。

更要命的是真实事故:我见过一个变量叫 activeUsers,团队都以为是"活跃用户",结果里面混着"被停用但还没删除"的用户,群发活动短信时全发出去了,被投诉一大片。名字没写错语法,但它误导了所有人

高级工程师怎么写? 用「业务含义」命名——让名字直接说人话:

代码语言:javascript
复制
const subscription = await getSubscription(subscriptionId);
if (subscription.isBillable) {          // isBillable = 这笔订阅能不能扣费
  await chargeSubscription(subscription);
}

看出区别了吗?第二种代码自己会"说话":这是一笔订阅(不是模糊的 data),关键条件是能不能扣费(不是模糊的 status)。下一个人不用猜,扫一眼就懂。

打个比方:这就像你冰箱里的剩菜。贴个标签写"2 月 14 日·红烧肉·三天内吃完",跟只写个"食物",完全是两回事。后者你过两天就不敢吃了——只能扔,或者赌一把。

代码语言:javascript
复制
// 名字长一点没关系,关键是表达清楚业务规则
const usersEligibleForReactivation = await findUsersEligibleForReactivation();
// 一看就懂:符合"可重新激活"条件的用户

💡 别走极端:循环里的 i、临时小回调,简单点没问题。真正要起好名字的,是那些"搞错了会出业务事故"的概念。

习惯 3:在"外部系统"周围筑一道墙

📌 先看个场景

你的项目接了微信支付。回调里要拿用户信息,小白直接这么取:

代码语言:javascript
复制
const userName = response.data.user_name;
const isActive = response.data.status === "ACTIVE";
const plan = response.data.subscription.plan_name;

一开始只在一个文件里写。慢慢地,controller 写一遍、后台任务写一遍、埋点写一遍、前端看板又写一遍……半个项目都知道微信返回的字段长什么样了。

然后灾难来了:微信某次小版本更新,把 user_name 改成了 userName。你的项目里有十几处直接引用了这个字段,全挂。改不难,难的是把每一处都找出来,找漏一个就是线上 bug。

高级工程师怎么做? 在外部系统和你自己的代码之间,建一道「墙」,专业叫「适配层」:

代码语言:javascript
复制
// 所有外部数据,先经过这个"翻译官",转成我们自己说了算的结构
function mapWechatUser(res: WechatUserResponse): Customer {
  return {
    id: res.id,
    name: res.user_name,                          // 对方改字段,只改这一行
    isBillable: res.status === "ACTIVE",
    planName: res.subscription?.plan_name ?? "免费版",
  };
}

这道墙的意义是:让你的系统内部,永远只跟"自己定义的数据结构"打交道。微信怎么改字段,是墙外面的事,墙里面纹丝不动。

打个比方:这就像公司前台。所有外来快递、访客,都先到前台登记、换证,再进办公区。前台就是那道墙——外面再乱,进到里面都是统一规整的。如果没有前台,每个工位都直接对接外卖小哥和快递员,那才叫鸡飞狗跳。

代码语言:javascript
复制
   外部世界(你管不着)          墙          内部世界(你说了算)
┌──────────────────────┐    ┌────────┐    ┌──────────────────────┐
│ 微信支付 / 支付宝      │    │ 适配层  │    │  统一的 Customer 模型 │
│ 字段随版本乱改         │ →  │ map()  │ →  │  对方改名?只改墙这一处│
│ 第三方 SDK 响应        │    │ 一处   │    │  其它地方全不动        │
└──────────────────────┘    └────────┘    └──────────────────────┘

💡 小项目不用过度设计。但只要一份外部数据被多个功能用到,就该给它建一道墙。

习惯 4:让"不可能的状态"根本没法出现

📌 先看个场景

有次前辈 review 我代码,说了句让我当时挺恼火的话:**"你这写法,让不可能的情况也能存在。"** 功能明明能跑啊!结果两周后真崩了。

问题出在我把用户对象的字段全写成了"可选":

代码语言:javascript
复制
type User = {
  id?: string;       // 问号 = 这个字段可能有,也可能没有
  email?: string;
  role?: string;
  status?: string;
};

为啥全加问号?因为不加,TypeScript 就报错,加了就不报了,省事。但这不是省事,这是把问题往后拖。

结果整个项目到处都得提心吊胆地问:这用户有 ID 吗?有邮箱吗?能保存吗?能展示吗?——因为类型告诉所有人"这些都可能没有"。

真实事故:给一个"还没验证邮箱的用户"发了正式通知邮件;给一笔"从没扣过款的订单"执行了退款。因为代码假装"草稿用户"和"正式用户"是同一个东西。

高级工程师怎么做? 把不同的状态,老老实实分开建模

代码语言:javascript
复制
// 草稿用户:还没存进数据库,所以根本没有 id
type DraftUser = {
  email: string;
  role: "admin" | "member";
};

// 已保存用户:一定有 id 和 status,不用再问"有没有"
type SavedUser = {
  id: string;
  email: string;
  role: "admin" | "member";
  status: "active" | "disabled";
};

更进一步,像"订单状态"这种,可能有:待支付、已支付、已发货、已退款……与其用一个模糊的 status 字符串,不如把每种状态能带什么数据都写清楚:

代码语言:javascript
复制
type Payment =
  | { state: "pending"; id: string }                          // 待支付
  | { state: "captured"; id: string; receiptId: string }      // 已扣款,才有小票号
  | { state: "failed"; id: string; reason: string };          // 失败,才有原因

// 发收据的函数,只接受"已扣款"的支付
// 如果你传个"待支付"进来,代码编译都过不了 —— 错误在写的时候就被拦住
function sendReceipt(payment: Extract<Payment, { state: "captured" }>) {
  return emailReceipt(payment.receiptId);
}

打个比方:这就像考驾照。"学员"和"正式司机"是两种身份,不能混。让一个还没通过考试的学员单独上高速,本身就该是"系统不允许发生"的事,而不是"上了高速再去检查他有没有驾照"。

💡 这跟语言无关。用 JS 的话,靠校验、构造函数、schema 也能做到。核心纪律是:让错误用法在"写代码时"就暴露,而不是等到线上炸了才发现。

习惯 5:把"做不做的判断"和"具体动作"分开

📌 先看个场景

做退款功能。小白通常把"判断能不能退"和"实际退款动作"焊在一起:

代码语言:javascript
复制
async function refundInvoice(invoiceId) {
const invoice = await getInvoice(invoiceId);
if (invoice.status !== "paid") thrownewError("未付款,不可退");
if (invoice.refundedAt) thrownewError("已经退过了");
if (invoice.amount <= 0) thrownewError("金额非法");

await paymentProvider.refund(invoice.paymentId);  // 真正退款
await markInvoiceRefunded(invoice.id);            // 改数据库
await sendRefundEmail(invoice.customerId);        // 发邮件
}

能跑,也不算丑。但有个隐患:你想写个测试,验证"退款规则对不对",结果发现——为了测那几行 if,你得把支付接口、数据库、邮件服务全都假装(mock)一遍,太麻烦了。于是大家干脆不写测试。然后退款规则某天被改坏了,没人发现。

高级工程师怎么做? 把"判断"单独抽出来,做成一个只算结果、不碰外部世界的「纯函数」:

代码语言:javascript
复制
// 只负责判断:能不能退?为什么不能退? 不碰数据库、不发邮件
function getRefundEligibility(invoice: Invoice): RefundEligibility {
  if (invoice.status !== "paid") return { allowed: false, reason: "未付款" };
  if (invoice.refundedAt) return { allowed: false, reason: "已退款" };
  if (invoice.amount <= 0) return { allowed: false, reason: "金额非法" };
  return { allowed: true };
}

"动作"部分就变得很干净:

代码语言:javascript
复制
async function refundInvoice(invoiceId: string) {
const invoice = await getInvoice(invoiceId);
const eligibility = getRefundEligibility(invoice);   // 先问:能退吗?
if (!eligibility.allowed) thrownew ValidationError(eligibility.reason);

await paymentProvider.refund(invoice.paymentId);     // 能退,再动手
await markInvoiceRefunded(invoice.id);
await sendRefundEmail(invoice.customerId);
}

现在测"退款规则",直接喂几个假订单给 getRefundEligibility 就行,不用 mock 任何东西

打个比方:这就像医院。医生(判断)先看检查报告决定"能不能做手术",护士(动作)再去执行。你想考核医生的判断水平,给他几份病例看就行,不用真把病人推进手术室。判断和动作搅在一起,等于让医生边判断边开刀,没法单独检验任何一环。

💡 别矫枉过正:三行就能搞定、又不会变的简单逻辑,不用硬拆。但凡这个判断关系到钱、关系到权限,就给它一个名字、一个测试。

习惯 6:让报错对"下一个人"有用

📌 先看个场景

用户注册时邮箱重复了。小白的报错:

代码语言:javascript
复制
{ "message": "出错了" }

对谁有用? 用户一脸懵不知道哪错了;客服收到投诉不知道咋回事;半夜看日志的你,更是一头雾水。

更坑的是,前端为了区分错误,会去"猜"报错文字:

代码语言:javascript
复制
// 人质式代码:后端哪天把文案改成"邮箱已注册",这里立刻失效
if (error.message.includes("已存在")) {
  showEmailTakenError();
}

这就埋了个雷——后端改个文案、翻译改个措辞,前端逻辑就崩了,而且崩得悄无声息。

高级工程师怎么做? 把错误当成「给系统看的信号」,而不只是"给人看的一句话":

代码语言:javascript
复制
{
  "code": "USER_EMAIL_ALREADY_EXISTS",   // 给程序判断用的错误码,永远不变
  "message": "该邮箱已被注册",            // 给人看的提示,随便改
  "details": { "field": "email" },        // 错在哪个字段
  "requestId": "req_8f91a2"               // 追踪 ID,查日志一搜就到
}

前端只认 code,后端怎么改文案都不影响。日志里也带上结构化信息(但千万别记密码、token、身份证、银行卡这些敏感数据):

代码语言:javascript
复制
logger.warn("退款被拒", { invoiceId, customerId, reason: eligibility.reason, requestId });

打个比方:这就像看病。一句"我不舒服"医生没法治;但"我昨晚 8 点开始右下腹疼、伴随发烧、化验单编号 xxx",医生立刻能下手。好的报错,就是把"我不舒服"变成"可追踪的病历"。

💡 文案是给人看的,错误码是给系统看的。别指望一句模糊的人话,能同时喂饱用户、客服、日志和前端。

习惯 7:写代码要为"别人审查"着想,而不是为"演示一遍"着想

📌 先看个场景

你做完一个计费功能,本地跑得飞起,开心地提交代码,一个 PR(合并请求)里塞了一大堆改动:

代码语言:javascript
复制
反面教材的提交(活像一张"绑匪勒索信"):
feat: 更新计费流程
├─ 重构了 invoice 服务
├─ 重命名了支付字段
├─ 改了退款逻辑
├─ 顺手改了看板 UI
├─ 加了 webhook 处理
├─ 改了重试行为
├─ 又顺手修了个状态 bug
└─ 更新了测试
   审查的同事:哪些是必须的?哪些是你顺手改的?bug 藏哪了?😵

问题在哪? 功能是好的,但这堆改动混在一起,审查的人根本看不清。bug 藏在一堆噪音里。更糟的是——万一这功能上线出问题要回滚,一撤销,把你顺手做的清理、改的 UI 全带走了

高级工程师怎么做? 他们按「diff(改动差异)」来思考——一次只讲清楚一件事,拆成几个小 PR:

代码语言:javascript
复制
PR 1:重命名支付字段(不改任何行为)
PR 2:新增"退款资格判断" + 测试
PR 3:把退款判断接进计费流程
PR 4:看板 UI 展示退款原因
PR 5:加 webhook 重试

每个 PR 都在讲一个清楚的故事:改了什么、为什么改、行为有什么变化。审查快、出问题好定位、回滚也安全。

打个比方:这就像搬家。把书、衣服、厨具分箱打包、贴好标签,跟一股脑全塞进一个大箱子——搬的时候没差别,但找东西的时候,天壤之别。线上事故现场,一个小而清楚的改动是救命稻草;一个啥都改了的大改动,是灾难。

💡 记住:代码不是"在你电脑上跑通了"就算完成,而是"改动清楚、能审查、能测试、能安全撤回"才算完成。

7 个习惯背后,其实是同一件事

咱们回头看这 7 条:提前返回、好命名、筑墙、状态建模、拆分判断、有用的报错、可审查的改动——表面五花八门,骨子里是同一个理念:

减少惊喜(Reduce surprise)。

高级工程师不是不会复杂的东西,而是他们明白一个道理:复杂度总得待在某个地方。 你要是不把它放进清晰的命名、明确的边界、诚实的类型、聚焦的函数里,它就会跑进每个人的脑子里——然后整个团队都得靠"记忆"和"猜"来干活。

那才是软件让人精疲力竭的根源。一个混乱的代码库,会让人改之前犹豫、疯狂手动测试(因为不信任系统)、在群里反复问同样的问题、绕着老代码走、照抄看不懂的写法、提个小改动却一身冷汗。

这不是因为他菜,而是代码库一直在让每一次改动都显得很冒险。

我共事过的最好的工程师,从不想证明自己多聪明。他们想的,是让下一次改动别那么蠢——而那个"下一个人",常常就是三个月后的自己。

所以别再以为高手赢在"会得多"。

好代码不是炫耀你懂多少,而是让下一个人少几个需要瞎猜的理由。

如果你刚入行,这 7 个习惯不用一次全学会。挑一个,从下一行代码开始用起来,半年后回头看,你会发现自己的代码"无聊"了很多——而这,正是变强的样子。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 习惯 1:先把"坏情况"挡在门外,而不是把逻辑越埋越深
  • 习惯 2:给变量起"业务名字",而不是"技术名字"
  • 习惯 3:在"外部系统"周围筑一道墙
  • 习惯 4:让"不可能的状态"根本没法出现
  • 习惯 5:把"做不做的判断"和"具体动作"分开
  • 习惯 6:让报错对"下一个人"有用
  • 习惯 7:写代码要为"别人审查"着想,而不是为"演示一遍"着想
  • 7 个习惯背后,其实是同一件事
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档