mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-16 06:53:44 +08:00
mod
This commit is contained in:
125
极客时间专栏/研发效率破局之道/个人效能/21 | 高效工作:Facebook的10x程序员效率心法.md
Normal file
125
极客时间专栏/研发效率破局之道/个人效能/21 | 高效工作:Facebook的10x程序员效率心法.md
Normal file
@@ -0,0 +1,125 @@
|
||||
<audio id="audio" title="21 | 高效工作:Facebook的10x程序员效率心法" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/69/ae007b6781bc727c2e4e6d26b85bac69.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。从今天这篇文章开始,我们就正式进入个人效能模块了。今天,我要和你分享的主题是,程序员如何高效地进行开发工作。
|
||||
|
||||
最近比较流行的一个说法是10x程序员,也就是10倍程序员,意思是一个好的程序员,工作效率可以达到普通程序员的10倍。要做到这一点并不容易,我们需要在编程技术、工作方式、工具使用等方面全面提高。
|
||||
|
||||
今天这篇文章,我将聚焦于如何提高自己的编程技术,给出在实践中被证明有效的3条原则,包括抽象和分而治之、快速迭代,以及DRY(Don’t Repeat Yourself),并针对每条原则给出几个高效实践。而关于工作方式、工具使用等方面的内容,我会在后面几篇文章中与你详细讨论。
|
||||
|
||||
## 第一条原则:抽象和分而治之
|
||||
|
||||
虽然我们面对的世界非常复杂,但大脑只能同时处理有限的信息,那怎么平衡这个有限和复杂之间的矛盾呢?
|
||||
|
||||
**最好的办法是**,把一个系统拆分为几个有限的子系统,每个子系统涵盖某一方面的内容,并将其复杂性隐藏起来,只对外暴露关键信息。
|
||||
|
||||
这样,我们在研究这个系统的时候,就无需考虑其子系统的细节,从而对整个系统进行有效的思考。如果我们需要深入了解某一个子系统,再打开这个子系统来看即可。
|
||||
|
||||
以此类推,如果这个子系统还是很复杂,我们可以再对其进行拆分。这样一来,在任何时候,我们思考时面对的元素都是有限的,复杂度也下降到了大脑的能力范围之内,从而完成对一个复杂系统的理解和处理。
|
||||
|
||||
**这个拆分处理的过程,就是我们常说的分而治之;而用子系统来隐藏一个领域的内部细节,就是抽象。**抽象和分而治之,是我们理解世界的基础。
|
||||
|
||||
比如,我们在了解一张简单的桌子时,首先想到的是它由1个桌面和4条桌腿组成。那么,桌面和桌腿就是子系统:桌面就是一个抽象,代表实现摆放物品功能的一个平面;桌腿也是一个抽象,代表支撑桌面的结构。
|
||||
|
||||
如果我们需要进一步了解桌面或者桌腿这两个子系统,可以再进一步去看它们的细节,比如两者都有形状、重量、材料、颜色等。但如果一上来就考虑这些细节的话,我们对桌子的理解就会陷入无尽的细节当中,无法快速形成对整个桌子的认知。
|
||||
|
||||
软件开发也是这个道理,我们必须做好抽象和分而治之,才能做出好的程序。
|
||||
|
||||
所以,拿到一个任务之后,我们要做的**首先就是进行模块的定义,也就是抽象,然后对其分而治之**。
|
||||
|
||||
为方便理解,我再和你分享一个在Facebook时,几个前后端开发者同时开发一个功能的案例吧。
|
||||
|
||||
这个功能由一个前端开发者和两个后端开发者完成,整个研发过程至少涉及3个抽象和分而治之的操作:
|
||||
|
||||
- 第一步,前后端模块进行自然的拆分。这时,前后端开发者一定会一块儿认真讨论,明确前后端代码运行时的流程,后端需要提供的API,以及交付这些API的时间。
|
||||
- 第二步,两个后端开发者对后端工作进行拆分,确定各自的工作任务和边界。
|
||||
- 第三步,每个开发者对自己负责的部分再进行抽象和拆分。
|
||||
|
||||
在这个过程中,一定要明确模块之间的依赖关系,尽快确定接口规格和可调用性。比如,在前后端的拆分中,常常会采用这几个步骤处理API:
|
||||
|
||||
1. 前后端开发者一起讨论,明确需要的API。
|
||||
1. 后端人员会先实现API的Mock,返回符合格式规范的数据。在这个过程中,后端开发者会尽快发出代码审查的要求给另一个后端和前端开发者,以确保格式正确。
|
||||
1. Mock实现之后尽快推到主仓的master上(也就是origin/master),并尽快将其部署到内部测试环境,让前端开发者可以使用内部测试环境进行开发和调试。
|
||||
1. 这些API还不能面对用户,通常会先使用功能开关让它只对公司开发人员可见。这样的话,即使API的代码在origin/master上部署到了生产环境,也不会对用户产生影响。
|
||||
|
||||
通过这样的操作,前后端的任务拆分就顺利完成了。
|
||||
|
||||
提高抽象和分而治之效率的一个技巧是,在设计代码架构时注意**寻找合适的设计模式**。
|
||||
|
||||
设计模式指的是,设计过程中可以反复使用的、可以解决特定问题的设计方法,最经典的莫过于[《设计模式:可复用面向对象软件的基础》](https://book.douban.com/subject/1052241/)中列举的23个设计模式,以及针对企业软件架构的[《企业应用架构模式》](https://book.douban.com/subject/1230559/)。同时,我们还要注意公司内部具体的常用模式。这些模式都是经实践检验有效的,且传播较广容易理解,都可以作为你进行模块拆分的参照。
|
||||
|
||||
具体实现功能的过程中,也会处处体现分而治之的思想。最主要的一个表现是,每个开发者都会把自己的**代码尽量做到原子性**。代码的原子性指的是,一个提交包含一个不可分割的特性、修复或者优化。
|
||||
|
||||
在实际工作中,功能往往比较大。如果只用一个提交完成一个功能,那这个提交往往会比较大,所以我们需要把这个功能再拆分为子功能。
|
||||
|
||||
比如,某个后端API的实现,我们很可能会把它拆分成数据模型和API业务两部分,但如果这样的提交还是太大的话,可以进一步将其拆小,把API业务再分为重构和添加新业务两部分。
|
||||
|
||||
总之,我们的目的是让每个提交都做成能够独立完成一些任务,但是又不太大。一般来说,一个提交通常不超过800行代码。
|
||||
|
||||
## 第二条原则:快速迭代
|
||||
|
||||
通过前面的文章,我们已经明确了快速迭代对提高研发效能的重要意义。接下来,我们就看看在具体的编程中,快速迭代的一些实践吧。
|
||||
|
||||
**第一,不要追求完美,不要过度计划,而是要尽快实现功能,通过不断迭代来完善。**优秀的架构往往不是设计出来的,而是在实现过程中逐步发展、完善起来的。
|
||||
|
||||
Facebook有一条常见的海报标语,叫作“Done is better than perfect”,意思就是完成比完美要重要。要实现快速迭代,我们在设计和实现功能时都要注意简单化。
|
||||
|
||||
有些开发者过于追求技术,投入了大量时间去设计精美、复杂的系统。这样做没有问题,但一定要有一个度,切忌杀鸡用牛刀。因为复杂的系统虽然精美,但往往不容易理解,维护成本也比较高,修改起来更是不容易。
|
||||
|
||||
所以,我们在Facebook进行开发的时候,尽量使用简单实用的设计,然后快速进行版本迭代。
|
||||
|
||||
**第二,在设计的实现中,尽量让自己的代码能够尽快运行起来,从而尽快地验证结果。**我们常常会先实现一个可以运行起来的脚手架,然后再持续地往里面添加内容。
|
||||
|
||||
在工作中,因为往往是在一个比较大的系统里工作,不能很容易地运行新代码。这时,我们可以编写脚本或者单元测试用例来触发新写的代码。通常情况下,我们更倾向于使用后者,因为这些测试用例,在功能开发完成上线之后,还可以继续用于保证代码质量。
|
||||
|
||||
在我看来,在开发过程中,能触发新写的代码帮助我开发,是单元测试的一个重要功能。
|
||||
|
||||
**第三,为了能够快速进行验证,一个重要实践是设置好本地的代码检验**,包括静态扫描、相关单元测试的方便运行,以及IDE能够进行的实时检查等。
|
||||
|
||||
**第四,代码写好之后,尽快提交到主代码仓并保证不会阻塞其他开发人员**。
|
||||
|
||||
实际上,这是代码提交原子性的另外一个重要特点,即代码提交的原子性,可以保证主代码仓在理论上能够随时基于master分支上的任何提交,构建出可以运行的、直接面对用户的产品。在这种方式下,每个开发者在任何时候都可以基于origin/master进行开发,从而确保Facebook几千人共主干开发时分而治之能够顺利进行。
|
||||
|
||||
关于实现代码提交的原子性,我还有一个小技巧,就是如果当前编写的代码提交实在不方便马上推送到origin/master分支上,我们也可以频繁地fetch origin/master的代码到本地,并在本地对orgin/master进行rebase来解决冲突。这样就可以确保,我们开发的代码是基于最新的主仓代码,从而降低代码完成之后push时冲突的可能性。
|
||||
|
||||
## 第三条原则:DRY
|
||||
|
||||
DRY,也就是不要重复你自己,是很多开发模式的基础,也是我们非常熟悉的一条开发原则了。比如,我们把一段经常使用的代码封装到一个函数里,在使用它的地方直接调用这个函数,就是一个最基本的DRY。
|
||||
|
||||
**代码逻辑的重复**,不仅仅是工作量的浪费,还会大大降低代码的质量和可维护性。所以,我们在开发时,需要留意重复的代码逻辑,并进行适当的处理。
|
||||
|
||||
具体来说,首先是寻找重复的逻辑和代码。在动手实现功能之前,我们会花一些时间在内部代码仓和知识库中进行查找,寻找是否有类似的功能实现,以及一些底层可以复用的库,过程中也可以直接联系类似功能的实现者进行讨论和寻求帮助。另外,有一些IDE,比如Intellij IDEA,可以在编码的过程中自动探测项目中可能的代码重复。
|
||||
|
||||
找到重复的逻辑和代码之后,主要的处理方式是,把共同的部分抽象出来,封装到一个模块、类或者函数等结构中去。
|
||||
|
||||
如果在开发新功能时发现有需要重构的地方,一个常见的有效办法是,先用几个提交完成重构,然后再基于重构用几个提交实现新功能。
|
||||
|
||||
在编程工作中,除了代码的重复外,比较常见的还有**流程的重复**。比如测试中,我们常常需要重复地产生一些测试数据,运行完测试之后再把这些数据删除。
|
||||
|
||||
这些重复的流程也需要DRY,最主要的办法是自动化。以重复的测试数据产生、删除流程为例,一般的做法是,编写脚本进行自动化,当然有些时候也需要写一些复杂的可执行程序来生成数据。
|
||||
|
||||
流程重复还有一个特点是,它常常和团队相关,也就是说很多成员可能都会重复某些操作,这样的操作更值得自动化。比如,团队的很多成员常常都需要产生测试数据,这时我推荐你主动对其进行自动化、通用化,并提交到代码仓的工具文件夹中供团队使用。
|
||||
|
||||
## 小结
|
||||
|
||||
今天,我针对如何使自己成长为10x程序员,首先给出了在编程技术方面的3个原则,分别是抽象和分而治之、快速迭代,以及DRY。然后,针对每一条原则,我给出了Facebook高效开发者的一些常用实践。
|
||||
|
||||
其实,我们还可以从这3条原则中延伸出其他很多有效的实践。
|
||||
|
||||
比如,好的代码注释。对子系统设计进行合理的注解,可以方便其他开发者在不同的抽象层面对软件结构有更直观的了解。而且如果系统拆分得当的话,需要注释的地方就会比较少。又比如,代码的设计时审查,就是帮助我们及早进行架构讨论,从而实现快速迭代。
|
||||
|
||||
为方便你理解并运用到自己的开发工作中,我将这些实践总结到了一张表格中,如下所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cc/d1/ccb6ec868814aa9d86a3558739cd8dd1.jpg" alt="">
|
||||
|
||||
另外,关于编程技术的高效实践也是不断演化和发展的。以设计模式为例,最近几年又出现了针对Kubernetes开发场景的模式,你可以参考[《Kubernetes Patterns》](https://www.amazon.com/Kubernetes-Patterns-Designing-Cloud-Native-Applications-ebook/dp/B07QH3JCC6/ref=sr_1_1?crid=1M8SGJX67RR5J&keywords=kubernetes+patterns&qid=1570073661&sprefix=kubernetes%2Caps%2C335&sr=8-1)这本书;针对云原生(Cloud Native)开发,也有了业界比较认可的[12-factor原则](https://12factor.net/zh_cn/)等。将来必定还会有其他新的设计模式产生。比如,伴随着AI的逐渐成熟,针对AI的设计模式必定会出现。
|
||||
|
||||
所以,作为一名软件开发者,我们必须要持续学习。我之前在一家创业公司时,有一个刚大学毕业两年的同事,他有一个非常好的习惯,就是每天早上比其他同事早半个小时到办公室,专门来学习和提高自己。正是因为他的持续学习,使得他虽然工作时间不长,但在整个团队里一直处于技术领先的位置。你也可以借鉴这个方法,或者采用其他适合自己的方法来持续地提升自己。
|
||||
|
||||
## 思考题
|
||||
|
||||
1. 我今天提到的关于分而治之的实践,哪一条对你触动最大呢?同时,也和我分享一下你在工作实践中的感受吧。
|
||||
1. 你还知道哪些编程技术方面的高效原则和实践吗?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
159
极客时间专栏/研发效率破局之道/个人效能/22 | 深度工作:聚焦最有价值的事儿.md
Normal file
159
极客时间专栏/研发效率破局之道/个人效能/22 | 深度工作:聚焦最有价值的事儿.md
Normal file
@@ -0,0 +1,159 @@
|
||||
<audio id="audio" title="22 | 深度工作:聚焦最有价值的事儿" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/af/b7/af35045310370097905652ec726d1fb7.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我来和你聊聊深度工作这个话题。
|
||||
|
||||
我在上一篇文章中提到,要想成为一个10x程序员,我们需要在编程技术、工作方式、工具使用等方面全面提高自己。今天,我们就从工作方式的角度来看看如何提高个人效能吧。
|
||||
|
||||
毋庸置疑,身边充斥的各种信息,无时无刻不在吸引着我们的注意力,导致我们的可用时间越来越少,需要处理的信息、事情却越来越多。这中间的关系一旦处理不好,很容易就会出现问题。比如:
|
||||
|
||||
- 工作从早忙到晚,但一直被业务拖着跑,绩效一般,个人也得不到成长。
|
||||
- 碎片时间很努力地学习相关技术,似乎学了不少,但不成系统,学完也就完了,没什么效果。
|
||||
- 工作太忙,没有时间锻炼、放松,效率越来越低,可能自己还察觉不到。
|
||||
- 工作总是被打断,无法静下心来工作和学习。
|
||||
|
||||
所以今天,我就和你聊一个看似简单的问题:在任务多、干扰多的现状下,如何最高效地利用时间,去做最重要的事儿,同时有更多的时间来放松和享受生活。
|
||||
|
||||
在我看来,这个问题的答案就是深度工作,聚焦最有价值的事儿。[深度工作](https://book.douban.com/subject/27056409/)这个概念,是由卡尔 · 纽波特(Cal Newport)提出的,指的是在无干扰的状态下,才能专注地进行的专业活动。这样的活动能够使个人的认知能力发挥到极限,从而让我们创造出最大价值,成为一个不可替代的人。
|
||||
|
||||
就我个人而言,我也一直在想办法去聚焦最有价值的事儿,也读了不少GTD(Geting Things Done,尽管去做)的书,尝试了不少方法,积累了不少经验,也踩了不少坑。总结来看,实现深度工作的办法说起来很简单,可以概括为以下三步:
|
||||
|
||||
1. 以终为始,寻找并聚焦最重要的任务;
|
||||
1. 追根究底,寻找最高效的解决方案;
|
||||
1. 安排时间和精力,高效执行解决方案。
|
||||
|
||||
但,这些办法真正实行起来,就比较有挑战了,所以接下来我会从这3个方面与你分享些实用的方法和技巧。
|
||||
|
||||
## 1. 以终为始,寻找并聚焦最重要的任务
|
||||
|
||||
我们都清楚,事情做得快非常重要,但做真正有效的事情更重要。这也就意味着,我们首先要找到目前最最重要的任务。
|
||||
|
||||
在找最重要的任务时,我建议你按照以下三步来操作。
|
||||
|
||||
1. 自己定义任务;
|
||||
1. 聚焦目标,以终为始;
|
||||
1. 无情的筛选。
|
||||
|
||||
下面我们分别看看这三步具体如何实施吧。
|
||||
|
||||
### 第一步,自己定义任务
|
||||
|
||||
GTD的创始人大卫 · 艾伦(David Allen)提出,日常任务可以分为3种:
|
||||
|
||||
- 预先计划的任务(Pre-defined Work),比如迭代之初就计划好了的功能开发任务;
|
||||
- 临时产生的任务(Work As It Appears),比如Bug、邮件、临时会议等;
|
||||
- 自己定义的任务(Defining Work),即根据当前状况,自己决定需要做的任务。
|
||||
|
||||
在我看来,我们要把更多的时间和精力放到自己定义的任务上。你可能会觉得,前两种任务已经够多了,也非常重要。但其实,我们容易忽略的第3种任务,可以帮我们减少前两种任务中不必要的浪费。
|
||||
|
||||
因为,预先计划好的任务的优先级常常会随着情况的改变而改变,而临时产生的任务很可能当时觉得很紧急,但实际上是可以推迟或者甚至不做的。我们根据当前情况决定要做的任务,就可以避免把时间花在低优先级的任务上。
|
||||
|
||||
我的建议是,在每天的工作间隙,花一些时间考虑自己去定义任务,问自己这几个问题:
|
||||
|
||||
- 预先定义的任务是否还需要做?
|
||||
- 有没有什么更重要的任务要替代现在手上的任务?
|
||||
- 临时产生的干扰任务怎么处理才好?需要我来处理吗?
|
||||
- ……
|
||||
|
||||
### 第二步,聚焦目标,以终为始
|
||||
|
||||
聚焦目标,以终为始,其实就是在自己定义任务。因为时间有限,为目标服务的任务才最重要。
|
||||
|
||||
作为高效开发者,常见的目标包括业务成功、帮助团队,以及个人成长这3个。如果能找到三者重合的任务就最好不过了。
|
||||
|
||||
我在Facebook工作的时候,有个朋友发现日常工作中自己和团队常常要用到面板,而现有的面板系统对他们的场景不够友好。于是,他自己花时间实现了一个基于Python的DSL的面板系统。他的工作中就会大量使用Python,所以这个项目对他的个人技术成长很有好处。
|
||||
|
||||
工具做好第一版之后,团队成员非常喜欢这个工具,并很快就扩展到其他的很多团队,比如我也用它给我们团队做了一个面板。这个工具非常好用,半年后在公司立项,并由这个朋友主导,逐渐发展为公司最受欢迎的面板系统。
|
||||
|
||||
这个面板工具任务,就是一个帮助业务成功、帮助团队和个人成长三者相结合的案例。
|
||||
|
||||
日常工作中更常见的情况是,三者不能兼得。这时,我们首先应该关注业务成功,因为它是我们工作的最基本目标,是基础。在此之上,我推荐先考虑帮助团队成长。因为帮助团队的同时,往往会给自己带来一些直接或间接的成长机会。
|
||||
|
||||
### 第三步,无情的筛选,少即是多
|
||||
|
||||
生命有限,而工作无限,所以我们必须要无情地排优先级。
|
||||
|
||||
很多人都有一个倾向,就是贪多,认为越多越好,我曾经也这样。在自己的书单里添加了几百本书,书签页中添加了几百篇要读的技术文章,Todo List里添加的任务也越来越多,还计划学习这个语言、那个框架。但实际做起来,却因为时间有限,不但只能浅尝而止,还让自己很疲倦。痛定思痛,我下决心去做减法。
|
||||
|
||||
在我看来,**“数字3原则”很有效,也就是强制把要做的事、要达到的目标,都限制在3个以内。**
|
||||
|
||||
我曾参加过一个增进对自己了解的工作坊,期间有一个练习,帮助我们明确自己最关注的道德品质。
|
||||
|
||||
首先,我们要从一个有50项品质的列表上,勾选出25项自认为最重要的;然后,在这25项品质中,再选出10项最重要的;最后,从这10项里再筛选出3个。
|
||||
|
||||
第一轮筛选很轻松,第二轮筛选时就有一点儿困难了,到了第3轮10选3时真的非常痛苦。它强迫我仔细思考,到底哪3个品质对我才是最重要的。作出决定之后,你就会发现对自己的了解更深了。
|
||||
|
||||
这对我帮助非常大,所以我在后续的工作计划中,也强迫自己使用数字3原则,无论是三年规划、半年规划、本周规划,还是当天工作,都做一个无情的筛选,找出最最重要的3件事儿。我推荐你也尝试一下。
|
||||
|
||||
经过这三步,我们已经明确了最重要的任务,接下来我们再看看如何寻找高效的解决方案吧。
|
||||
|
||||
## 2. 追根究底,寻找最高效的解决方案
|
||||
|
||||
回想一下你和产品经理打交道的过程,是不是下面这样呢?产品经理决定如何在业务上满足用户需求,而开发人员就是,从产品经理手中接过任务,马上开始技术上的设计和实现。
|
||||
|
||||
这种工作方法我们很熟悉,但其实有很大的局限性。因为开发人员对技术实现最熟悉,如果我们能结合业务进行思考,追根究底,常常能对业务的解决方案做出一些改进,甚至是重新设计出更好的方案。而这样的改进,对个人研发效能的提高帮助非常大。
|
||||
|
||||
我在做Phabricator项目时,曾收到一个任务,要解决用户进行inline讨论时,模态弹窗(modal)挡住弹窗下代码的问题。你可以先思考下,自己收到这样的任务会如何做,然后再对比看看我的经历,收获会更大。
|
||||
|
||||
这个任务的描述很简单,就是去改进Phabricator使用的JavaScript库,让它支持用鼠标拖动模态弹窗。但是,当我了解这个JavaScript库后,发现工作量非常大,需要三四天才能完成。正当我考虑如何下手时,一个经验丰富的同事提醒我先别着急去做这样的大改动,可以想想看还有没有其他方法,能够更简单地解决这个问题。
|
||||
|
||||
我觉得这个建议非常有道理,于是仔细思考了用户需求,并与产品经理进行沟通和讨论。结论是,要解决代码被遮挡的问题,并不一定非要拖动模态弹窗,还有一个比较简单的办法,就是放弃使用模态弹窗做讨论功能,而是直接在被讨论的代码和下一行代码之间插入一个文本框供用户进行讨论。
|
||||
|
||||
这样一来,不但解决了代码被遮挡的问题,还因为无需改动底层库,工作量直接降为了一天,同时给用户提供了预期之外的方便。因为没有模态弹窗,用户写注释时还可以上下滚动代码窗口,来查看更大范围内的代码,体验更好了。
|
||||
|
||||
此后,我在接到任何任务时,都会先考虑它到底要解决什么问题,有没有更好的解决方案,花些时间去深入思考,往往都能帮我节省后面的很多时间。
|
||||
|
||||
所以**我认为,开发人员也要对业务有一定的了解**。面对任务的时候,多问几个Why,并与产品经理和团队成员充分沟通,了解它到底要解决什么问题,只有这样,我们才能以解决问题为出发点,找到最高效的解决方案。
|
||||
|
||||
接下来,我们再看看如何管理时间和精力,去具体实现这些方案吧。
|
||||
|
||||
## 3. 安排时间和精力,高效执行解决方案
|
||||
|
||||
信息爆炸、时间碎片化,给我们的时间管理和精力管理带来了很大挑战。关于这个话题,也有很多理论和实践了,我也尝试过很多方法。在这里,我给你推荐3种我认为最有效的方法,包括用番茄工作法来记录深度工作时间、用拥抱无聊来控制对手机的依赖,以及用反向行事日历(Reverse Calendar)来确保休息和高效。
|
||||
|
||||
接下来,我们分别看看这3种方法吧。
|
||||
|
||||
### 用番茄工作法来记录深度工作时间
|
||||
|
||||
简单地说,番茄工作法就是把时间划分为固定时长的工作时间和休息时间。一个番茄时间包含两部分,25分钟的工作学习和5分钟的休息时间。
|
||||
|
||||
番茄工作法的精髓是,在每一个工作时间段,避免干扰、全神贯注。因为只有精力高度集中,减少上下文切换,才能进入深度工作状态,进而最大程度地发挥我们的心智能力,提高个人效能。
|
||||
|
||||
根据我的精力以及工作性质,我对番茄时钟做了一点调整,把每一个工作时段调整成为40分钟。在这个时间段内,我尽量避免实时聊天工具和电话的干扰,集中精力去做当前最重要的事情。条件允许的话,我会把手机调成静音,并关闭电脑上的提醒功能,甚至干脆离开办公区去走走,边散步边进行专注的思考。
|
||||
|
||||
### 用拥抱无聊来控制手机依赖
|
||||
|
||||
手机依赖症越来越普遍,我也深受其害,有段时间甚至去卫生间都一定要带着手机。
|
||||
|
||||
后来,我发现了一个“拥抱无聊”的方法。具体来说就是,在一些非常碎片化的时间,不要因为无聊就马上抄起手机,去找一些有意思的东西放松或者学习,而是尝试适应无聊。
|
||||
|
||||
其实,无聊也是一种不错的状态,试着去享受它,不要让自己的大脑一直处于活跃状态。这样可以让我们得到休息,而且更重要的是,能够比较好地避免自己打扰自己的倾向。所谓“自己打扰自己”,就是会不自觉地去想、去做一些与手上工作不相干的事儿,自己干扰自己。
|
||||
|
||||
不知道你有没有这样的感受,当习惯一有空闲就使用手机之后,在工作中稍有一点空闲,或者是遇到比较难以处理的问题时,就会不自觉地想停下来看一看手机。我认为,这就是因为我们习惯了用手机来缓解紧张和压力而形成的条件反射,就是对手机的一种依赖。
|
||||
|
||||
### 用反向行事日历来确保休息和高效工作
|
||||
|
||||
反向行事日历主要用来处理因为工作太多导致的忽略休息、影响健康和效率低下的问题。
|
||||
|
||||
我们在安排日程时,通常会把工作任务放到日历中,而不会把运动、休息、吃饭等非工作任务放入其中。反向日历正好相反,首先就是把这些非工作任务作为最高优先级放到日历中,然后再在剩余的时间中安排工作任务。
|
||||
|
||||
在我看来,这个方法有两个非常明显的好处:
|
||||
|
||||
- 第一,可以强制我们去休息和锻炼;
|
||||
- 第二,可以让我们更有紧迫感,提高工作效率。当我们在日历上把运动、休息和吃饭的时间标注出来之后,就会清楚看到剩下可以用来工作的时间很有限,必须要提高工作效率才能完成任务。
|
||||
|
||||
## 小结
|
||||
|
||||
我在[开篇词](https://time.geekbang.org/column/article/120801)中指出,团队研发效能的定义包括有效性(准确)、效率(快速)和可持续性3个方面。实际上,个人研发效能也包括这3个方面。其中,有效性就是要做对的事儿,效率就是要高效地执行,而可持续性就是要安排好时间和精力,有张有弛才能持续发展。
|
||||
|
||||
针对这3点,我今天就与你分享了聚焦最重要的事儿的3个步骤:第一,以终为始,寻找并聚焦最重要的任务;第二,追根究底,寻找最高效的解决方案;第三,安排时间和精力,高效执行解决方案。
|
||||
|
||||
可以看到,这三步就是从个人效能的3个方面入手的,即:聚焦最重要的任务,是帮助我们提高有效性;寻找高效的解决方案并高效执行,是帮助我们快速完成开发任务;而合理安排时间和精力,就是让我们持续地进行高效开发。
|
||||
|
||||
知易行难,同样适用于这些原则。所以,我非常希望你能实践并坚持这些原则和方法,当然了你也可以去寻找更适合自己的方法,让自己这块好钢能用到刀刃上。
|
||||
|
||||
## 思考题
|
||||
|
||||
1. 你有其他克服手机依赖症的好方法吗?
|
||||
1. 你觉得碎片时间,更适合碎片化学习/娱乐,还是拥抱无聊呢?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
198
极客时间专栏/研发效率破局之道/个人效能/23 | 效率工具:选对用对才能事半功倍.md
Normal file
198
极客时间专栏/研发效率破局之道/个人效能/23 | 效率工具:选对用对才能事半功倍.md
Normal file
@@ -0,0 +1,198 @@
|
||||
<audio id="audio" title="23 | 效率工具:选对用对才能事半功倍" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/58/07/58108212d4d1f40cffb792f82dffd807.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我来和你聊一聊效率工具。
|
||||
|
||||
在前两篇文章中,我分别从编程技术和工作方式两个方面,与你介绍了如何提高个人研发效能,成长为10x程序员。今天,我再与你分享些工具使用方面的技巧和方法,来帮助你全方位地提高自己的工作效率。
|
||||
|
||||
谈到研发效能,一定离不开工具。工具使用得当,可以大幅提升研发效率,甚至可以因此引发业务发展、开发行为的质变。比如,我在[第11篇文章](https://time.geekbang.org/column/article/136070)中与你分享的截屏工具链带来的效率提升,就使得其被广泛用在了Commit Message中,大幅提高了整个过程的效率。
|
||||
|
||||
我一直对各种工具很感兴趣,从业这些年来,在这上面花了不少时间,也踩了一些坑。比如,我曾经在Emacs上面花费了大量的时间,去自动化和优化很多细枝末节的操作。结果是,花在优化和自动化上的时间,比节省下的时间还要多,得不偿失。
|
||||
|
||||
所以,我总结得出,**对于工具一定要选对和用对**。
|
||||
|
||||
选对工具指的是,我们要针对不同的任务,找到合适的工具来提高效率。而用对工具指的是,我们要分配适量的时间和精力来选择工具,要时刻注意投入产出比。
|
||||
|
||||
关于用对工具,我推荐一个比较好的方式是,**留意工作中经常重复和繁琐的工作步骤,隔一段时间就做些工具方面的调研,看看有没有更合适的工具或者使用方法来优化这些流程。**对于一些非常繁琐、执行频率又非常高的操作,如果没有现成的工具,甚至可以考虑自己开发一些工具和脚本进行优化。频次越高的操作,越值得优化。
|
||||
|
||||
接下来,我就按照功能和你分享一些有助于提高工作效率的工具。
|
||||
|
||||
因为我最近的工作电脑是macOS系统,所以我今天推荐的适用于苹果操作系统和生态的工具会偏多一些。但,这些优化的方向和思路是一致的,在Windows操作系统上你也能找到类似的工具。另外,安卓系统更加灵活,有比苹果生态更多的工具。
|
||||
|
||||
## 第一个任务类别是,操作系统上的通用操作
|
||||
|
||||
操作系统上的通用操作,主要有窗口切换、程序启动、窗口管理、剪贴板管理等。这些操作普遍且频繁,比较值得优化。
|
||||
|
||||
**关于窗口切换**,各操作系统都有一些自带的支持。比如,macOS系统自带的方式是cmd+tab,Windows系统自带的是Alt+Tab或者Win+Tab。**关于程序启动**,macOS系统有Spotlight,Windows系统可以使用Win键或者Win+R启动任务。
|
||||
|
||||
但这两个操作实在是频繁,且操作系统自带的功能只是基础功能。所以。我推荐你进行进一步优化。
|
||||
|
||||
在macOS系统上,Alfred、Quicksilver这两个工具就很不错。Windows系统的话,我推荐[Wox](http://www.wox.one)。
|
||||
|
||||
这些工具的用法也很简单,使用一个快捷键启动,然后输入过滤条件,查找需要运行或者切换的程序,回车即可。使用Quicksilver启动/切换程序的方法,如下所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/80/ba2319c749fed30712d51c189b7b4d80.gif" alt="">
|
||||
|
||||
**关于窗口管理**,有一个常用操作,是把一个窗口自动缩放,并摆放到屏幕的某一个位置(比如屏幕的左上角)。
|
||||
|
||||
在macOS系统中,我一般使用[BetterTouchTool](https://folivora.ai)这个工具实现,也推荐你使用。通过它,我可以非常方便地实现窗口缩放和拖动。有两个操作,我觉得特别方便:
|
||||
|
||||
- 一是,按住alt键,无论光标在窗口的任何位置,都可以通过挪动鼠标拖动窗口;
|
||||
- 二是,按住cmd+alt键,无论光标在窗口的任何位置,都可以通过移动鼠标来改变窗口大小。
|
||||
|
||||
BetterTouchTool的配置和使用,如下面两幅图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d5/26/d5ba52955fc684e464555d3edd7d1b26.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/24/25/2438f319fb1d0f3ed131676400baf125.gif" alt="">
|
||||
|
||||
最后是,**剪贴板管理**。目前,操作系统自带的剪贴板只能保存一条记录,但如果我们使用剪贴板历史管理工具的话,可以非常方便地保存和使用多条历史拷贝内容。
|
||||
|
||||
在macOS系统上,我用的工具是Quicksilver。Windows上类似的工具有很多,比如[Ditto](https://sspai.com/post/43700)。
|
||||
|
||||
## 第二个任务类别是输入
|
||||
|
||||
关于输入,我主要和你推荐4类工具,分别是语音输入、文字快速输入、重新定义按键和具体的键盘。
|
||||
|
||||
**我们先看看第一类工具,语音输入**。我在电脑上常用的是[讯飞语音输入](https://apps.apple.com/cn/app/%E8%AE%AF%E9%A3%9E%E8%AF%AD%E9%9F%B3%E8%BE%93%E5%85%A5-%E6%99%BA%E8%83%BD%E8%AF%86%E5%88%AB-%E8%BD%BB%E6%9D%BE%E5%86%99%E4%BD%9C/id1243368435?mt=12),手机上常用的是[讯飞输入法](https://apps.apple.com/cn/app/%E8%AE%AF%E9%A3%9E%E8%BE%93%E5%85%A5%E6%B3%95-%E6%99%BA%E8%83%BD%E8%AF%AD%E9%9F%B3%E8%BE%93%E5%85%A5%E6%B3%95/id917236063)。在电脑上使用语音输入有一个技巧是,把手机作为麦克风。这样输入的效果非常好,尤其是旁边有人说话的时候,手机麦克风的过滤功能就非常棒了。
|
||||
|
||||
在手机上,我经常使用讯飞输入法的语音便签功能,保存录音的同时还可以直接把语音转为文字,对于记录灵感来说特别方便。
|
||||
|
||||
**第2类工具是文字快速输入**。Mac上最流行的是[TextExpander](https://textexpander.com/),它可以方便地生成预先定义的字符串、当天日期,也可以很方便地指定文字生成之后的光标所在位置等。
|
||||
|
||||
比如,经过下面的配置之后,只要输入缩写“;mj”就可以生成日记模板,并把光标放到第一个任务处。
|
||||
|
||||
```
|
||||
## %Y-%m-%d:
|
||||
今天关键任务
|
||||
%|
|
||||
-
|
||||
|
||||
成功之处,不足之处
|
||||
-
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/f2/1d887ea891669d78b878e95aecf23ff2.gif" alt="">
|
||||
|
||||
在Windows系统上,开源免费的[AutoHotkey](https://www.autohotkey.com)非常强大。它不但可以进行文本扩展,还可以运行程序。我来和你举一个运行程序的例子吧。比如,你可以使用Win+V直接切换到VIM。如果VIM先前没启动的话,则会直接其启动。
|
||||
|
||||
```
|
||||
;;; switch to vim
|
||||
#v::
|
||||
if WinExist( "ahk_class Vim" )
|
||||
{
|
||||
WinActivate
|
||||
}
|
||||
Return
|
||||
|
||||
```
|
||||
|
||||
**第三类工具是,重新定义按键**。在我看来,对程序员最实用的一个功能是,把大写锁定键(caps lock)转换成ctrl键。因为caps lock的位置很方便使用频率却很低,而编程中常会用到ctrl键,所以这个重新定义对程序员非常有用。
|
||||
|
||||
至于重新定义按键的工具,在Mac上我使用的是[Karabiner-Elements](https://github.com/tekezo/Karabiner-Elements),在Windows上使用AutoHotkey就可以。
|
||||
|
||||
另外,我对caps lock键的定义不是简单地重新定义为ctrl,而是如果和其他键共同使用的话就是ctrl键,单独使用的话就是esc键。因为,esc键的使用频率也很高,尤其是在VIM中。
|
||||
|
||||
在Karabiner-Elements中的具体设置方法是:
|
||||
|
||||
```
|
||||
"rules": [
|
||||
{
|
||||
"manipulators": [
|
||||
{
|
||||
"description": "Change ctrl to esc if pressed alone.",
|
||||
"from": {
|
||||
"key_code": "caps_lock",
|
||||
"modifiers": {
|
||||
"optional": [
|
||||
"any"
|
||||
]
|
||||
}
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"key_code": "left_control"
|
||||
}
|
||||
],
|
||||
"to_if_alone": [
|
||||
{
|
||||
"key_code": "escape"
|
||||
}
|
||||
],
|
||||
"type": "basic"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
**第四类工具是,具体的键盘**。开发者要经常输入,选择一款顺手的键盘就很重要了。键盘的选用,我看重的是手感和键盘布局。手感的话,机械键盘的确好一些,你可以根据自己的体验选择一款合适的。而布局的话,左右手按键分离较远的一般来说会好一些。
|
||||
|
||||
根据这些原则,我平时使用[Kinesis Pro2键盘](https://kinesis-ergo.com/keyboards/advantage2-keyboard/)。因为,它的中间是凹进去的,所以手指与每一行按键的距离都差不多,输入时很轻松。另外,特殊键由大拇指控制,这个特点我也非常喜欢。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0a/d7/0a67b27cf7a965869d30417bd46e10d7.png" alt="">
|
||||
|
||||
## 第三个任务类别是,知识管理
|
||||
|
||||
对学习和知识管理,我主要与你分享6类工具。
|
||||
|
||||
第一类工具是**云盘**,用来确保存储的内容不会因为本地电脑的意外情况而丢失。目前,市面上的各种云盘都可以满足这个需求。这里我有一个小建议,使用云盘时最好能够使用本地的自动同步工具,自动地将本地文件夹里的内容同步到云盘。
|
||||
|
||||
第2类工具是**笔记**。我挑选笔记工具的原则,包括支持云同步,支持电脑和手机端同时访问。印象笔记、Mac自带的Notes、石墨文档等都不错。
|
||||
|
||||
第3类工具是**写文档的工具**。相较于笔记工具来说,写文档的工具还有一个挑选原则是支持Markdown。我现在是组合使用iCloud、1Writer和Typora:使用iCloud进行同步,在手机使用1Writer,在电脑上使用Typora。
|
||||
|
||||
第4类工具是**思维导图**。对思维导图,比较重要的特性包括跨平台、方便使用快捷键、方便导入\导出其他格式。我以前一直用的是[FreeMind](http://freemind.sourceforge.net/wiki/index.php/Main_Page),它基于Java实现,在Windows、macOS和Linux系统上都可以使用,快捷键配置也很强大,但它的一大缺陷是在手机上不能使用。所以,最近两年我转移到了[XMind Zen](https://www.xmind.net/zen/)上面。它可以在电脑和手机上同时使用并自动同步,显示也比较美观。
|
||||
|
||||
第5类工具是**截屏、录屏**。我对截屏、录屏工具的选用原则是标注功能要好,另外要可以把录屏保存成GIF格式。我现在最常用的是对这两方面都支持不错的[MonoSnap](https://monosnap.com)。另外,录屏的时候,我们常常希望把当前的键盘输入显示到屏幕上,我推荐使用[KeyCastr](https://github.com/keycastr/keycastr);编辑GIF文件的话,我使用的是[GIF Brewery 3](https://apps.apple.com/cn/app/gif-brewery-3-by-gfycat/id1081413713?mt=12)。
|
||||
|
||||
这个专栏中,你见到的录屏和截屏图片,我就是使用这三个工具完成的。另外,还有一个对命令行终端录制比较方便的工具[asciinema](https://asciinema.org/about)。它可以把你在终端里面的操作和输出用JSON形式保存下来,也可以上传到asciinema的网站上,还可以方便地在你自己的网站上引用显示。但这个工具的缺点是,无法显示键盘的按键操作。
|
||||
|
||||
第6类是**处理PDF文件的工具**。这样的工具我主要关注基本的标注功能,以及在平板电脑上可以使用电子笔操作。在电脑上,我一般使用[Foxit Reader](https://download.cnet.com/Foxit-Reader/3000-18497_4-10313206.html)和macOS自带的Preview;在手机上,我主要使用的是[PDF Expert](https://apps.apple.com/cn/app/pdf-expert-7-pdf-editor/id743974925)。
|
||||
|
||||
在我看来,一个比较高效的方式是,把PDF文件放到笔记软件中,然后在笔记中直接对其进行操作。印象笔记和苹果自带的Notes都支持这个功能。
|
||||
|
||||
## 第四个任务类别是,浏览网页
|
||||
|
||||
访问互联网是非常高频的操作,所以我把它单独列成一类。对开发人员,我推荐使用Chrome和Firefox,因为两者都有很强的扩展功能,对开发活动比较友好。
|
||||
|
||||
因为最近几年我主要在使用Chrome,所以接下来,我与你介绍一些我常用的Chrome插件和技巧。
|
||||
|
||||
第一个插件是[Octotree](https://www.octotree.io)。它会在你访问GitHub查看项目时,在窗口左侧以树形结构清晰明了地显示代码仓结构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/f7/d02f3b1258264c337a5e2af7d59ff3f7.png" alt="">
|
||||
|
||||
第二个插件是[Pocket for Chrome](https://getpocket.com/chrome/)。它需要与[Pocket](https://app.getpocket.com)配合使用,实际上就是一个在线的网页书签保存服务,但它的推荐功能很优秀,并且对手机端的支持也很好。
|
||||
|
||||
第三个插件是[smartUp Gestures](https://chrome.google.com/webstore/detail/smartup-gestures/bgjfekefhjemchdeigphccilhncnjldn?hl=en)。通过它,你可以按住鼠标右键不放,在屏幕上画一些形状,也就是手势(Gesture),来进行一些操作。
|
||||
|
||||
我最常使用的功能有:切换到左边(或右边)一个标签页、向下(或向上)翻页至网页末尾(或网页开头)、返回网页访问历史的上一页(或下一页),以及关闭当前标签页。
|
||||
|
||||
在文稿里我有一个录屏供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4c/df/4c9dd735eb9d141fe51a9ebfb1cd9cdf.gif" alt="">
|
||||
|
||||
## 第5个任务类别是,开发
|
||||
|
||||
和开发工作相关的工具比较多,包括编辑器(比如VS Code、VIM)、IDE(比如Visual Studio和JetBrains系列)、代码仓管理工具(比如Git和HG)、API测试工具(比如Postman),以及命令行工具等。
|
||||
|
||||
关于开发工具的使用,我会在后面的文章中与你详细介绍其中的使用技巧,帮你提高编码效率。
|
||||
|
||||
## 小结
|
||||
|
||||
无论“磨刀不误砍柴工”“工欲善其事,必先利其器”,还是乔布斯曾说过的“You cannot mandate productivity, you must provide the tools to let people become their best.(你不能强制要求大家提高生产力,你必须提供工具,让大家发挥他们的最大能力)” ,强调的都是工具的重要性。
|
||||
|
||||
所以,作为开发人员,我们一定要选对工具、用对工具,才能提高效能。在今天这篇文章中,我从操作系统上的通用操作、输入、知识管理、网页浏览和开发5个方面给你推荐了一些工具及使用方法。当然了,关于开发工具这个重头戏我还会在后面的文章中与你详细分享。
|
||||
|
||||
其实,使用哪款工具,是一个仁者见仁,智者见智的问题。所以,通过这篇文章,我更希望达到的目的是,能帮助你对日常工作中最常见的操作进行思考,寻找值得优化和可以使用工具优化的地方,从而提高个人的研发效能。
|
||||
|
||||
但,我还要强调的是,工具只是辅助,编程工作更重要的还是思考。所以,我建议你不要花费过多的时间在工具研究上,要时刻留意投入产出比。
|
||||
|
||||
## 思考题
|
||||
|
||||
除了今天提到的操作系统上的通用操作、输入、知识管理、网页浏览和开发5个方面外,你觉得还有哪些方面的操作比较频繁吗,有什么值得优化的地方吗,又有什么值得推荐的工具吗?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
334
极客时间专栏/研发效率破局之道/个人效能/24 | VIM:如何高性价比地学习VIM的实用技巧?.md
Normal file
334
极客时间专栏/研发效率破局之道/个人效能/24 | VIM:如何高性价比地学习VIM的实用技巧?.md
Normal file
@@ -0,0 +1,334 @@
|
||||
<audio id="audio" title="24 | VIM:如何高性价比地学习VIM的实用技巧?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/cf/1daf168653a172ae2e7bf014242d06cf.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我来和你聊聊VIM的使用技巧。
|
||||
|
||||
在“[特别放送 | 每个开发人员都应该学一些VIM](https://time.geekbang.org/column/article/144470)”这篇文章中,我和你详细介绍了VIM提高研发效能背后的原因。我推荐每个开发者都应该学一些VIM的原因,主要有两个:
|
||||
|
||||
- 独特的命令模式,可以大量减少按键次数,使得编辑更高效;
|
||||
- 支持跨平台,同时可以在很多其他IDE、编辑器中作为插件使用,真正做到一次学习,处处使用。
|
||||
|
||||
VIM确实可以帮助我们提高效率,但面对这样一个学习曲线长而且陡的编辑器,我们很容易因为上手太难而放弃。所以,如何性价比高地学习VIM的使用技巧非常重要。
|
||||
|
||||
我推荐你按照以下三步,来高效地学习如何使用VIM:
|
||||
|
||||
1. 学习VIM的命令模式和命令组合方式;
|
||||
1. 学习VIM最常用的命令;
|
||||
1. 在自己的工作环境中使用VIM,比如与命令行环境的集成使用。
|
||||
|
||||
接下来,我们分别看看这三步吧。
|
||||
|
||||
## VIM的模式机制
|
||||
|
||||
VIM的基本模式是命令模式,在命令模式中,敲击主体键的效果不是直接插入字符,而是执行命令实现对文本的修改。
|
||||
|
||||
### 使用VIM的最佳工作流
|
||||
|
||||
在我看来,在命令模式下工作,效率高、按键少,所以我推荐你**尽量让VIM处于命令模式,使用各种命令进行工作。进入编辑模式完成编辑工作之后也立即返回命令模式。**
|
||||
|
||||
事实上,我们从命令模式进入编辑模式修改文件,之后再返回命令模式的全过程,就是一个编辑命令。它跟其他的命令,比如使用dd删除一行,并没有本质区别。接下来,我们一起看个具体的例子吧。
|
||||
|
||||
比如,我要在一行文字后面加上一个大括号,后面再写一行代码。开始编辑时,光标处于这一行的开头。
|
||||
|
||||
```
|
||||
config =
|
||||
^
|
||||
|
||||
```
|
||||
|
||||
修改的目标是这样:
|
||||
|
||||
```
|
||||
config = {
|
||||
timeout: 1000ms,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在VIM中,我的操作是,首先敲击大写字母A,将光标移到这一行的末尾,并进入编辑模式:
|
||||
|
||||
```
|
||||
config =
|
||||
^
|
||||
|
||||
```
|
||||
|
||||
然后,输入“{ timeout: 1000ms,回车}”,在文件中插入内容。最后,敲击Esc键回到命令模式。编辑完成。
|
||||
|
||||
```
|
||||
config = {
|
||||
timeout: 1000ms,
|
||||
}
|
||||
^
|
||||
|
||||
```
|
||||
|
||||
实际上,整个过程就是执行了一条“在本行末尾插入文字”的命令。整条命令的输入是“A{ timeout: 1000ms,回车}Esc”。虽然比较长,但仍然是一条文本编辑命令。
|
||||
|
||||
所以实际上,**我们在VIM中的工作,正是在命令模式里执行一条条的命令完成的**。理解了这一点,你就可以有意识地学习、设计命令来高效地完成工作了。
|
||||
|
||||
为了让命令更加高效,VIM还提供了强大的命令组合功能,使得命令的功能效果呈指数级增长。
|
||||
|
||||
### 命令的组合方式
|
||||
|
||||
在VIM中,有相当一部分命令可以扩展为3部分:
|
||||
|
||||
- 开头的部分是一个数字,代表重复次数;
|
||||
- 中间的部分是命令;
|
||||
- 最后的部分,代表命令的对象。
|
||||
|
||||
比如,命令3de中,3表示执行3次,d是删除命令,e表示从当前位置到单词的末尾。整条命令的意思就是,从当前位置向后,删除3个单词。类似的,命令3ce表示从当前位置向后,删除三个单词,然后进入编辑模式。
|
||||
|
||||
可以看到,命令组合的前两个部分比较简单,但第三个部分也就是命令对象,技巧就比较多了。所以接下来,我就与你详细介绍下到底有**哪些命令对象可以使用。**
|
||||
|
||||
其实,对命令对象并没有一个完整的分类。但我根据经验,将其总结为光标移动命令和文本对象两种。
|
||||
|
||||
**第一种是光标移动命令**。比如,`$`命令是移动光标到本行末尾,那么`d$`就表示删除到本行末尾;再比如,`4}`表示向下移动4个由空行隔开的段落,那么`d4}`就是删除这4个段落。
|
||||
|
||||
这一类命令功能很强大,我们也很熟悉了,但它有一个缺陷,就是选择的出发点始终是当前光标所在位置。而我们在处理文本的过程中,尤其是在编写程序的时候,光标所在位置常常是处于需要修改内容的中间,而不是开头。比如,我们常常在写一个注释字符串的时候,需要修改整个字符串:
|
||||
|
||||
```
|
||||
comment: 'this is standalone mode',
|
||||
^
|
||||
|
||||
```
|
||||
|
||||
如果使用上面的光标移动作为命令对象的话,我们需要执行bbbct’命令,也就是向左移动三个单词之后,再向右删除到第一个单引号的地方,操作起来很麻烦。
|
||||
|
||||
针对这种情况,VIM提供了第二种命令对象:**文本对象**。具体来说就是,用字符代表一定的文字单位。比如,i"代表两个双引号之间的字符串,aw表示当前的单词。使用这种文本对象很方便,比如上面的编辑例子,我们只需要命令ci"就可以实现,比bbbct’命令方便了很多。
|
||||
|
||||
具体来说,文本对象命令由两部分组成:
|
||||
|
||||
- 第一部分只能是字符i或者a,表示是否包含对象边界。比如,i"表示不包括两边的引号,而a"就包括引号;
|
||||
- 第二部分选择比较多,表示各种不同的文本对象。比如
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/17/22/17ef1f7dba10731966b0048dac716c22.jpg" alt="">
|
||||
|
||||
如果你要查看完整的文本对象,可以使用:help text-objects。
|
||||
|
||||
这一组文本对象选择功能很强大,而且是VIM自带功能,不需要安装任何插件。神奇的是,不知道为什么,很多使用VIM很久的人都不知道这个功能。我个人把它叫作vip功能,如果你以前没有用过,可以在文件中输入命令vip看看效果。相信不会让你失望。
|
||||
|
||||
可见,VIM的命令操作及其组合功能非常强大,要高效使用VIM,我们就必须使用命令模式以及命令组合。我看到有些开发者使用VIM时,一上来就使用i命令进入编辑模式,然后在编辑模式中工作。但是,编辑模式的功能很有限,完全发挥不出VIM提供的高效文本编辑功能。
|
||||
|
||||
接下来,我们进入第二步,也就是学习VIM最常用的命令。
|
||||
|
||||
## 命令模式中的基础命令
|
||||
|
||||
VIM有大量的命令可供我们使用,这里我主要与你介绍针对单个文件编辑的命令,因为这是编辑工作最基础的部分,也是各种跨平台场景,尤其在其他IDE中通过插件使用VIM的方式的共性部分。
|
||||
|
||||
至于更多的命令和细节,你可以参考我的“[命令模式中的基础命令](https://jungejason.github.io/vim-commands/)”这篇博客,或者自行搜索查询。
|
||||
|
||||
接下来,我会按照以下编辑文件的逻辑顺序,来与你介绍关键知识点和技巧:
|
||||
|
||||
1. 打开文件,以及进行设置操作;
|
||||
1. 移动光标;
|
||||
1. 编辑文本;
|
||||
1. 查找、替换。
|
||||
|
||||
### 第一组常用命令是,打开文件以及对文件的设置
|
||||
|
||||
这些命令包括打开文件、退出、保存、设置等。关于设置,如果在远端的服务器上,我常常运行一条命令进行基本设置:
|
||||
|
||||
```
|
||||
:set ic hls is hid nu
|
||||
|
||||
```
|
||||
|
||||
其中,ic表示搜索时忽略大小写,hls表示高亮显示搜索结果,is表示增量搜索,也就是在搜索输入的过程中,在按回车键之前就实时显示当前的匹配结果,hid表示让VIM支持多文件操作,nu表示显示行号。
|
||||
|
||||
如果要关闭其中某一个选项的话,在前面添加一个no即可。比如,关闭显示行号,使用
|
||||
|
||||
```
|
||||
:set nonu
|
||||
|
||||
```
|
||||
|
||||
### 第二组常用命令是,移动光标
|
||||
|
||||
VIM提供了非常细粒度的光标移动命令,包括水平移动、上下移动、文字段落的移动。这些命令之间的差别很细微。比如,w和e都是向右边移动一个单词,只不过w是把光标放到下一个单词开头,而e是把光标放到这一个单词结尾。
|
||||
|
||||
虽然差别很小,但正是这样的细粒度,才使得VIM能够让我们使用最少的按键次数去完成编辑任务。
|
||||
|
||||
### 第三组常用命令是,编辑命令
|
||||
|
||||
编辑工作中我们应该大量使用命令组合来提高效率。关于组合命令的威力,我建议你看下“[命令模式中的基础命令](https://jungejason.github.io/vim-commands/)”那篇博客,我与你详细解释了一个ct"的例子。
|
||||
|
||||
这里,**我重点与你分享一个设计命令的技巧**。VIM的取消命令u、重复命令.,都是针对上一个完整的编辑命令而言。所以,我们可以设计一个完整的编辑命令,从而可以使用重复命令.来提高效率。比如,你要把一段文本中的几个时间都修改成20ms,这段文本原来是
|
||||
|
||||
```
|
||||
timeout: 1000000ms,
|
||||
waiting: 300000ms,
|
||||
starting: 40000ms,
|
||||
|
||||
```
|
||||
|
||||
希望改变成
|
||||
|
||||
```
|
||||
timeout: 20ms,
|
||||
waiting: 20ms,
|
||||
starting: 20ms,
|
||||
|
||||
```
|
||||
|
||||
在VIM中有多种实现方法。一种还不错的方法是,使用修改命令c和移动命令l的组合,在每一行用一个命令搞定。具体操作是,将光标挪到1000000ms里数字1的地方,用c7l20命令对第一行进行修改,然后在第二行、第三行的相同位置分别使用c6l20和c5l20进行修改。
|
||||
|
||||
这个命令可以完成工作,但每条命令都不一样,不能重复。实际上,还有一种更好的方法。
|
||||
|
||||
你可以设计一条命令,在每一行重复使用。在这个例子里,这个命令是ctm20<esc>,意思是从当前位置删除到下一个字母m的位置,进入编辑模式,插入20,然后返回命令模式。</esc>
|
||||
|
||||
使用这条命令对第一行进行修改之后,你可以把光标挪到第二、三行使用重复命令.来完成编辑任务,如下所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/92/5e/92124bb53e6bfa3c5fb0c1f3d5273a5e.gif" alt="">
|
||||
|
||||
当然了,你还可以使用正则表达式进行查找替换,但是远比上面这个重复命令要复杂。这个设计命令并重复使用的方法非常好用,建议你上手实践下。
|
||||
|
||||
关于重复命令,VIM另外还有一个录制命令q和重复命令@,功能更强大,但也比较重。如果你想深入了解的话,[可以参考这篇文章](https://xu3352.github.io/linux/2018/11/04/practical-vim-skills-chapter-11)。
|
||||
|
||||
### 第四组常用命令是,查找、替换
|
||||
|
||||
这组命令主要有/、*和s等,很常用,网络上也有很多描述,这里我就不再详细介绍了。比如,我在“[命令模式中的基础命令](https://jungejason.github.io/vim-commands/)”这篇博客中详细介绍了s命令,可供你参考。
|
||||
|
||||
除了上述4种常见命令外,VIM中还有一种很强大的可视模式(Visual Mode),可以提高编辑体验。接下来,我们再一起看看。
|
||||
|
||||
### 可视模式
|
||||
|
||||
可视模式一共有3种,包括基于字符的可视模式、基于行的可视模式和基于列的可视模式,详见“[命令模式中的基础命令](https://jungejason.github.io/vim-commands/)”这篇博客中的介绍。
|
||||
|
||||
接下来,我以基于列的可视模式为例,带你看看可视模式是如何提高编辑体验的吧。
|
||||
|
||||
我们先来看看第一个例子。有一段文本,每一行都用var声明变量(比如var gameMode),现在我希望改成使用const来声明。我们来看看具体的实现方式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ca/63/ca00ba7234b33295581aa3ff04469963.png" alt="">
|
||||
|
||||
第一步,把光标挪到第一行开头的 v 字符处,使用组合键Ctrl-v进入列可视模式,然后使用7j下移动七行,再用e向右移动一个单词,从而选中一个包含所有var的矩形区域。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/b4/71de442222209adc9b9c86813da264b4.png" alt="">
|
||||
|
||||
第二步是开始编辑。输入c,删除所有的var,并进入编辑模式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1b/58/1bcd320829837a41bb513978b3432a58.png" alt="">
|
||||
|
||||
第三步是输入const,并输入Esc完成编辑操作,回到命令模式,整个修改完成。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/37/e8/377269a6d42aa02d4d8f6ade37bf77e8.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/58/ff/58aaa90b1938cd51464f6bdf2330acff.png" alt="">
|
||||
|
||||
我们再来看看第二个例子。我希望在上面这段文字的每一行末尾都添加一个分号。具体的操作步骤如下所示:
|
||||
|
||||
首先,用$命令把光标挪到最后一行的末尾,然后Ctrl-v进入列模式,再用7j命令将光标挪到第一行,这时,每一行的末尾都被包含到了一个方块里。不过因为每一行的长度不同,所以方块没有显示完全。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/ad/3cea82dcf9ba1c4a9053ccd4d5e839ad.png" alt="">
|
||||
|
||||
然后,输入A命令,光标挪到每一行的末尾,并进入编辑模式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/db/84/dbecf5cbb5b75cf002270c154abbdc84.png" alt="">
|
||||
|
||||
最后,输入要插入的分号,输入Esc完成编辑。这样,每一行的末尾都添加了一个分号。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/be/8b/be6fa0acecd2dee663df3deb1929b68b.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a7/4b/a7ac6e2c639334b799058b2dd243c64b.png" alt="">
|
||||
|
||||
最后,关于可视模式我还有三个比较有用的小贴士:
|
||||
|
||||
- 第一,在退出了可视模式之后,可以使用gv命令重新进入可视模式并选择上一次的选择。这个命令出乎意料得有用,因为我们常常需要对上一次的选择进行进一步的操作。
|
||||
- 第二,进入可视模式之后,可以直接使用v、V、Ctrl-v在三种模式之间切换,而不需要退出可视模式再重新进入。比如,你一开始使用v进入了字符可视模式,这时发现你需要删除几行,就可以直接输入V进入行的可视模式。
|
||||
- 第三,修改选取的区域范围。当你进入可视模式之后,光标默认处于所选区域的结尾处,你可以挪动光标调节选择区域的结尾部分。但,如果你想调节的是所选区域的开头部分,则可以使用命令o将光标跳到选择区域的开始部分,再次输入命令o光标又会跳到选择区域的末尾。一个更好玩的情况是,在列模式中,你可以使用小写o在对角线上跳转,大写O在水平方向跳转,从而可以灵活调整所选矩形的四个角。
|
||||
|
||||
以上就是命令模式中的常用基础命令了。理解了这些命令,再加上命令组合,你就可以比较高效地使用VIM进行文本编辑了。但,要更高效地使用VIM,我们还有一个问题没有解决:VIM虽然强大,但也只是整个工作环境中的一个工具,怎样才能在整个工作中高效地使用VIM技能呢?
|
||||
|
||||
所以,接下来我将带你探索如何寻找合适的VIM使用场景。
|
||||
|
||||
## 寻找合适的VIM使用场景
|
||||
|
||||
寻找合适的适用场景,常常包括两种情况,一是VIM不是主力IDE的情况,二是VIM与其他工具的集成。接下来,我们先看第一种情况。
|
||||
|
||||
### 我的工作环境中,VIM不是主力IDE怎么办?
|
||||
|
||||
这种情况下,我会建议你先看一下你的主力IDE是否支持VI模式。目前,绝大部分主流IDE都支持,而且大都做得不错。如果你的主力IDE中能使用VIM命令的话,那从VIM特有的命令模式就能让你收益颇丰。
|
||||
|
||||
从我的经验来看,我只有在Facebook那几年使用VIM作为主力IDE,其他时间使用的编辑器主要是Intellij的IDE系列和VS Code。在这些IDE里,我一直在使用VI模式,效果也都很好。
|
||||
|
||||
除了在主力IDE中使用VI模式外,你还可以选择把VIM只作为一个单纯的文本编辑器,需要强大的编辑功能时,临时用一下就可以。比如,我在写微信小程序的时候,一开始使用的是原生的微信开发工具,编辑功能不是太强。所以在遇到一些重量级的编辑工作时,我就打开VIM快速搞定,然后再回到微信原生开发工具IDE里继续工作。
|
||||
|
||||
### VIM能在Linux命令行环境中与其他工具结合的很好
|
||||
|
||||
Unix/Linux系统的一个设计理念是,每个工具都只做一个功能,并且把这个功能做到极致,然后由操作系统把这些功能集成起来。VI当初就是作为Unix系统里的编辑器而存在的,到了40年后的今天,虽然VIM已经可以被配置成为一个强大的IDE,但很大程度上依然是Unix/Linux系统的基础编辑器,仍然可以很方便地和Unix/Linux的其他工具集成。
|
||||
|
||||
为了帮助你理解,我再和你分享3个适用场景。
|
||||
|
||||
**第一个场景:使用VIM作为其他工具的编辑器。**
|
||||
|
||||
在大部分Linux系统里,默认的编辑器就是VIM。如果不是的话,可以使用如下命令来设置:
|
||||
|
||||
```
|
||||
// 全局使用VIM
|
||||
export EDITOR='vim'
|
||||
|
||||
```
|
||||
|
||||
**第二个适用场景:使用管道(Pipe)。**
|
||||
|
||||
管道是Linux环境中最常用的工具之间的集成方式。VIM可以接收管道传过来的内容。比如,我们要查看GitHub上用户xxx的代码仓的情况,可以使用下面这条命令
|
||||
|
||||
```
|
||||
curl -s https://api.github.com/users/xxx/repos | vim -
|
||||
|
||||
```
|
||||
|
||||
curl命令访问GitHub的API,把输出通过管道传给VIM,方便我直接在VIM中查看用户xxx的代码仓的细节。
|
||||
|
||||
这里需要注意的是,在VIM命令后面有一个-,表示VIM将使用管道作为输入。
|
||||
|
||||
**第三个适用场景:在VIM中调用系统工具**
|
||||
|
||||
除了系统调用VIM和使用管道进行集成之外,我们还可以在VIM中反过来调用系统的其他工具。这里,我与你介绍一个最实用的命令:**在可视模式中使用!命令调用外部程序。**
|
||||
|
||||
比如,我想给一个文件里的中间几行内容前加上从1000开始的数字序号。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f7/cc/f72d9d852cdca00c79544ef3a41dd3cc.png" alt="">
|
||||
|
||||
在Linux系统里,我们可以使用nl这个命令行工具完成,具体的命令是nl -v 1000。所以,我们可以把这一部分文本单独保存为文件,使用nl处理后,再把处理结果拷贝回VIM中。
|
||||
|
||||
虽然可以达到目的,但过程繁琐。幸运的是,我们可以在VIM里面直接使用nl,并把处理结果插入VIM中,具体操作如下。
|
||||
|
||||
首先,使用命令V进入可视模式,并选择这一部分文字。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/69/37/6962fb43054994be72fcd30f89802437.png" alt="">
|
||||
|
||||
然后,输入命令!,VIM的最后一行会显示’<,’>! ,表示由外部程序操作选中的部分。这时,我们输入nl -v 1000并回车。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0d/7d/0dd3edaf5ca34ae776d1527fbbee547d.png" alt="">
|
||||
|
||||
VIM就会把选择的文本传给nl,nl在每一行前面添加序号,再把处理结果传给VIM,从而完成了我们想要的编辑结果,也就是在所选的几行文字前面添加从1000到1006的序号。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5a/fd/5a7aa3f398c73283234d386a603bc3fd.png" alt="">
|
||||
|
||||
同时,在VIM的底部,会显示7 lines filtered,意思是VIM里面的7行文本被使用外部工具处理过。是不是很方便?
|
||||
|
||||
我每次使用这样的功能时都觉得很爽,因为这可以让我聚焦在工作上,而不会把时间花在繁琐的操作上。
|
||||
|
||||
以上就是3个最常用的VIM和其他工具集成的例子。这种工具的集成工作方式,可以大大提高单个工具所能带来的效率提升,我还会在后面的文章中与你详细讨论。
|
||||
|
||||
## 总结
|
||||
|
||||
在今天这篇文章中,我与你介绍了高效学习VIM的三个步骤,即:第一,学习VIM的命令模式和命令组合方式;第二,学习文本编辑过程中各个环节最常用的命令;第三,在自己的工作环境中找到适用VIM的场景。
|
||||
|
||||
事实上,我今天与你讲述的VIM命令和使用技巧,只是最常见的跨平台使用中的共性部分,对效能提升带来的效果也最直接、最明显。
|
||||
|
||||
但,这些远没有覆盖VIM的强大功能。比如,我没有与你讨论多文件、多Tab、多窗口编辑的场景,以及插件的话题。如果你需要的话,可以把VIM配置成一个类似IDE的开发环境,进入沉浸式的VIM使用体验。关于插件方面的内容,我推荐3个插件:
|
||||
|
||||
- [pathogen](https://github.com/tpope/vim-pathogen)。它是一个插件管理软件,很好地解决了VIM自带插件删除不理想的问题。关于pathogen的使用,你可以参考下[这篇文章](https://gist.github.com/romainl/9970697)。
|
||||
- [nerdtree](https://github.com/scrooloose/nerdtree),在VIM中添加文件夹管理的功能。
|
||||
- [fugitive](https://github.com/tpope/vim-fugitive),在VIM中添加查看、编辑Git内容的功能。它的功能简直是强大到变态。
|
||||
|
||||
其中,pathogen和fugitive的作者都是[Tim Pope](https://github.com/tpope),一个VIM牛人。如果你想了解更多的插件的话,可以看看他的其他VIM插件。
|
||||
|
||||
不容置疑的是,VIM的学习曲线非常长,即使我已经使用了15年,仍然会时不时地学习到一些新东西。只要你愿意,就可以一直学习一直提高。
|
||||
|
||||
## 思考题
|
||||
|
||||
你想要分享的最炫酷的VIM技巧是什么呢?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
596
极客时间专栏/研发效率破局之道/个人效能/25 | 玩转Git:五种提高代码提交原子性的基本操作.md
Normal file
596
极客时间专栏/研发效率破局之道/个人效能/25 | 玩转Git:五种提高代码提交原子性的基本操作.md
Normal file
@@ -0,0 +1,596 @@
|
||||
<audio id="audio" title="25 | 玩转Git:五种提高代码提交原子性的基本操作" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c2/8e/c2199a4ee6a45ba1fc1dfc50f066068e.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我们来聊一聊Git吧。
|
||||
|
||||
毫无疑问,Git是当前最流行的代码仓管理系统,可以说是开发者的必备技能。它非常强大,使用得当的话可以大幅助力个人效能的提升。一个最直接的应用就是,可以帮助我们提升代码提交的原子性。如果一个团队的成员都能熟练使用Git的话,可以大大提高团队代码的模块化、可读性、可维护性,从而提高团队的研发效能。但可惜的是,现实情况中,由于Git比较复杂,用得好的人并不多。
|
||||
|
||||
所以接下来,我会通过两篇文章,与你详细讲述如何使用Git助力实现代码原子性。今天这篇文章,我先与你分享Git支持原子性的5种基础操作;下一篇文章,则会给你介绍Facebook开发人员是怎样具体应用这些基础操作去实现代码原子性的。
|
||||
|
||||
通过这两篇文章,我希望你能够:
|
||||
|
||||
1. 了解在分布式代码仓管理系统中,如何通过对代码提交的灵活处理,实现提交的原子性;
|
||||
1. 帮你学习到Git的实用技巧,提高开发效率。
|
||||
|
||||
我在[第21篇文章](https://time.geekbang.org/column/article/148170)中提到,代码提交的原子性指的是,一个提交包含一个不可分割的特性、修复或者优化。如果用一个提交完成一个功能,这个提交还是会比较大的话,我们需要把这个功能再拆分为子功能。
|
||||
|
||||
为什么要强调代码提交的原子性呢?这是因为它有以下3大好处:
|
||||
|
||||
- 可以让代码结构更清晰、更容易理解;
|
||||
- 出了问题之后方便定位,并可以针对性地对问题提交进行“回滚”;
|
||||
- 在功能开关的协助下,可以让开发者尽快把代码推送到origin/master上进行合并。这正是持续集成的基础。
|
||||
|
||||
而Git之所以能够方便我们实现原子性提交,主要有两方面的原因:
|
||||
|
||||
- Git提供方便、灵活的提交、分支处理功能,使得我们可以灵活地产生提交、修改提交、拆分提交,甚至改变提交的先后顺序。
|
||||
- Git是一个分布式代码仓管理系统,每个开发人员在本地都有一个代码仓,从而可以放心在本地代码仓中使用上述功能,不用操心会影响到远程共享代码仓。
|
||||
|
||||
下面,我就来与你分享Git支持原子性的5种基础操作,具体包括:
|
||||
|
||||
1. 用工作区改动的一部分产生提交;
|
||||
1. 对当前提交进行拆分;
|
||||
1. 修改当前提交;
|
||||
1. 交换多个提交的先后顺序;
|
||||
1. 修改非当前提交。
|
||||
|
||||
需要注意的是,在接下来的两篇文章里,我只会与你详细介绍针对原子性相关的操作,而关于Git的一些基础概念和使用方法,推荐你参考“[图解Git](https://marklodato.github.io/visual-git-guide/index-zh-cn.html)”这篇文章。
|
||||
|
||||
## 基本操作一:把工作区里代码改动的一部分转变为提交
|
||||
|
||||
如果是把整个文件添加到提交中,操作很简单:先用git add <文件名>把需要的文件添加到Git暂存区,然后使用git commit命令提交即可。这个操作比较常见,我们应该都比较熟悉。
|
||||
|
||||
但在工作中,一个文件里的改动常常会包含多个提交的内容。比如,开发一个功能时,我们常常会顺手修复一些格式规范方面的东西;再比如,一个功能比较大的时候,改动常常会涉及几个提交内容。那么,在这些情况下,为了实现代码提交的原子性,我们就需要只把文件里的一部分改动添加到提交中,剩下的部分暂时不产生提交。针对这个需求,Git提供了git add -p命令。
|
||||
|
||||
比如,我在index.js文件里有两部分改动,一部分是添加一个叫作timestamp的endpoint,另一部分是使用变量来定义一个魔术数字端口:
|
||||
|
||||
```
|
||||
## 显示工作区中的改动
|
||||
> git diff
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..986fcd8 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,8 +1,14 @@
|
||||
+var port = 3000 ## <-- 魔术数字变量化
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
## vvv 添加endpoint
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port) ## <-- 端口魔术数字变量化
|
||||
|
||||
```
|
||||
|
||||
这时,运行git add -p index.js命令,Git会把文件改动分块儿显示,并提供操作选项,比如我可以通过y和n指令来选择是否把当前改动添加到Git的提交暂存区中,也可以通过s指令把改动块儿再进行进一步拆分。通过这些指令,我就可以选择性地只把跟端口更改相关的改动添加到Git的暂存区中。
|
||||
|
||||
```
|
||||
> git add -p index.js
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..986fcd8 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,8 +1,14 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
Stage this hunk [y,n,q,a,d,s,e,?]? s
|
||||
Split into 3 hunks.
|
||||
@@ -1,3 +1,4 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
|
||||
@@ -1,7 +2,11 @@
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? n
|
||||
@@ -4,5 +9,6 @@
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
Stage this hunk [y,n,q,a,d,K,g,/,e,?]? y
|
||||
|
||||
```
|
||||
|
||||
当整个文件的所有改动块儿都处理完成之后,通过git diff --cached命令可以看到,我的确只是把需要的那一部分改动,也就是端口相关的改动,添加到了暂存区:
|
||||
|
||||
```
|
||||
> git diff --cached
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..7b82693 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,3 +1,4 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
@@ -5,4 +6,5 @@ app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
|
||||
```
|
||||
|
||||
通过git diff命令,我们可以看到,endpoint相关的改动仍留在工作区:
|
||||
|
||||
```
|
||||
> git diff
|
||||
diff --git a/index.js b/index.js
|
||||
index 7b82693..986fcd8 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -2,6 +2,10 @@ var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
最后,再通过git commit命令,我就可以产生一个只包含端口相关改动的提交,实现了将本地代码改动的一部分转变为提交的目的。
|
||||
|
||||
如果你想深入了解git add -p的内容,可以参考[这篇文章](https://johnkary.net/blog/git-add-p-the-most-powerful-git-feature-youre-not-using-yet/)。
|
||||
|
||||
通过git add -p,我们可以把工作区中的代码拆分成多个提交。但是,如果需要拆分的代码已经被放到了一个提交中,怎么办?如果这个提交已经推送到了远程代码仓共享分支,那就没有办法了。但如果这个提交还只是在本地,我们就可以对它进行拆分。
|
||||
|
||||
## 基本操作二:对当前提交进行拆分
|
||||
|
||||
所谓当前提交,指的是当前分支的HEAD指向的提交。
|
||||
|
||||
我继续以上面的代码示例向你解释应该如何操作。假如,我已经把关于endpoint的改动和端口的改动产生到了同一个提交里,具体怎么拆分呢?
|
||||
|
||||
这时,我可以先“取消”已有的提交,也就是把提交的代码重新放回到工作区中,然后再使用git add -p的方法重新产生提交。这里的取消是带引号的,因为**在Git里所有的提交都是永久存在的,所谓取消,只不过是把当前分支指到了需要取消的提交的前面而已。**
|
||||
|
||||
首先,我可以用git log查看历史,并使用git show确认提交包含了endpoint改动和端口改动:
|
||||
|
||||
```
|
||||
## 查看提交历史
|
||||
> git log --graph --oneline --all
|
||||
* 7db082a (HEAD -> master) Change magic port AND add a endpoint
|
||||
* 352cc92 Add gitignore file for node_modules
|
||||
* e2dacbc (origin/master) Added the simple web server endpoint
|
||||
...
|
||||
|
||||
|
||||
## 查看提交
|
||||
> git show
|
||||
commit 7db082ab0f105ea185c89a0ba691857b55566469 (HEAD -> master)
|
||||
...
|
||||
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..986fcd8 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,8 +1,14 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
|
||||
```
|
||||
|
||||
然后,用git branch temp命令产生一个临时分支temp,指向当前HEAD。temp分支的作用是,预防代码丢失。如果后续工作出现问题的话,我可以使用git reset --hard temp把代码仓、暂存区和工作区都恢复到这个位置,从而不会丢失代码。
|
||||
|
||||
```
|
||||
> git branch temp
|
||||
|
||||
> git log --graph --oneline --all
|
||||
* 7db082a (HEAD -> master, temp) Change magic port AND add a endpoint
|
||||
* 352cc92 Add gitignore file for node_modules
|
||||
* e2dacbc (origin/master) Added the simple web server endpoint
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
接下来,运行git reset HEAD^命令,把当前分支指向目标提交HEAD^,也就是当前提交的父提交。同时,在没有接–hard或者–soft参数时,git reset会把目标提交的内容同时复制到暂存区,但不会复制到工作区。所以,工作区的内容仍然是当前提交的内容,仍然有endpoint相关改动和端口相关改动。也就是说,这个命令的效果,就是让我回到了对这两个改动进行提交之前的状态:
|
||||
|
||||
```
|
||||
> git reset HEAD^
|
||||
Unstaged changes after reset:
|
||||
M index.js
|
||||
|
||||
> git status
|
||||
On branch master
|
||||
Your branch is ahead of 'origin/master' by 1 commit.
|
||||
(use "git push" to publish your local commits)
|
||||
|
||||
Changes not staged for commit:
|
||||
(use "git add <file>..." to update what will be committed)
|
||||
(use "git checkout -- <file>..." to discard changes in working directory)
|
||||
|
||||
modified: index.js
|
||||
|
||||
no changes added to commit (use "git add" and/or "git commit -a")
|
||||
15:06:58 (master) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo
|
||||
|
||||
|
||||
## 改动在工作区
|
||||
> git diff
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..986fcd8 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,8 +1,14 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
+app.get('/timestamp', function (req, res) {
|
||||
+ res.send('' + Date.now())
|
||||
+})
|
||||
+
|
||||
app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
|
||||
|
||||
## 输出为空
|
||||
> git diff --cached
|
||||
|
||||
```
|
||||
|
||||
最后,我就可以使用上面介绍过的git add -p的方法把工作区中的改动拆分成两个提交了。
|
||||
|
||||
## 基本操作三:修改当前提交
|
||||
|
||||
如果只需要修改Commit Message的话,直接使用git commit --amend命令,Git就会打开你的默认编辑器让你修改,修改完成之后保存退出即可。
|
||||
|
||||
如果要修改的是文件内容,可以使用git add、git rm等命令把改动添加到暂存区,再运行git commit --amend,最后输入Commit Message保存退出即可。
|
||||
|
||||
## 基本操作四:交换多个提交的先后顺序
|
||||
|
||||
有些时候,我们需要把多个提交交换顺序。比如,master分支上有两个提交A和B,B在A之上,两个提交都还没有推送到origin/master上。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/18/9976bf834b37be7ac877eb80b73bac18.png" alt="">
|
||||
|
||||
这时,我先完成了提交B,想把它先单独推送到origin/master上去,就需要交换A和B的位置,使得A在B之上。我可以使用git rebase --interactive(选项–interactive可以简写为-i)来实现这个功能。
|
||||
|
||||
首先,还是使用git branch temp产生一个临时分支,确保代码不会丢失。然后,使用git log --oneline --graph来确认当前提交历史:
|
||||
|
||||
```
|
||||
> git log --oneline --graph
|
||||
* 7b6ea30 (HEAD -> master, temp) Add a new endpoint to return timestamp
|
||||
* b517154 Change magic port number to variable
|
||||
* 352cc92 (origin/master) Add gitignore file for node_modules
|
||||
* e2dacbc Added the simple web server endpoint
|
||||
* 2f65a89 Init commit created by installing express module
|
||||
|
||||
```
|
||||
|
||||
接下来,运行
|
||||
|
||||
```
|
||||
> git rebase -i origin/master
|
||||
|
||||
```
|
||||
|
||||
Git会打开我的默认编辑器,让我选择rebase的具体操作:
|
||||
|
||||
```
|
||||
pick b517154 Change magic port number to variable
|
||||
pick 7b6ea30 Add a new endpoint to return timestamp
|
||||
|
||||
# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)
|
||||
#
|
||||
# Commands:
|
||||
# p, pick <commit> = use commit
|
||||
# r, reword <commit> = use commit, but edit the commit message
|
||||
# e, edit <commit> = use commit, but stop for amending
|
||||
# s, squash <commit> = use commit, but meld into previous commit
|
||||
# f, fixup <commit> = like "squash", but discard this commit's log message
|
||||
# x, exec <command> = run command (the rest of the line) using shell
|
||||
# b, break = stop here (continue rebase later with 'git rebase --continue')
|
||||
# d, drop <commit> = remove commit
|
||||
# l, label <label> = label current HEAD with a name
|
||||
# t, reset <label> = reset HEAD to a label
|
||||
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
|
||||
# . create a merge commit using the original merge commit's
|
||||
# . message (or the oneline, if no original merge commit was
|
||||
# . specified). Use -c <commit> to reword the commit message.
|
||||
#
|
||||
# These lines can be re-ordered; they are executed from top to bottom.
|
||||
#
|
||||
# If you remove a line here THAT COMMIT WILL BE LOST.
|
||||
#
|
||||
# However, if you remove everything, the rebase will be aborted.
|
||||
#
|
||||
# Note that empty commits are commented out
|
||||
|
||||
```
|
||||
|
||||
rebase命令一般翻译为变基,意思是改变分支的参考基准。**具体到git rebase -i origin/master命令**,就是把从origin/master之后到当前HEAD的所有提交,也就是A和B,重新有选择地放到origin/master上面。你可以选择放或者不放某一个提交,也可以选择放置顺序,还可以选择将多个提交合并成一个,等等。另外,这里说的放一个提交,指的就是在HEAD之上应用一个提交的意思。
|
||||
|
||||
Git rebase -i打开编辑器时,里面默认的操作列表是把原有提交全部原封不动地放到新的参考基准上去,具体到这个例子,是用两个pick命令把A和B先后重新放到origin/master之上,如果我直接保存退出的话,结果跟rebase之前没有任何改变。
|
||||
|
||||
这里,因为我需要的操作是交换A和B的顺序,所以交换两个pick指令行,保存退出即可。Git rebase就会先后把B和A放到origin/master上。
|
||||
|
||||
```
|
||||
pick 7b6ea30 Add a new endpoint to return timestamp
|
||||
pick b517154 Change magic port number to variable
|
||||
|
||||
# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
至此,我就完成了交换两个提交的先后顺序。接下来,我可以用git log命令,来确认A和B的确是交换了顺序。
|
||||
|
||||
```
|
||||
## 以下是 git rebase -i origin/master 的输出结果
|
||||
Successfully rebased and updated refs/heads/master.
|
||||
|
||||
|
||||
## 查看提交历史
|
||||
> git log --oneline --graph --all
|
||||
* 65c41e6 (HEAD -> master) Change magic port number to variable
|
||||
* 40e2824 Add a new endpoint to return timestamp
|
||||
| * 7b6ea30 (temp) Add a new endpoint to return timestamp
|
||||
| * b517154 Change magic port number to variable
|
||||
|/
|
||||
* 352cc92 (origin/master) Add gitignore file for node_modules
|
||||
* e2dacbc Added the simple web server endpoint
|
||||
* 2f65a89 Init commit created by installing express module
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6a/18/6a1a8cc66255460cd2b45d2430c93718.png" alt="">
|
||||
|
||||
值得注意的是,A和B的commit SHA1改变了,因为它们实际上是新产生出来的A和B的拷贝,原来的两个提交仍然存在(图中的阴影部分),我们还可以用分支temp找到它们,但不再需要它们了。如果temp分支被删除,A和B也会自动被Git的垃圾收集过程gc清除掉。
|
||||
|
||||
其实,git rebase -i的功能非常强大,除了交换提交的顺序外,还可以删除提交、和并多个提交。如果你想深入了解这部分内容的话,可以参考“[Git 工具 - 重写历史](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2)”这篇文章。
|
||||
|
||||
## 基本操作五:修改非头部提交
|
||||
|
||||
在上面的基本操作二、三、四中,我与你介绍的都是对当前分支头部的一个提交或者多个提交进行操作。但在工作中,为了方便实现原子性,我们常常需要修改历史提交,也就是修改非头部提交。对历史提交操作,最方便的方式依然是使用强大的git rebase -i。
|
||||
|
||||
接下来,我继续用上面修改A和B两个提交的顺序的例子来做说明。在还没有交换提交A和B的顺序时,也就是B在A之上的时候,我发现我需要修改提交A。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/18/9976bf834b37be7ac877eb80b73bac18.png" alt="">
|
||||
|
||||
首先,我运行git rebase -i origin/master;然后,在弹出的编辑窗口中把原来的“pick b517154”的一行改为“edit b517154”。其中,b517154是提交A的SHA1。
|
||||
|
||||
```
|
||||
edit b517154 Change magic port number to variable
|
||||
pick 7b6ea30 Add a new endpoint to return timestamp
|
||||
|
||||
# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)
|
||||
# ...
|
||||
|
||||
```
|
||||
|
||||
而“edit b517154”,是告知Git rebase命令,在应用了b517154之后,暂停后续的rebase操作,直到我手动运行git rebase --continue通知它继续运行。这样,当我在编辑器中保存修改并退出之后,git rebase 就会暂停。
|
||||
|
||||
```
|
||||
> git rebase -i origin/master
|
||||
Stopped at b517154... Change magic port number to variable
|
||||
You can amend the commit now, with
|
||||
|
||||
git commit --amend
|
||||
|
||||
Once you are satisfied with your changes, run
|
||||
|
||||
git rebase --continue
|
||||
|
||||
22:29:35 (master|REBASE-i) ~/jksj-repo/git-atomic-demo >
|
||||
|
||||
```
|
||||
|
||||
这时,我可以运行git log --oneline --graph --all,确认当前HEAD已经指向了我想要修改的提交A。
|
||||
|
||||
```
|
||||
> git log --oneline --graph --all
|
||||
* 7b6ea30 (master) Add a new endpoint to return timestamp
|
||||
* b517154 (HEAD) Change magic port number to variable
|
||||
* 352cc92 (origin/master) Add gitignore file for node_modules
|
||||
* e2dacbc Added the simple web server endpoint
|
||||
* 2f65a89 Init commit created by installing express module
|
||||
|
||||
```
|
||||
|
||||
接下来,我就可以使用基本操作二中提到的方法对当前提交(也就是A)进行修改了。具体来说,就是修改文件,之后用git add <文件名>,然后再运行git commit --amend。
|
||||
|
||||
```
|
||||
## 检查当前HEAD内容
|
||||
> git show
|
||||
commit b51715452023fcf12432817c8a872e9e9b9118eb (HEAD)
|
||||
Author: Jason Ge <gejun_1978@yahoo.com>
|
||||
Date: Mon Oct 14 12:50:36 2019
|
||||
|
||||
Change magic port number to variable
|
||||
|
||||
Summary:
|
||||
It's not good to have a magic number. This commit changes it to a
|
||||
varaible.
|
||||
|
||||
Test:
|
||||
Run node index.js and verified the root endpoint still works.
|
||||
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..7b82693 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,3 +1,4 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
@@ -5,4 +6,5 @@ app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server
|
||||
+app.listen(port)
|
||||
|
||||
|
||||
## 用VIM对文件进行修改,在注释部分添加"at a predefined port"
|
||||
> vim index.js
|
||||
|
||||
|
||||
## 查看工作区中的修改
|
||||
> git diff
|
||||
diff --git a/index.js b/index.js
|
||||
index 7b82693..eb53f5f 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -6,5 +6,5 @@ app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-// Start the server
|
||||
+// Start the server at a predefined port
|
||||
app.listen(port)
|
||||
22:40:10 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo
|
||||
> git add index.js
|
||||
|
||||
|
||||
## 对修改添加到提交A中去
|
||||
> git commit --amend
|
||||
[detached HEAD f544b12] Change magic port number to variable
|
||||
Date: Mon Oct 14 12:50:36 2019 +0800
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
22:41:18 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo
|
||||
|
||||
|
||||
## 查看修改过后的A。确认其包含了新修改的内容"at a predefined port"
|
||||
> git show
|
||||
commit f544b1247a10e469372797c7dd08a32c0d59b032 (HEAD)
|
||||
Author: Jason Ge <gejun_1978@yahoo.com>
|
||||
Date: Mon Oct 14 12:50:36 2019
|
||||
|
||||
Change magic port number to variable
|
||||
|
||||
Summary:
|
||||
It's not good to have a magic number. This commit changes it to a
|
||||
varaible.
|
||||
|
||||
Test:
|
||||
Run node index.js and verified the root endpoint still works.
|
||||
|
||||
diff --git a/index.js b/index.js
|
||||
index 63b6300..eb53f5f 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,3 +1,4 @@
|
||||
+var port = 3000
|
||||
var express = require('express')
|
||||
var app = express()
|
||||
|
||||
@@ -5,4 +6,5 @@ app.get('/', function (req, res) {
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
-app.listen(3000)
|
||||
+// Start the server at a predefined port
|
||||
+app.listen(port)
|
||||
|
||||
```
|
||||
|
||||
执行完成之后,我就可以运行git rebase --continue,完成git rebase -i的后续操作,也就是在A之上再应用提交B,并把HEAD重新指向了B,从而完成了对历史提交A的修改。
|
||||
|
||||
```
|
||||
## 继续运行rebase命令的其他步骤
|
||||
> git rebase --continue
|
||||
Successfully rebased and updated refs/heads/master.
|
||||
|
||||
|
||||
## 查看提交历史
|
||||
> git log --oneline --graph --all
|
||||
* 27cba8c (HEAD -> master) Add a new endpoint to return timestamp
|
||||
* f544b12 Change magic port number to variable
|
||||
| * 7b6ea30 (temp) Add a new endpoint to return timestamp
|
||||
| * b517154 Change magic port number to variable
|
||||
|/
|
||||
* 352cc92 (origin/master) Add gitignore file for node_modules
|
||||
* e2dacbc Added the simple web server endpoint
|
||||
* 2f65a89 Init commit created by installing express module
|
||||
|
||||
```
|
||||
|
||||
经过rebase命令,我重新产生了提交A和B。同样的,A和B是新生成的两个提交,原来的A和B仍然存在。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/75/48/75e70f4be9da61fcafdca3b427414748.png" alt="">
|
||||
|
||||
以上,就是修改历史提交内容的步骤。
|
||||
|
||||
如果我们需要对历史提交进行拆分的话,步骤也差不多:首先,使用git rebase -i,在需要拆分的提交处使用edit指令;然后,在git rebase -i暂停的时候,使用基本操作2的方法对目标提交进行拆分;拆分完成之后,运行git rebase --continue即可。
|
||||
|
||||
## 小结
|
||||
|
||||
今天,我与你介绍了Git支持代码提交原子性的五种基本操作,包括用工作区改动的一部分产生提交、对当前提交进行拆分、修改当前提交、交换多个提交的先后顺序,以及对非头部提交进行修改。
|
||||
|
||||
掌握这些基本操作,可以让我们更灵活地对代码提交进行修改、拆分、合并和交换顺序,为使用Git实现代码原子性的工作流打好基础。
|
||||
|
||||
其实,这些基本操作非常强大和实用,除了可以用来提高提交的原子性外,还可以帮助我们日常开发。比如,我们可以把还未完成的功能尽快产生提交,确保代码不会丢失,等到后面再修改。又比如,可以产生一些用来帮助自己本地开发的提交,始终放在本地,不推送到远程代码仓。
|
||||
|
||||
在我看来,Git学习曲线比较陡而且长,帮助手册也可以说是晦涩难懂,但一旦弄懂,它能让你超级灵活地对本地代码仓进行处理,帮助你发现代码仓管理系统的新天地。git rebase -i命令,就是一个非常典型的例子。一开始,你会觉得它有些难以理解,但搞懂之后就超级有用,可以帮助你高效地解决非常多的问题。所以,在我看来,在Git上投入一些时间绝对值得!
|
||||
|
||||
为了方便你学习,我把这篇文章涉及的代码示例放到了[GitHub](https://github.com/jungejason/git-atomic-demo)上,推荐你clone下来多加练习。
|
||||
|
||||
## 思考题
|
||||
|
||||
1. 对于交换多个提交的先后顺序,除了使用rebase -i命令外,你还知道什么其他办法吗?
|
||||
1. 文章中提到,如果一个提交已经推送到了远程代码仓共享分支,那就没有办法对它进行拆分了。这个说法其实有些过于绝对。你知道为什么绝大部分情况下不能拆分,而什么情况下还可以拆分呢?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
1150
极客时间专栏/研发效率破局之道/个人效能/26 | Facebook怎样实现代码提交的原子性?.md
Normal file
1150
极客时间专栏/研发效率破局之道/个人效能/26 | Facebook怎样实现代码提交的原子性?.md
Normal file
File diff suppressed because it is too large
Load Diff
353
极客时间专栏/研发效率破局之道/个人效能/27 | 命令行:不只是酷,更重要的是能提高个人效能.md
Normal file
353
极客时间专栏/研发效率破局之道/个人效能/27 | 命令行:不只是酷,更重要的是能提高个人效能.md
Normal file
@@ -0,0 +1,353 @@
|
||||
<audio id="audio" title="27 | 命令行:不只是酷,更重要的是能提高个人效能" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d3/4a/d305df11cd909f9146d06d90f8924d4a.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我要与你分享的主题是,命令行下的高效工作技巧。
|
||||
|
||||
我先和你讲一个有意思的话题吧。命令行工具常常会给人一种黑客的感觉,好莱坞的电影里面常常出现命令行窗口的使用。不知道你听说过没有,很多好莱坞电影在拍摄时使用的其实是一个叫作[nmap](https://nmap.org)的工具。这个工具是做安全扫描的,只不过因为它的显示特别花哨,所以被很多电影采用。在nmap官方网站上,还专门列出来了这些电影的名单。
|
||||
|
||||
类似这种可以让自己看起来很忙的工具还有很多,比如[Genact](https://github.com/svenstaro/genact)。下面是一个使用Genact的录屏,当然这里的命令并没有真正运行。这可能是整个专栏中,唯一一个让你看起来效率很高,实际上却是降低效率的工具,但说不定对你有用,你懂的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2d/97/2dba0dfdd5f0260ad3b3082a7d3d2697.gif" alt="">
|
||||
|
||||
讲完这个娱乐性的话题,我们进入正题吧。
|
||||
|
||||
## 为什么要使用命令行?
|
||||
|
||||
GUI图形界面的出现是计算机技术的变革,极大方便了用户。但在这么多年后的今天,命令行工具为什么仍然有如此强大的生命力呢?
|
||||
|
||||
在我看来,对软件工程师来说,想要高效开发就必须掌握命令行,主要原因包括:
|
||||
|
||||
- 虽然鼠标的移动和点击比较直观,但要完成重复性的工作,使用键盘会快捷得多。这一点从超市的结算人员就可以看出来,使用键盘系统的收银员总是噼里啪啦地很快就可以完成结算,而使用鼠标点击的话明显慢很多。
|
||||
- 作为开发人员,可以比较容易地使用命令行的脚本,对自己的工作进行自动化,以及和其他系统工具联动。但使用GUI的话,就会困难得多。
|
||||
- 命令行通常可以暴露更完整的功能,让使用者对整个系统有更透彻的理解。Git就是一个典型的例子,再好的GUI Git系统都只能封装一部分Git命令行功能,要想真正了解Git,我们必须要使用命令行。
|
||||
- 有一些情况是必须使用命令行的,比如SSH到远程服务器上工作的时候。
|
||||
|
||||
为了演示命令行的强大功能给我们带来的方便,下面是一个在本地查看文件并上传到服务器的流程的录屏。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/29/1deb0006ea65cd5c4a28819a2cf50629.gif" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/95/40/958987c6594699ff1fb97b1622b21a40.gif" alt="">
|
||||
|
||||
通过这个案例,你可以看到命令行的以下几个功能:
|
||||
|
||||
- 在提示行会高亮显示时间、机器名、用户名、Git代码仓的分支和状态,以及上一个命令的完成状态。
|
||||
- 输入命令的时候,高亮显示错误并自动纠错。
|
||||
- 使用交互的形式进行文件夹的跳转,并方便查找文件,还可以直接在命令行里显示图片。
|
||||
- 使用交互的工具,把文件上传到远端的服务器,并快速连接到远端查看传输是否成功。
|
||||
|
||||
整个流程全部都是在命令行里完成的,速度非常快,用户体验也非常好。正因为如此,我看到的硅谷特别高效的开发人员,绝大多数都大量使用命令行。那,面对成百上千的命令行工具,我们**怎样才能高效地学习和使用**呢?
|
||||
|
||||
我将高效学习使用命令行的过程,归纳为两大步:
|
||||
|
||||
1. 配置好环境;
|
||||
1. 针对自己最常使用命令行的场景,有的放矢地选择工具。
|
||||
|
||||
今天,我就与你详细讲述**环境配置**这个话题。而关于选择工具的话题,我会在下一篇文章中与你详细介绍。总结来讲,环境配置主要包括以下四步:
|
||||
|
||||
1. 选择模拟终端;
|
||||
1. 选择Shell;
|
||||
1. 具体的Shell配置;
|
||||
1. 远程SSH的处理。
|
||||
|
||||
这里需要注意的是,在命令行方面,macOS和Linux系统比Windows系统要强大许多,所以我主要以macOS和Linux系统来介绍,而关于Windows的环境配置,我只会捎带提一下。不过,macOS和Linux系统中的工具选择和配置思路,你可以借鉴到Windows系统中。
|
||||
|
||||
## 第一步,选择模拟终端
|
||||
|
||||
我将一个好的终端应该具有的特征,归纳为4个:
|
||||
|
||||
- 快,稳定;
|
||||
- 支持多终端,比如可以方便地水平和纵向分屏,有tab等;
|
||||
- 方便配置字体颜色等;
|
||||
- 方便管理常用SSH的登录。
|
||||
|
||||
macOS系统自带的终端不太好用,常见的替代工具有iTerm2、Terminator、Hyper和Upterm。我平时使用的iTerm2,是一个免费软件,功能强大,具备上面提到的4个特征。下面我就以iTerm2为例展开介绍。其他几个工具上也有类似功能,所以你不必担心。
|
||||
|
||||
在多终端的场景方面,iTerm2支持多窗口、多标签页,同一窗口中可以进行多次水平和纵向分屏。这些操作以及窗口的跳转都有快捷键支持,你可以很方便地在网络上搜索到。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d6/bf/d63c4054e8e23136da22b1d9d6e4c2bf.png" alt="">
|
||||
|
||||
在管理常用SSH的登录方面,iTerm2使用Profile(用户画像)来控制。比如,下面是一个连接到远程服务器案例的录屏。
|
||||
|
||||
可以看到,在我的工作环境里常会用到4个Profile,其中有两个是连接到远端服务器的,包括Mosh Remote Server 1和SSH Remote Server 2。工作时,我使用Cmd+O,然后选择Server 1这个Profile,就可以打开一个新窗口,连接到这个远程服务器上。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ec/ae/ec60c3bf9bb2688ea59e76b1576078ae.gif" alt="">
|
||||
|
||||
每一个Profile都可以定义自己的字体、颜色、shell命令等。比如,Server 1是类生产服务器,我就把背景设置成了棕红色,提醒自己在这个机器上工作时一定要小心。所以在上面的录屏中你可以看到,连接到远端的SSH标签页,它的背景、标签页都是棕红色。另外,下面是如何对Profile颜色进行设置的截屏。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/54/58/54888d3d58612f9b557af6e45f40b758.png" alt="">
|
||||
|
||||
除了这些基础功能外,iTerm2还有很多贴心的设计。比如:
|
||||
|
||||
- **在屏幕中显示运行历史**(Cmd+Opt+B/F)。有些情况下,向上滚动终端并不能看到之前的历史,比如运行VIM或者Tmux的时候。这时,浏览显示历史就特别有用了。
|
||||
- **高亮显示当前编辑位置**,包括高亮显示当前行(Cmd+Opt+;)高亮显示光标所在位置(Cmd+/)。
|
||||
- **与上一次运行命令相关的操作**,包括显示上一次运行命令的地方(Cmd+Shift+up),选中上一个命令的输出(Cmd+Shift+A)。
|
||||
|
||||
其中第2、3项功能是由一组[macOS的集成工具](https://www.iterm2.com/documentation-utilities.html)提供的。这个工具集还包括显示图片的命令imgls、imgcat,显示自动补全命令,显示时间、注释,以及在主窗口旁显示额外信息等。这些设计虽然很小,但非常实用。
|
||||
|
||||
关于Windows系统,2019年5月微软推出了[Windows Terminal](https://devblogs.microsoft.com/commandline/introducing-windows-terminal/),支持多Tab,定制性很强,据说体验很不错。
|
||||
|
||||
选择好了终端,环境设置的第二步就是选择Shell。
|
||||
|
||||
## 第二步,选择Shell
|
||||
|
||||
在我看来,选择Shell主要有普遍性和易用性这两条原则。
|
||||
|
||||
Linux/Unix系统下,**Bash**最普遍、用户群最广,但是易用性不是很好。常用来替代Bash的工具有**Zsh**和**Fish**,它们的易用性都很好。下面是两张图片,用于展示Zsh和Fish在易用性方面的一些功能。
|
||||
|
||||
Zsh:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/31/a6/3198a7c972939d35de0c03bd26c4a8a6.gif" alt="">
|
||||
|
||||
Fish:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/08/32/082720f4dbe7e335a290938993d84732.gif" alt="">
|
||||
|
||||
我个人觉得Fish比Zsh更方便。事实上,Fish是Friendly Interactive Shell的简称。所以,交互是Fish的强项。可惜的是,Fish不严格遵循POSIX的语法,与Bash的脚本不兼容,而Zsh则兼容,所以我目前主要使用的是Zsh。
|
||||
|
||||
选好了模拟终端和Shell之后,便是配置环境的第三步,具体的Shell配置了。
|
||||
|
||||
## 第三步,具体的Shell配置
|
||||
|
||||
接下来,我以我自己使用的设置为例,向你介绍Bash、Zsh、Fish的具体配置吧。这里,主要包括**命令行提示符**的配置和其他配置两个方面。
|
||||
|
||||
之所以把命令行提示符单独提出来,是因为它一直展现在界面上,能提供很有用的价值,对命令行高效工作至关重要。下面是一张图片,展示了Bash、Zsh和Fish的命令行提示符。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cd/bf/cd10736ba6a970f9a7f8d2703a4b45bf.png" alt="">
|
||||
|
||||
这个窗口分为三部分,最上面是Bash,中间是Zsh,最下面是Fish,都配置了文件路径、Git信息和时间戳等信息。接下来,我带你一起看看这3个工具应该如何配置吧。
|
||||
|
||||
**Bash比较麻烦,**配置文件包括定义颜色和命令行提示符的两部分:
|
||||
|
||||
```
|
||||
## 文件 $HOME/.bash/term_colors,定义颜色
|
||||
# Basic aliases for bash terminal colors
|
||||
N="\[\033[0m\]" # unsets color to term's fg color
|
||||
|
||||
# regular colors
|
||||
K="\[\033[0;30m\]" # black
|
||||
R="\[\033[0;31m\]" # red
|
||||
G="\[\033[0;32m\]" # green
|
||||
Y="\[\033[0;33m\]" # yellow
|
||||
B="\[\033[0;34m\]" # blue
|
||||
M="\[\033[0;35m\]" # magenta
|
||||
C="\[\033[0;36m\]" # cyan
|
||||
W="\[\033[0;37m\]" # white
|
||||
|
||||
# empahsized (bolded) colors
|
||||
MK="\[\033[1;30m\]"
|
||||
MR="\[\033[1;31m\]"
|
||||
MG="\[\033[1;32m\]"
|
||||
MY="\[\033[1;33m\]"
|
||||
MB="\[\033[1;34m\]"
|
||||
MM="\[\033[1;35m\]"
|
||||
MC="\[\033[1;36m\]"
|
||||
MW="\[\033[1;37m\]"
|
||||
|
||||
# background colors
|
||||
BGK="\[\033[40m\]"
|
||||
BGR="\[\033[41m\]"
|
||||
BGG="\[\033[42m\]"
|
||||
BGY="\[\033[43m\]"
|
||||
BGB="\[\033[44m\]"
|
||||
BGM="\[\033[45m\]"
|
||||
BGC="\[\033[46m\]"
|
||||
BGW="\[\033[47m\]"
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
## 文件 $HOME/.bashrc,设置提示符及其解释
|
||||
###### PROMPT ######
|
||||
# Set up the prompt colors
|
||||
source $HOME/.bash/term_colors
|
||||
PROMPT_COLOR=$G
|
||||
if [ ${UID} -eq 0 ]; then
|
||||
PROMPT_COLOR=$R ### root is a red color prompt
|
||||
fi
|
||||
|
||||
#t Some good thing about this prompt:
|
||||
# (1) The time shows when each command was executed, when I get back to my terminal
|
||||
# (2) Git information really important for git users
|
||||
# (3) Prompt color is red if I'm root
|
||||
# (4) The last part of the prompt can copy/paste directly into an SCP command
|
||||
# (5) Color highlight out the current directory because it's important
|
||||
# (6) The export PS1 is simple to understand!
|
||||
# (7) If the prev command error codes, the prompt '>' turns red
|
||||
export PS1="\e[42m\t\e[m$N $W"'$(__git_ps1 "(%s) ")'"$N$PROMPT_COLOR\u@\H$N:$C\w$N\n\["'$CURSOR_COLOR'"\]>$W "
|
||||
export PROMPT_COMMAND='if [ $? -ne 0 ]; then CURSOR_COLOR=`echo -e "\033[0;31m"`; else CURSOR_COLOR=""; fi;'
|
||||
|
||||
```
|
||||
|
||||
命令行提示符之外的其他方面的配置,在Bash方面,我主要设置了一些命令行补全(completion)和别名设置(alias):
|
||||
|
||||
```
|
||||
## git alias
|
||||
alias g=git
|
||||
alias gro='git r origin/master'
|
||||
alias grio='git r -i origin/master'
|
||||
alias gric='git r --continue'
|
||||
alias gria='git r --abort'
|
||||
|
||||
|
||||
## ls aliases
|
||||
alias ls='ls -G'
|
||||
alias la='ls -la'
|
||||
alias ll='ls -l'
|
||||
|
||||
|
||||
## git completion,请参考https://github.com/git/git/blob/master/contrib/completion/git-completion.bash
|
||||
source ~/.git-completion.bash
|
||||
|
||||
```
|
||||
|
||||
**Zsh的配置就容易得多了,而且是模块化的**。基本上就是安装一个配置的框架,然后选择插件和主题即可。具体来说,我的Zsh命令行提示符配置步骤包括以下三步。
|
||||
|
||||
第一,[安装oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh)。这是一个对Zsh进行配置的常用开源框架。
|
||||
|
||||
```
|
||||
brew install zsh
|
||||
|
||||
```
|
||||
|
||||
第二,[安装powerline字体](http://powerline),供下一步使用。
|
||||
|
||||
```
|
||||
brew install powerlevel9k
|
||||
|
||||
```
|
||||
|
||||
第三,在~/.zshrc中配置ZSH_THEME,指定使用powerlevel9k这个主题。
|
||||
|
||||
```
|
||||
ZSH_THEME="powerlevel9k/powerlevel9k"
|
||||
|
||||
```
|
||||
|
||||
命令行提示符以外的其他配置,主要是通过安装和使用oh-my-zsh插件的方式来完成。下面是我使用的各种插件,供你参考。
|
||||
|
||||
```
|
||||
## 文件~/.zshrc.sh 中关oh-my-zsh的插件列表,具体插件细节请参考https://github.com/robbyrussell/oh-my-zsh,以及使用Web搜索查询
|
||||
plugins=(
|
||||
git
|
||||
z
|
||||
vi-mode
|
||||
zsh-syntax-highlighting
|
||||
zsh-autosuggestions
|
||||
osx
|
||||
colored-man-pages
|
||||
catimg
|
||||
web-search
|
||||
vscode
|
||||
docker
|
||||
docker-compose
|
||||
copydir
|
||||
copyfile
|
||||
npm
|
||||
yarn
|
||||
extract
|
||||
fzf-z
|
||||
)
|
||||
|
||||
source $ZSH/oh-my-zsh.sh
|
||||
|
||||
```
|
||||
|
||||
**至于Fish的配置,和Zsh差不多,也是安装一个配置的框架,然后选择插件和主题即可。**在配置命令行提示符时,主要步骤包括以下两步。
|
||||
|
||||
第一,安装配置管理框架oh-my-fish:
|
||||
|
||||
```
|
||||
curl -L https://get.oh-my.fish | fish
|
||||
|
||||
```
|
||||
|
||||
第二,查看、安装、使用oh-my-fish的某个主题,主题会自动配置好命令行提示符:
|
||||
|
||||
```
|
||||
omf theme
|
||||
omf install <theme>
|
||||
omf theme <theme>
|
||||
|
||||
## 我使用的是bobthefish主题
|
||||
omf theme bobthefish
|
||||
|
||||
```
|
||||
|
||||
这里有一篇不错的关于[使用oh-my-fish配置的文章](https://www.ostechnix.com/oh-fish-make-shell-beautiful/),供你参考。
|
||||
|
||||
Fish的其他方面的配置,也是使用oh-my-fish配置会比较方便。关于具体的配置方法,建议你参照[官方文档](https://github.com/oh-my-fish/oh-my-fish)。
|
||||
|
||||
关于环境的最后一个配置,是远程SSH的处理。
|
||||
|
||||
## 第四步,远程SSH的处理
|
||||
|
||||
SSH到其他机器,是开发人员的常见操作,最大的痛点是,怎样保持多次连接的持久性。也就是说,连接断开以后,远端的SSH进程被杀死,之前的工作记录、状态丢失,导致下一次连接进去需要重新设置,交易花销太大。有两类工具可以很好地解决这个问题。
|
||||
|
||||
**第一类工具是Tmux或者Screen**,这两个工具比较常见,用来管理一组窗口。
|
||||
|
||||
接下来,我以Tmux为例,与你描述其工作流程:首先SSH到远程服务器,然后用远程机器上的Tmux Client连接到已经运行的Tmux Session上。SSH断开之后,Tmux Client被杀死,但Tmux Session仍然保持运行,意味着命令的运行状态继续存在,下次SSH过去再使用Tmux Client连接即可。
|
||||
|
||||
如果你想深入了解Tmux的概念和搭建过程,可以参考[这篇文章](http://cenalulu.github.io/linux/tmux/)。
|
||||
|
||||
**第二类是一个保持连接不中断的工具,移动Shell**(Mobile Shell)。这也是我目前唯一见到的一个。这个工具是MIT做出来的,知道的人不多,是针对移动设备的网络经常断开设计的。
|
||||
|
||||
它的具体原理是,每次初始登录使用SSH,之后就不再使用SSH了,而是使用一个基于UDP的SSP协议,能够在网络断开重连的时候自动重新连接,所以从使用者的角度来看就像从来没有断开过一样。
|
||||
|
||||
接下来,**我以阿里云ECS主机、运行Ubuntu18.04为例,与你分享Mosh+Tumx的具体安装和设置方法。**
|
||||
|
||||
第一,服务器端安装并运行Mosh Server。
|
||||
|
||||
```
|
||||
junge@iZ4i3zrhuhpdbhZ:~$ sudo apt-get install mosh
|
||||
|
||||
```
|
||||
|
||||
第二,打开服务器上的UDP端口60000~61000。
|
||||
|
||||
```
|
||||
junge@iZ4i3zrhuhpdbhZ:~$ sudo ufw allow 60000:61000/udp
|
||||
|
||||
```
|
||||
|
||||
第三,在阿里云的Web界面上修改主机的安全组设置,允许UDP端口60000~61000。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/52/107e8927ab12b30699699db5ba54af52.jpg" alt="">
|
||||
|
||||
第四,在客户端(比如Mac上),安装Mosh Client。
|
||||
|
||||
```
|
||||
jasonge@Juns-MacBook-Pro-2@l$ brew install mosh
|
||||
|
||||
```
|
||||
|
||||
第五,客户端使用Mosh,用与SSH一样的命令行连接到服务器。
|
||||
|
||||
```
|
||||
jasonge@Juns-MacBook-Pro-2@l$ mosh junge@<server-ip-or-name>
|
||||
|
||||
```
|
||||
|
||||
下面这个录屏演示的是,我日常工作中使用Mosh + Tmux的流程。期间我会断开无线网,你可以看到Mosh自动连接上了,就好像来没有断过一样。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9b/69/9beb7e70191ccd475d530aa4a5654a69.gif" alt="">
|
||||
|
||||
## 小结
|
||||
|
||||
今天,我与你分享的是使用命令行工具工作时,涉及的环境配置问题。
|
||||
|
||||
首先,我与你介绍了选择模拟终端、选择和配置Shell的重要准则,并结合案例给出了具体的工具和配置方法,其中涉及的工具包括iTerm2、Bash、Zsh、Fish等。然后,我结合远程SSH这一常见工作场景,给出了使用Tmux和Mosh的优化建议。
|
||||
|
||||
掌握了关于环境配置的这些内容以后,在下一篇文章中,我将与你介绍具体命令行工具的选择和使用。
|
||||
|
||||
其实,我推荐开发者多使用命令行工具,并不是因为它们看起来炫酷,而是它们确实可以帮助我们节省时间、提高个人的研发效能。而高效使用命令行工具的前提,是配置好环境。
|
||||
|
||||
以Mosh为例,我最近经常会使用iPad SSH到远端服务器做一些开发工作。在这种移动开发的场景下,iPad的网络经常断开,每次重新连接开销太大,基本上没办法工作。于是,我最终发现了Mosh,并针对开发场景进行了设置。现在,每次我重新打开iPad的终端时,远程连接自动恢复,好像网络从没有断开过一样。这样一来,我就可以在移动端高效地开发了。
|
||||
|
||||
而对研发团队来说,如果能够对命令行工作环境进行优化和统一,毫无疑问会节省个人选择和配置工具的时间,进而提升团队的研发效能。
|
||||
|
||||
## 思考题
|
||||
|
||||
你觉得Tmux和Screen的最大区别是什么?是否有什么场景,我们必须使用其中的一个吗?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
338
极客时间专栏/研发效率破局之道/个人效能/28 | 从工作场景出发,寻找炫酷且有效的命令行工具.md
Normal file
338
极客时间专栏/研发效率破局之道/个人效能/28 | 从工作场景出发,寻找炫酷且有效的命令行工具.md
Normal file
@@ -0,0 +1,338 @@
|
||||
<audio id="audio" title="28 | 从工作场景出发,寻找炫酷且有效的命令行工具" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4e/e3/4e5e381a96163df2bba03076663bd7e3.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我继续和你分享命令行工具的使用。
|
||||
|
||||
在上一篇文章中,我与你介绍了命令行环境中的终端、Shell,以及远程连接的设置,解决了环境配置的问题。今天,我们再来看看具体的命令行工具的使用技巧。我会根据常见的工作场景来组织这些工具,因为优化工作流程、提高效率才是学习工具的真正目的。
|
||||
|
||||
从我的经验来看,开发人员最常见的、使用命令行的场景主要包括两个:
|
||||
|
||||
- 日常的操作,比如文件夹跳转、处理和搜索文件夹和文件内容、查看和管理系统信息;
|
||||
- 开发中常见的工作,比如Git的使用、API调试、查看日志和网络状况等。
|
||||
|
||||
我会重点与你分享这些场景中有哪些推荐工具以及使用技巧。而至于这些工具如何安装的内容,网络上已经有很多了,我就不再详细描述了。
|
||||
|
||||
## 日常操作中的工具和技巧
|
||||
|
||||
关于日常操作,Linux/Unix系统已经自带了一些工具,另外还有些产生已久、为我们所熟悉的工具。不过,要更高效地完成工作,我们还有更强大的工具可以选择。
|
||||
|
||||
### 第一个场景:列举文件夹和文件,查看文件
|
||||
|
||||
**列举文件**的默认工具是ls。除此之外,一个常用的工具是tree,可以列出文件夹的树形结构:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5c/3f/5cee9ed0d0fb2beb25961108acdf633f.png" alt="">
|
||||
|
||||
另外,还有些比tree更方便的工具,比如alder和exa。exa尤其好用,优点包括:
|
||||
|
||||
- 默认就有漂亮的颜色显示,并且不同种类文件颜色不同;
|
||||
- 可以像ls一样显示当前文件夹,也可以像tree一样显示树形结构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d6/3b/d667327bc4bedc74fb665a50d8aa423b.png" alt="">
|
||||
|
||||
另外,exa还支持对文件状态显示的加强。
|
||||
|
||||
比如,添加–git选项,exa会显示文件的git状态,在文件名的左边,用两个字母来表示文件在工作区和Git暂存区的状态。其中,N表示新文件,M表示文件修改等。exa的显示和git status命令输出的简单对比,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6a/a0/6a9969a87d53e4fc0a073f8918e72ba0.png" alt="">
|
||||
|
||||
再比如,使用–extend选项显示文件的额外信息。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cb/92/cb7d9a0bb21f0b740f6eee812f582392.png" alt="">
|
||||
|
||||
再比如,使用group-directories-first选项,先显示文件夹再显示文件。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/47/37/47474d86ceb15eabbf6e6b8a0866a537.png" alt="">
|
||||
|
||||
至于**查看文件**,Linux默认的工具是cat。相比起来,bat是一个更好用的替代品,除高亮显示外,还可以显示Git的更改状态。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f9/e4/f96d0ab6e8f12728c1d0e9ab2baa10e4.png" alt="">
|
||||
|
||||
### 第二个场景:查找并打开文件,进行查看和编辑
|
||||
|
||||
一个常用的办法是,使用上面提到的工具来列出文件名,然后使用grep进行过滤查看,或者VIM进行查看和编辑。比如,使用命令
|
||||
|
||||
```
|
||||
tree -I "node_modules" -f | grep -C3 index.md
|
||||
|
||||
```
|
||||
|
||||
可以得到当前文件夹中的所有index.md文件。
|
||||
|
||||
命令中,tree的参数-I,表示排除文件夹node_modules;tree的参数-f,表示显示文件时包含文件路径,方便你拷贝文件的全名;grep的参数-C3,代表显示搜索结果3行的上下文。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e1/54/e1611236c70cacc9193420afcab76154.png" alt="">
|
||||
|
||||
我们也可以使用VIM代替grep,进行更复杂的查找和编辑工作。我可以把tree的输出传给VIM,然后在VIM中查找index.md,使用n跳到下一个搜索结果,使用VIM命令gF直接打开文件,进行编辑后使用\bd命令关闭这个index.md文件。然后,用同样的方式查找并编辑第二、三、四个index.md,从而实现对当前文件夹下每一个index.md文件的查看和修改:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f4/f5/f409aa3e83ce0412b6275fd4c042eff5.gif" alt="">
|
||||
|
||||
事实上,这正是一个**很常见的命令行工作流**:把某一个命令的输出传给VIM,输出里包含有其他文件的完整路径,比如上面例子中index.md的路径,然后在VIM里使用gF命令查看并处理这些文件。我推荐你也尝试使用这种工作流。
|
||||
|
||||
另外,在上面的例子中,我使用tree命令来列举文件名。其实,很多时候我们**使用find这种专门用来查找文件的命令会更加方便**。不过,我今天要介绍的不是find,而是它的一个替代品,即fd。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/35/9d/354fc862f7145dd00f2ebda10500dc9d.png" alt="">
|
||||
|
||||
我推荐fd的原因主要有3个:
|
||||
|
||||
- 语法比find简单;
|
||||
- fd默认会忽略.gitignore文件里指定的文件;
|
||||
- 忽略隐藏文件。
|
||||
|
||||
后两点对开发者来说非常方便。比如,我在搜索时,并不关心node_modules里面的文件,也不关心.git文件夹里的文件,fd可以自动帮我过滤掉。
|
||||
|
||||
另外,fd高亮显示,速度也很快。至于对查找到的文件进行编辑,跟上面提到的方法一样,用管道(Pipe)传给VIM,然后使用gF命令即可。
|
||||
|
||||
```
|
||||
fd index.md | vim -
|
||||
|
||||
```
|
||||
|
||||
另外,关于查找文件内容的工具grep,我常用的一个替代品是RipGrep(rg)。跟fd类似,它也很适合开发者,有如下4个特点:
|
||||
|
||||
- 默认忽略.gitignore文件里指定的文件;
|
||||
- 默认忽略隐藏文件;
|
||||
- 默认递归搜索所有子目录;
|
||||
- 可以指定文件类型。
|
||||
|
||||
比如,使用rg tags就可以方便地查找当前目录下所有包含tags的文件。它的查找速度非常快,显示也比grep要漂亮:
|
||||
|
||||
```
|
||||
> rg tags
|
||||
package-lock.json
|
||||
2467: "common-tags": {
|
||||
2469: "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
|
||||
5306: "common-tags": "^1.4.0",
|
||||
5446: "common-tags": "^1.4.0",
|
||||
|
||||
src/pages/2019-05-24-procrastination/index.md
|
||||
6:tags: ['自我成长', '拖延症']
|
||||
|
||||
src/pages/2018-07-21-first-post/index.md
|
||||
5:tags: ['this', 'that']
|
||||
|
||||
```
|
||||
|
||||
### 第三个场景:文件夹之间跳转
|
||||
|
||||
关于文件夹间的跳转,在Bash中有cd和dirs命令;在Zsh和Fish中,可以使用文件夹名字直接跳转;另外,Zsh支持`..`、`...`和`-`等别名,用来分别跳转到父目录、父目录的父目录,以及目录历史中上一次记录,而不需要写cd。
|
||||
|
||||
接下来,我与你介绍**几个新的工具来支持更快的跳转**。
|
||||
|
||||
实际上文件夹的跳转,有两种常见的情况:
|
||||
|
||||
- 一种是,快速跳转到文件夹跳转历史中的某条记录,即之前曾经去过的某个文件夹;
|
||||
- 另一种是,快速找到当前文件夹中的某个子文件夹,并跳转过去。
|
||||
|
||||
对于第一种情况,常用的工具有两个,一个是fasd,另一个是z,它们差别不是特别大。我用的是z,具体用法是:z会按照访问频率列出最近访问过的文件夹,并使用字符串匹配的方式让你实现快速跳转。
|
||||
|
||||
比如,用z dem<tab>来进行匹配和自动补全,找到我想去的demo文件夹,回车直接完成跳转。同时,我也可以用z dem<回车> 直接跳转。</tab>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/65/3e/65630c3d48a262195d63fad17e4e133e.gif" alt="">
|
||||
|
||||
对于第二种情况,即快速定位某个子文件夹,我介绍一个**超级酷的工具fzf**。本质上讲,fzf是一个对输入进行交互的模糊查询工具。它的使用场景非常多,文件夹的跳转只是一个应用。所以,我还在再后面文章做更多的详细讨论。
|
||||
|
||||
安装好fzf之后,你就可以使用Ctrl+T进行文件夹的交互式查询,或者使用Alt+C进行文件夹跳转。
|
||||
|
||||
比如,我想跳转到src/component文件夹中,可以输入Alt+C,fzf就会列出当前文件夹下的所有文件夹。比如,我输入com,没输入其他字符,fzf会更新匹配到的文件夹,这时可以使用Ctrl+P、Ctrl+N进行上下选择,按下回车就可以进入选中的文件夹。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/cd/f3892b1c26be577cdf7b3993242830cd.gif" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/af/cd/af4e2d8ac7c21c48aaeb0c9c55dd4bcd.gif" alt="">
|
||||
|
||||
### 第四个场景:文件管理
|
||||
|
||||
系统自带的文件管理工具有cp、mv、rsync等。这里,我再介绍一些更方便的工具。
|
||||
|
||||
首先是一个用来**重命名文件的小工具,叫作vidir**。顾名思义,vidir就是VI来编辑目录的。具体使用方法很简单,vidir命令后面接一个文件夹时,vidir会打开VIM,VIM里面列举该文件夹中所包含的文件和子文件夹,然后使用VIM的命令来修改文件和文件夹的名字之后保存退出。这时,vidir会自动帮助我们完成对文件和文件夹的重命名。
|
||||
|
||||
vidir之所以使用VIM来修改文件,是因为VIM功能强大,修改非常方便。另外,vidir也可以从管道接收文件夹和文件的列表。比如,我想把当前文件夹下所有前缀为index的文件,都在文件名前添加“new-”。这时,我可以使用命令fd | vidir -。
|
||||
|
||||
这样,fd命令会把当前文件夹下所有文件名传给vidir。然后,vidir打开VIM,我们在VIM界面中修改文件名即可。如下所示的录屏图片中,包括了使用VIM的重复命令.的技巧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/21/2d/21ef569cff63ed639fed4a00968d6d2d.gif" alt="">
|
||||
|
||||
另外一组方便进行文件管理的工具是,**命令行的文件管理器**,即使用键盘命令在终端界面进行文件夹跳转、查看文件和移动文件等操作。这种命令行界面上的UI叫做TUI(Terminal UI)。十多年前的Borland终端IDE,就是这一类工具的翘楚,使用熟练之后效率会很高。
|
||||
|
||||
TUI的文件管理器我用过3个:Midnight Commander (以下简称mc)、Ranger和nnn。
|
||||
|
||||
mc是两个窗口的文件管理器。如果你使用过Windows Commander(Total Commander)的话,你就会对它的用法很熟悉。重要的命令有:使用tab进行两个窗口的切换、使用F4进行编辑、使用F5进行拷贝、使用F9进入菜单、使用F10退出。
|
||||
|
||||
我提供了一张录屏图片,简单演示了在一台远端服务器上使用mc进行多文件拷贝和编辑,并通过菜单修改显示主题的场景。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bb/3a/bb49eec06b2b1293e1d2c0620c81fe3a.gif" alt="">
|
||||
|
||||
Ranger和nnn是单窗口的文件管理器。Ranger稍微有一点延迟,所以我一般使用nnn。因为是单窗口,所以与我们平时在GUI中使用的文件管理器比较相似。
|
||||
|
||||
比如,在拷贝文件的时候,需要先进入文件所在文件夹,选择文件,然后进入目标文件夹,再使用拷贝命令把文件拷贝过去。我在录屏中演示了在nnn中进行文件夹的跳转、创建,文件的选择、拷贝,使用系统工具打开当前文件,查看帮助等功能。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/26/48/261bf3fae35b5899ca66aa5e44a39648.gif" alt="">
|
||||
|
||||
总的来说,这3个工具中我使用最多的是nnn。跟mc相比,它最大的好处是快捷键设置跟VIM一致,不需要大量使用功能键F1~F12。
|
||||
|
||||
## 开发中常见的工作
|
||||
|
||||
### Git
|
||||
|
||||
命令行中的Git工具,除了原生的Git之外,常见的还有tig、grv、lazygit和gitin。
|
||||
|
||||
我常用的是tig。因为在tig中,我可以方便地进行查看改动、产生提交、查看历史(blame)等操作,功能非常强大。比如,在查看文件改动时,我们可以方便地使用命令1有选择性地把一个文件中改动的一部分添加到一个提交当中,实现[第26篇文章](https://time.geekbang.org/column/article/154378)中提到的git add -p的功能。
|
||||
|
||||
另外,我还可以通过tig快捷地查看一个文件的历史信息。
|
||||
|
||||
关于这两个功能的使用,你可以参考下面的录屏图片。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/70/d0/7007909425230ece1fac2726d610d0d0.gif" alt="">
|
||||
|
||||
### Web 访问
|
||||
|
||||
我常用的Web访问工具是HTTPie,是curl命令的一个补充。HTTPie的强项在于,专门针对HTTP协议,所以可以做到格式简单、易用性强两点。
|
||||
|
||||
而curl的优势,则包括功能强大、支持多种协议和基本所有服务器上都有预装。
|
||||
|
||||
关于这两个工具,我的建议是,curl肯定要学,HTTPie如果用得到也值得花时间学习。
|
||||
|
||||
### 对JSON进行处理
|
||||
|
||||
在命令行对JSON文本进行处理,最常见的工具是jq。它能够对JSON进行查询和修改处理,功能很强大。
|
||||
|
||||
举一个查询的例子,我们有这样一个person.json文件列举某个人的详细信息:
|
||||
|
||||
```
|
||||
$ cat person.json
|
||||
{ "id": { "bioguide": "E000295", "thomas": "02283", "fec": [ "S4IA00129" ], "govtrack": 412667, "opensecrets": "N00035483", "lis": "S376" }, "name": { "first": "Joni", "last": "Ernst", "official_full": "Joni Ernst" }, "bio": { "gender": "F", "birthday": "1970-07-01" }, "terms": [ { "type": "sen", "start": "2015-01-06", "end": "2021-01-03", "state": "IA", "class": 2, "state_rank": "junior", "party": "Republican", "url": "http://www.ernst.senate.gov", "address": "825 B&C Hart Senate Office Building Washington DC 20510", "office": "825 B&c Hart Senate Office Building", "phone": "202-224-3254" } ] }
|
||||
|
||||
```
|
||||
|
||||
可以方便地使用cat person.json | jq .”对JSON进行格式化输出,
|
||||
|
||||
```
|
||||
$ cat people.json | jq .
|
||||
{
|
||||
"id": {
|
||||
"bioguide": "E000295",
|
||||
"thomas": "02283",
|
||||
"fec": [
|
||||
"S4IA00129"
|
||||
],
|
||||
"govtrack": 412667,
|
||||
"opensecrets": "N00035483",
|
||||
"lis": "S376"
|
||||
},
|
||||
"name": {
|
||||
"first": "Joni",
|
||||
"last": "Ernst",
|
||||
"official_full": "Joni Ernst"
|
||||
},
|
||||
"bio": {
|
||||
"gender": "F",
|
||||
"birthday": "1970-07-01"
|
||||
},
|
||||
"terms": [
|
||||
{
|
||||
"type": "sen",
|
||||
"start": "2015-01-06",
|
||||
"end": "2021-01-03",
|
||||
"state": "IA",
|
||||
"class": 2,
|
||||
"state_rank": "junior",
|
||||
"party": "Republican",
|
||||
"url": "http://www.ernst.senate.gov",
|
||||
"address": "825 B&C Hart Senate Office Building Washington DC 20510",
|
||||
"office": "825 B&c Hart Senate Office Building",
|
||||
"phone": "202-224-3254"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
以及使用jq ".terms[0].office"命令查询他的第一个工作任期的办公室地址。
|
||||
|
||||
```
|
||||
$ cat person.json | jq ".terms[0].office"
|
||||
"825 B&c Hart Senate Office Building"
|
||||
|
||||
```
|
||||
|
||||
但,jq存在的最大问题是,它有一套自己的查询处理语言。如果使用jq的频次没那么高的话,很难记住,每次都要去查帮助才可以。
|
||||
|
||||
针对这种情况,有人设计了另一种类似的工具,直接使用JavaScript作为查询处理语言,典型代表是fx和jq.node。这,就大大方便了使用JavaScript的开发者,因为可以使用已经熟悉了的语法。
|
||||
|
||||
比如,对于上个案例的JSON文件,我可以方便地在fx工具中使用JavaScript的函数filter()进行过滤查询。
|
||||
|
||||
```
|
||||
$ cat person-raw.json| fx 'json => json.terms.filter(x => x.type == "top")'
|
||||
[
|
||||
{
|
||||
"type": "top",
|
||||
"office": "333 B&c Hart CIrcle Building",
|
||||
"phone": "202-224-3254"
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
### 查找、关闭进程
|
||||
|
||||
通常情况下,我们使用kill和pkill,来查找和关闭进程。但,使用fzf之后,我们可以方便地进行交互式的查找目标进程。具体使用方法是,输入kill <tab>,fzf就会提供一个交互式的界面供你查找目标进程,然后回车确认即可。</tab>
|
||||
|
||||
在命令行上进行交互式的操作,非常爽,我推荐你一定要试试。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7d/0c/7de54d49ff68186ee6002bc842e95d0c.gif" alt="">
|
||||
|
||||
### 查看日志文件
|
||||
|
||||
关于查看日志文件的工具,我推荐lnav。它比tail -F要方便、强大得多,有很多很棒的功能,包括:
|
||||
|
||||
- 支持很多日志格式,比如syslog、sudo、uWSGI等,并可以根据格式高亮显示;
|
||||
- 支持多个日志同时显示,并用不同颜色区分;
|
||||
- 支持正则表达式进行过滤等。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/52/86/52caa8ef22d6b8b82ce721588a9ac886.png" alt="">
|
||||
|
||||
### 命令行本身的实用技巧
|
||||
|
||||
关于命令行的使用技巧,有两个非常值得一提:一个是!$,另一个是!!。
|
||||
|
||||
**第一个!$**,代表上一个命令行的最后一个参数。比如,如果我上一条命令使用
|
||||
|
||||
```
|
||||
$ vim src/component/README.txt
|
||||
|
||||
```
|
||||
|
||||
下一步我想为它产生一个备份文件,就可以使用!$:
|
||||
|
||||
```
|
||||
## 以下命令即 copy vim src/component/README.txt vim src/component/README.txt.bak
|
||||
$ cp !$ !$.bak
|
||||
|
||||
```
|
||||
|
||||
**第二个常用的是!!**,表示上一个命令的完整命令。最常用的场景是,我先拷贝一个文件,发现没有权限,需要sudo,下一步我就可以用sudo !!来用sudo再次运行拷贝命令。
|
||||
|
||||
```
|
||||
## 因为权限不足,命令失败
|
||||
$ cp newtool /usr/local/bin/
|
||||
|
||||
## 重新使用sudo运行上一条命令。即 sudo wtool /usr/local/bin/
|
||||
$ sudo !!
|
||||
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
今天,我与你介绍了很多工具。使用工具提高研发效能,最关键的是找到真正常用的工作场景,然后去寻找对应的工具来提高效率。
|
||||
|
||||
需要注意的是,只有重复性高的工作,才最适合使用命令行工具;否则,用来适应工具的时间,可能比节省下的时间还要多。这,是命令行的一个基本特点。
|
||||
|
||||
最后,我把今天与你讨论的Linux/Unix系统自带工具和替代工具,整理为了一张表格,以方便你复习:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/46/80/4649bffbfd17cb90d217a25d0382f980.jpg" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
不知道你有没有注意到,在录屏中我多次用到了一个叫作tldr的工具。你知道它是什么作用吗?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
250
极客时间专栏/研发效率破局之道/个人效能/29 | 1+1>2,灵活的工具组合及环境让你的工作效率翻倍.md
Normal file
250
极客时间专栏/研发效率破局之道/个人效能/29 | 1+1>2,灵活的工具组合及环境让你的工作效率翻倍.md
Normal file
@@ -0,0 +1,250 @@
|
||||
<audio id="audio" title="29 | 1+1>2,灵活的工具组合及环境让你的工作效率翻倍" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d6/33/d6814fc2f97bc90572b604611e2aca33.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我们来聊一聊工具的组合和环境。
|
||||
|
||||
在前面几篇文章,我与你介绍了很多工具,有开发工具,也有跟开发不直接相关的工具。毫无疑问,这些工具都很好用。但,如何配置这些工具,才能真正发挥它们的价值,提高我们的研发效能呢?
|
||||
|
||||
我们都很清楚,工具的使用离不开具体的工作环境。如果我们在环境中使用的各个工具是割裂开的话,不仅会提高我们的学习成本、记忆成本,还会有工具间交互的衔接问题。所以,用好这些工具,我们还需要做两件事:
|
||||
|
||||
- 尽量把工具无缝集成,解决工具切换不顺畅的问题;
|
||||
- 减少并优化常用的工作入口,从而提高工具一致性,降低使用多个工具时的心智负担。
|
||||
|
||||
只有这样,我们才能把工具配置成一套好的环境,真正聚焦在产生价值的工作上,发挥工具提升研发效能的作用,实现1+1>2的效果。
|
||||
|
||||
所以接下来,我会从工具集成和提高工具一致性两个方面,与你介绍如何把多个工具组合成为高效的工具环境。
|
||||
|
||||
## 工具的集成
|
||||
|
||||
工具的集成,最值得优化的情况包括两种:一是,使用管道(Pipe)对命令行工具进行集成;二是,对集成开发工具环境(IDE)进行配置,让IDE和周边工具集成。
|
||||
|
||||
### 使用管道(Pipe)对命令行工具进行集成
|
||||
|
||||
其实,我在前面的文章中已经使用过管道很多次了,只是使用场景比较简单而已。
|
||||
|
||||
比如,在[第24篇文章](https://time.geekbang.org/column/article/150779)中,我使用命令curl -s <github_url> | vim -,来查看GitHub上某个用户的代码仓情况,通过管道把curl命令的输出传给VIM,以方便我在VIM中查看较长的输出。
|
||||
|
||||
```
|
||||
> curl -s https://api.github.com/users/jungejason/repos | vim -
|
||||
Vim: Reading from stdin...
|
||||
|
||||
## 进入VIM窗口,显示所有repo的array
|
||||
[
|
||||
{
|
||||
"id": 213849635,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkyMTM4NDk2MzU=",
|
||||
"name": "counter-redux-sample",
|
||||
"full_name": "jungejason/counter-redux-sample",
|
||||
"private": false,
|
||||
"owner": {
|
||||
"login": "jungejason",
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
这个案例使用了一次管道,比较简单。
|
||||
|
||||
而高效使用管道,首先是**使用多个管道灵活地把多个工具连接起来**。一个常见的场景是,grep和xargs命令的组合。比如,ps aux | grep vim | grep -v grep | awk ‘{print $2}’ 可以找到我当前运行的VIM程序并将其关闭。
|
||||
|
||||
```
|
||||
ps aux | grep vim | grep -v grep | awk '{print $2}' | xargs kill
|
||||
|
||||
```
|
||||
|
||||
整个管道链是这样工作的:
|
||||
|
||||
- ps aux打印所有运行程序,每行打印一个。
|
||||
- grep vim显示包含vim的那些行。
|
||||
- grep -v grep表示过滤掉包含grep的那一行。这是因为,上面一步grep vim命令中包含“vim”字样,所以它也会被ps aux显示出来,我们需要将其过滤掉。
|
||||
- awk ‘{print $2}’ 表示抽取出输出中的PID,也就是进程ID。
|
||||
- xargs kill 接收上一步传过来的PID,组成命令kill <pid>并执行,杀死vim进程。</pid>
|
||||
|
||||
上面管道每一步的执行输出,如下所示:
|
||||
|
||||
```
|
||||
> ps aux
|
||||
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
|
||||
_windowserver 228 14.9 2.3 13044220 382952 ?? Ss 9Oct19 679:18.68 /System/Library/PrivateFrameworks/SkyLight.framewo
|
||||
jasonge 49393 14.4 2.1 5586816 356860 ?? S Sun12AM 583:37.42 /Applications/XMind.localized/XMind.app/Contents/F
|
||||
jasonge 49371 9.7 0.4 4826572 64204 ?? S Sat11PM 167:30.01 /Applications/XMind.localized/XMind.app/Contents/F
|
||||
...
|
||||
|
||||
> ps aux | grep vim
|
||||
jasonge 31383 0.0 0.0 4310340 4740 s001 S+ 9:57AM 0:00.11 vim
|
||||
jasonge 32304 0.0 0.0 4268056 704 s004 R+ 10:17AM 0:00.00 grep vim
|
||||
|
||||
> ps aux | grep vim | grep -v grep
|
||||
jasonge 31383 0.0 0.0 4310340 4740 s001 S+ 9:57AM 0:00.11 vim
|
||||
|
||||
> ps aux | grep vim | grep -v grep | awk '{print $2}'
|
||||
31383
|
||||
|
||||
> ps aux | grep vim | grep -v grep | awk '{print $2}' | xargs kill
|
||||
|
||||
```
|
||||
|
||||
这是使用多个管道的一个具体例子。在工作中,你可以根据实际情况,使用管道来封装常用的工具。
|
||||
|
||||
**在命令行中高效使用管道的第二个重要方法,是使用模糊查询工具(Fuzzy Finder)。**
|
||||
|
||||
常见的模糊查询工具,有 fzf、pick、selecta、ctrlp和fzy等。其中,最有名的应该就是fzf(你可以再回顾下[第28篇文章](https://time.geekbang.org/column/article/156024)中关于这个工具的介绍)。
|
||||
|
||||
在管道中使用模糊查询工具,可以大幅度提高使用体验。
|
||||
|
||||
在开发工作中,我们常常需要对文本进行搜索和过滤,一般会使用grep来实现。这个操作虽然简单,但频率非常高,很值得优化。比如,上面提到的杀死vim进程的操作。我们使用ps aux命令列举出所有进程之后,需要使用多个grep命令来进行搜索过滤,最终查找到我们需要的那一行。
|
||||
|
||||
在这个操作中,grep过滤命令常常会比较复杂,为了达到正确的过滤效果,我们可能要调试多次才能找到正确的grep命令。另外,还可能会出现比较难以使用grep过滤的情况,比如有多个vim进程同时打开时,使用grep选出其中一个就不是很方便。
|
||||
|
||||
这时,使用模糊查询工具就很方便了。在我看来,**模糊查询工具的本质,就是交互式的文本过滤工具**。它接收文本,然后提供界面让用户输入查询条件,并在用户输入的同时实时过滤。当用户找到需要的结果回车确认后,输出结果文本,使用起来很自然。
|
||||
|
||||
以杀死vim进程的操作为例,我可以使用ps aux | fzf命令把进程列表发送给fzf,fzf首先会列举出所有进程供我搜索过滤。随着我的输入,fzf进行实时过滤,去除不符合条件的那些行。在我输入了’vim后,就只剩下几行结果了。这时,我可以使用上下键进行选择,回车之后fzf就会把这一行内容输出到stdout中。
|
||||
|
||||
可以看到,整个过程中我可以实时调整搜索条件,免去了使用grep需要多次调整参数的过程,使用起来非常便捷。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/15/2b/158b70bfdde5ab3b1b940b1abe21932b.gif" alt="">
|
||||
|
||||
当然,除了把过滤结果输出到stdout外,也可以通过管道把输出传给其他工具,比如传给awk和kill,来实现杀死vim进程的目的,具体的命令是ps aux | fzf | awk ‘{print $2}’ | xargs kill。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/56/6a/561acdf6734212a36edd91295a00876a.gif" alt="">
|
||||
|
||||
可以看到,这个命令和前面使用grep的命令差不多,唯一区别是把之前命令中的两个grep替换成了fzf。但正是这个区别,把中间的非交互文本过滤变成了交互式过滤,效果非常好。
|
||||
|
||||
实际上,你还可以更进一步,把这个使用fzf的命令保存为一个shell脚本,成为一个交互式的kill命令。如下所示:
|
||||
|
||||
```
|
||||
#!/bin/bash
|
||||
|
||||
ps aux | eval "fzf" | awk '{print $2}' | xargs kill
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/59/da/59ce8d67bffa2e15e68665399511bdda.gif" alt="">
|
||||
|
||||
从非交互文本过滤到交互文本过滤的简单转变,着实可以为我们带来意想不到的巨大方便。比如:
|
||||
|
||||
- 在切换工作路径时,可以用find命令列举出当前文件夹的所有子文件夹,然后进行交互式的过滤,最后切换路径。
|
||||
- 在使用apt或者brew安装软件包时,可以用来过滤可用软件包,方便软件包的选择。
|
||||
|
||||
它甚至可以与一些网站服务对接,比如这个使用[fzf和caniuse服务集成](https://sidneyliebrand.io/blog/combining-caniuse-with-fzf)的例子,就很酷。此外,[fzf官网上](https://github.com/junegunn/fzf/wiki/examples)还有很多类似的脚本例子,推荐你去看看。
|
||||
|
||||
### IDE和周边工具集成
|
||||
|
||||
我们的目标是能在IDE中进行常见的软件研发活动,从而减少工具的切换。
|
||||
|
||||
具体来说,基本的IDE集成功能包括:
|
||||
|
||||
- 编码;
|
||||
- 构建;
|
||||
- 实时检查语法错误;
|
||||
- 实时检查编码规格;
|
||||
- 运行单元测试,并在测试输出中点击文件名进行跳转;
|
||||
- 在本地运行服务,并可以设置断点进行单步调试;
|
||||
- 连接远程服务进行单步调试。
|
||||
|
||||
这些功能比较常见,你可以根据自己使用的IDE在网上搜索相关资料,我就不再赘述了。接下来,我会重点与你分享些不是那么传统,但非常有效的集成功能。
|
||||
|
||||
**首先,是IDE跟命令行工具的集成。**
|
||||
|
||||
命令行里有很多的工具,所以IDE只要集成了命令行终端,就可以把它们一次性都集成了进来,所以效果非常好。
|
||||
|
||||
这里的集成有两个层次:
|
||||
|
||||
- 第一个层次是,终端窗口成了IDE应用的一个子窗口,这样使用比独立的终端窗口使用起来更方便。比如,可以使用快捷键方便地打开、关闭集成终端窗口。方便窗口管理。
|
||||
- 第二个层次是,终端子窗口和IDE其他部分的一些交互。这个层次更深,也更重要,可以让我们更好地在IDE中使用命令行工具,是我们要留意的。我**推荐你在你所用的IDE文档中,查看有哪些第二个层次的集成**。
|
||||
|
||||
以VS Code为例,属于第二层次的集成有以下四种。
|
||||
|
||||
**第一种**,在IDE中拖动文件夹或者文件到集成终端窗口中,文件夹或文件的完整路径名会自动复制到终端里。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ba/a7/bac1f0bfbf2b446c164bcc65e4877ea7.gif" alt="">
|
||||
|
||||
第二种,在集成终端中运行当前编辑窗口中打开的文件,也可以在编辑窗口中选中一段文本直接在终端运行。方法是运行命令Terminal: Run Active File in Active Terminal,或者是Terminal: Run Selected Text in Active Terminal。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ff/26/ff8f05f311750112deb548266f452d26.gif" alt="">
|
||||
|
||||
第三种,集成终端中的命令输出,如果包含文件名,可以用Cmd+鼠标点击,直接在编辑器中打开这个文件。这个操作我经常用,比如搜索文件时,因为我对fd和rg命令很熟悉,所以有时会直接使用fd + rg的组合,而不是用VS Code的原生文件查找功能了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bd/67/bdf559052af53c995433c487ed0f2767.gif" alt="">
|
||||
|
||||
第四种,可以安装针对VS Code的命令行工具。比如,使用了[oh-my-zsh中的vscode插件](https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/vscode)后,我们可以在集成终端中使用vscr在当前VS Code中打开文件,用vscd进行文件比较等工作,实现了终端和IDE的更紧密集成。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0d/25/0da8a4c117fe709fc7462134a0171725.gif" alt="">
|
||||
|
||||
**其次,是IDE和代码仓的集成,包括Gist、GitHub Pull、Git Graph。**
|
||||
|
||||
在IDE中进行代码仓的操作集成,我个人是比较谨慎的态度。我会比较多地使用命令行的Git,因为我认为目前还没有GUI工具能暴露Git的全面功能。在IDE中,我只选择最适合在图形界面中使用的一些场景。
|
||||
|
||||
比如,在VS Code中,我只使用了3个插件:
|
||||
|
||||
- [Gist](https://marketplace.visualstudio.com/items?itemName=kenhowardpdx.vscode-gist),方便在编辑器中直接上传文件。
|
||||
- [GitHub Pull Request](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github),方便管理GitHub上的PR处理,可以直接在VSCode里查看和进行讨论,非常方便。
|
||||
- [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph),用于查看历史。它可以显示提交历史的图结构,点击提交可以直接查看文件,比命令行工具更快捷。
|
||||
|
||||
如果你想使用这几个插件,可以参考它们的官方文档。
|
||||
|
||||
以上就是工具集成方面的内容,接下来我们来看看如何提高工具一致性。
|
||||
|
||||
## 提高工具一致性
|
||||
|
||||
提高工具的一致性,主要方法是减少常用的工作入口,并对这些入口进行优化。
|
||||
|
||||
工具太多容易混乱,但如果能控制入口数量,也就是说进行工作时,只从几个有限的入口开始操作,同时对这些入口进行优化,就能提升工具使用体验的一致性,降低使用工具带来的负担。
|
||||
|
||||
要减少并优化常用的工作入口,那我们首先需要明确**经常使用的、必要的工作入口**。
|
||||
|
||||
在我看来, 命令行、IDE、桌面快捷启动工具(Launcher)和浏览器这4个工作入口非常必要,是值得重视并优化的。
|
||||
|
||||
### 1. 命令行
|
||||
|
||||
我比较喜欢命令行的一个重要原因是,它是一系列工具的共同入口。绝大部分命令行的命令都遵循一定的规律,又有统一的man命令查看手册,很容易找到使用方法。
|
||||
|
||||
使用命令行减少工作入口还有一个办法,是使用统一的客户端工具进行多种操作。比如,Git命令带有很多子命令(比如,log、diff、show等),从而一个Git命令行工具可以完成很多工作。
|
||||
|
||||
在Facebook,很多工作都通过Arcanist(Phabricator 的命令行工具)的各种子命令来完成。这样就实现了命令使用的一致性,降低了学习成本。
|
||||
|
||||
在日常工作中,你也可以尝试开发一个命令行工具,对常用工作进行封装。之前我在Stand的时候,就使用Node.js的[c](https://www.npmjs.com/package/commander)[ommander](https://www.npmjs.com/package/commander)模块,对日常研发工作进行了封装。
|
||||
|
||||
### 2. IDE
|
||||
|
||||
上面已经提过,IDE是一个重要入口,我们应该把较多的开发工作集中到IDE中直接进行。
|
||||
|
||||
### 3. 桌面快捷启动工具
|
||||
|
||||
桌面快捷启动工具,也是一个非常重要的入口,我们可以通过它进行很多工作。以Mac上非常有名的Alfred来说,你可以用它来启动程序、切换程序、搜索文件、进行网页搜索、管理系统剪贴板历史、进行计算、运行系统命令(比如,重启机器、锁定机器、关闭特定程序、关闭所有程序等)等操作。
|
||||
|
||||
用好这个工具,可以大大提高工具使用的一致性。
|
||||
|
||||
### 4. 浏览器
|
||||
|
||||
现在,我们会大量使用网站应用,那浏览器是必不可少的入口。你可能会觉得,在浏览器中不就是使用URL吗,还能怎么提高呢?
|
||||
|
||||
这里,我就和你分享几种方法吧。
|
||||
|
||||
其实,我们比较熟悉的书签功能,就是对在浏览器中使用网页应用的一个优化。因为它可以记录常用的工具,你不用每次都输入。
|
||||
|
||||
此外,还有一种你可能不太熟悉但非常有效的办法,就是自己运行一个搜索引擎,并把它设为浏览器的默认搜索。这样,你就可以在你的搜索引擎中定义规则,并在浏览器的地址栏输入符合规则的操作。
|
||||
|
||||
比如,你可以在搜索引擎后端中实现对“t <task-id>”的解析,用来打开任务系统中序号为task-id的任务。当你在浏览器地址栏中输入“t 123"的时候,搜索引擎自动解析出任务ID 123,然后跳转到这个任务的URL,比如http://tasktool/123.html。当然这个任务工具可能是Trello,也可能是Jira。</task-id>
|
||||
|
||||
在Facebook内部,就有一个这样的公用引擎,支持快捷跳转到公司几乎所有的网站中,同时还可以使用它直接进行各种搜索工作,比如内部网页搜索、wiki搜索等,极大提高了公司各种网站应用使用时的一致性。Facebook开源了这个搜索引擎框架,叫作[Bunny1](https://github.com/ccheever/bunny1/)。
|
||||
|
||||
事实上,这种浏览器使用方式相当于把浏览器的地址栏变成了一个命令行入口。因为搜索引擎后端就是一个通用的后端服务,可以实现各种各样的强大功能,所以你可以自己做一个这样的搜索引擎,在浏览器地址栏里充分发挥想象力。
|
||||
|
||||
## 小结
|
||||
|
||||
只有把工具放到具体的工作环境中,才能发挥它们的真实价值。
|
||||
|
||||
今天,我从两个方面与你介绍了怎样把多个工具配置成为高效的工具环境。一是,如何对工具进行集成,减少工具切换时的不顺畅,提高个人的研发效能。二是,如何减少并优化工具的入口,从而提高工具使用体验的一致性,以降低工具使用成本,提高研发效能。
|
||||
|
||||
其实,工具环境的配置,是一个需要不断摸索,去寻找最佳平衡点的过程。比如,在IDE中进行集成,应该集成最常用的功能,而不应该尝试把所有的功能都使用插件集成到IDE中。否则,IDE就会变得臃肿不堪,性能下降。
|
||||
|
||||
我推荐的办法是采用80/20法则。也就是,集中精力对工作中最常用的20%的操作,进行集成和优化。从我个人的体验来看,这样的平衡点是比较合适的。
|
||||
|
||||
总之,我们时刻要牢记,配置环境的目的是,配置出一个强大而灵活的环境,让一切工作流畅,从而提高生产效率,一定要注意投入产出比。
|
||||
|
||||
## 思考题
|
||||
|
||||
在浏览器作为工作入口的例子中,如果一个任务描述搜索到的任务不止一条时,我们可以怎么处理呢?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
80
极客时间专栏/研发效率破局之道/个人效能/30 | 答疑篇:关于价值导向和沟通.md
Normal file
80
极客时间专栏/研发效率破局之道/个人效能/30 | 答疑篇:关于价值导向和沟通.md
Normal file
@@ -0,0 +1,80 @@
|
||||
<audio id="audio" title="30 | 答疑篇:关于价值导向和沟通" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2b/1d/2b979dff7ac74c92c0b4fd160b488c1d.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。今天,我来针对“个人效能”模块的留言问题,和你做一次展开吧。
|
||||
|
||||
在“个人效能”模块的文章中,很多同学留下了精彩留言,阐述了自己对个人效能的认识、遇到的问题以及解决方案。看得出来,大家平时都对提高个人效能有着比较深入的思考。
|
||||
|
||||
其中,尤其让我印象深刻的有@我来也 对各种命令行工具的讨论、@Johnson 对编辑器的讨论,以及@Jxin 对价值和目标的思考,真的是非常精彩。
|
||||
|
||||
谢谢你们的留言!我很喜欢与你们交流的过程,所以会详细回答你们的每个问题。今天,我从留言的许多问题中,梳理出了价值导向和高效沟通这两个话题,再与你详细聊一聊。
|
||||
|
||||
## 工作中的价值导向
|
||||
|
||||
在第21篇文章“[高效工作:Facebook的10x程序员效率心法](https://time.geekbang.org/column/article/148170)”中,@Jxin同学与我展开了关于价值取向的一些讨论。从这个专栏的一开始,我就强调了目标的重要性,**研发效能三要素中的第一条,就是准确性**。我们只有先找对目标,后续的行动才会有意义。
|
||||
|
||||
所以,在工作中,我们需要考虑以什么目标作为最根本的出发点,是职业发展、薪酬、工作生活平衡、工作稳定性,还是实现价值?具体怎么选择,就要看你个人认可的价值到底是什么了。
|
||||
|
||||
比如,@Jxin提出以实现价值为出发点,尽量把企业价值和个人价值的发展摆到一条线,能够做兼顾两者利益的任务。如果实在不能统一,就要多考虑些公司的发展,少考虑些个人短期利益的追求。对此,我很是赞同。
|
||||
|
||||
又比如,十多年前我在西雅图微软工作的时候,有一个朋友特别看重工作生活平衡和工作稳定性。刚毕业,她就在寻找一个有稳定工作机会的方向,后来选定了数据库方向。她认为这个方面的工作会长期稳定,于是在这个方向做到专精。事实证明,她的选择到现在看还是很不错。不用学习太多的新东西,仍然能够有稳定的工作。
|
||||
|
||||
虽然我个人更看重不断接触新领域,不断提高、发展自己,和她的价值取向不太一致,但我觉得她的做法也非常棒。她能明确自己想要追求的东西,于人于己都有好处,还能找到具体的实施路径,非常棒!
|
||||
|
||||
所以我建议,**平时多考虑下自己最在意的到底是什么**。这样工作起来才能有的放矢,把时间花在对自己最有价值的事情上。
|
||||
|
||||
有些时候,找到自己最在意的事儿并不容易,将其实现,则常常更有挑战性。所以,我再向你推荐一个提高实现目标效率和过滤不现实目标的方法:WOOP。WOOP分别是Wish(愿望)、Outcome(效果)、Obstacle(障碍)和Planning(计划)四个单词的首字母。
|
||||
|
||||
**使用WOOP的具体方法是**,抽出你不会被干扰的5分钟时间,针对WOOP的4个组成部分进行冥想。
|
||||
|
||||
第一步是Wish,也就是认真考虑我到底希望得到什么,希望实现什么?这里的“希望”,既可以是长期的希望,比如5年、10年,也可以是短期的希望,比如一个月,或者就是今天。专注于一个愿望,并用SMART原则来具体描述它。SMART是一个目标管理工具,我在下一篇文章中会详细展开,你可以先参考下“[SMART 原则以及实际案例](https://blog.csdn.net/limuzi13/article/details/52245983)”这篇文章。
|
||||
|
||||
第二步是Outcome,也就是愿望实现之后,能够达到哪些好结果。在大脑中形象地描绘并体验实现了这个愿望之后,我的生活能有哪些正向改变。有研究证明,这种形象化考虑正面结果的办法,可以大幅提高后续执行计划的积极性和效率。
|
||||
|
||||
第三步是Obstacle,也就是实现这个愿望时可能有哪些障碍。在这一步,也要形象地描绘2~3个最主要障碍的具体细节。静下心来思考的时候,常常会发现平时的一些习惯,其实是实现这些愿望的重要障碍。这一步可以让我们在后续执行计划时,更敏锐地觉察到障碍的出现,从而更好地处理它们。
|
||||
|
||||
第四步是Planning,也就是针对每一个具体可能出现的障碍,计划用什么办法去解决它。我们可以使用if-then的结构来处理,即if出现障碍A,then使用计划B来解决。同样地,我们要具象化这些计划,才能在后续工作时迅速反应,使用事先计划好的最佳方法去克服障碍。
|
||||
|
||||
在我看来,**使用WOOP方法主要有两大好处**:
|
||||
|
||||
- 让我们对自己的目标更清晰。通过WOOP中的具体思考,我们能对愿望有更清晰的认识,加深对自己的了解。在第四步计划的时候,我们可能会发现有的障碍,没办法克服。这时,我们就要果断放弃这个愿望。这样,WOOP能帮助我们过滤不现实的愿望,避免浪费时间。
|
||||
- 提高实现目标的效率。通过具体的这四个步骤,我们会对自己的愿望更有激情,对实施过程中可能出现的困难也更有预期,并且能够更有准备地去解决困难。
|
||||
|
||||
在我看来,WOOP的效果非常好。在不被干扰的时候,花五分钟进行一次WOOP思考,对理清自己的目标、清除干扰非常有效,推荐你也试试。另外,在Coursera上有一个很棒的视频,是[对WOOP方法创始人Gabriele Oettingen的访谈](https://www.coursera.org/learn/the-science-of-well-being/lecture/idieS/interview-with-gabriele-oettingen),也推荐你看一下。
|
||||
|
||||
## 沟通
|
||||
|
||||
在第22篇文章“[深度工作:聚焦最有价值的事儿](https://time.geekbang.org/column/article/149479)”中,@Landy提出了一个关于沟通建议的问题。
|
||||
|
||||
沟通的确是高效工作的重要软技能,也是这些年来我一直在努力提高的方向。接下来,我就与你分享**3个高效沟通的原则**吧。
|
||||
|
||||
**第一个原则是,沟通时要有同理心。**我们沟通的目的,是在对方身上起作用,要从别人的角度考虑怎样去沟通。所以,我们需要了解以下三个方面的信息:
|
||||
|
||||
- 一是,对方的知识背景,从而使用他能理解的语言去沟通;
|
||||
- 二是,对方想要知道什么,才能不绕弯路高效答复;
|
||||
- 三是,对方的出发点,根据他的出发点将对话引向双赢的方向。
|
||||
|
||||
**第二个原则是,外在表现很关键。**中国的文化比较内敛,我们从小受到的教育也强调内在美,忽视外表,这就会引导我们形成忽视外在表现的习惯。因此,我在美国工作时吃了不少亏,做出的东西,却被会表现的人抢走了大部分功劳。这才逐渐认识到,这种“内敛”实际上是不对的,很多情况下外在表现更重要。
|
||||
|
||||
比如,你的内心对一个人非常好,但是你的脾气很暴躁,总对TA发脾气,做出对TA不好的举动。那,你对这个人到底是好还是不好呢?
|
||||
|
||||
我认为你的实际行为、外在表现,对这个人造成了真实伤害,你内心的好并没有实际作用。所以,你对TA是不好的。
|
||||
|
||||
外在表现很关键这个原则,在开发工作上表现为我们不但要重视实际的工作,也要重视别人对我们工作成果的感知。**很多开发者从内心抵触PPT,我觉得这就是高效工作的一个重要阻碍。**我们确实需要花一些精力,去考虑如何把我们做出的东西更好地呈现出来。当然,我并不是说,要来虚的,具体做出东西才是王道,是前提。
|
||||
|
||||
**第三个原则是,冰山原则,**是美国软件界大佬Joel Spolsky(周思博)提出来的。
|
||||
|
||||
这个原则对软件技术人员和非技术人员间的交流非常有用,指的是一个软件应用系统比较复杂,就像一座冰山。但是,它表现在外面的只是一小部分,就像冰山露出水面的部分一样。如果你沟通的对象对软件不熟悉的话,他就会认为,冰山上的可见部分就是全部工作,或者说是绝大部分的工作。
|
||||
|
||||
这样的结果就是,软件开发人员在做项目进展演示的时候,如果演示得很完整、漂亮,对方就会认为你的工作做得差不多了,即使你已经提前强调过这只是在界面上做的一个Demo,也不会有效果。因为对方在潜意识里,就认为你的工作已经做得差不多了。
|
||||
|
||||
所以,我们**在做演示的时候,要尽量把界面的完成程度和项目的进展程度对应起来**。比如,不要把界面做得太漂亮,显示的文字可以用“XXX”,而不要用真实数据等。只有这样,才能让对方对项目的真实进展有比较客观的感知。Joel有[一篇博客](https://www.joelonsoftware.com/2002/02/13/the-iceberg-secret-revealed/)专门讲这个话题,很有意思,推荐你阅读下。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,以上就是今天的主要内容了,希望能对你梳理目标和提高沟通能力有所帮助。如果有你希望深入了解还未涉及的话题,那就直接给我留言吧。
|
||||
|
||||
接下来,我们就会开启新的模块了,进入管理和文化的讨论。在下一个模块,我会重点与你介绍硅谷的管理、文化,以及在国内的一些落地实践,希望帮助你了解如何利用管理推动高效研发的落地,以及如何使用文化来激发团队成员的内驱力和创造力,提高团队的研发效能。
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
||||
|
||||
|
147
极客时间专栏/研发效率破局之道/个人效能/特别放送 | 每个开发人员都应该学一些VIM.md
Normal file
147
极客时间专栏/研发效率破局之道/个人效能/特别放送 | 每个开发人员都应该学一些VIM.md
Normal file
@@ -0,0 +1,147 @@
|
||||
<audio id="audio" title="特别放送 | 每个开发人员都应该学一些VIM" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cb/17/cb75b544b4933aac7befa74731fb5f17.mp3"></audio>
|
||||
|
||||
你好,我是葛俊。
|
||||
|
||||
在“研发流程”和“工程方法”模块中,我主要是从团队的角度和你分享如何提高研发效能,所以很多同学希望我能分享一些工具的使用,来提高自己的效能。所以今天,我准备了一篇关于VIM的文章。在这篇文章中,我会着重带你深入了解VIM的两个特点。因为正是基于这两个特点,VIM可以很好地提高我们的工作效率。至于更多的、具体的VIM使用方法和技巧,我会在接下来的“个人效能”模块中,用另一篇文章专门详细与你介绍。
|
||||
|
||||
如果你已经是一个VIM的使用者了,那我希望文中关于VIM原理的讨论,可以帮助你更深入地理解它,进而可以更高效地使用它。而如果你还不是VIM的使用者,那我推荐你学习它的基本方法,并寻找适当的场景去使用它。
|
||||
|
||||
其实,向开发者们推荐编辑器,尤其是像VIM这样一个比较容易引起争议的编辑器,是一件有风险的事儿。但,基于我对VIM的了解和它能给开发者带来的巨大好处,我认为这个风险是值得的,相信你也能从中有所收获。
|
||||
|
||||
我们下来看看什么是VIM。
|
||||
|
||||
## 什么是VIM?
|
||||
|
||||
VIM是一个老牌的编辑器,前身是VI,第1个版本发行于1978年,距离今天已经有41年的历史了。
|
||||
|
||||
VIM是VI Improved,是提高版的VI,相对来说比较新,但实际上它的第1个版本也早在1991年就发布,也已经有28年的历史了。
|
||||
|
||||
VIM和我们日常使用的编辑器,比如VS Code、Notepad++、Sublime Text等,差别很大,而且上手比较难,新手在使用时常常会手足无措。**一个常见的问题是,打开了VIM就不知道怎么退出。**比如,有人就曾在[Stack Overflow](https://stackoverflow.com/questions/11828270/how-do-i-exit-the-vim-editor)上提问怎么退出VIM,6年以来的阅读量已经接近200万。又比如,我还听到过一个玩笑,问怎样产生一个随机字符串呢,答案是让一个不会使用VIM的人打开VIM并尝试退出。
|
||||
|
||||
虽然如此,但在美国对开发人员进行的最喜欢的编辑器的调研中,VIM往往能排进前5名。我个人的看法是,每一个开发者都应该学一些VIM,原因有二:
|
||||
|
||||
1. VIM基于命令行模式的特色,能**让文本编辑工作更高效**;
|
||||
1. VIM有极高的跨平台性,**可以一次学习然后多处使用,尤其可以作为很多其他IDE的插件使用。**
|
||||
|
||||
比如,当前我在进行微信小程序的项目开发,使用VS Code作为我主力IDE。在VS Code中,我每天都在使用VIM插件,VIM的命令操作大大提高了我的开发效率。作为开发者,编辑文本是最基本的工作,所以花些时间去了解最基本的VIM操作来提高效率和手指健康,是相当值得的。
|
||||
|
||||
在我看来,VIM有两大特点:
|
||||
|
||||
1. 具有独特的命令行模式;
|
||||
1. 跨平台非常棒,更能作为插件在很多其他IDE中使用。
|
||||
|
||||
而这两个特点,也正是我推荐你学习VIM的最主要原因。
|
||||
|
||||
## 特点一:VIM独特的命令模式使得编辑文档非常高效
|
||||
|
||||
非VI系列的编辑器一般只有编辑模式这一种模式,也就是说,敲击任何主体键都会直接修改文件内容。比如,敲击键盘上的e,文件里就添加了e这个字符。这里需要注意的是,键盘的**主体键**指的是,能显示在文件里的键,包括a-z、数字、字符等。
|
||||
|
||||
而VIM有多种模式,其中最主要的是命令模式和编辑模式。命令模式是VIM的默认模式,我们用VIM打开一个文件的时候,默认进入的就是这个模式。在命令模式中,敲击主体键的效果不是直接插入字符,而是执行命令。比如:
|
||||
|
||||
- 敲击字母e,表示将光标向右移动到当前单词最后一个字符;
|
||||
- 敲击符号*,表示在当前文件搜索光标所在的单词。
|
||||
|
||||
另外,在命令模式中输入“:<命令>回车键”,可以执行一些命令行命令以及进行系统配置。比如:
|
||||
|
||||
- 输入:q!表示,不保存文件并退出VIM;
|
||||
- 输入:set hlsearch表示,打开搜索高亮。
|
||||
|
||||
至于我们在其他非VIM编辑器中熟悉的编辑模式,需要在命令模式中敲击某些命令才能进入。比如:
|
||||
|
||||
- 敲击i表示,在当前位置进入编辑模式;
|
||||
- 敲击O表示,在本行上面添加一个空行并进入编辑模式。
|
||||
|
||||
进入编辑模式之后,使用体验就跟其他非VIM的编辑器效果差不多了,也就是说敲击主体键会直接插入字符。完成编辑工作之后,你需要再敲击Esc键返回命令模式。
|
||||
|
||||
请注意,**在编辑模式时,我们无法退出VIM**。你只能在命令模式中使用ZZ、ZQ、:qa!等命令退出VIM。如果你不会使用VIM,然后不小心在命令行窗口中打开了它,没有菜单可以选择,的确很难找到办法退出,所以就有了各种不能退出VIM的笑话。
|
||||
|
||||
**另外这里需要指出的是**,VIM实际上有多个模式,它的官方文档列举了一共7个基本模式和7个附加模式,而我在这篇文章中只做了命令模式和编辑模式两种模式的划分,是一个巨大的简化。事实上,命令模式中包含了常规模式(normal mode)、命令行模式(command-line mode)等,编辑模式则包括了插入模式(insert mode)、替换模式(replace mode)等。我之所以用命令模式和编辑模式的简单区分,一方面可以帮助你快速理解VIM,另一方面也不会影响你对VIM的使用。
|
||||
|
||||
总结来说,拥有命令模式是VIM系列编辑器与非VIM系列编辑器的最大差别。那,VIM为什么会有这种特性呢?
|
||||
|
||||
这是由VIM的历史决定的。VIM的前身是VI,VI的前身是Ex。Ex是Unix诞生时代的编辑器。那个时候因为计算机技术以及计算机系统资源的局限性,编辑器只能使用命令来编辑文件。所以VIM就一直保留了命令模式。**这个命令模式是初学者难以适应VIM的最主要原因,但同时也是VIM能高效编辑文档的关键所在**。
|
||||
|
||||
为什么这样说呢?在一个非VIM的编辑器中,如果要做一个非编辑操作的时候,你需要敲击一个非主体键,或者组合键才能完成;而在VIM的命令模式中,你通常只需要敲击主体键。比如
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/46/ee/4621ad994d6a424b0ca33648a6b1d7ee.jpg" alt="">
|
||||
|
||||
我们在编辑文件的时候,有大量的非输入操作,比如挪动光标、查找、删除等,所以在非VIM的编辑器里,我们要大量使用非主体建和组合键。而在VIM中,我们可以大量使用主体键,从而大大减少使用键盘主要部分(也叫工作区)之外的特殊键,同时使用组合键的次数也大大减少了。
|
||||
|
||||
所以,综合来讲,虽然VIM中的模式切换会带来一些额外按键操作,但次数远远小于它节省的按键次数,总的按键数量明显减少。
|
||||
|
||||
接下来,我们通过一个具体的案例对比一下效果吧。我在输入一行代码注释时,希望输入的结果是
|
||||
|
||||
```
|
||||
// This is making sure that userTotalScore is not null
|
||||
|
||||
```
|
||||
|
||||
但写到“not”的时候,我注意到我前面有一个拼写错误,把“making”写成了“mkaing”了
|
||||
|
||||
```
|
||||
// This is mkaing sure that userTotalScore is not
|
||||
^
|
||||
|
||||
```
|
||||
|
||||
现在,我需要修改这个错误,修改之后再回到行尾,补充“ null”写完这句话。以Mac为例,不使用VIM和使用VIM的操作对比如下表:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8c/88/8cf8ad75bd83c536898c816971c2c788.jpg" alt=""><br>
|
||||
统计下总次数,如下表所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/3a/4d9cd2d035140d8f61451f3d5b70e23a.jpg" alt=""><br>
|
||||
可以看到,在这个场景中**,使用VIM可以明显减少按键次数,包括组合键次数和特殊键次数。**在真实的编辑场景中,我的经验是减少的按键次数会更多,对文本编辑效率的提高非常明显。
|
||||
|
||||
另外,组合键和非主体键这两种按键方式非常容易对手腕和手指造成伤害。其实,我之前是Emacs的重度使用者,但使用了四年之后,我的左手小拇指开始不舒服,这是因为在Emacs中我常常需要用这个手指按住Ctrl键来完成组合键操作。比如,使用Ctrl+f向右移动光标,使用Ctrl-x Ctrl-S保存文件。
|
||||
|
||||
为了手指健康,我试着从Emacs向VIM转移。一个月之后,手指不舒服的症状明显减轻了。于是,我逐渐停止了使用Emacs,全面转向VIM。此后,双手再长时间使用键盘工作,也不容易疲劳了。
|
||||
|
||||
## 特点二:VIM是跨平台做得最好的编辑器,没有之一
|
||||
|
||||
因为VIM的悠久历史,同时一直在持续更新,所以在各大操作系统上都有适用的VIM版本,你可以到[VIM的官网](https://www.vim.org/download.php)上查看详情。所以,你掌握的VIM技能**基本可以用在所有的操作系统上**。
|
||||
|
||||
具体来说,在Unix系统上都有预装VI。因为VIM的命令是向上兼容的,所以你熟悉的VIM的基本功能在VI上仍然可以使用。Linux系统自带的基本都是VIM,比如Ubuntu18.04自带的版本就是VIM8.0。苹果操作系统因为是Unix的一个分支,所以预装有VIM。
|
||||
|
||||
Windows上没有预装VIM。不过你可以很方便地安装GVIM,或者直接运行一个免安装的可执行GVIM程序。
|
||||
|
||||
在移动端操作系统上,VIM在iOS和Android端都有移植:
|
||||
|
||||
- iOS上面比较好用的叫iVIM。我在iPad中进行一些重量级文本编辑的时候,就会使用iVIM。具体的使用方法是,将需要编辑的文本拷贝到VIM里面,编辑好了之后再拷出来,使用体验也还不错。
|
||||
- Android上的VIM移植得比较多,比如DroidVim就还不错。
|
||||
|
||||
VIM跨平台特性的另外一个表现是,**很多其他编辑器及IDE都有VI模式**,支持最基本的VIM操作。
|
||||
|
||||
比如,IntelliJ系列的IDE上有[IdeaVim插件](https://www.jetbrains.com/help/idea/using-product-as-the-vim-editor.html)、VS Code里有[VSCodeVim插件](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim),甚至VIM的老对手Emacs里也有好几个VI插件,最有名的是[Viper Mode](https://www.emacswiki.org/emacs/ViperMode)。
|
||||
|
||||
我最近半年使用最多的编辑器VS Code,所以我以它为例,与你说明如何在其他编辑器中使用VIM。
|
||||
|
||||
VSCodeVim插件的安装很简单,使用默认的VS Code插件安装方法很容易就能搜索到,并一键安装;配置也简单,默认的配置使用体验就非常不错。我在使用VSCode一个月后,对VS Code比较熟悉了,开始试用VSCodeVim插件,之后就再也回不到原生模式了。因为,VIM带来的效能提升,以及给手指带来的舒适感觉实在是太明显了。
|
||||
|
||||
VIM的跨平台特性甚至超越了编辑器这个范畴,**在一些不是编辑器的软件里面也有VI模式**。比如,Chrome浏览器和FireFox浏览器中都有VI插件,用户可以使用VI的快捷键方式来操作。
|
||||
|
||||
在我看来,在浏览器上使用VI模式的最大好处是,可以减少鼠标的操作。这一点对我的吸引力不是很大,不过我的另外两个朋友,一直在使用Chrome的VI模式插件[Vimnium](https://vimium.github.io/),反馈都是很好用。如果你非常偏好键盘而不是鼠标的话,推荐你也试试看。
|
||||
|
||||
在**配置**方面,VIM的默认配置就基本够用。所以,我一般只在自己的主力开发机上,才会添加一组我的常用配置及插件来提高使用体验,其他不常用机器就保留默认配置。
|
||||
|
||||
总的来说,VIM的跨平台做到了极致,因此我在很多地方都能用到积累的VIM经验。VIM肌肉记忆不断强化,一直在帮助我提高工作效率。
|
||||
|
||||
## 小结
|
||||
|
||||
在这篇文章中,我着重与你讲述了VIM的命令模式与跨平台特性这两大特点。通过对这两个特点的深入探讨,阐述我认为每个开发人员都应该学一些VIM的理由。
|
||||
|
||||
VIM编辑器的命令模式,是与其他非VIM编辑器的最大区别。也正是因为这个特性,使得其入门比较难,令很多新手望而生畏。但也正是因为命令模式,才使得VIM对于个人研发效能的提升帮助非常大。
|
||||
|
||||
而跨平台特性,使得我们一旦掌握了VIM技能,就基本可以用在所有的操作系统上,甚至是其他IDE中通过插件使用,从而最大程度地实现经验复用。
|
||||
|
||||
其实,除了命令模式和跨平台特性外,VIM还有一些其他好处,比如速度快、免费、可扩展性强等。但是,我认为这两点从根本上把VIM和其他编辑器区别开来了,它们能让我们非常高效、健康的编辑文本。所以说,付出一些成本去学习VIM的基本使用是非常值得的。
|
||||
|
||||
有一种说法是,说人的双手在一生中能够按键盘的总次数是一定的,达到这个总次数之后,手指就不能很好地使用键盘工作了。不知道你信不信,反正我信了。
|
||||
|
||||
关于VIM的话题,我们今天就讨论到这里了。在“个人效能”模块,我还会与你详细分享如何高效地学习VIM,并分享关于VIM的一些使用方法和技巧,帮你学会、用好VIM这个工具。
|
||||
|
||||
## 思考题
|
||||
|
||||
1. 除了Windows,你见过没有预安装VI的系统吗?那个系统上自带编辑器是什么呢?在这个系统上,你又是如何完成文本编辑工作的呢?
|
||||
1. 你见过VIM教徒和Emacs教徒的争吵吗?
|
||||
|
||||
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
|
Reference in New Issue
Block a user