软件工程师工作经历

In 2018, I started working at Bloomberg. Things have changed a lot since then. I’m not the most junior member in the company anymore and I’ve mentored quite a few new engineers, which has been amazing. It helped me observe how others differ from me, absorb their best practices, and figure out things I’ve unconsciously been doing pretty well.

2018年,我开始在彭博社工作。 从那时起,情况发生了很大变化。 我不再是公司中最初级的成员,并且我已经指导了很多新工程师,这真是太了不起了。 它帮助我观察其他人与我的不同之处,吸收他们的最佳实践,并弄清自己在不知不觉中一直做得很好的事情。

Yearly work reviews are a good way to condense these lessons I’ve learned. They’re valuable for pattern matching, too. Only when I zoom out do certain patterns become visible. I can then start tracking these patterns consciously. The broad theme for this year is zooming out and challenging the boundaries. It’s also about zooming in and adding nuance to the sections from last year. It’s more fun if you’ve read last year’s review first: You can then differentiate my growth.¹

每年的工作回顾是总结这些经验教训的好方法。 它们对于模式匹配也很有价值。 仅当我缩小时,某些图案才可见。 然后,我可以开始有意识地跟踪这些模式。 今年的主要主题是缩小范围并挑战边界。 这也是关于放大和添加细微差别的部分。 如果您先阅读了去年的评论,那就会更加有趣:然后您就可以区分我的成长了。¹

It all began with a question: How do I grow further?

一切都始于一个问题:我如何进一步发展?

Table of ContentsGrowing Using Different Ladders of AbstractionLearning What People Around Me Are DoingLearning Good Habits of MindThinking WellStrategies for Making Day-to-Day More EffectiveAcquiring New Tools for Thought & Mental ModelsProtect Your SlackAsk QuestionsNoticing ConfusionForce MultipliersOn OwnershipEmbrace FearAdding NuanceWriting CodeTestingDesignGathering RequirementsSome Hacks That Have Worked Very Well for MeSuper PowersSome Gotchas With GrowingSometimes, I Feel I Need to Know the Answer to EverythingSometimes, I Lose My CoolNeophiliaQuestions

使用不同的抽象梯子进行增长 (Growing Using Different Ladders of Abstraction)

Entering my second year, I had all the basics in place. I had picked all the low hanging fruit, and my rate of growth slowed down. Not good. The big question on my mind was “How do I grow further?”

进入第二年,我掌握了所有基础知识。 我已经摘下了所有悬而未决的水果,我的成长速度放慢了。 不好。 我脑海中最大的问题是“我如何进一步成长?”

There was only so much I could do to improve my coding skills. Most blogs espousing techniques to write cleaner code, repeating yourself, not repeating yourself, etc. are micro-optimizations. Almost none of them would make me instantly impactful.²

我只能做很多事情来提高自己的编码技能。 大多数博客拥护写更清晰的代码,重复自己,不重复自己等的技术,都是微优化。 几乎没有一个能让我立即具有影响力。²

However, I did figure out something insightful. I’m working inside the software development lifecycle, but this lifecycle is part of a bigger lifecycle: the product and infrastructure development lifecycle. I decided to go broader instead of deeper. Surprisingly, the breadth provided more depth to what I knew.

但是,我确实发现了一些有见地的东西。 我正在研究软件开发生命周期,但是这个生命周期是更大生命周期的一部分:产品和基础架构开发生命周期。 我决定更广泛而不是更深入。 令人惊讶的是,广度使我所知道的更加深入。

I zoomed out in three broad directions: learning what people around me are doing, learning good habits of mind, and acquiring new tools for thought.

我向三个主要方向进行了扩展:学习周围人在做什么,学习良好的心智习惯以及获取新的思考工具。

了解我周围的人在做什么 (Learning What People Around Me Are Doing)

Since we’re not in a closed system, it makes sense to better understand the job of the product managers, the salespeople, and the analysts. In the end it’s a business making money through products. The goal isn’t to write code, it’s to be a profitable business.³

由于我们不在封闭系统中,因此有必要更好地了解产品经理,销售人员和分析师的工作。 最终,这是通过产品赚钱的业务。 目标不是编写代码,而是要盈利。³

Most big companies aren’t doing just one thing, which means there are different paths to making money in the same company. Everyone is on at least one path, if they weren’t, they wouldn’t be here.⁴ Tracking these paths, and the path I’m on was pretty valuable. It helped me see how I matter, and what levers I can pull to become more effective. Sometimes, it’s about making the sales jobs easier, so they can make more sales. Other times, it’s about building a new feature for clients. And some other times, it’s about improving a feature that keeps breaking.

大多数大公司不只是做一件事,这意味着在同一家公司中有不同的赚钱途径。 每个人都至少在一条道路上,如果没有,他们就不会在这里。⁴跟踪这些道路以及我所走的道路非常有价值。 它帮助我了解自己的重要性,以及可以提高效率的方法。 有时,这是为了简化销售工作,以便他们可以增加销售量。 其他时候,这是关于为客户构建新功能。 在其他时候,这是关于改进一项不断突破的功能。

Product managers are the best source for this. They know how the business makes money, who are the clients, and what do clients need. Over the year, I set up quite a few meetings with everyone on my path. A second benefit this gave me was the context of other’s jobs. It helped me communicate better. Framing things in the right way is powerful.

产品经理是最好的选择。 他们知道企业如何赚钱,谁是客户以及客户需要什么。 在过去的一年中,我与沿途的每个人都召开了很多会议。 这给我带来的第二个好处是他人工作的背景。 它帮助我更好地沟通。 以正确的方式构架是强大的。

For example, one conversation helped me appreciate why Sarah in Sales wants a bulk update tool. Some companies have lots of employees, and updating them one by one is a pain. The code I write would literally ease Sarah’s pain.

例如,一次对话帮助我理解了为什么Sales中的Sarah想要批量更新工具。 一些公司有很多员工,而一个接一个地更新他们是一个痛苦的过程。 我写的代码从字面上减轻了莎拉的痛苦。

学习良好的心智习惯 (Learning Good Habits of Mind)

Software engineering entails thinking well and making the right decisions. Programming is implementing those decisions. A habit of mind is something your brain does regularly. This could be thinking of X whenever you see Y happen, or applying thinking tool X to problem Y. In short, habits of mind facilitate better thinking. I suspected if I learn the general skill, I should be able to apply it better to software engineering.

软件工程需要认真思考并做出正确的决策。 编程正在执行这些决定。 习惯是大脑定期做的事情。 每当您看到Y发生时,这可能就是在思考X,或者将思考工具X应用于问题Y。简而言之,思维习惯有助于更好的思考。 我怀疑如果我学习了一般技能,我应该能够将其更好地应用于软件工程。

好好思考 (Thinking Well)

Software engineering is an excellent field to practice thinking well in. The feedback loops are shorter, and gauging correctness doesn’t take too long. I dove into cognitive science studies. It’s a permanent skill that’s worth exploring, a force multiplier for whatever I end up doing, and pays dividends throughout my life. One output was a framework for critical thinking. It’s compounding, and compounding is powerful.

软件工程是一个很好地进行思考的极好的领域。反馈循环更短,并且度量的正确性不会花费太长时间。 我从事认知科学研究。 这是一项值得探索的永久技能,是我最终所做的事的力量乘数,并终生付出。 一种输出是批判性思维的框架 。 这很复杂,而且强大。

There’s lots of good things that came out of this, which I’ll talk about in a bit. They deserve their own section.

由此产生了很多美好的东西,我将在稍后讨论。 他们应有自己的职责。

提高日常效率的策略 (Strategies for Making the Day-to-Day More Effective)

The other side of the coin is habits that allow you to think well. It starts with noticing little irritations during the day, inefficiencies in meetings, and then figuring out strategies to avoid them. These strategic improvements are underrated.

硬币的另一面是让您好好思考的习惯。 首先要注意白天的烦恼,会议效率低下,然后找出避免这种烦恼的策略。 这些战略改进被低估了。

You decide what to do, and then let it run on automatic, freeing up the brain to think of more fun stuff. Of course, that’s what a habit is, too. Some good habits I’ve noticed:

您可以决定要做什么,然后让它自动运行,使大脑有更多的精力去思考更多有趣的事情。 当然,这也是一种习惯。 我注意到一些良好的习惯:

  • Never leave a meeting without making the decision / having a next action决不做决定/不采取下一步行动就离开会议
  • Decide who is going to get it done. Things without an owner rarely get done.决定谁来完成它。 没有所有者的事情很少能完成。
  • Document design decisions made during a project记录项目期间做出的设计决策

This pattern became visible during the review, so I’m keen to pay attention and collect more strategies next year. Having an excellent scrum master who holds me accountable has helped me get better at following these strategies.

这种模式在审核期间变得可见,因此我非常希望关注并在明年收集更多策略。 拥有一个出色的Scrum主管让我负责,这有助于我更好地遵循这些策略。

获得思想和心理模型的新工具 (Acquiring New Tools for Thought & Mental Models)

New tools for thought are related to thinking well, but more specific to software engineering. Tools for thought help me think better about specific engineering problems.

新的思考工具与良好思考相关,但更特定于软件工程。 思考工具可帮助我更好地思考特定的工程问题。

I’ve adopted a just-in-time approach to this. I look for new tools only when I get stuck on something, or when I find out my abstractions and design decisions aren’t working well.

我采用了一种及时的方法。 我只会在遇到问题或发现抽象和设计决策无法正常工作时才寻找新工具。

