全栈工程应避免的错误
在其他条件相同的情况下,简单的解释通常比复杂的解释更好——奥卡姆剃刀
过去十年的十大经验教训
挑选优秀导师的秘诀在于,始终寻找那些曾经成功走过你即将踏上的道路的人。问问他们做了什么,为什么成功,以及如何成功?
这是我年轻时得到的建议。我很荣幸能与一些优秀的导师共事,我自然而然地想要报答他们的恩情。在这篇文章中,我将分享我迄今为止在全栈工程领域学到的经验教训。
谢谢
在此之前,我要衷心感谢我所有的导师、经理和同事,是你们教会了我这些经验,并陪伴我走过了职业生涯的征程。你们知道自己是谁,我很感激我们一起度过的时光。
为什么是我?为什么是现在?
我在科技行业和硅谷工作了十年。这段时间里,我曾在高增长的初创公司工作,经历了随之而来的各种高潮和低谷。从开发下一代电子邮件客户端,到在全球范围内推广电动汽车,再到在线购物结账,我学到了很多。如果您想了解更多关于这些公司的信息,可以在文章底部找到我的个人简介。回顾我的经验教训、犯过的错误以及错失的机会,我不禁想到,其中一些是可以避免的,因此我想与大家分享。
机制
这份清单编写起来很不容易,但我可以保证,我翻遍了过去十年里所有的待办事项列表软件(Google Keep、Mac Notes、Evernote、Gmail)和日记。我根据我认为经得起时间考验并在未来几年依然适用的经验,将其提炼为十大经验教训。这份清单从前端开始,然后是后端 API 和数据库,最后是工程最佳实践/流程。
经验教训:
-
CSS 特殊性
-
从组件层次结构设计状态
-
后端意大利面条、千层面、馄饨
-
Postgres 在生产中的应用
-
慢慢行动,测试事物
-
投资自动化
-
掌握你的工具
-
最有价值球员
-
研究支持的发展
-
科学调试
1. CSS 特殊性
错误:我的 CSS 没有生效。我打算使用!important
教训:应该将“使用”!important
保留用于特殊情况,因为它们会破坏整个 CSS 层次结构并强制使用特定样式。相反,应该学习 CSS 特异性。
CSS 优先级是浏览器用来决定哪种 CSS 样式更具体的一组规则。可以将其视为一个基于点的系统,它决定哪种 CSS 样式具有优先级,并最终应用于 DOM 元素。
如果你想知道为什么你的 CSS 代码没有被应用,那肯定与 CSS 优先级有关。这在大型项目中非常常见,因为这些项目会使用 SCSS 等预处理器来处理复杂的 CSS 层次结构。了解 CSS 优先级可以帮助你仅在极少数情况下使用 !important,例如当你需要覆盖 CSS 库或让 iframe 覆盖宿主网站样式时。
本质上,ID 选择器 > Class 选择器 > 类型选择器是优先顺序。!important 和行内样式属性会覆盖所有 CSS。对于应用于元素的每个 CSS,您可以轻松确定哪种样式将生效。例如,如果您加载上面的 HTML:
在此示例中,ID 选择器优先于类型选择器。如果冲突的 CSS 选择器优先级相同,则将选择 CSS 文件中的最后一个。最后,Chrome DevTools(你为什么要使用其他浏览器呢:))将显示如上图所示的优先级顺序。如果你的 CSS 无法应用,请查看 Chrome 使用的优先级顺序,然后添加更具体的选择器(id、class、type),以使你的 CSS 更具体,并指示浏览器选择它。如果你不想在脑海中计算,请查看这个优先级计算器:https://specificity.keegan.st/
2. 从组件层次结构设计状态
错误:我需要添加这个新状态,我只是要把它放在这个减速器中......嗯,不确定为什么这个减速器有所有其他状态......哦,好吧!
教训: Redux 状态管理不当会导致开发人员感到困惑,并引发 bug。如果您使用 React 和 Redux 构建前端应用程序,那么您可以考虑使用这种可视化技术,从 UI 组件层次结构构建状态和 Reducer 层次结构。从零开始构建统一的组件状态层次结构需要三个步骤:
-
在线框中可视化 UI
-
可视化状态层次结构以反映 UI
-
构建 Reducer 层次结构来镜像状态层次结构
让我们看一个例子,我们正在构建一个博客网站,该网站有两个页面,一个用于博客列表,另一个用于单个博客:
步骤 1:以线框形式可视化 UI
步骤2:可视化状态层次结构以反映UI
相应的状态层次图如下所示:
注意,通用的 Header 状态是如何被拉到根状态中的。同样,任何共享状态都可以在层级结构中向上冒泡,因此子组件之间共享该状态的情况一目了然。
步骤3:构建reducer层次结构以镜像状态层次结构
这是一个简单但功能强大的示例,展示了如何构建状态和 Reducer 层次结构以匹配 UI。此过程可以轻松扩展,适用于复杂的应用程序和大型团队。最后,您可以在此结构之上构建操作和表示层。
3. 后端从意大利面条到千层面再到馄饨
错误:这个代码库是如何组织的?也许我可以把这个文件添加到这里,好像所有其他代码库的代码都放在这里。
经验:三种意大利风格的代码我都写过。说实话,我觉得把迷你千层面塞进馄饨里才是正道。组织和培训所有开发人员以这种方式构建代码,可以保证代码库的可维护性、可测试性,最重要的是,保持敏捷性。你可以轻松地更改某个馄饨(也就是功能)的实现细节,而不会影响其他功能。
千层面是:
-
分层架构
-
外层为 I/O,内层为纯数据结构
-
依赖关系向内注入
-
内层不依赖于外层
-
优先选择组合而不是继承
馄饨是:
-
切片与分层
-
文件夹和文件的空间局部性
-
可以是微服务
将它们组合在一起,您将获得一个可扩展且易于维护的代码库。如果您能找到一种按功能名称(例如一个单独的馄饨)来组织文件夹的方法,并在每个功能中实现清晰的架构方法,那么它将持续很长时间。
4. Postgres 在生产环境中的应用
错误:为什么这个查询很慢?我觉得 Postgres 很慢。我需要分片,或者我觉得是 ORM 的问题,又或者我需要一个不同的数据库,Postgres 对我来说不太合适。
教训:如果你在生产环境中运行 Postgres,那么你**正在**处理缓慢的查询、表锁、无限等待的迁移以及错误。如果你没有遇到这些情况,那真是太好了,你是怎么做到的呢?这并不意味着 Postgres 不再是合适的工具,而是意味着你需要揭开幕幕,看看背后发生了什么。
到目前为止,我发现解决几乎所有 Postgres 问题的最佳工具是pgbadger。它是一个 Perl 命令行工具,以 Postgres(如果你使用的是 AWS,则为 RDS)日志作为输入并输出报告。报告的质量取决于你在 Postgres 上启用的日志。因此,作为第一步,你可能需要启用以下日志:
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
log_error_verbosity = default
log_min_duration_statement = 1s
此外,您可能还希望启用 pg_stat_statements 来实时分析查询,并启用 auto_explain 来自动解释分析日志中运行缓慢的查询。
运行报告:
pgbadger --prefix '%m %u@%d %p %r %a : ' /pglog/postgresql.log
此报告将汇总数据,并提供大量有关 Postgres 运行状况的信息。您将找到有关错误、最慢查询、等待时间最长的查询、获取的锁类型、是否使用临时文件进行排序、检查点运行频率、清理频率等信息。借助这些数据,您可以识别和修复运行缓慢的查询,并通过调优来提升 Postgres 的性能。
您可以持续运行此报告(CLI 支持增量模式),从而掌握新问题。
另外,如果您想理解解释输出,可以使用此工具:https://tatiyants.com/pev/#/plans/new。该工具以解释 JSON 和原始查询作为输入,并以如下所示的可视化树状图形式解释解释输出。
如你所见,节点会带有“最大”、“最慢”、“最昂贵”等标签。这将帮助你根据 Postgres 的执行方式来优化查询。
最后,如果培养 Postgres 能力不可行,我建议聘请像Percona这样的 DB 咨询公司。
5. 慢慢行动,不断测试
错误: LGTM 让我们发货吧!
课:
来自维基百科:https://en.wikipedia.org/wiki/List_of_system_quality_attributes
在“快速行动、打破常规”的时代,这或许并非最流行的建议,但慢慢来终将受益。与其快速行动却交付糟糕的代码,不如循序渐进、循序渐进,从而减少甚至杜绝生产错误。
优秀的工程师会考虑软件系统的所有“功能”。他们不仅关注代码覆盖率,还会关注可能破坏相同代码路径的异常输入。在分层架构中,他们会实现模拟层,并仅测试所考虑的层。他们不仅会执行单元测试,还会执行集成测试和功能测试,或者,如果您的团队有 QA 工程师,他们会与他们合作测试这些用例。
慢一点没关系,但最好正确。
6.投资自动化
错误:我们临时手动部署到暂存区和沙盒。生产环境也是手动部署,但每天一次。
经验:使用 CI/CD 系统管理部署意味着结果更加可预测。软件在推广策略中贯穿整个流程,临时部署则被归类为特殊情况。这确保了所交付软件的稳定性和可靠性,而这正是工程团队的首要职责。
投资于:
-
培训团队成员如何进行代码审查。您的团队成员可能拥有各种各样的技能,并非每个人都知道如何进行有效的代码审查。投资于学习和教授代码审查的最佳实践。
-
使用像peril和hound这样的自动化代码审查系统。Peril 可以检查代码变更,并根据预配置设置标记警告和构建失败。例如,如果数据库迁移文件缺少 statement_timeout 或包含不必要的 DEFAULT NULL ,则可能导致拉取请求失败。您可以编写许多类似的检查和特定于团队的规则,并让 peril 成为变更的守门人。HoundCI 可以执行类似的操作,并且规则完全可配置。
- 使用CircleCI等工具设置具有自动化推广策略的 CI/CD 流水线。随着时间的推移,优化构建和部署流水线。
7. 掌握你的工具
错误:哦,我需要找到这个实现的接口,让我搜索一下。它以前在这个文件夹中。呃,现在不在了。我们去那里看看……问问别人吧。
教训:不懂如何使用工具会让你效率低下。你能想象一个裁缝用缝纫机时马虎了事吗?这不仅关乎代码的运行结果,也关乎你构建软件的效率。
图片来自https://blog.jetbrains.com/go/2017/10/26/gogland-eap-17-type-hierarchy-call-hierarchy-parameter-hints-vendor-scope-and-more/
了解你的工具,学习快捷键。你的代码编辑器可能是你最先需要掌握的工具。这是你的日常工作。你应该知道如何设置标签页排序、就地源代码探索、正向和反向代码深度遍历、打开上次编辑的文件、导航到接口/实现、读取文件中的文件结构以及显示调用图。如果你使用没有图形用户界面的文本编辑器,那也没问题。像 Vim 这样的编辑器有很多实用技巧需要掌握。
注意你手动执行的常用操作,并学习如何通过快捷键来完成它们。一个简单的方法是记下 5 个快捷键,掌握它们直到形成肌肉记忆,然后再学习接下来的 5 个。
全栈工程师每天接触并应该掌握的其他常用工具是终端、docker、tableplus/pgadmin/一些其他数据库客户端 UI、chrome 开发工具。
8. 最有价值球员
错误:我认为这个功能会很有用。我打算使用一个分布式容错、高可用、多副本的数据存储。我还将构建一个基于插件的架构,使这个软件具有极强的可扩展性。
经验:在构建任何东西之前,一定要确保它是正确的东西。这就是 MVP 的用武之地。
理想的 MVP 应该尽量少地触及所有层,而不是仅仅触及一层。这是一种降低风险的做法。尽量少地构建所有层,比只完善某一层要好。MVP 并不意味着技术债务、糟糕的编码或缺乏测试。它不是一次性代码。
如果 MVP 花费的时间太长(某种程度上来说很长),那么它很可能是错误的,并且可能有一个更简单的解决方案。
在其他条件相同的情况下,简单的解释通常比复杂的解释更好——奥卡姆剃刀
这个纸杯蛋糕的比喻是解释 MVP 的另一种方式。与其试图做一个大蛋糕或打造一个完美的底层,不如先做一个纸杯蛋糕,收集反馈并不断迭代。至少,你会知道人们是否喜欢这个新口味。
9. 研究支持的发展
错误:我(工程师)认为这就是我们应该建造的
经验:开发之前应该进行强有力的研究来支持你的主张。与其跟随你的直觉,不如进行用户研究。通过面对面或视频访谈、开展调查问卷、查看日志来了解用户的需求。这将帮助你更好地理解你的用户。然后,你可以提出假设并进行实验。在形成假设时,运用反证法来反驳你的观点。投资一个可以让你进行实验的 A/B 测试框架。
时间宝贵,要明智地利用它。** **最聪明的工程师会尝试优化一些本不该存在的东西。尽早提出正确的问题至关重要。
10. 科学调试
错误:有一个 bug。嗯,我觉得是因为代码改动。我去看看这个文件。也可能是内存问题。也可能是两者兼而有之。
经验教训:作为一名工程师,无论是在事故现场还是在本地环境中,你都需要调试软件问题。如果不通过结构化推理,调试过程可能会非常痛苦且缓慢。
我们如何才能系统地找出程序失败的原因?如果没有“直觉”、“敏锐思维”之类的模糊概念,我们该如何做到这一点?我们需要的是一种解释失败的方法——一种能够:
-
无需先验知识
-
系统性
-
我们可以确保找到根本原因并随意复制
运用科学方法调试问题,是建立故障理论的一种客观方法。科学调试的步骤如下:
-
重现错误(通常是时间、数据、用户、操作系统、调试器的组合)
-
观察事实(彻底阅读日志、错误跟踪等)
-
在日志中明确陈述假设,而不是在心里默想
-
如果您发现程序的某个部分存在错误,请使用结构化方法来缩小错误范围,例如二进制搜索
-
测试假设:使用日志记录、断点、断言
-
如果验证通过,则应用修复并确保没有新的损坏
-
如果无效,请重复步骤#3至#6
对于简单的调试情况来说,这似乎有点过度,但对于涉及许多团队的复杂分布式系统,系统的科学调试过程提供了消除歧义所需的结构。
奖金
如果你已经读到这里,不妨继续阅读这3个额外的学习内容。它们涵盖了软性/个人成长方面的内容。
1. 分享学习成果,服务他人
非凡的行为正在帮助他人成长。当你需要用别人能理解的方式解释某件事时,你的思维会获得一定程度的清晰。
每天在 Slack 上分享一些有见地的链接,组织午餐会、演示,赞扬他人的积极行为,挑战不明确的决定,并在你希望与某人或某项决定的方向不同时提供建设性的反馈。你可以使用“感谢 ABC……希望 XYZ”的语法来提供反馈,你感谢的事情和你希望的事情的平均比例为 3:1。
这样做可以打造个人品牌,从而获得职业资本。研究表明,拥有强大个人品牌、网络影响力和助人记录的人更容易获得成功,更重要的是,他们拥有令人满意的职业生涯。
2.塑造你自己的世界
你无需接受世界的本来面目。你可以掌控自己的命运,塑造你所感知的世界。
这可能意味着在设计讨论和代码评审期间表达你的意见,或者修复关键的不稳定测试。很多人会告诉你要多说话,提高知名度,这样才能在公司内部发展,但他们从不解释如何做到这一点。做到这一点的最佳方法是拥有坚定的意见和自信,从而吸引人们向你的方向发展。不要害怕组建小型特警团队来构建/改进事物。不要向你的恐惧屈服。大声说出来,只要你不失礼,你就可以说。
负面情绪是改变的强大动力。如果有什么事情困扰着你,问问自己为什么,并思考如何引领改变。如果你把每一天都当作成长的途径,那么生活就会变成一场锻炼。
3. 结识新朋友
如果你像我一样,正在努力弄清楚什么对你真正重要,那么就多去结识人,尤其是那些你兴趣不明的人。这可能意味着参加会议、参与在线社区、参与黑客马拉松和项目,或者任何类似的活动。这些接触能帮助你弄清楚自己想做什么。这能让你对那些不重要的事情说“不”,并对你重要的机会保持开放的态度。
许多成功人士感到幸运,并表示自己在正确的时间出现在了正确的地点,他们从一开始就知道自己想要什么。这让他们能够随心所欲地抓住机会,并最大程度地减少遗憾。在遇到聪明人时,他们通常会说“是” 。
在这段时间里,我发现自己更注重广度而非深度,重视创造力和自由,享受多样性和非正式的人际关系。结构化的重复性工作、例行公事、稳定和安全感是我不太适合的。这让我能够选择与我契合且互补的项目和人。
如果你知道自己想要什么,世界就会给你所需的信息。
全栈编程很有趣。这是一个不断发展的领域,有广阔的学习空间可供探索。不要太在意自己或自己的错误。分享它们,不断成长。
如果这些学习内容引起了你的共鸣,请告诉我哪些内容引起了你的共鸣。如果没有,也没关系,尽管告诉我。在你的全栈职业生涯(无论长短)中,你学到了哪些有趣的事情?请在下方评论区分享。如果你有灵感,想写下你自己的十大学习心得,我鼓励你这样做。
资源:
-
CSS 特殊性计算器:https://specificity.keegan.st/
-
清洁架构:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
-
尖叫建筑:https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html
-
pgbadger:https://github.com/darold/pgbadger
-
Postgres 查询解释工具:https://tatiyants.com/pev/#/plans/new
-
软件功能:https://codesqueeze.com/the-7-software-ilities-you-need-to-know/
-
HoundCI:https://github.com/houndci/hound
-
CircleCI:https://circleci.com/
-
MVP 纸杯蛋糕比喻:https://www.intercom.com/blog/start-with-a-cupcake/