1. 从“能跑就行”到“敢改敢重构”:工程师成长的第一个分水岭
刚入行那会儿,我对“工程师”这三个字的理解特别朴素——能把需求做出来、代码能跑通、测试不报错,就算完成任务。直到有一次接手一个离职同事留下的模块,我才意识到问题没那么简单。那个模块表面上运行正常,但里面塞满了硬编码的路径、重复三遍的业务逻辑、以及一个注释写着“别动,动了会崩”的函数。我当时的第一反应是“能跑就行,别碰”,但需求方第二天就提了一个新功能,必须改这个模块。
这就是我想聊的第一个分水岭:你什么时候开始不满足于“能跑就行”,而是愿意去理解代码为什么这样写、能不能写得更好。这个转变不是靠看几篇文章就能完成的,它往往来自一次被迫的修改、一次线上事故、或者一次代码评审被人问得哑口无言。
我后来复盘那段经历,发现“能跑就行”的心态背后其实是三个恐惧:怕改坏、怕看不懂、怕担责任。而突破这三个恐惧,需要的不是天赋,是一套可操作的方法。
1.1 先建立“代码地图”,再动手改任何一行
很多人拿到陌生代码就急着打开IDE逐行读,这是效率最低的方式。我的做法是先画一张“代码地图”,具体分三步:
第一步,找到入口。不管是Web服务、定时任务还是命令行工具,一定有一个启动入口。从入口开始,沿着主流程往下追,只追主干,不追细节。比如一个HTTP接口,就从路由注册追到Controller,再到Service,再到DAO,把这条链路画出来。
第二步,标出数据流。每个环节输入什么、输出什么、依赖哪些外部资源(数据库、缓存、消息队列、第三方接口),用箭头标清楚。这一步能帮你快速识别哪些模块是核心、哪些是边缘。
第三步,标记“雷区”。那些注释里写着“临时方案”“待优化”“不要动”的地方,全部标红。不是让你马上改,而是让你知道哪些地方改的时候要格外小心。
这张地图不需要多精美,一张A4纸或者一个思维导图就够了。我自己的习惯是用纸笔手画,因为手画的过程会强迫你思考模块之间的关系,而不是机械地复制粘贴。
注意:画地图的时候不要陷入细节。看到一个复杂的算法实现,先跳过,标记“算法细节待查”,继续追主流程。地图的目的是建立全局观,不是替代代码阅读。
1.2 用“最小改动”验证理解,而不是“大重构”证明能力
新手最容易犯的错,是刚看懂一点代码就想着重构。我见过一个同事,接手一个老模块后花了两周时间重写,结果上线后出了三个P1故障,最后被回滚。不是说重构不对,而是时机不对。
正确的做法是:先用最小改动验证你对代码的理解是否正确。比如你要改一个业务逻辑,先找到对应的单元测试(如果没有就补一个),然后只改最核心的那几行,跑通测试,观察线上表现。如果一切正常,说明你的理解是对的;如果出了问题,说明你对某个依赖或边界条件的理解有偏差,这时候再去深入排查。
这个方法的好处是风险可控。最小改动意味着影响面小,即使出错也容易回滚。而且每次成功的最小改动都会增强你的信心,让你逐步敢碰更复杂的部分。
我自己的经验是,接手一个新模块的前两周,只做最小改动,不改结构、不换框架、不优化性能。两周之后,当你对模块的脾气摸得差不多了,再考虑做更大的调整。
1.3 把“怕担责任”转化为“可追溯的决策记录”
很多人不敢改代码,本质是怕出了问题背锅。这个心理很正常,但可以通过流程来化解。我的做法是:每一次非 trivial 的改动,都写一段简短的决策记录。
这段记录不需要多正式,可以就是一个Markdown文件或者代码注释,包含四个要素:
- 改了什么(What)
- 为什么改(Why)
- 考虑过哪些替代方案(Alternatives)
- 如果出问题,怎么回滚(Rollback)
比如:“将用户查询接口的缓存从本地缓存改为Redis,因为本地缓存在多实例部署下不一致。考虑过用一致性哈希,但实现成本高。回滚方案:改回本地缓存并重启。”
这段记录的作用不是给别人看,而是给你自己一个“决策锚点”。当线上出问题时,你能快速回忆起当时的思考过程,而不是一脸茫然。而且当别人质疑你的改动时,你有据可查,不是拍脑袋决定的。
2. 技术选型不是选“最好的”,而是选“最不坏的”
工作几年后,你会发现一个现象:同一个问题,永远有不止一种技术方案,而且每种方案都有人能说出它的好。消息队列选Kafka还是RabbitMQ?缓存用Redis还是Memcached?前端框架用React还是Vue?这些问题在技术社区里能吵上几天几夜。
但真实项目里的技术选型,往往不是在“最好”和“次好”之间选,而是在“最不坏”和“更不坏”之间选。因为每个方案都有它的代价,你要做的是找到那个代价你能承受、团队能驾驭、业务能接受的方案。
2.1 先搞清楚“约束条件”,再谈方案优劣
我见过太多选型讨论变成“信仰之争”,根本原因是大家没有先对齐约束条件。约束条件包括但不限于:
- 团队规模和技术栈熟悉度
- 项目时间线和人力预算
- 现有系统的兼容性要求
- 运维能力和监控体系
- 未来的扩展预期
举个例子,如果团队只有三个人,没人有Kafka运维经验,那即使Kafka在吞吐量上完胜RabbitMQ,对你来说Kafka也可能是更坏的选择。因为一旦出问题,你连排查的人都找不到。
我自己的习惯是,在选型讨论开始前,先花半小时把约束条件列出来,贴在白板上。任何方案如果明显违反约束条件,直接排除,不浪费时间争论。
2.2 用“原型验证”代替“PPT对比”
技术选型最怕的就是纸上谈兵。看了一堆博客和官方文档,觉得某个方案完美契合,结果一上手发现文档里没写的坑一大堆。
我的做法是:对每个候选方案,花半天到一天时间做一个最小原型。这个原型不需要实现完整功能,只需要验证三件事:
- 核心API用起来顺不顺手
- 官方文档和社区资料够不够解决常见问题
- 和你现有系统的集成有没有硬伤
比如选缓存方案,原型就是:起一个实例,写一个读写Demo,模拟一下缓存穿透和雪崩的场景,看看客户端库的API设计是否合理。这个过程花不了多少时间,但能帮你排除掉很多“看起来很美”的方案。
提示:原型验证的重点不是性能测试,而是“开发体验”和“集成成本”。性能数据可以查官方Benchmark,但开发体验只有自己上手才知道。
2.3 给选型留一个“退出通道”
技术选型最怕的是“锁死”。一旦选了一个方案,发现不合适,想换却换不掉,因为代码已经深度耦合了。
所以我在做任何选型时,都会问自己一个问题:如果半年后要换掉这个方案,成本有多大?如果成本很高,那就要在设计上做一层抽象,把具体实现隔离起来。
比如用消息队列,不要在业务代码里直接调用Kafka的Producer API,而是封装一个MessageBus接口,业务代码只依赖这个接口。这样将来要换RabbitMQ,只需要换一个实现类,业务代码不用动。
这层抽象会增加一点前期开发成本,但和“锁死”的风险相比,这点成本完全值得。我经历过一次从ActiveMQ迁移到RabbitMQ的过程,因为当初做了接口隔离,迁移只花了三天;而另一个没做隔离的模块,迁移花了两周,还出了一次线上故障。
3. 线上问题排查:从“瞎猜”到“有章法”
线上出问题的时候,最怕的不是问题本身,而是慌乱。我见过不少工程师,一听到告警就手忙脚乱,东改一下西改一下,结果问题没解决,反而引入了新问题。
排查线上问题是有章法的。这套章法不是天生的,是踩了足够多的坑之后总结出来的。下面是我自己常用的一套流程,不一定适用于所有场景,但能帮你从“瞎猜”过渡到“有章法”。
3.1 先止损,再定位,最后复盘
这是排查线上问题的铁律。但很多人在第一步就做错了——他们想先找到原因再止损。这是本末倒置。
止损的优先级永远高于定位。如果线上服务挂了,第一件事是恢复服务,而不是查日志找原因。恢复的手段包括:回滚、重启、切流量、降级。这些操作不需要你知道根因,只需要你知道“哪个操作能让服务先活过来”。
我自己的习惯是,在告警响起的那一刻,先问三个问题:
- 影响面有多大?(多少用户受影响,核心功能还是边缘功能)
- 有没有现成的止损手段?(回滚按钮在哪,降级开关在哪)
- 谁在负责这个模块?(找最熟悉的人,而不是自己硬扛)
这三个问题能在两分钟内帮你理清思路,避免一上来就扎进日志里。
止损之后,才是定位。定位的核心是缩小范围。不要一上来就看全量日志,而是先确定问题发生在哪个环节。比如一个请求链路是A→B→C→D,先看D的日志有没有报错,如果没有,再看C,以此类推。或者用二分法,先看中间的B,如果B正常,问题就在C或D。
3.2 日志不是越多越好,关键是要有“上下文”
很多团队的问题不是日志太少,而是日志太多。一个请求打了几百行日志,真正有用的就那几行。排查的时候像大海捞针。
我的经验是,日志要围绕“请求上下文”来打,而不是围绕“代码位置”来打。具体来说,每个请求进来时生成一个唯一的traceId,然后所有相关的日志都带上这个traceId。这样排查时只需要grep一个traceId,就能看到这个请求的完整链路。
除了traceId,还要记录关键的业务标识,比如用户ID、订单ID、设备ID。这样当用户反馈问题时,你能快速定位到他的请求。
另外,日志级别要合理。ERROR级别只记录真正需要人工介入的问题,WARN级别记录可恢复的异常,INFO级别记录关键状态变更。不要把DEBUG级别的日志打到线上,那会淹没真正重要的信息。
注意:日志里不要打印敏感信息,比如密码、身份证号、银行卡号。这不仅是合规问题,也是安全问题。我见过一个团队因为日志里打印了用户密码,被安全审计通报。
3.3 复盘不是追责,而是改进系统
问题解决之后,一定要复盘。但复盘的目的不是追责,而是改进系统。如果复盘变成了“谁的责任”的讨论,那下次出问题大家都会选择隐瞒,而不是及时上报。
一个好的复盘应该回答四个问题:
- 问题是什么?(客观描述,不带情绪)
- 根本原因是什么?(用“五个为什么”往下挖)
- 为什么没有更早发现?(监控、告警、测试的盲区)
- 怎么防止再次发生?(具体的改进措施,有负责人和时间点)
我参与过的最有效的一次复盘,是某个服务频繁OOM。大家一开始以为是内存泄漏,后来用“五个为什么”挖下去,发现根本原因是某个定时任务每次加载全量数据到内存,而数据量在半年内涨了十倍。改进措施不是“加内存”,而是“改成分页加载”。这个改进不仅解决了OOM,还顺带提升了任务执行速度。
4. 和“非技术”同事沟通:把技术语言翻译成业务语言
工程师的日常工作里,有相当一部分时间是在和非技术同事沟通——产品经理、设计师、运营、市场、客服。这些沟通的质量,直接影响你的工作产出和职业发展。
我见过很多技术很强的工程师,因为沟通问题导致项目延期、需求反复、甚至和产品经理关系紧张。问题往往不在于技术能力,而在于没有把技术语言翻译成业务语言。
4.1 产品经理说“这个需求很简单”,你怎么回应
“这个需求很简单,怎么要这么久?”——这句话大概是工程师最常听到、也最反感的一句话。但反感归反感,问题还是要解决。
我的做法是:不要直接反驳“不简单”,而是把“简单”拆解成具体的成本项。比如产品经理说“加一个导出Excel的功能,很简单吧”,你可以这样回应:
“导出功能本身不复杂,但有几个点需要确认:第一,导出的数据量有多大?如果超过十万行,需要异步导出,不然会超时;第二,导出的字段有哪些?如果需要关联多个表,查询逻辑要重新写;第三,导出文件的格式有没有要求?如果需要复杂的样式,可能要用专门的库。这三个点确认了,我才能给你一个准确的时间。”
这样回应的好处是,你把“简单”变成了具体的、可讨论的问题。产品经理要么提供更多信息,要么意识到确实没那么简单。而且你的态度是合作的,不是对抗的。
4.2 用“用户故事”代替“技术方案”来描述工作
和非技术同事沟通时,少讲技术方案,多讲用户故事。比如你要解释为什么要做数据库索引优化,不要说“因为查询走了全表扫描,需要加联合索引”,而要说“现在用户打开订单列表要等五秒,优化后能降到一秒以内”。
再比如,你要解释为什么要做服务拆分,不要说“因为单体应用耦合度太高,需要解耦”,而要说“现在改一个功能要全量发布,风险很大;拆分后可以单独发布,出问题也只影响一个模块”。
技术方案是你的事,用户价值是大家的事。当你用用户价值来描述工作时,非技术同事更容易理解你的工作量和优先级。
4.3 学会说“不”,但要有替代方案
工程师经常面临需求堆积的问题。产品经理、运营、老板都在提需求,你不可能全部做完。这时候要学会说“不”,但说“不”的方式很重要。
直接说“做不了”是最差的回应。好的回应是:“这个需求我现在做不了,因为手上有更高优先级的任务。但我可以给你一个替代方案:要么等两周,要么先用一个临时方案顶一下,你看哪个合适?”
替代方案可以是:简化版功能、手动操作流程、第三方工具、或者延期到下一个迭代。关键是让对方有选择,而不是被拒绝。
我自己的经验是,当你给出替代方案时,对方往往会接受,因为他们的核心诉求是“解决问题”,而不是“必须用你提的方案”。而且这种方式能建立信任——你不是在推卸工作,而是在帮对方想办法。
5. 持续学习:不是学更多,而是学得更聪明
技术行业的变化速度很快,新框架、新工具、新范式层出不穷。很多工程师陷入“学习焦虑”——觉得自己什么都要学,但什么都学不深。
我的观点是:持续学习的关键不是学更多,而是学得更聪明。具体来说,就是建立一套“学习过滤器”,帮你从海量信息中筛选出真正值得投入时间的内容。
5.1 区分“工具型知识”和“原理型知识”
工具型知识是具体的API、配置、命令,比如“怎么用Docker Compose启动一个MySQL”。这类知识更新快,但学习成本低,需要的时候查文档就行。
原理型知识是底层的机制、设计思想、权衡取舍,比如“数据库索引为什么用B+树而不是哈希表”。这类知识更新慢,但学习成本高,一旦掌握就能迁移到很多场景。
我的时间分配是:70%花在原理型知识上,30%花在工具型知识上。原理型知识让你有判断力,工具型知识让你能干活。两者缺一不可,但原理型知识的复利效应更大。
比如你理解了HTTP协议的原理,那不管是RESTful API还是GraphQL,你都能快速上手;你理解了并发模型,那不管是Java的线程池还是Go的goroutine,你都能理解它们的适用场景。
5.2 用“输出”倒逼“输入”
单纯地看书、看视频、看博客,学习效果其实很差。因为输入是被动的,你觉得自己懂了,但一动手就发现不会。
我的做法是:每学一个新东西,就强迫自己输出一篇笔记或者一个Demo。输出的形式可以是:
- 写一篇博客,用自己的话解释这个概念
- 做一个最小Demo,跑通核心流程
- 在团队内做一次分享,回答同事的提问
输出的过程会暴露你理解上的盲区。比如你以为自己懂了“CAP理论”,但当你试图向别人解释“为什么P和A不能同时满足”时,你会发现有些地方说不清楚。这些说不清楚的地方,就是你真正需要补的地方。
我自己的博客和笔记,大部分都是学习过程中的副产品。写的时候很痛苦,但写完之后的收获远大于痛苦。
5.3 建立“问题驱动”的学习习惯
最有效的学习不是“我要学XX”,而是“我要解决XX问题”。当你有一个具体问题时,学习就有了明确的目标和反馈。
比如你想学Kubernetes,不要从“Kubernetes架构”开始看,而是先问自己:“我要解决什么问题?”如果问题是“本地开发环境太复杂,想一键启动所有依赖”,那你就去学Docker Compose和Kind;如果问题是“线上服务扩容太慢,想自动化”,那你就去学Deployment和HPA。
问题驱动的学习有三个好处:第一,目标明确,不会迷失在细节里;第二,有即时反馈,解决了问题就是学会了;第三,记忆深刻,因为知识和场景绑定了。
我自己的经验是,那些真正掌握的技术,都是在解决实际问题中学到的;而那些为了“充实自己”而学的技术,大部分都忘了。
6. 职业路径:技术专家还是技术管理,不是二选一
工作五到八年后,大部分工程师会面临一个选择:继续走技术路线,还是转管理。这个问题没有标准答案,但有一些思考框架可以帮你理清思路。
6.1 先搞清楚“管理”到底做什么
很多工程师对管理的理解是“开会、分配任务、写PPT”。这确实是管理的一部分,但不是全部。管理的核心是通过他人拿结果,具体包括:
- 设定目标和优先级
- 分配资源和任务
- 辅导和培养团队成员
- 协调跨团队合作
- 对结果负责
如果你喜欢“自己搞定一件事”的成就感,那管理可能会让你痛苦,因为管理的成就感来自“团队搞定一件事”。如果你喜欢“帮别人成长”,那管理可能会让你满足。
我的建议是:在转管理之前,先尝试一些管理相关的工作。比如带一个实习生、负责一个子项目、组织一次技术分享。这些经历能帮你判断自己是否适合管理,而不是凭想象做决定。
6.2 技术专家不是“技术更强的人”,而是“技术判断力更强的人”
很多人以为技术专家就是代码写得最多、技术最强的人。其实不是。技术专家的核心价值是技术判断力——在多个方案中选出最合适的,在技术风险出现前识别并规避,在团队迷茫时给出方向。
技术判断力来自哪里?来自大量的实践和复盘。你踩过的坑越多,你的判断力越强。你解决过的复杂问题越多,你的判断力越准。
所以,如果你想走技术专家路线,不要只追求“写更多代码”,而要追求“解决更复杂的问题”和“做更难的决策”。主动争取那些有挑战性的项目,哪怕会失败,也比重复做简单的事情成长更快。
6.3 技术和管理不是对立的,而是可以切换的
最后想说一点:技术和管理不是二选一,而是可以切换的。我见过不少人,做了几年管理后回到技术岗,也见过技术专家转管理后做得很好。
关键不是“选哪个”,而是“在每个阶段做最适合自己的选择”。刚入行时,专注技术;有一定积累后,尝试带人;如果发现自己更喜欢技术,就回到技术路线;如果发现自己擅长管理,就继续走下去。
职业路径不是一条直线,而是一张网。你走的每一步都算数,没有白走的路。
7. 一些没人告诉你,但很重要的“软技能”
最后聊几个我觉得很重要、但很少有人在正式场合提的“软技能”。这些技能不会写在JD里,但直接影响你的工作体验和职业发展。
7.1 学会写“让人看懂”的文档
工程师的文档能力普遍偏弱。很多人觉得“代码就是文档”,但代码只能说明“怎么做”,不能说明“为什么这么做”和“什么时候不该这么做”。
好的技术文档应该包含:背景(为什么要做这个)、方案(怎么做)、权衡(考虑过哪些替代方案)、使用说明(怎么用)、注意事项(什么情况下会出问题)。
我自己的习惯是,每做一个稍微复杂的功能,就写一篇设计文档。不需要多长,一两页就够。写文档的过程会强迫你把思路理清楚,很多设计上的漏洞在写文档时就会暴露出来。
7.2 学会“向上管理”
“向上管理”不是拍马屁,而是主动让你的上级知道你在做什么、遇到什么困难、需要什么支持。
很多工程师觉得“我把活干好就行了,不用汇报”。但你的上级可能管着十几个人,不可能知道每个人的细节。如果你不主动同步,他可能不知道你的贡献,也不知道你的困难。
我的做法是:每周给上级发一封简短的周报,包含三部分:本周完成、下周计划、需要支持。不需要多正式,几行字就行。这个习惯坚持了几年,效果很好——上级对我的工作有清晰的预期,我也能及时获得资源支持。
7.3 学会“保护自己的时间”
工程师的时间很容易被碎片化——临时会议、紧急需求、同事求助。如果不主动保护,你一天可能写不了几行代码。
我的做法是:每天上午留出两小时“免打扰时间”,关掉即时通讯工具,专注做最重要的事。这两小时不处理邮件、不参加会议、不回复消息。紧急的事情可以打电话,不紧急的事情等我中午统一处理。
这个习惯一开始会让人不适应,但坚持一段时间后,同事会知道你的节奏,也会尊重你的时间。而且你会发现,很多“紧急”的事情其实没那么紧急,等两小时完全没问题。
7.4 学会“接受不完美”
最后一点,也是最重要的一点:接受不完美。代码不可能完美,架构不可能完美,职业路径也不可能完美。你做的每一个决策,都是在信息不完全的情况下做出的。事后看可能有更好的选择,但当时你已经做了最好的判断。
不要因为一次线上故障就否定自己,不要因为一次选型失误就怀疑能力,不要因为一次沟通不畅就害怕表达。工程师的成长,就是在不断犯错和修正中完成的。
我自己的经验是,那些让我最痛苦的经历,往往也是让我成长最快的经历。所以,放轻松,接受不完美,继续往前走。