For example, I was recently struggling with a domain with lots of complex business logic. Edge cases were the norm, and we wanted to design a system that handles this cleanly. That’s when I read about Domain-Driven Design⁵. I could instantly put it to practice and make a big difference. Subsequently, I grasped these concepts better. I acquired a new mental model of how to create enterprise software.

例如,我最近在一个具有许多复杂业务逻辑的领域中苦苦挣扎。 边缘情况是常态,我们希望设计一个可以清晰处理这一问题的系统。 那就是我读到有关域驱动设计的内容。 我可以立即将其付诸实践并发挥很大的作用。 随后,我更好地理解了这些概念。 我获得了有关如何创建企业软件的新思维模型。

The second way I keep learning and acquiring new mental models is via reading what surfaces on Hacker News. They are interesting ideas, some of which I’ve put to practice, but this is a lot less effective than the technique above. The only reason I still do this is to map the territory, it keeps me aware of techniques that exist, so when I face a problem, I know there’s a method that might help.

我不断学习和获得新的心理模型的第二种方法是通过阅读Hacker News上的内容。 它们是有趣的想法,我已经将其中一些实践了,但这远没有上面的技术有效。 我仍然这样做的唯一原因是绘制区域图 ,它使我知道存在的技术,因此当我遇到问题时,我知道有一种方法可能会有所帮助。

The final way I acquire better mental models is by learning new diverse languages. The diversity bit is important. Learning yet another dialect of lisp has a lot less benefit than say, learning C++03, a functional programming language, a dynamic typed language, and a lisp. Today, J seems interesting, and one I might consider learning. It’s a thinking model I haven’t used before.

我获得更好的心理模型的最后方法是学习新的多样的语言。 分集位很重要。 学习lisp的另一种方言比学习C ++ 03,功能编程语言,动态类型化语言和lisp所带来的好处要少得多。 今天, J似乎很有趣 ,我可能会考虑学习。 这是我以前从未使用过的思维模型。

I’ve gotten lots of value from doing this. Each language has its own vocabulary and grammar, and vocabulary is a meta-mental model. It’s a new lens to look at how to do things.

通过这样做,我获得了很多价值。 每种语言都有自己的词汇和语法,词汇是一种元思维模型。 这是研究如何做事的新视角。

When memory management is in your control, you understand how pointers and allocators work. When Python then abstracts this away, you appreciate the complexity reduction. When maps and filters in a functional language show up, you appreciate how Python’s for loops can be improved. Indeed, that’s what list comprehensions are. And then you notice how some things are easier with object-oriented programming. There’s no one magic tool that fits everything well. And then you understand that despite this, you don’t have to switch tools. You can adapt best practices from one into another to solve your problems: like writing functional javascript. It’s the principles that matter more than their expression.

当内存管理处于您的控制范围内时,您将了解指针和分配器的工作方式。 当Python然后将其抽象化时,您会体会到复杂性的降低。 当使用功能性语言显示地图和过滤器时,您会欣赏如何改进Python的for循环。 确实,这就是列表理解。 然后您会注意到,使用面向对象的编程如何使某些事情变得更容易。 没有一种魔术工具可以很好地适应所有情况。 然后您了解到尽管如此,您也不必切换工具。 您可以将最佳实践相互适应,以解决您的问题:例如编写功能强大的javascript。 这些原则比其表达更重要。

Broadly, that’s all I did this year. What follow are insights that sprang forth thanks to zooming out.

概括地说,这就是我今年要做的。 接下来的是由于缩小而产生的见解。

保护你的松弛 (Protect Your Slack)

When I say slack, I don’t mean the company, but the adjective. One thing that gives me high output and productivity gains is to “slow down.” Want to get more done? Slow down. Caveats apply, but here’s what I mean:

当我说懈怠时,我不是指公司,而是形容词。 使我获得高产出和生产率收益的一件事是“放慢脚步”。 想要完成更多工作? 慢一点。 注意事项适用,但这是我的意思:

I’ve noticed people rush to solve problems. It can be something they’ve done before, or something we have a template for. It feels pretty good to smash through things. I’ve done that before, too. However, there are very specific cases where this makes sense.⁶

我注意到人们急于解决问题。 可以是他们以前做过的事情,也可以是我们有模板的事情。 粉碎事物感觉很好。 我之前也做过。 但是,在非常特殊的情况下,这很有意义。⁶

Whenever I’m working on something new, I take the time to learn things about the system I’m working on, and things closely related to it. If it’s too massive, I optimize for learning as much as I can. Every time I revisit the system, I aim to learn more.

每当我在研究新事物时,我都会花时间学习有关正在研究的系统以及与之密切相关的知识。 如果太大,我会尽可能地优化学习。 每次我重新访问该系统时,我都希望了解更多信息。

When there is slack, you get a chance to experiment, learn, and think things through. This means you get enough time to get things done. When there is no slack, deadlines are tight, and all your focus goes into getting shit done. Protecting your slack means not letting deadlines wrap tight around you. Usually, this is as simple (or hard) as communicating.⁷

闲暇时 ,您就有机会进行实验,学习和思考。 这意味着您有足够的时间完成工作。 如果没有懈怠,那么最后期限会很紧,您的所有精力都将集中在完成事情上。 保护您的闲暇时间意味着不要让最后期限紧缩。 通常,这就像交流一样简单(或困难)。⁷

Slack might have a negative connotation with “slackers,” but protecting slack is a superpower. It’s a long term investment into building yourself up at the cost of short term efficiency. When I’m quickly dishing out stories, I also have a much harder time fixing bugs. I don’t take the time to create proper mental models of the system, which means my assumptions don’t match the code, and this mismatch is where most bugs lie. I protect my slack, so I can take the time out to prioritize learning things over doing things.⁸

松弛可能对“松弛者”具有负面含义,但是保护松弛是一种超级大国。 这是一项长期投资,需要以短期效率为代价来建立自己。 当我Swift讲出故事时,我也很难修复错误。 我没有花时间创建正确的系统思维模型,这意味着我的假设与代码不匹配,而这种不匹配是大多数错误的根源。 我保护自己的休闲时间,因此我可以抽出时间优先学习事物而不是做事。⁸

One of my favorite use cases for slack is experimentation. Sometimes, I’ll find a bug that makes zero sense to me. I notice I’m confused, find an answer on Stack Overflow, and continue. However, this keeps bugging me until I understand the bug. Stack Overflow answered it, but didn’t explain what was wrong in my understanding. To build up my understanding, I need to experiment.

我最喜欢的松弛用例之一是实验。 有时,我会发现一个对我来说意义非零的错误。 我注意到我很困惑,在Stack Overflow上找到答案,然后继续。 但是,这一直困扰着我,直到我了解错误为止。 Stack Overflow回答了这个问题,但是在我的理解中没有解释什么错误。 为了建立我的理解,我需要进行实验。

If I have no slack, I have no time to experiment, which means I have to forget about the bug. When there’s slack, I can run experiments to find out exactly what was missing from my understanding. I love moments like these; when I uncover something new about the system. It makes me a lot more effective the next time around.

如果没有懈怠,就没有时间进行试验,这意味着我不得不忘记该错误。 闲暇时,我可以进行实验以找出我所了解的确切缺失。 我喜欢这样的时刻; 当我发现有关系统的新知识时。 下次我会更有效率。

问问题 (Ask Questions)

We’re generally bad at asking questions. Either we fear they’ll make us look dumb, so we don’t ask them at all, or we ask them with long preambles that’s more about how we’re not dumb, rather than learning more about the thing.

我们通常不敢问问题。 我们要么害怕他们会让我们看起来很愚蠢,所以我们根本不问他们,或者我们以长长的序言问他们,这更多地是关于我们如何不愚蠢,而不是更多地了解事情。

The thing is, you can’t judge a question as dumb until you figure out the answer. The way I get around this is to declare I’ll ask lots of questions. This frees me up to start from the bottom and patch the holes in my understanding. A positive team culture helps, too.

关键是,在弄清楚答案之前,您不能将问题判断为愚蠢的。 我解决这个问题的方法是声明我会问很多问题。 这使我从底部开始解放,并填补了我的理解上的漏洞。 积极的团队文化也有帮助。

For example, here’s my journey learning about packaging software:

例如,这是我学习打包软件的过程:

  • Q: What is a package?

    :什么是包裹?

    Q: What is a package?A: It’s code wrapped together that can be installed on a system.

    :什么是包裹? :它是包装在一起的代码,可以安装在系统上。

  • Q: Why do I need packages?

    :为什么需要包装?

    Q: Why do I need packages?A: They give a consistent way of getting all the files you need in the right place. Without them, things are easy to mess up. You need to ensure every file is where it’s supposed to be, the system paths are set up, and dependent packages are available.

    :为什么需要包装? :它们提供了一致的方式,可以在正确的位置获取所需的所有文件。 没有它们,事情很容易搞砸。 您需要确保每个文件都在预期的位置,设置了系统路径,并且相关包可用。

  • Q: How do packages differ from applications I can install on my system?

    :软件包与可以在系统上安装的应用程序有何不同?

    Q: How do packages differ from applications I can install on my system?A: It’s a very similar idea. Windows installer is like a package manager that helps install applications. Similarly, DPKG and rpm packages are like .exe that you can install on Linux systems, with the help of apt and yum package managers, which are like the Windows installers.

    :软件包与可以在系统上安装的应用程序有何不同? :这是一个非常相似的想法。 Windows安装程序就像一个程序包管理器,可以帮助安装应用程序。 同样, DPKGrpm软件包就像.exe一样,您可以在aptyum软件包管理器的帮助下安装在Linux系统上,就像Windows安装程序一样。

  • Q: I see. So, this setup.py in python somehow converts into a dpkg? How does that work?

    :我知道。 那么,python中的setup.py以某种方式转换为dpkg吗? 这是如何运作的?

    Q: I see. So, this setup.py in python somehow converts into a dpkg? How does that work?A: We have a python-debhelper that runs setup.py for the conversion.

    :我知道。 那么,python中的setup.py以某种方式转换为dpkg吗? 这是如何运作的? :我们有一个python-debhelper ,它运行setup.py进行转换。

  • Q: Oh, how very interesting! How did you figure this out?

    :哦,多么有趣! 您是如何知道的?

    Q: Oh, how very interesting! How did you figure this out?A: The debian/rules file contains instructions on how to create a dpkg. I looked at it to figure this out.

    :哦,多么有趣! 您是如何知道的? debian/rules文件包含有关如何创建dpkg 。 我看着它来解决这个问题。

