1. 从“能跑就行”到“敢改敢删”:工程师成长的第一个分水岭
刚入行那几年,我特别迷恋一种状态:代码能跑通,功能能交付,线上不出事,就觉得自己已经“稳了”。直到有一次接手一个老项目,需要在一个核心模块里加一个看似简单的字段。我花了整整两天,改了十几处地方,每改一处都要重新跑一遍全量回归,生怕漏掉某个隐藏的依赖。最后功能是上线了,但我心里清楚,这不是我厉害,是我运气好——那个模块的耦合还没到“动一发而牵全身”的程度。
后来我才慢慢意识到,工程师之路的第一个分水岭,不是你会多少框架、背多少八股,而是你面对一坨能跑的代码时,敢不敢动、知不知道从哪里动、动完之后能不能证明它还是对的。这个能力,我把它叫做“代码掌控力”。它跟工作年限没有必然关系,我见过三年经验就能把遗留系统拆得干干净净的人,也见过八年经验还在“能跑就行”里打转的人。
为什么“能跑就行”是一个陷阱?因为软件的本质是变化。需求会变、流量会变、依赖会变、团队会变。一个只能“跑”但不能“改”的系统,它的每一次变化都在积累技术债务。债务本身不可怕,可怕的是利息——当你发现改一个按钮颜色需要动五个服务、发三次版、等两天审批的时候,你就知道利息已经高到还不起了。
那怎么建立代码掌控力?我的经验是从三个动作开始:读、画、拆。读,不是从头到尾看一遍,而是带着问题读——这个功能的入口在哪里?数据从哪来到哪去?哪些地方是“只读”的,哪些地方是“写”的?画,是把读到的信息画成图,不一定是UML,白板上画几个框几条线就行,关键是让隐性的依赖显性化。拆,是在画的基础上找边界,把能独立出来的逻辑先独立出来,哪怕只是一个函数、一个类、一个模块。
这三个动作听起来简单,但做起来需要刻意练习。我自己的习惯是,每接手一个新模块,先花半天时间只读不写,把入口、出口、核心数据结构、外部依赖列成一张表。这张表不需要给任何人看,但它是我后续所有改动的“地图”。没有这张地图就动手,跟蒙着眼睛拆炸弹没什么区别。
提示:如果你现在手上的项目让你觉得“不敢动”,先别急着重构。花一周时间只做“读”和“画”,把依赖关系摸清楚。很多时候,恐惧来源于未知,而不是复杂度本身。
2. 技术选型不是选“最好的”,而是选“最不坏的”
工作前几年,我特别容易陷入一种争论:这个场景到底该用A框架还是B框架?A性能好,B生态全,C语法优雅,D社区活跃。每次讨论都能列出一堆对比表格,但最后往往是谁声音大听谁的。后来我慢慢发现,技术选型这件事,大多数时候不是在选“最好的”,而是在选“最不坏的”——因为每个选项都有代价,你要做的是找到那个代价你能承受、且愿意承受的。
举个例子。有一年我们要做一个内部工具,团队里有人提议用当时很火的一个前端框架,理由是“生态好、组件多、招人容易”。但我们的场景是什么?是一个只有十几个页面、用户不超过五十人、生命周期可能只有半年的内部系统。用那个框架意味着什么?意味着构建配置、状态管理、路由、打包优化,一整套东西都要配齐,光脚手架搭起来就要两天。而如果用最朴素的HTML加一点轻量脚本,可能半天就能出原型。
这不是说那个框架不好,而是说它的“好”在我们的场景里用不上,但它的“重”我们得全盘接受。技术选型的本质是匹配:匹配团队能力、匹配业务阶段、匹配维护成本、匹配未来可能的变化方向。你选的不是一个工具,而是一整套工作方式和协作契约。
我后来总结了一个简单的判断框架,叫“三问法”:
- 第一问:这个技术解决的核心问题,是不是我们当前最大的痛点?如果我们的痛点是人手不够、交付慢,那选一个学习曲线陡峭但“未来可扩展”的技术,就是错配。
- 第二问:如果明天负责这个模块的人离职了,剩下的人能不能接住?技术选型要考虑团队的平均水平,而不是最高水平。一个只有一个人会用的技术栈,就是一颗定时炸弹。
- 第三问:三年后我们还在用它的概率有多大?这不是让你预测未来,而是让你评估这个技术的稳定性和社区健康度。如果一个技术半年发一个大版本、每次都有破坏性变更,那它就不适合放在核心链路上。
这三问没有标准答案,但它能帮你把讨论从“哪个更好”拉到“哪个更适合我们”。我见过太多团队在选型上吵得不可开交,最后选了一个“技术上最优”的方案,结果因为团队没人熟悉、文档跟不上、出问题找不到人问,反而拖慢了整个项目。选型不是考试,没有标准答案,只有合不合适。
注意:不要因为“别人都在用”就选一个技术。别人的场景和你的场景可能完全不同。选型前先把自己的场景写下来:用户量、数据量、团队规模、交付节奏、维护周期。这些数字比任何对比表格都有说服力。
3. 调试不是“猜”,是“缩小范围”的系统工程
如果说写代码是工程师的日常,那调试就是工程师的照妖镜。我见过很多人调试的方式是:改一行、跑一下、不行再改一行、再跑一下。这种方式在简单问题上有效,但一旦遇到复杂问题,就会变成“随机漫步”——你改的每一行都可能引入新的变量,最后你连问题是什么都说不清楚了。
调试的本质是什么?是在不确定中寻找确定。你有一个现象(比如接口返回500),你有一堆可能的原因(代码bug、配置错误、依赖挂了、数据问题、网络抖动),你要做的是通过一系列操作,把“可能的原因”一个一个排除掉,直到剩下那个“确定的原因”。这个过程不是靠灵感,是靠方法。
我的调试方法分四步:复现、隔离、假设、验证。
复现是第一步,也是最容易被跳过的一步。很多人一看到报错就急着去改代码,但连问题能不能稳定复现都不知道。如果一个问题只能偶现,那它的根因大概率不在业务逻辑里,而在并发、资源、时序这些地方。复现的目的是把“偶发”变成“必现”,把“线上”搬到“本地”,把“不可控”变成“可控”。
隔离是第二步。一旦能复现,就要开始缩小范围。怎么缩?二分法。如果是接口问题,先看是入口参数的问题还是内部逻辑的问题——把参数写死,看还报不报错。如果是内部逻辑,把逻辑分成两半,注释掉一半,看问题还在不在。二分法的好处是,你不需要理解整个系统,只需要理解“这一半”和“那一半”的边界。
假设是第三步。隔离到一定程度,你心里应该有几个候选原因了。这时候不要急着改代码,先写下来:我怀疑是A导致的,因为B现象支持这个怀疑。然后设计一个实验去验证这个假设。实验的设计原则是:如果假设成立,结果应该是什么;如果假设不成立,结果又应该是什么。这样你才能从实验结果中得出确定结论,而不是“好像好了”。
验证是最后一步。改完代码、问题消失,不代表调试结束。你还要回答两个问题:第一,这个问题为什么之前没发现?是测试覆盖不够,还是监控缺失?第二,同样的问题会不会在别的地方出现?如果是共性问题,就要考虑加防护、加告警、加测试用例。
我自己的经验是,调试最耗时的不是“找原因”,而是“确认原因”。很多时候你改了一个地方,问题消失了,但你不确定是不是真的因为这个改动。这时候如果不去验证,下次同样的问题换个形式出现,你还是束手无策。所以调试的终点不是“问题没了”,而是“我知道问题为什么没了”。
提示:调试时养成写“调试日志”的习惯。不是代码里的日志,是你自己的笔记:我试了什么、结果是什么、我排除了什么、我下一步打算试什么。这个习惯能让你在复杂问题面前保持清醒,也能在事后复盘时找到自己的思维盲区。
4. 从“完成任务”到“定义问题”:工程师的第二次跃迁
如果说第一个分水岭是“敢改代码”,那第二个分水岭就是“会定义问题”。这两个阶段的工作方式完全不同。第一个阶段,你拿到的是一个明确的任务:把这个按钮改成蓝色、把这个接口响应时间降到200毫秒、把这个bug修掉。第二个阶段,你拿到的是一个模糊的需求:用户说“不好用”、老板说“要提升效率”、产品说“要增加留存”。这时候没有人告诉你具体要做什么,你需要自己把“不好用”翻译成“哪个环节的哪个操作导致了什么体验问题”,把“提升效率”翻译成“哪个流程的哪个步骤可以自动化或简化”。
这个翻译过程,就是定义问题。它比解决问题更难,因为解决问题有标准答案,定义问题没有。你定义得对不对,往往要等做出来才知道。但如果你定义错了,做得再快再好也是白费。
我经历过一次典型的“定义问题”失败。当时业务方说“报表加载太慢”,我们团队的第一反应是优化查询、加缓存、换数据库。花了两周,把加载时间从8秒降到了2秒。结果业务方说:“还是慢。”我们问:“2秒还慢?”他们说:“我们要的不是加载快,我们要的是不用等——打开页面就能看到数据。”这时候我们才意识到,真正的问题不是“加载速度”,而是“等待体验”。解决方案不是优化查询,而是预计算加推送。方向错了,努力全废。
那怎么定义问题?我的方法是往上问三层,往下拆三层。
往上问三层,是问“为什么”。业务方说“报表慢”,为什么慢?因为数据量大。为什么数据量大?因为历史数据没清理。为什么没清理?因为没人负责。问到第三层,你可能会发现真正的问题是“数据治理缺失”,而不是“查询性能不足”。往上问的目的是找到问题的根源,而不是停留在表面现象。
往下拆三层,是拆“是什么”。业务方说“不好用”,不好用是什么?是操作步骤多?是反馈不及时?是错误提示不清晰?拆到第三层,你可能会发现“不好用”具体指的是“提交表单后没有成功提示,用户不知道有没有提交成功”。往下拆的目的是把模糊的感受变成具体的、可操作的问题描述。
这两个方向结合起来,你就能把一个“模糊需求”变成一个“可执行的问题定义”。比如:“用户在提交表单后,由于缺乏明确的成功反馈,导致重复提交率高达15%。我们需要在提交后立即显示成功状态,并禁用提交按钮。”这个定义里,有场景、有数据、有原因、有目标。基于这个定义去设计方案,就不会跑偏。
注意:定义问题的时候,一定要跟需求方确认你的理解。不要自己闷头拆完就开干。把你的问题定义用一句话写出来,发给需求方:“我理解你要解决的是XXX,对吗?”这一句话能帮你省掉后面两周的返工。
5. 工程师的“软技能”不是软,是另一种硬
很多技术出身的人有一个误区:觉得只要技术够硬,其他都不重要。我早期也这么想,直到有一次做技术方案评审,我准备了一堆架构图、性能数据、对比分析,讲了四十分钟,结果评审会上业务方问了一个问题:“这个方案上线后,我们运营团队需要多做什么操作?”我愣住了,因为我根本没想过这个问题。方案技术上没问题,但运营团队要多学一套后台、多走三个审批流程,他们不干了。
这件事让我意识到,工程师的“软技能”不是锦上添花,而是决定你的技术方案能不能落地的关键。你技术再强,如果沟通不清楚、协作不顺畅、不考虑上下游的感受,你的方案就是空中楼阁。
那工程师需要哪些“软技能”?我自己的体会是三个:翻译、对齐、兜底。
翻译是把技术语言翻译成业务语言,也把业务语言翻译成技术语言。你跟产品经理说“这个接口的QPS扛不住”,他听不懂;你说“这个功能上线后,如果同时有一百个人用,页面会卡住”,他就懂了。反过来,产品经理说“这个体验要丝滑”,你要翻译成“首屏加载时间小于1秒、操作响应小于200毫秒、无阻塞式弹窗”。翻译能力决定了你能不能准确理解需求,也决定了别人能不能准确理解你的方案。
对齐是在动手之前,确保所有相关方对目标、范围、优先级有一致的理解。很多项目延期不是因为技术难,是因为大家对“做完”的定义不一样。开发觉得“功能跑通”就是做完,测试觉得“没有bug”才是做完,产品觉得“用户能用起来”才是做完。对齐就是把这些不同的“做完”拉到同一个标准上。怎么对齐?写下来。把目标、范围、验收标准写成文档,让所有人确认。不要怕麻烦,这一步省下来的时间,后面会加倍还回去。
兜底是在方案设计时,考虑“如果出问题了怎么办”。这不是悲观,是负责。你设计一个功能,要考虑如果依赖的服务挂了怎么办、如果数据量超预期怎么办、如果用户操作不符合预期怎么办。兜底不是让你做所有可能的异常处理,而是让你在关键路径上有预案。比如:核心功能降级、非核心功能熔断、数据不一致时的修复工具。这些东西平时用不上,但用上的时候能救命。
我后来带团队,面试的时候除了看技术能力,一定会问一个问题:“讲一个你跟别人合作时遇到的冲突,你是怎么处理的?”这个问题没有标准答案,但能看出一个人有没有“协作意识”。技术可以学,但协作意识往往决定了一个人能不能从“个人贡献者”走到“团队贡献者”。
提示:每次做技术方案,除了技术部分,加一页“影响面分析”:这个改动会影响哪些团队、哪些流程、哪些数据。这一页往往比技术部分更能帮你赢得支持。
6. 持续学习不是“学新东西”,是“更新操作系统”
技术行业的人都知道要持续学习,但很多人对“学习”的理解是:学一个新框架、学一门新语言、学一个新工具。这当然没错,但如果只停留在“学新东西”的层面,你会发现越学越焦虑——因为新东西永远学不完,而且你学得再快,也快不过技术迭代的速度。
我后来把持续学习分成两个层面:应用层学习和操作系统层学习。应用层学习就是学具体的技术、工具、框架,这些东西有生命周期,今天有用明天可能就过时了。操作系统层学习是更新你的思维方式、认知模型、判断框架,这些东西不会过时,而且能帮你更快地学会应用层的东西。
举个例子。你学了一个新框架,这是应用层学习。但如果你在学习的过程中,总结出了“这个框架解决的是什么类型的问题”“它的设计取舍是什么”“它在什么场景下不适用”,那你就是在更新操作系统。下次再遇到一个新框架,你不需要从头学起,只需要问同样的问题,就能快速判断它适不适合你的场景。
那怎么更新操作系统?我的方法是三件事:复盘、输出、跨界。
复盘不是写流水账,是问自己:这件事我做对了什么?做错了什么?如果重来一次,我会怎么做?复盘的关键是找到“可复用的经验”和“可避免的坑”。我自己的习惯是每做完一个项目,写一份“项目复盘”,不发给别人,只给自己看。这份复盘里没有套话,只有真实的想法和教训。
输出是逼自己把学到的东西讲清楚。写文章、做分享、带新人都可以。输出的过程是二次思考的过程,很多你以为想明白的事情,一写出来就发现逻辑不通。输出还能帮你建立个人品牌,让机会主动来找你。我很多合作机会都是因为别人看了我写的东西找过来的。
跨界是跳出技术看技术。我有一段时间特别喜欢看产品、设计、运营方面的书,一开始觉得跟技术没关系,后来发现这些视角能帮我更好地理解需求、更好地设计方案。技术不是孤立的,它服务于业务、服务于用户、服务于团队。你越理解上下游,你的技术方案就越容易落地。
持续学习的终点不是“什么都会”,而是“知道自己不会什么,并且知道怎么快速学会”。技术变化越快,这个能力就越重要。你不可能永远跑在技术最前沿,但你可以保持一个“随时能学”的状态。
注意:不要为了学习而学习。学一个东西之前,先问自己:我为什么要学它?它能解决我当前的什么问题?如果答不上来,就先别学。学习的时间是有限的,要花在刀刃上。
7. 关于职业选择:没有最优解,只有当下最合适的解
工作这些年,我换过几家公司,也见过很多人换工作。我发现一个规律:大多数人在职业选择上的焦虑,不是因为选项太少,而是因为想要“最优解”。想去大厂怕螺丝钉,想去创业怕不稳定,想留在大城市怕压力,想回老家怕没机会。每个选项都有利有弊,于是就在纠结中消耗了大量时间。
我自己的体会是,职业选择没有最优解,只有当下最合适的解。什么叫“当下最合适”?就是结合你当前的能力、资源、阶段、目标,选一个能让你“下一步走得更好”的选项。注意,是“下一步”,不是“终点”。职业发展不是一条直线,是一连串的台阶。你不需要一次跳到最高处,你只需要找到下一个能踩稳的台阶。
那怎么判断一个机会是不是“当下最合适”?我一般看三个维度:成长、回报、消耗。
成长是这个机会能不能让你学到新东西、接触新场景、提升新能力。如果一份工作你闭着眼睛都能做,那它对你的成长价值就很低。成长不一定是学新技术,也可以是学新业务、新协作方式、新管理方法。
回报是物质回报和精神回报的总和。物质回报包括薪资、福利、期权,精神回报包括成就感、被认可、工作氛围。这两者都重要,但不同阶段权重不同。刚入行的时候,成长权重大一些;有了家庭之后,物质回报的权重会上升。这没有对错,只有选择。
消耗是这个机会的隐性成本。加班多不多?通勤远不远?团队氛围好不好?业务方向你认不认同?这些消耗不会写在offer里,但它们每天都在影响你的状态。一份消耗太大的工作,即使成长和回报都不错,也很难持久。
这三个维度没有标准答案,但你可以给自己打分。比如成长8分、回报6分、消耗3分(消耗越低越好),综合下来就是一个参考。当然,打分是主观的,但打分的过程能帮你理清自己真正在意什么。
我见过很多人因为“别人都说好”而选了一个机会,结果去了之后发现完全不适合自己。也见过很多人因为“怕选错”而一直不选,结果错过了最佳的时间窗口。职业选择这件事,没有完美的决策,只有承担后果的勇气。你选了A,就接受A的代价;你选了B,就接受B的代价。最怕的是选了A又惦记B,最后两边都做不好。
提示:做职业选择的时候,不要只看“这个公司怎么样”,要看“这个岗位的具体工作是什么”。同一个公司,不同岗位的体验可能天差地别。面试的时候多问细节:团队多少人、项目什么阶段、我进去负责什么、考核标准是什么。这些信息比公司名气重要得多。
8. 写给刚入行的同学:前三年最重要的事
如果你刚入行,或者还在学校准备入行,我想分享几个我自己的体会。这些体会不一定对,但都是我踩过坑之后总结出来的。
第一,前三年不要怕做“脏活累活”。很多人觉得写业务代码没意思,想做架构、做底层、做算法。但业务代码是理解系统的最好入口。你写多了业务代码,才知道哪些地方容易出问题、哪些设计是合理的、哪些需求是伪需求。这些经验是架构设计的基础。没有业务体感的架构师,设计出来的东西往往好看不好用。
第二,养成写文档的习惯。不是那种给领导看的汇报文档,是给自己看的笔记。你今天解决了一个什么问题、怎么解决的、为什么这么解决、有没有更好的方案。这些笔记当时看没什么用,但半年后你回头看,会发现自己的成长轨迹。而且写文档能逼你把问题想清楚,很多你以为懂了的东西,一写就露馅。
第三,主动找反馈。不要等绩效评估的时候才知道自己做得好不好。平时就多问:这个方案你觉得怎么样?我哪里可以改进?有没有更好的做法?主动找反馈的人,成长速度是被动等待的人的几倍。因为反馈能帮你看到自己的盲区,而盲区是自己看不到的。
第四,建立自己的“工具箱”。这个工具箱里不只有技术工具,还有方法论、模板、检查清单。比如:调试的检查清单、方案评审的模板、项目复盘的框架。这些东西能让你在遇到类似问题时快速上手,不用每次都从头想。
第五,保护好你的热情。工程师这个职业,热情比能力更重要。能力可以培养,但热情一旦没了,就很难找回来。怎么保护热情?做你觉得有意义的事、跟让你舒服的人合作、给自己留出学习的时间。如果一份工作让你每天都不想打开编辑器,那它可能不适合你。
我到现在还记得我写的第一行上线代码,是一个很简单的功能,但我盯着监控看了半个小时,生怕出问题。那种紧张和兴奋,现在还能想起来。后来代码写多了,紧张感少了,但兴奋感还在——每次解决一个难题、每次看到自己的方案落地、每次收到用户的正面反馈,那种感觉就是我一直做下去的动力。
工程师这条路很长,没有捷径,但有很多风景。你不需要跑得最快,你只需要一直在跑。