Then I know it’s time for me to look at the documentation. I have enough pieces to make sense of the outline. Turns out, this wasn’t as simple as I thought, and it wasn’t a dumb question to ask.

然后,我知道该该看看文档了。 我有足够的作品来理解轮廓。 事实证明,这并不像我想的那么简单,也不是一个愚蠢的问题。

This is a habit of mind I’ve cultivated, and there are some good questions you can always ask. Most of them are context-dependent, but I do have one favourite general question. It’s called playing the meta: How did you find out X?

这是我养成的一种心智习惯,您总是可以问一些好的问题。 它们中的大多数是上下文相关的,但是我确实有一个最喜欢的一般性问题。 称为播放元数据:您是如何找到X的?

When I ask someone something, and they answer it, the next thing I ask is how did they figure it out? That helps me do it myself the next time around. I did this above, which taught me about the debian/rules file and how it works.

当我问某人某事并且他们回答时,我接下来要问的是他们是如何发现的? 这可以帮助我下次自己做。 我是在上面做的,这使我了解了debian/rules文件及其工作方式。

Another good question is to ask about what confuses you.⁹

另一个好问题是问什么使您感到困惑。⁹

注意混乱 (Noticing Confusion)

One fine day, I was working with date-times in Python. These were dates our search engine would index, and we wanted them in UTC. So, I modified our pipeline to convert dates to UTC before ingestion. This required making these dates timezone-aware.

有一天,我在Python中处理日期时间。 这些是我们的搜索引擎可以索引的日期,我们希望在UTC中使用它们。 因此,我修改了管道,以在提取之前将日期转换为UTC。 这要求使这些日期成为时区感知的。

I created a datetime like this:

我创建了这样的datetime

import datetimefrom pytz import timezoneindexed_date = datetime.datetime(2019, 11, 20, 12, 2, 0, tzinfo=timezone('Asia/Kolkata'))

In my tests, this conversion was off by 23 minutes. I didn’t notice it at the time, but seeing this confused me. So, I modified the test offset to -23 minutes, so the tests would pass. It’s a pretty shitty way of thinking. Once I noticed this, I couldn’t un-see it. It sometimes still haunts me that I let this pass. Of course, someone commented on the PR with “this looks wrong,” which jerked me out of my default thinking, to actually figure out what went wrong here.

在我的测试中,此转换截止到23分钟。 当时我没有注意到,但是看到这让我感到困惑。 因此,我将测试偏移量修改为-23分钟,以便测试通过。 这是一种很卑鄙的思维方式。 一旦注意到这一点,便无法取消看到它。 有时候,让我过去的仍然困扰着我。 当然,有人在PR上用“这看起来不对劲”发表评论,这使我脱离了我的默认思维,从而弄清楚了这里出了什么问题。

It’s a pretty epic bug. Pytz has had timezone information throughout the ages. Before 1942, the timezone for Asia/Calcutta was +5:53:20. (Yes, even the city name was different). When pytz timezones are passed into a new date, there’s no reference date to match the timezone to the year. So, it defaults to the first available timezone, which is wrong. The docs mention this, too. The right way is to use tzinfo.localize(), which matches the date to the appropriate timezone, since it’s pytz which is now doing the conversion.

这是一个非常史诗般的错误。 多年来,Pytz拥有时区信息。 1942年之前,亚洲/加图塔的时区为+5:53:20。 (是的,甚至城市名称也不同)。 当pytz时区传递到新日期时,没有参考日期将时区与年份匹配。 因此,它默认为第一个可用时区,这是错误的。 文档也提到了这一点。 正确的方法是使用tzinfo.localize() ,它将日期与适当的时区匹配,因为现在是pytz正在进行转换。

import datetimefrom pytz import timezonetz=timezone('Asia/Kolkata')indexed_date = tz.localize(datetime.datetime(2019, 11, 20, 12, 2, 0))

I wouldn’t have found out about this if that PR review didn’t trigger me. It exposed this scary mode of thinking where I push confusion under the rug. I’ve been wary ever since.

如果PR审查没有触发我,我就不会发现这一点。 它暴露了这种令人恐惧的思维方式,使我在混乱中产生困惑。 从那以后我一直很警惕。

To stop this from happening again, I’ve started training my “noticing muscles.” This is called noticing confusion. Not just when writing code, but with everything, there’s a tendency to explain away confusion, pushing it under the rug. Every time you hear something that sounds weird, and you rush to explain why it must be true, you’re pushing confusion under the rug.

为了阻止这种情况再次发生,我已经开始训练“注意肌肉”。 这称为注意混乱。 不仅在编写代码时,而且在编写所有代码时,都有一种趋势来解释混乱,将混乱推到底层。 每次您听到听起来很怪异的东西,然后急于解释为什么它必须是真实的时,您就会在混乱中推波助澜。

Once you start noticing confusion, you can ask questions about what confuses you. That might have sounded trite in the previous section, but I hope this context helps. The tricky bit is noticing what confused you.

一旦开始注意到混乱,您可以提出有关使您感到困惑的问题。 在上一节中,这听起来有些陈词滥调,但是我希望这种情况有所帮助。 棘手的一点是注意到什么让您感到困惑。

力乘数 (Force Multipliers)

One fine sprint, I accidentally felt the power of the Force.

一次短距离冲刺,我无意中感受到了力量的力量。

“The Force is what gives a Jedi his power. It’s an energy field created by all living things. It surrounds us and penetrates us; it binds the galaxy together.”―Obi-Wan Kenobi, Star Wars

“力量是赋予绝地武士力量的力量。 这是所有生物创造的能量场。 它包围着我们并渗透我们; 它将银河系在一起。”-《星球大战》中的欧比旺·基诺比

I think Obi-Wan Kenobi was onto something, albeit in the wrong domain. It’s something I can leverage in software engineering: becoming a force multiplier.

我认为Obi-Wan Kenobi涉足某些领域,尽管领域不正确。 我可以在软件工程中利用这一点:成为力量倍增器。

That sprint I didn’t get much done myself. I wrote very limited code. Instead, I co-ordinated which changes should go out when (it was a complicated sprint), tested they worked well, did lots of code reviews, made alternate design suggestions, and pair-programmed wherever I could to get things un-stuck. We got everything done, and in this case, zooming out helped make decisions for PRs easier. It was one of our highest velocity sprints.

我自己没有做太多的冲刺。 我写了非常有限的代码。 相反,我协调了哪些更改应该在什么时候出现(这是一个复杂的冲刺),测试它们是否工作正常,进行大量代码审查,提出替代设计建议以及在可能的情况下进行配对编程,以解决问题。 我们已完成所有工作,在这种情况下,缩小有助于简化PR的决策。 这是我们最高速度的冲刺之一。

“The Force is what gives an engineer his power. It’s an energy field created by all things. It surrounds us and penetrates us; it binds the code together.” — Neil Kakkar

“力量是赋予工程师力​​量的力量。 这是万物创造的能量场。 它包围着我们并渗透我们; 它将代码绑定在一起。” —尼尔·卡卡(Neil Kakkar)

Alright, I won’t stretch this analogy any further.

好吧,我不再赘述。

Figuring out how to become a force multiplier sounds more valuable to me than 10 developers. In practice, a good force multiplier (or divider) is the team culture. Just as I can create habits of mind to multiply my output, so can the entire team. This happens with the team culture. Retrospectives, reviews, and experiments are everything a team does to mould their culture. The culture is always in flux, as team members come and go, adding their personal touches.

与10个开发人员相比,弄清楚如何成为力量倍增器对我来说更有价值。 在实践中,良好的力量乘数(或除数)是团队文化。 正如我可以养成精神习惯来增加我的产出一样,整个团队也可以。 这与团队文化有关。 回顾,评论和实验是团队塑造其文化的一切。 随着团队成员的来来往往,文化不断变化,增加了他们的个人风格。

A culture that empowers is a force multiplier. I was able to do what I did above because our culture allowed it. Our team culture looks at the team’s output for the sprint, not the individual outputs. This allowed me to optimise for the team getting lots done, instead of focusing on myself. The team shapes the culture, and the culture shapes the team. This idea even extends to cities and nations:

赋予权力的文化是力量的乘数。 因为我们的文化允许,我能够做到以上所做的事情。 我们的团队文化着眼于团队的冲刺输出,而不是个人输出。 这使我可以优化团队的工作效率,而不必专注于自己。 团队塑造文化,而文化塑造团队。 这个想法甚至扩展到城市和国家:

“A society that is under constant military threat will have a culture that celebrates martial virtues, a society that features a cooperative economy will strongly stigmatize laziness, an egalitarian society will treat bossiness as a major personality flaw, an industrial society with highly regimented work schedules will prize punctuality, and so on.” — Why the Culture Wins

“一个经常受到军事威胁的社会将拥有一种庆祝军事美德的文化,一个以合作经济为特色的社会将严重污蔑懒惰,一个平等社会将把领导能力视为主要的人格缺陷,将工作日程安排得井井有条的工业社会会守时,等等。” - 为什么文化获胜

关于所有权 (On Ownership)

We’re three teams at BNEF, and we share a Jenkins setup for automated testing. There was a big Jenkins maintenance task upcoming, and I chose to own it. This meant figuring out how to do things, arranging meetings to discuss improvements and alternatives, and finally, coordinating implementation. Except, I didn’t know I’d be doing all that when I chose to own it. I just thought it would be fun.

我们是BNEF的三个团队,我们共享Jenkins的自动测试设置。 Jenkins的维护工作非常艰巨,我选择拥有它。 这意味着要弄清楚如何做事,安排会议以讨论改进和替代方案,最后是协调实施。 除此之外,当我选择拥有它时,我不知道自己会做所有的事情。 我只是觉得这很有趣。

I messaged on our group chat about alternatives I had come up with. The conversation soon died, possibly because everyone was busy with something. I noticed feeling “I don’t know what I’m supposed to do here now.” So I decided to get on with my other sprint tasks.

我在小组讨论中向我传达了我提出的替代方案。 谈话很快就消失了,可能是因为每个人都忙于某些事情。 我注意到感觉“我现在不知道该怎么办。” 因此,我决定继续进行其他冲刺任务。

My instinct here went “Oh well, I tried. Someone will reply someday and then we can continue the conversation.” I had played the role of the owner, without becoming the owner. I was surprised when I noticed this. It was a hilariously bad way of managing. Everyone is working on something, and that is what they’re thinking about, not my stuff. So, it’s my responsibility to bring their attention to it.

我的直觉是:“哦,我尝试过。 某天某人会回复,然后我们可以继续对话。” 我曾扮演所有者的角色 ,而没有成为所有者。 当我注意到这一点时,我感到很惊讶。 这是一种非常糟糕的管理方式。 每个人都在做某事,这就是他们在想的,而不是我的东西。 因此,引起他们注意是我的责任。

Two days after the initial chat (that’s how long it took me to reflect and figure out I was in the wrong), I messaged again explaining what I decided, and what work will spill over to which team. This was the second time I was surprised: everyone agreed. It wasn’t that they didn’t care, it’s just that they had nothing more to add after the first chat.

最初的聊天两天后(这就是我思考并弄清楚自己错了的时间),我再次发消息解释了我的决定,以及哪些工作将涉及到哪个团队。 这是我第二次感到惊讶:每个人都同意。 并不是他们不在乎,只是他们在第一次聊天后没有其他要添加的内容。

I cherish this experience a lot. It taught me some important habits: always follow up, and if you own a task, it’s your responsibility to move it forward. Don’t get stuck playing the role, actually get shit done: be it by delegating or doing it yourself.

我非常珍惜这种经历。 它教会了我一些重要的习惯:始终跟进,如果您拥有一项任务,则有责任将其推进。 不要卡在扮演角色中,而是要完成任务:通过委派任务或自己完成任务。

It also reinforced a meta habit: cherish surprise. Surprise is a measure of mismatch between what you predicted and what actually happened. This is a brilliant opportunity to change your mind.

它还强化了一个元习惯:珍惜惊喜。 惊喜是衡量您的预测与实际发生的不匹配的一种度量。 这是一个改变主意的绝好机会。

拥抱恐惧 (Embrace Fear)

Okay, one final story. Last year, I worked on a side project that failed. It was one of those projects where I learn a new language, a new way of doing things, and test a product hypothesis. It was surprisingly difficult to stick to the project, I felt fear whenever I’d think about it.

好吧,最后一个故事。 去年,我从事了一个失败的附带项目 。 这是我学习一种新语言,一种新的做事方式并检验产品假设的项目之一。 坚持这个项目非常困难,我一想到就会感到恐惧。

This was a huge ball of feelings I couldn’t ignore. It primed me to notice subtler pangs of the same feeling, especially at work. Whenever there’s a daunting task ahead of me and I don’t already know how to do it, this feeling creeps back. “Ugh, how would this work? I have no idea yet.”

这是我无法忽视的巨大感觉。 我特别注意到在工作中有同样感觉的微妙的困惑。 每当我面前有一项艰巨的任务,而我却不知道该如何去做时,这种感觉就会回荡。 “ U,这将如何工作? 我还不知道。”

I’ve learned to embrace this feeling. It excites me. It’s information about what I’m going to learn. I’ve taken it so far that I’ve started tracking it in my human log, “Did I feel fear this week?” If the answer is no too many weeks in a row, I’ve gotten too comfortable.¹⁰ Fear is information.

我学会了接受这种感觉。 它使我兴奋。 这是关于我要学习的信息。 到目前为止,我已经开始在自己的人工日志中对其进行跟踪,“本周我感到恐惧了吗?” 如果答案不是连续几个星期,那我就太自在了。¹⁰恐惧就是信息。

This meta-skill of noticing what’s going on in the brain is a powerful monitoring and diagnostic tool. Just like cron jobs that periodically check the health of the system, reviews check and improve your health: mental and physical. That’s exactly the purpose of this post too: it’s my annual work review.

注意到大脑中正在发生的事情的这种元技巧是一种强大的监视和诊断工具。 就像定期检查系统运行状况的cron作业一样,复查检查并改善您的健康状况:身心健康。 这也正是这篇文章的目的:这是我的年度工作回顾。

增加细微差别 (Adding Nuance)

This review wouldn’t be complete without adding nuance to last years sections.

如果不对前几年的章节添加细微差别,此评论将是不完整的。

编写代码 (Writing Code)

Source资源

There’s this funny meme in software engineering which reduces things down to copying from Stack Overflow. It’s a dangerous pattern when new engineers start believing the meme. There’s a lot of things happening, the nuance of which is lost when we say “copy from SO.”

软件工程中有一个有趣的模因,它可以将事情简化为从Stack Overflow复制。 当新工程师开始相信模因时,这是一种危险的模式。 发生了很多事情,当我们说“从SO复制”时,它们之间的细微差别会丢失。

Here’s an example of what copying from SO might look like. Let’s say I’m trying to list all permutations from a generator. Then:

这是从SO复制的外观示例。 假设我正在尝试列出生成器的所有排列。 然后:

  1. This is not a coding interview, so I can look for libraries that do this for me. I don’t know what to use, yet.这不是一次编码采访,所以我可以寻找适合我的库。 我还不知道该用什么。
  2. I Google it, and find I can use itertools.permutations([1,2,3,4]) to generate permutations of a list.

    我用Google搜索它,发现可以使用itertools.permutations([1,2,3,4])生成列表的排列。

  3. Okay, golden! So now I convert the generator to a list, copy this code, and then pass the list in. I’m done.好,金! 现在,我将生成器转换为列表,复制此代码,然后将列表传递进来。

Now, let’s say product requirements are to sort these in lexicographic order. So I write a sort function that works on lists of lists. Except, it doesn’t work. I find out that permutations returns a list of tuples, so I go back to my sorting function and convert it to work on a list of tuples. A while later, product comes back with new requirements: these permutations are too long, and we want to make things faster. We only need permutations of length 4, no matter how big the list.

现在,假设产品需求是按照字典顺序对它们进行排序。 所以我写了一个对列表列表起作用的排序函数。 除此以外,它不起作用。 我发现permutations返回了一个元组列表,因此我返回到排序函数并将其转换为可用于元组列表的函数。 过了一会儿,产品又提出了新的要求:这些排列太长了,我们想让事情变得更快。 无论列表多大,我们都只需要长度为4的排列。

Ugh. Okay. Since I already have a function for generating all permutations, I do that and take the first four elements from each permutation tuple. I realise this leads to duplicates, so I put these tuples in a set, then apply the sorting function to get them in the right order. And now I’m done. Phew, this was hard work, but hey, everyone is sort of happy! The permutation function is still pretty slow for long lists, so I add an item in the backlog to get to it sometime.

啊。 好的。 由于我已经具有生成所有排列的功能,因此可以这样做,并从每个排列元组中获取前四个元素。 我意识到这会导致重复,因此我将这些元组放在一组中,然后应用排序功能以正确的顺序获取它们。 现在我完成了。 ew,这是艰苦的工作,但是,嘿,每个人都很高兴! 对于长列表,排列功能仍然非常慢,因此我在待办事项列表中添加了一个项目,以便有时可以使用它。

If I had taken the time to check the documentation for itertools.permutations, to understand what it does, I would have noticed: it has a parameter for the length of permutations you want to return. It returns a list of tuples. And, it returns them in sorted order. Further, the input argument is not a list, but an iterable, so I could’ve passed in the generator. It was going to get converted into a tuple anyway though, so this doesn’t matter.

如果我花时间检查itertools.permutations的文档以了解其功能,我会注意到:它具有一个参数,用于表示要返回的排列长度。 它返回一个元组列表。 并且,它按排序顺序返回它们。 此外,输入参数不是列表,而是可迭代的,因此我可以传入生成器。 无论如何它都会被转换成元组 ,所以没关系。

This example might seem trivial, but the thinking machinery behind it is not. I’ve noticed this almost happen to me with sufficiently complex APIs and misdirecting names. In short, my rule is “I don’t write code I don’t understand.” Just like the “copy from SO” meme, this rule has tacit knowledge that gets lost in translation. For example, what does it mean to understand code?

这个例子看似微不足道,但背后的思考机制却并非如此。 我已经注意到,使用足够复杂的API和错误的名称,这几乎会发生在我身上。 简而言之,我的规则是“我不写我不懂的代码”。 就像“从SO复制”模因一样,此规则具有隐性知识,这些隐性知识在翻译中会丢失。 例如,理解代码意味着什么?

There are at least three different levels of understanding: you might understand exactly what itertools.permutations would produce, you might understand how it does it, or at an even deeper level, you might understand why it makes those implementation decisions.

至少有三个不同的理解级别:您可能完全了解itertools.permutations会产生什么,可能会了解它是如何实现的,或者甚至更深入地了解您为什么会做出这些实现决策。

Level 1 is understanding what the function or API does.Level 2 is understanding how it does it (the code).Level 3 is understanding why it does it the way it does.

1级了解功能或API的功能; 2级了解其功能(代码)的方式; 3级了解其功能的实现方式。

For well-designed APIs, and things you don’t want to learn in-depth, Level 1 works. However, Level 1 is the bare minimum. Level 0 is what we saw in the example above, and it’s problematic. Another example is copying existing team templates for the first time, which is somewhere between a level 0 and 1 understanding.

对于精心设计的API,以及您不想深入学习的东西,第1级适用。 但是,级别1是最低要求。 级别0是我们在上面的示例中看到的,这是有问题的。 另一个示例是第一次复制现有的团队模板,这在0级和1级理解之间。

Yes, there’s a trade-off. Level 0 is super quick, while getting to Level 3 takes a lot of time. I slow things down when I don’t copy-paste existing templates. But when I have enough slack¹¹, I choose to get a Level 1 understanding before I write code. This usually means I’m slow the first time around, but over time, I get much faster. I deepen my understanding a little bit every time, and this helps me solve bugs quickly. I prioritise learning over getting things done.

是的,需要权衡。 级别0超级快,而级别3则需要很多时间。 当我不复制粘贴现有模板时,我会放慢速度。 但是,当我有足够的空余时间¹¹时,我选择在编写代码之前先获得一级的理解。 这通常意味着我第一次走慢,但是随着时间的流逝,我会变得更快。 每次我都会加深理解,这有助于我快速解决错误。 我优先学习而不是完成工作。

And, yes, I do break the rule sometimes. Some situations demand a quick and easy hack.

而且,是的,我有时会违反规则。 在某些情况下,需要快速轻松地破解。

Sometimes, open-source documentation sucks. When this happens, you need a level 2 understanding to give you the level 1 understanding: you go read the source code. Whenever I have to do this, I remember to preserve context for future-me. It’s hard work to understand someone else’s code, especially if it’s in a language you’re not familiar with. Optimise for not having to do this hard work again and again. When you figure out something important, write it down, that’s what comments are for. Plus, your team will thank you for it. It’s an easy way to build up the force multiplier.

有时,开源文档很烂。 发生这种情况时,您需要具有2级理解才能使您具有1级理解:您可以阅读源代码。 每当我必须这样做时,我都记得要保留上下文以便将来。 很难理解别人的代码,尤其是在您不熟悉的语言中。 优化,不必一次又一次地辛苦工作。 当您发现重要内容时,请写下来,这就是评论的目的。 另外,您的团队会感谢您的。 这是建立力乘数的简单方法。

This is a lot like “saving” information packets. They’re units of work you’ve already done, so you don’t do them again the next time. The levels of understanding apply to the code your team owns as well, not just code you copy-paste, or ‘inherit’ from others. Ideally, you ought to have a level 2 understanding of your team’s code, and a level 3 understanding of code you own. This understanding is building a mental model of how the code works.

这很像“保存”信息包。 它们是您已经完成的工作单元,因此下次无需再次执行它们。 理解水平也适用于您团队所拥有的代码,而不仅仅是您复制粘贴或从其他人“继承”的代码。 理想情况下,您应该对团队的代码有2级的了解,并且对自己拥有的代码有3级的了解。 这种理解为代码的工作建立了思维模型。

I’ve noticed that code reviews help a lot in building this mental model. I do as many reviews as I can: it keeps me in the loop for what my team is working on. There’s also a very interesting feedback mechanism built into this. I can judge how well I understand the code by my review comments. The less familiar I am with the code base, the more trivial my comments. As my mental model improves, I start seeing the system as a whole and how this new part will interact with everything else. I can spot inconsistencies, and figure out when something wouldn’t work. When I start making comments like these, I know I’m inching towards a level 2–3 understanding. Since the code is always evolving, this is a constant process: your understanding can go up and down depending on how out of touch you get.

我注意到代码审查对建立这种思维模型有很大帮助。 我会尽我所能地进行评论:这使我可以随时了解团队的工作。 内置了一个非常有趣的反馈机制。 我可以通过评论来判断我对代码的理解程度。 我对代码库越不熟悉,我的评论就越琐碎。 随着我的思维模型的改进,我开始看到整个系统,以及这个新部分将如何与其他所有部分交互。 我可以发现不一致之处,并找出什么时候行不通。 当我开始发表这样的评论时,我知道我正在朝着2-3级理解迈进。 由于代码总是在不断发展,所以这是一个持续不断的过程:您的理解可能会上下浮动,具体取决于您失去联系的方式。

Another reason to get a level 2–3 understanding is to seek inspiration. When you understand the code of a new system, you figure out what decisions they made, and why. This increases your repertoire of things to work with¹². This is one big reason why I dove into Unix, and wrote about how it works. This is also a very good reason to understand the tools you use, which is why I learned how Git works

获得2-3级理解的另一个原因是寻求灵感。 当您了解新系统的代码时,便会弄清它们做出了哪些决定以及原因。 这增加了您可以使用的事物库。 这是我深入Unix并撰写有关其工作原理的一个重要原因。 这也是了解您使用的工具的一个很好的理由,这就是为什么我了解了Git的工作原理的原因

To summarize:

总结一下:

  1. Don’t write code you don’t understand不要写不懂的代码
  2. Prioritise learning whenever possible尽可能优先考虑学习
  3. Preserve context for future you为将来的您保留上下文
  4. Aim for a level 2–3 understanding of code your team owns旨在对您的团队拥有的代码有2–3级的理解
  5. Code reviews help keep your mental models up to date代码审查有助于使您的心理模型保持最新状态

测试中 (Testing)

Say you build a new system, and testing reveals it to be too slow. You designed it considering how long each component would take, but looks like some of your assumptions failed you. What’s the next thing you do?¹³

假设您构建了一个新系统,测试表明它太慢了。 您在设计该组件时考虑了每个组件将花费多长时间,但看起来您的某些假设使您无法做到。 接下来要做的是什么?¹³

I would measure how long each component takes to identify where I can make the biggest impact. Some things are indeed out of your control, like the request latency. You’re probably not going to launch a satellite to make your code faster. Measuring timing and figuring out where you can improve is critical.

我会衡量每个组件需要多长时间来确定可以在哪里产生最大的影响。 确实有些事情是您无法控制的,例如请求延迟。 您可能不会发射卫星来提高代码速度。 衡量时间安排并找出可以改进的地方至关重要。

I’ve tried going in guns blazing, optimising whatever looks suboptimal to me, like converting dicts to sets, but the final solution is usually never this obvious. Dicts are probably not the reason your request is taking a second longer.¹⁴ Measure instead of assuming.

我曾尝试开火,优化对我而言似乎不太理想的内容,例如将dicts转换为sets,但是最终的解决方案通常从来都不是那么明显。 辞典可能不是您的请求花费一秒钟更长的时间的原因。¹⁴而不是假设。

In last year’s review, I wrote:

在去年的评论中,我写道:

“If there’s an environment mismatch between test and deploy machines, you’ll be in trouble. And here’s where deployment environments come in. […] The idea is to try and catch errors that unit and system testing wouldn’t. For example, an API mismatch between requesting and responding system.”

“如果测试和部署计算机之间的环境不匹配,那么您将遇到麻烦。 这就是部署环境的用武之地。[…]想法是尝试捕获单元和系统测试不会出现的错误。 例如,请求系统和响应系统之间的API不匹配。”

I didn’t quite appreciate a clean testing environment until it bit me. By clean, I mean it replicates your prod environment completely. It allows you to test exactly what will happen in prod. Of course, you don’t need a physical machine, docker works well here.

直到它咬住了我,我还是不太欣赏干净的测试环境。 干净是指它完全复制了您的产品环境。 它使您能够准确测试生产中将发生的情况。 当然,您不需要物理机,docker在这里可以很好地工作。

I’ve found docker to be one of the biggest productivity tools for testing. It allows me to whip up new environments, test things locally, and reduces friction. This fast feedback loop allows me to develop quicker. It’s frustrating when I have to wait five to ten minutes to check if I deployed it well, trigger a test, check outputs, etc. Docker is all of that, right on my machine.

我发现docker是最大的测试生产力工具之一。 它使我能够开发新的环境,在本地进行测试并减少摩擦。 这种快速的反馈循环使我可以更快地发展。 当我不得不等待五到十分钟来检查我是否正确部署它,触发测试,检查输出等时,这真令人沮丧。Docker就是所有这些,就在我的机器上。

One final thing I learned was to optimise for zero false positives. It’s easy to write tests that pass without testing what you intended to. For example, iterating through a database cursor and checking the values? Well if the iterator returns nothing, your test has passed without checking anything.

我学到的最后一件事是优化零误报。 编写无需通过您想要的测试即可通过的测试很容易。 例如,遍历数据库游标并检查值? 好吧,如果迭代器什么都不返回,则您的测试已通过,并且未进行任何检查。

These are false positives, and they’re sinister for giving you a false sense of confidence. How do I fix these? Well, I start by being extra careful during code reviews. The second, sure-fire way of testing this is to make your tests fail. I switch around an equals to a not-equals. If tests still pass, I have a problem. This is something I’ve started doing recently, once I saw my first false positive.

这些都是误报,并且会给您一种虚假的自信感,使他们险恶。 我该如何解决? 好吧,我首先在代码审查期间格外小心。 第二种肯定的测试方法是使测试失败。 我将等于转为不等于。 如果测试仍然通过,我有问题。 一旦看到第一个误报,这就是我最近开始做的事情。

In summary:

综上所述:

  1. With optimisation problems, measure instead of assuming.对于优化问题,请测量而不是假设。
  2. Have a clean staging environment. Containerisation is cool.拥有一个干净的暂存环境。 容器化很酷。
  3. Optimise for 0 false positives.针对0个误报进行优化。

设计 (Design)

Almost every system design is about trade-offs. The good engineers make these trade-offs explicit. These trade-offs rise out of the constraints on us and on the product we want. Speaking of, requirements and constraints are not the same. Constraints are real-world limits. For example, we can’t send messages from New York to Australia in one millisecond, yet. There are also product constraints, like we don’t want users to see more than three pop-ups at any time.

几乎每个系统设计都需要权衡。 好的工程师将这些权衡因素明确化。 这些权衡取舍于对我们以及我们想要的产品的限制。 说到需求和约束是不一样的。 约束是现实世界的限制。 例如,我们还不能在一毫秒内将消息从纽约发送到澳大利亚。 还有产品限制,例如我们不希望用户在任何时候看到三个以上的弹出窗口。

Requirements, on the other hand, are flexible. They are things we want to happen, but often times we don’t know what we want. Asking myself “what am I really trying to do?” helps uncover the constraints from the requirements. Usually, people jump too quickly into the requirements, which is just one of the many possible paths from the constraints. So, whenever I feel the requirements don’t make sense, I go back to the constraints and reason up to reach alternative requirements. I learned to do this from my PM, he’s excellent, and from @shreyas Twitter threads.¹⁵ There’s no holy grail design that will always work.

另一方面,需求是灵活的。 它们是我们想要发生的事情,但是很多时候我们都不知道我们想要什么。 问自己“我真正想做什么?” 帮助从需求中发现约束。 通常,人们跳入需求的速度太快了,这只是摆脱约束的许多可能途径之一。 因此,每当我觉得要求没有意义时,我就会回到约束条件并进行推理,以达到替代要求。 我是从我的PM中学到的,他是优秀的,从@shreyas Twitter线程中学到了做到这一点。¹⁵没有一个圣杯设计可以一直工作。

When designing systems, I’ve noticed two broad themes. The first is that there are a limited number of components we’ve invented: queues, caches, databases, and connectors (or code to make them work together). Every possible design is a permutation of these components, each of which presents its own trade-offs. Some are much faster, some are much more maintainable, and some are much more scalable, depending on your use-case. Given your constraints, one arrangement will be better than the other. Your goal is finding that arrangement. From time to time, there are brilliant hacks you can do to reduce complexity, or make things faster. However, the basic infrastructure doesn’t change.

在设计系统时,我注意到了两个广泛的主题。 首先是我们发明了数量有限的组件:队列,缓存,数据库和连接器(或使它们协同工作的代码)。 每种可能的设计都是这些组件的排列,每个组件都有自己的权衡。 根据您的用例,有些速度更快,有些更易于维护,有些更具可扩展性。 考虑到您的限制,一种安排会比另一种更好。 您的目标是找到这种安排。 您可以不时地采取一些出色的技巧来降低复杂性或加快处理速度。 但是,基本的基础结构不会改变。

The second is that everyone has a few happy-themes to go back to, which they’ve seen work well in the past. These are different lenses to look at the system. Design is about figuring out which permutations conform to this lens. For example, I love reducing state and keeping things simple. Reducing state helps me reason better about systems, and helps me write better tests. Same for keeping things simple. Both lead to fewer bugs. Of course, it can’t be too simple: it can’t violate the constraints.

第二个是每个人都有一些快乐主题可以回溯,他们过去认为效果很好。 这些是查看系统的不同镜头。 Design is about figuring out which permutations conform to this lens. For example, I love reducing state and keeping things simple. Reducing state helps me reason better about systems, and helps me write better tests. Same for keeping things simple. Both lead to fewer bugs. Of course, it can't be too simple: it can't violate the constraints.

Like I said last year, it’s worth thinking about speed, as well as local development and testing. If two designs are equivalent, but one is much easier to set up locally and write tests for, I’ll almost always choose the one that’s easier to write tests for. I like figuring out other people’s lenses, and try to adopt lenses I don’t have. That’s another reason I read tech blogs.

Like I said last year, it's worth thinking about speed, as well as local development and testing. If two designs are equivalent, but one is much easier to set up locally and write tests for, I'll almost always choose the one that's easier to write tests for. I like figuring out other people's lenses, and try to adopt lenses I don't have. That's another reason I read tech blogs.

When designing, it’s worth preserving context too, just like when writing code. Often times, I’ve seen myself come back to very old code, forget the assumptions we had then, and think “Why did we do it like this?” Making our constraints and trade-offs explicit helps keep things in perspective, and helps judge whether you made the right decision.

When designing, it's worth preserving context too, just like when writing code. Often times, I've seen myself come back to very old code, forget the assumptions we had then, and think “Why did we do it like this?” Making our constraints and trade-offs explicit helps keep things in perspective, and helps judge whether you made the right decision.

Finally, when designing systems that replace existing systems, I find it very important to talk about the migration paths: How will we manage to move from the old system to the new system? If you’ve ever noticed a system with half of the things running on the new code, and half on the old code, that’s a flawed migration path. Not thinking about the migration path leads to mounting tech debt: you now have to manage and maintain both the new and the old systems. Sometimes, this happens because priorities switch, and you’re left in the middle. In either case, these abnormalities don’t age well.

Finally, when designing systems that replace existing systems, I find it very important to talk about the migration paths: How will we manage to move from the old system to the new system? 如果您曾经注意到一个系统,其中一半运行在新代码上,一半运行在旧代码上,那么这就是一条有缺陷的迁移路径。 不考虑迁移路径会导致技术负担增加:您现在必须管理和维护新旧系统。 有时,发生这种情况是因为优先级发生了变化,而您却处于中间位置。 无论哪种情况,这些异常都不会老化。

Good migration paths that might take longer than a sprint take into account the state they leave the system in. If priorities change, will we get stuck in a state where we can’t do anything? Or is our migration incremental, which is robust to changing priorities? Of course, the incremental migration isn’t always the right solution. Sometimes, the clean break is a lot easier. The important part there is communicating well: we can’t deal with changing priorities for this migration.

Good migration paths that might take longer than a sprint take into account the state they leave the system in. If priorities change, will we get stuck in a state where we can't do anything? Or is our migration incremental, which is robust to changing priorities? Of course, the incremental migration isn't always the right solution. Sometimes, the clean break is a lot easier. The important part there is communicating well: we can't deal with changing priorities for this migration.

In summary:

In summary:

  1. Every system design is about trade-offs.Every system design is about trade-offs.
  2. There’s limited technical components to every design.There's limited technical components to every design.
  3. People have definite lenses with which they approach design, just like mental models.People have definite lenses with which they approach design, just like mental models.
  4. Preserve context when designing: write down your constraints and trade-offs.Preserve context when designing: write down your constraints and trade-offs.
  5. When replacing old systems, have a clear migration path.When replacing old systems, have a clear migration path.

Gathering Requirements (Gathering Requirements)

Going with the above theme, gathering requirements is actually gathering constraints. Like we saw above, requirements are sometimes a translation of the constraints into tech requirements, which isn’t always the way to go forward. In my team culture, there’s enough trust in both the team and the PM that we’re free to challenge each other on this. Asking the question suffices. A checklist of questions works well here.

Going with the above theme, gathering requirements is actually gathering constraints. Like we saw above, requirements are sometimes a translation of the constraints into tech requirements, which isn't always the way to go forward. In my team culture, there's enough trust in both the team and the PM that we're free to challenge each other on this. Asking the question suffices. A checklist of questions works well here.

This final section dives into a few gotchas, some things I did wrong, and a summary of everything that went right.

This final section dives into a few gotchas, some things I did wrong, and a summary of everything that went right.

Some Hacks That Have Worked Very Well for Me (Some Hacks That Have Worked Very Well for Me)

  • Doing as many code reviews as possible. The more you miss, the wronger your mental model for the code becomes, and the more time it takes you to figure out how to design the new thingy.Doing as many code reviews as possible. The more you miss, the wronger your mental model for the code becomes, and the more time it takes you to figure out how to design the new thingy.
  • Playing the meta: An important second question to ask is “How did you find out X,” where X is the answer to your first question.Playing the meta: An important second question to ask is “How did you find out X,” where X is the answer to your first question.
  • The first person to review my PRs is me. Always. I like doing this a lot. It’s something I learned from writing: The first phase is writing out the substance, the second phase is editing for flow. It’s similar in code. Code review is the edit phase, and doing this on my code makes me better at writing code, noticing inconsistencies, and figuring out how others would approach the review.The first person to review my PRs is me. 总是。 I like doing this a lot. It's something I learned from writing: The first phase is writing out the substance, the second phase is editing for flow. It's similar in code. Code review is the edit phase, and doing this on my code makes me better at writing code, noticing inconsistencies, and figuring out how others would approach the review.

Super Powers (Super Powers)

Just like in a video game, there are a few power-ups you can obtain. These help give you powers in the real world. Just like in a video game, you need to go on quests to obtain them.

Just like in a video game, there are a few power-ups you can obtain. These help give you powers in the real world. Just like in a video game, you need to go on quests to obtain them.

Here are a few I’ve discovered, and possible quests to get them through.

Here are a few I've discovered, and possible quests to get them through.

  1. Getting into the source code when documentation isn’t enough

    Getting into the source code when documentation isn't enough

    Quest: Reading open-source code.

    Quest: Reading open-source code.

  2. Quickly build a mental model for the code you’re looking at

    Quickly build a mental model for the code you're looking at

    Quest: Reading open-source code.

    Quest: Reading open-source code.

  3. Embracing fear

    Embracing fear

    Quest: Build a side project.

    Quest: Build a side project.

  4. Confidence to express ignorance

    Confidence to express ignorance

    Quest: Overcome the first gotcha with growing.

    Quest: Overcome the first gotcha with growing.

  5. Defining my terms. Letting people know exactly what I’m talking about.

    Defining my terms. Letting people know exactly what I'm talking about.

    Quest: ???

    Quest: ???

Some Gotchas With Growing (Some Gotchas With Growing)

Just as engineers appreciate documentation that includes common gotchas, I think people appreciate reading about common gotchas with growing, mistakes I noticed myself making and then corrected.

Just as engineers appreciate documentation that includes common gotchas, I think people appreciate reading about common gotchas with growing, mistakes I noticed myself making and then corrected.

Sometimes, I Feel I Need to Know the Answer to Everything (Sometimes, I Feel I Need to Know the Answer to Everything)

As I figure out more things, more people reach out to me with questions. This feels great. However, there are bound to be questions I don’t know the answer to. In this case, chasing the feeling, and feigning intelligence is a trap. A trap that stops me from learning. Will people stop coming to me if I say I don’t know? Probably not. Further, they’re going to find out the answer anyway, since they’re competent and smart too. How dumb would it be to not soak in that knowledge too?

As I figure out more things, more people reach out to me with questions. This feels great. However, there are bound to be questions I don't know the answer to. In this case, chasing the feeling, and feigning intelligence is a trap. A trap that stops me from learning. Will people stop coming to me if I say I don't know? 可能不是。 Further, they're going to find out the answer anyway, since they're competent and smart too. How dumb would it be to not soak in that knowledge too?

Confidence to express ignorance is a superpower. One good way I hone this skill is by saying “Nothing to add” when I have nothing to add, instead of repeating what other people said. It feels powerful to me. I got this one from Charlie Munger.

Confidence to express ignorance is a superpower. One good way I hone this skill is by saying “Nothing to add” when I have nothing to add, instead of repeating what other people said. It feels powerful to me. I got this one from Charlie Munger.

Sometimes, I Lose My Cool (Sometimes, I Lose My Cool)

There are some times when I enter the panic & frustration mode. I stop reasoning about things rationally and write whatever garbage I can to solve the problem. Add a call, add a bracket, print random stuff, just get things to run some way. This usually starts when it takes me longer than expected to fix something.

There are some times when I enter the panic & frustration mode. I stop reasoning about things rationally and write whatever garbage I can to solve the problem. Add a call, add a bracket, print random stuff, just get things to run some way. This usually starts when it takes me longer than expected to fix something.

Here’s a concrete example. I was working on tests for a new queue system we built, and I wanted to simulate starving and competing queue consumers. So, I decided to spawn several threads in the test, all running the consumer, which would run for five seconds, competing for one single message in the queue. I’d expect only one of them to get the message (that’s the queue semantics we implemented). And I’d expect none of them to crash.

Here's a concrete example. I was working on tests for a new queue system we built, and I wanted to simulate starving and competing queue consumers. So, I decided to spawn several threads in the test, all running the consumer, which would run for five seconds, competing for one single message in the queue. I'd expect only one of them to get the message (that's the queue semantics we implemented). And I'd expect none of them to crash.

For the test, I joined the threads with a timeout of five seconds. These tests didn’t seem to work. I tried simulating things manually, and everything would work as expected. But with the threads, sometimes the tests would fail. I couldn’t figure this out. I tried every random thing I could. In one great moment of desperation, I re-ordered the tests. It felt funny doing this, how could this possibly help? Turns out, the first test passed again, and the other one, which was passing beforehand started failing.

For the test, I join ed the threads with a timeout of five seconds. These tests didn't seem to work. I tried simulating things manually, and everything would work as expected. But with the threads, sometimes the tests would fail. I couldn't figure this out. I tried every random thing I could. In one great moment of desperation, I re-ordered the tests. It felt funny doing this, how could this possibly help? Turns out, the first test passed again, and the other one, which was passing beforehand started failing.

This is when I noticed I had lost my cool, trying random things that didn’t make sense. I calmed down and started investigating what was happening in the threads. Turns out, join just waits, and doesn’t kill the process even after timeout, terminate() is what kills the process. If I had taken the time to read the docs properly, I wouldn’t have felt so frustrated.

This is when I noticed I had lost my cool, trying random things that didn't make sense. I calmed down and started investigating what was happening in the threads. Turns out, join just waits, and doesn't kill the process even after timeout, terminate() is what kills the process. If I had taken the time to read the docs properly, I wouldn't have felt so frustrated.

The threads weren’t being terminated, and these orphans would mess with the following tests. Usually, this happens when I’m in a rush, when I haven’t protected my slack, and as a result, I’m not prioritising learning over doing. Other times, it’s because it’s a hard piece of code, and no low-hanging fruit solved the problems.

The threads weren't being terminated, and these orphans would mess with the following tests. Usually, this happens when I'm in a rush, when I haven't protected my slack, and as a result, I'm not prioritising learning over doing. Other times, it's because it's a hard piece of code, and no low-hanging fruit solved the problems.

Noticing I’m doing this is usually enough to snap me out of it. I move from ad-hoc bug fixing to strategic bug fixing.

Noticing I'm doing this is usually enough to snap me out of it. I move from ad-hoc bug fixing to strategic bug fixing.

Neophilia (Neophilia)

It’s easy to take optimising learning over doing too far. For example, making the wrong design decisions to try out a new technology. I keep myself in check thanks to our team culture. We challenge each other’s decisions, and realise when we have no good reason to explain it, there’s a latent desire, which we then make explicit.

It's easy to take optimising learning over doing too far. For example, making the wrong design decisions to try out a new technology. I keep myself in check thanks to our team culture. We challenge each other's decisions, and realise when we have no good reason to explain it, there's a latent desire, which we then make explicit.

A concrete way I do this: When figuring out pros and cons for a design, I explicitly mention “this would be cool to learn,” so this desire stops hiding behind flimsy reasons. Make decisions for the right reasons, not to try something new out.

A concrete way I do this: When figuring out pros and cons for a design, I explicitly mention “this would be cool to learn,” so this desire stops hiding behind flimsy reasons. Make decisions for the right reasons, not to try something new out.

Adding a new technology to the team stack is a big decision, one not to be taken lightly.

Adding a new technology to the team stack is a big decision, one not to be taken lightly.

问题 (Questions)

To extend last year’s list, there are a few questions I don’t yet have the answer to.¹⁶ I’d like to think more about these this year.

To extend last year's list, there are a few questions I don't yet have the answer to.¹⁶ I'd like to think more about these this year.

  1. How do you build a culture which promotes X, Y, Z?How do you build a culture which promotes X, Y, Z?
  2. How do you judge culture fit? Hard to do top-down predictions when things are built bottom-up.How do you judge culture fit? Hard to do top-down predictions when things are built bottom-up.
  3. I suspect being precise with your words is yet another superpower. It’s effective communication + communicating the right thing. What’s one quest I can do to hone this?I suspect being precise with your words is yet another superpower. It's effective communication + communicating the right thing. What's one quest I can do to hone this?
  4. What are some open problems in software engineering?What are some open problems in software engineering?

and some questions from last year that I’d still like to think about

and some questions from last year that I'd still like to think about

  1. How to deal with documentation for code and workflows?How to deal with documentation for code and workflows?
  2. Explore De-risking further. What all strategies exist to de-risk projects?Explore De-risking further. What all strategies exist to de-risk projects?
  3. How to decrease the rate of system degradation?How to decrease the rate of system degradation?

My first year was all about absorbing all I could. I didn’t know enough to see the system, I could only see the parts. This year, I took a god's eye view of the system. I figured out places where I was suboptimal and worked on those. I looked at other parts of the system, absorbed their best practices, and become wary of practices that didn’t work for me. Over time I started looking inward for things I’m doing right, and before I knew it, others started seeing me as a senior software engineer.¹⁷

My first year was all about absorbing all I could. I didn't know enough to see the system, I could only see the parts. This year, I took a god's eye view of the system. I figured out places where I was suboptimal and worked on those. I looked at other parts of the system, absorbed their best practices, and become wary of practices that didn't work for me. Over time I started looking inward for things I'm doing right, and before I knew it, others started seeing me as a senior software engineer.¹⁷

Damn, I love engineering.

Damn, I love engineering.

[1]: Also, probably the first thing I learned this year was to use the American spelling of “learned” (not “learnt”), since most readers are from the States and some of them freaked out on HN and Reddit when they saw “learnt.” Funny.

[1]: Also, probably the first thing I learned this year was to use the American spelling of “learned” (not “learnt”), since most readers are from the States and some of them freaked out on HN and Reddit when they saw “learnt.” 滑稽。

[2] I haven’t stopped learning about these, I’ve just taken a different approach. More on this in acquiring new tools for thought.

[2] I haven't stopped learning about these, I've just taken a different approach. More on this in acquiring new tools for thought.

[3] My interpretation, not representing my employers. Same for the entire article.

[3] My interpretation, not representing my employers. Same for the entire article.

[4] Except for a few system inefficiencies.

[4] Except for a few system inefficiencies.

[5] Affiliate link

[5] Affiliate link

[6] Like when you know exactly what you’re doing and you’ve done it a few times before.

[6] Like when you know exactly what you're doing and you've done it a few times before.

[7] Just communicating is probably not enough in certain team cultures. I haven’t been a part of one like this yet, so I don’t know how to help there.

[7] Just communicating is probably not enough in certain team cultures. I haven't been a part of one like this yet, so I don't know how to help there.

[8] Again, caveats apply. For example, having slack is not an excuse to go fix that damn bug that irritates you, that’s a proper story/KTLO item. There are good and bad ways to use up your slack. I prefer using slack for understanding the depth of the current issue, new tech, etc.

[8] Again, caveats apply. For example, having slack is not an excuse to go fix that damn bug that irritates you, that's a proper story/KTLO item. There are good and bad ways to use up your slack. I prefer using slack for understanding the depth of the current issue, new tech, etc.

[9] Read more: Julia Evans on asking good questions.

[9] Read more: Julia Evans on asking good questions.

[10] One way to hack this would be to start getting fearful of the smallest things, but I’ve never been able to control what I’m afraid of, so I think I’m safe here.

[10] One way to hack this would be to start getting fearful of the smallest things, but I've never been able to control what I'm afraid of, so I think I'm safe here.

[11] read: I can protect my slack

[11] read: I can protect my slack

[12] Tools for thought!

[12] Tools for thought!

[13] Not rhetorical, I’d love to hear from you!

[13] Not rhetorical, I'd love to hear from you!

[14] Not usually, anyway.

[14] Not usually, anyway.

[15] You should go follow Shreyas!

[15] You should go follow Shreyas!

[16] After all, questions are more important than answers.

[16] After all, questions are more important than answers.

[17] No, I don’t have the title yet.

[17] No, I don't have the title yet.

翻译自: https://medium.com/better-programming/the-things-i-learned-to-become-a-senior-software-engineer-1083686d70cd

软件工程师工作经历


http://www.taodudu.cc/news/show-4084481.html

相关文章:

  • 34套Java项目教程+源码包含Java swing项目 Java web项目 Java控制台项目(视频教程+源码)
  • Angular4学习笔记(一):准备和环境搭建
  • Java、三角形类Triangle
  • c++三角类
  • 120. Triangle(三角矩阵)
  • 三维空间的三角剖分( 3D Delaunay Triangulated graph)第一部分:从二维空间的三角剖分做起
  • spark的数三角形算法_Graphx图算法【1】三角形TriangleCount
  • 数三角(triangle)
  • Triangle 三角形求最小路径和 @leetcode
  • Three.js三角形Triangle
  • 104Triangle Area三角面积
  • 三角(Triangle)
  • 运用类判断三角形的形状
  • java设计triangle三角形_Java:【三角形类Triangle】设计一个名为Triangle的类来扩展GeometricObject类。该类包括:...
  • TriangleCount三角形计数
  • Leetcode 120. Triangle 三角形问题(动态规划经典) 解题报告
  • java中编写一个三角形类,java 三角形类Triangle java 三角形类 Triangle的用法详解
  • Java的triangle方法_java三角形类Triangle用法代码解析
  • lintcode triangle 数字三角形
  • 三角网格剖分工具 Triangle 安装及使用
  • java三角形类_java 三角形类 Triangle的用法详解
  • Leetcode 120 Triangle 三角形最小路径和
  • java 三角形类 Triangle
  • 三角网格库Triangle的使用
  • spark graphx的Triangle三角形计数算法使用示例
  • 【保姆级教程】三角网生成库---triangle快速入门及使用说明(再不会就说不过去了啊兄弟)
  • 一瞬间、一刹那、一弹指具体是多少时间呢?
  • 一刹那,是幡然悔悟的一刹那
  • Element-Ui 双重el-tabs组件选中第二层时,刷新导致第一层选中样式丢失问题以及解决方法
  • 最美不过人间四月天

软件工程师工作经历_我学会成为高级软件工程师的经历相关推荐

  1. java软件工程师工作业绩_嵌入式软件工程师-简历范文,【工作经历+项目经验+自我评价】怎么写...

    嵌入式软件工程师-简历模板下载 [网盘下载]100+清新大气简历模板: https://zhuanlan.zhihu.com/p/115911695 https://zhuanlan.zhihu.co ...

  2. beyond compare类似软件_BIM工作是什么?需要哪些BIM软件来完成?

    BIM的运作是多种应用软件共同协力整合的成果.故若要能充份发挥BIM在营建生命周期上运用的效益,于各阶段选择适合的BIM软件是重要的.今天咱们就聊聊BIM需要什么软件来完成呢?主要应用软件介绍! 一. ...

  3. 各抓包软件的之间差异_系统软件和应用程序软件之间的差异

    各抓包软件的之间差异 什么是软件? (What is Software?) Software is referred to as a set of programs that are designed ...

  4. python爬虫工程师工作指标,【2020最新】Python爬虫工程师 - 3个月成为网络爬虫工程师...

    课程简介 1.平均薪资高 入行爬虫工程师薪资可达15w 高级爬虫工程师薪资可达30w 2.入行门槛低 从事爬虫工程师职位最多的学历是大专(来源:猎聘网) 3.就业前景广 未来可发展数据工程师全栈工程师 ...

  5. 三维cad应用工程师算计算机能力吗,三维CAD高级应用工程师的含金量有多少?

    2019-06-19 19:11黄玲香 如题所述的"三维CAD高级应用工程师"是软件厂商或企业颁发的表明软件操作技能水平的证书,它跟职称不属于同一个范畴,因此没有可比性.职称(Pr ...

  6. 桌面上的软件图标是白板_是时候结束对软件工程师的白板采访了

    桌面上的软件图标是白板 In 2017, prominent software engineers took to Twitter to confess that they would fail a ...

  7. python工程师工作总结_工程师的第一份工作小结

    时间过的真快,前不久才写了篇<对做技术的一点思考>博文,那时刚刚决定自己第一份工作的去处.现在,我已经在工作单位工作了将近2个月了.晚上回到住处,抽出休息时间小小回顾下这段时间的收获.这里 ...

  8. python爬虫工程师工作内容_爬虫岗位职责

    岗位职责: *针对复杂的网站架构主动获取相关数据信息: *负责数据获取.清洗和分析工作. 任职要求: *计算机科学.应用数学.统计学.物理学.天文学.商业分析.信息系统.数据科学或相关专业本科或以上学 ...

  9. 锁具行业电子工程师岗位职责_赏金猎人招募电子产品开发工程师产品结构工程师...

    "赏金猎人"专栏6期来啦! 这个专栏,可以让产业需求被更广大的社区看见 让社区更多有技能.有解决方案的小伙伴参与进来 最终促进科技在传统产业中的应用落地 专栏里面发布的猎人需求 只 ...

  10. 软件的接口设计图_基于GJB 5000A的软件配置管理研究与系统实现

    郝延刚 (海军装备部驻南京地区第二军事代表室,南京 211153) 摘 要:采用Spring.MyBatis以及Activiti等技术,设计了符合GJB 5000A软件配置管理要求的软件配置管理系统. ...

最新文章

  1. SAP MM 公司间STO里交货单PGI之后自动触发内向交货单功能的实现
  2. C# Winform继承窗体打开设计器白屏的一例解决方法
  3. pip导包CalledProcessError: Command '('lsb_release', '-a')'异常处理
  4. Netty权威指南之伪异步I/O编程
  5. 实战使用Axure设计App,使用WebStorm开发(4) – 实现页面UI
  6. AT2370-[AGC013D]Piling Up【dp】
  7. [转]其实每个男孩都想做一个感情专一的好男人
  8. Tether已在以太坊扩容方案Hermez Network上发行USDT
  9. 机器视觉系统中相机的分辨率怎么选择?
  10. css内容过长显示省略号的几种解决方法
  11. python java正则表达式_java 正则表达式
  12. spring-boot子模块打包去掉BOOT-INF文件夹
  13. 数据探索很麻烦?推荐一款史上最强大的特征分析可视化工具:yellowbrick
  14. 林轩田机器学习基石笔记(第16节)——概率论与机器学习建立连接
  15. 计算机公式加法A C,概率公式c怎么计算
  16. 大数据——Flink Window(窗口)机制
  17. jre运行环境jks证书导入
  18. 微软官方操作系统(需空U盘)
  19. RISC-V特权级寄存器及指令文档
  20. SAP中质量订单未维护结算规则导致月结报错的分析解决案例

热门文章

  1. 2018年12月份计算机,桌面CPU天梯图2018年12月最新版 十二月台式电脑处理器排名...
  2. Team Queue
  3. ansible(一)自动化运维工具
  4. Python与企业微信-3
  5. 江西省谷歌高清卫星地图下载
  6. C语言实现简单的五子棋
  7. 为什么uninapp制作微信小程序中uni.getUserInfo获取的微信名称是“微信用户“?
  8. PocketPC常用程序和设置打开命令参数列表
  9. AOKP接听震动PHONE.APK修改方法
  10. python爬虫获取下一页_Python爬虫怎么获取下一页的URL和网页内容?