This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View 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条原则包括抽象和分而治之、快速迭代以及DRYDont 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&amp;keywords=kubernetes+patterns&amp;qid=1570073661&amp;sprefix=kubernetes%2Caps%2C335&amp;sr=8-1)这本书针对云原生Cloud Native开发也有了业界比较认可的[12-factor原则](https://12factor.net/zh_cn/)等。将来必定还会有其他新的设计模式产生。比如伴随着AI的逐渐成熟针对AI的设计模式必定会出现。
所以,作为一名软件开发者,我们必须要持续学习。我之前在一家创业公司时,有一个刚大学毕业两年的同事,他有一个非常好的习惯,就是每天早上比其他同事早半个小时到办公室,专门来学习和提高自己。正是因为他的持续学习,使得他虽然工作时间不长,但在整个团队里一直处于技术领先的位置。你也可以借鉴这个方法,或者采用其他适合自己的方法来持续地提升自己。
## 思考题
1. 我今天提到的关于分而治之的实践,哪一条对你触动最大呢?同时,也和我分享一下你在工作实践中的感受吧。
1. 你还知道哪些编程技术方面的高效原则和实践吗?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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提出的指的是在无干扰的状态下才能专注地进行的专业活动。这样的活动能够使个人的认知能力发挥到极限从而让我们创造出最大价值成为一个不可替代的人。
就我个人而言我也一直在想办法去聚焦最有价值的事儿也读了不少GTDGeting 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. 你觉得碎片时间,更适合碎片化学习/娱乐,还是拥抱无聊呢?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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+tabWindows系统自带的是Alt+Tab或者Win+Tab。**关于程序启动**macOS系统有SpotlightWindows系统可以使用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( &quot;ahk_class Vim&quot; )
{
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中的具体设置方法是
```
&quot;rules&quot;: [
{
&quot;manipulators&quot;: [
{
&quot;description&quot;: &quot;Change ctrl to esc if pressed alone.&quot;,
&quot;from&quot;: {
&quot;key_code&quot;: &quot;caps_lock&quot;,
&quot;modifiers&quot;: {
&quot;optional&quot;: [
&quot;any&quot;
]
}
},
&quot;to&quot;: [
{
&quot;key_code&quot;: &quot;left_control&quot;
}
],
&quot;to_if_alone&quot;: [
{
&quot;key_code&quot;: &quot;escape&quot;
}
],
&quot;type&quot;: &quot;basic&quot;
}
]
}
]
```
**第四类工具是,具体的键盘**。开发者要经常输入,选择一款顺手的键盘就很重要了。键盘的选用,我看重的是手感和键盘布局。手感的话,机械键盘的确好一些,你可以根据自己的体验选择一款合适的。而布局的话,左右手按键分离较远的一般来说会好一些。
根据这些原则,我平时使用[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个方面外你觉得还有哪些方面的操作比较频繁吗有什么值得优化的地方吗又有什么值得推荐的工具吗
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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的最后一行会显示&lt;,&gt;! 表示由外部程序操作选中的部分。这时我们输入nl -v 1000并回车。
<img src="https://static001.geekbang.org/resource/image/0d/7d/0dd3edaf5ca34ae776d1527fbbee547d.png" alt="">
VIM就会把选择的文本传给nlnl在每一行前面添加序号再把处理结果传给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技巧是什么呢
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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 &lt;文件名&gt;把需要的文件添加到Git暂存区然后使用git commit命令提交即可。这个操作比较常见我们应该都比较熟悉。
但在工作中一个文件里的改动常常会包含多个提交的内容。比如开发一个功能时我们常常会顺手修复一些格式规范方面的东西再比如一个功能比较大的时候改动常常会涉及几个提交内容。那么在这些情况下为了实现代码提交的原子性我们就需要只把文件里的一部分改动添加到提交中剩下的部分暂时不产生提交。针对这个需求Git提供了git add -p命令。
比如我在index.js文件里有两部分改动一部分是添加一个叫作timestamp的endpoint另一部分是使用变量来定义一个魔术数字端口
```
## 显示工作区中的改动
&gt; 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 ## &lt;-- 魔术数字变量化
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) ## &lt;-- 端口魔术数字变量化
```
这时运行git add -p index.js命令Git会把文件改动分块儿显示并提供操作选项比如我可以通过y和n指令来选择是否把当前改动添加到Git的提交暂存区中也可以通过s指令把改动块儿再进行进一步拆分。通过这些指令我就可以选择性地只把跟端口更改相关的改动添加到Git的暂存区中。
```
&gt; 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命令可以看到我的确只是把需要的那一部分改动也就是端口相关的改动添加到了暂存区:
```
&gt; 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相关的改动仍留在工作区
```
&gt; 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改动和端口改动
```
## 查看提交历史
&gt; git log --graph --oneline --all
* 7db082a (HEAD -&gt; master) Change magic port AND add a endpoint
* 352cc92 Add gitignore file for node_modules
* e2dacbc (origin/master) Added the simple web server endpoint
...
## 查看提交
&gt; git show
commit 7db082ab0f105ea185c89a0ba691857b55566469 (HEAD -&gt; 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把代码仓、暂存区和工作区都恢复到这个位置从而不会丢失代码。
```
&gt; git branch temp
&gt; git log --graph --oneline --all
* 7db082a (HEAD -&gt; 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相关改动和端口相关改动。也就是说这个命令的效果就是让我回到了对这两个改动进行提交之前的状态
```
&gt; git reset HEAD^
Unstaged changes after reset:
M index.js
&gt; git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use &quot;git push&quot; to publish your local commits)
Changes not staged for commit:
(use &quot;git add &lt;file&gt;...&quot; to update what will be committed)
(use &quot;git checkout -- &lt;file&gt;...&quot; to discard changes in working directory)
modified: index.js
no changes added to commit (use &quot;git add&quot; and/or &quot;git commit -a&quot;)
15:06:58 (master) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo
## 改动在工作区
&gt; 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)
## 输出为空
&gt; git diff --cached
```
最后我就可以使用上面介绍过的git add -p的方法把工作区中的改动拆分成两个提交了。
## 基本操作三:修改当前提交
如果只需要修改Commit Message的话直接使用git commit --amend命令Git就会打开你的默认编辑器让你修改修改完成之后保存退出即可。
如果要修改的是文件内容可以使用git add、git rm等命令把改动添加到暂存区再运行git commit --amend最后输入Commit Message保存退出即可。
## 基本操作四:交换多个提交的先后顺序
有些时候我们需要把多个提交交换顺序。比如master分支上有两个提交A和BB在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来确认当前提交历史
```
&gt; git log --oneline --graph
* 7b6ea30 (HEAD -&gt; 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
```
接下来,运行
```
&gt; 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 &lt;commit&gt; = use commit
# r, reword &lt;commit&gt; = use commit, but edit the commit message
# e, edit &lt;commit&gt; = use commit, but stop for amending
# s, squash &lt;commit&gt; = use commit, but meld into previous commit
# f, fixup &lt;commit&gt; = like &quot;squash&quot;, but discard this commit's log message
# x, exec &lt;command&gt; = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop &lt;commit&gt; = remove commit
# l, label &lt;label&gt; = label current HEAD with a name
# t, reset &lt;label&gt; = reset HEAD to a label
# m, merge [-C &lt;commit&gt; | -c &lt;commit&gt;] &lt;label&gt; [# &lt;oneline&gt;]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c &lt;commit&gt; 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.
## 查看提交历史
&gt; git log --oneline --graph --all
* 65c41e6 (HEAD -&gt; 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 就会暂停。
```
&gt; 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 &gt;
```
这时我可以运行git log --oneline --graph --all确认当前HEAD已经指向了我想要修改的提交A。
```
&gt; 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 &lt;文件名&gt;然后再运行git commit --amend。
```
## 检查当前HEAD内容
&gt; git show
commit b51715452023fcf12432817c8a872e9e9b9118eb (HEAD)
Author: Jason Ge &lt;gejun_1978@yahoo.com&gt;
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对文件进行修改在注释部分添加&quot;at a predefined port&quot;
&gt; vim index.js
## 查看工作区中的修改
&gt; 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
&gt; git add index.js
## 对修改添加到提交A中去
&gt; 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。确认其包含了新修改的内容&quot;at a predefined port&quot;
&gt; git show
commit f544b1247a10e469372797c7dd08a32c0d59b032 (HEAD)
Author: Jason Ge &lt;gejun_1978@yahoo.com&gt;
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命令的其他步骤
&gt; git rebase --continue
Successfully rebased and updated refs/heads/master.
## 查看提交历史
&gt; git log --oneline --graph --all
* 27cba8c (HEAD -&gt; 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. 文章中提到,如果一个提交已经推送到了远程代码仓共享分支,那就没有办法对它进行拆分了。这个说法其实有些过于绝对。你知道为什么绝大部分情况下不能拆分,而什么情况下还可以拆分呢?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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=&quot;\[\033[0m\]&quot; # unsets color to term's fg color
# regular colors
K=&quot;\[\033[0;30m\]&quot; # black
R=&quot;\[\033[0;31m\]&quot; # red
G=&quot;\[\033[0;32m\]&quot; # green
Y=&quot;\[\033[0;33m\]&quot; # yellow
B=&quot;\[\033[0;34m\]&quot; # blue
M=&quot;\[\033[0;35m\]&quot; # magenta
C=&quot;\[\033[0;36m\]&quot; # cyan
W=&quot;\[\033[0;37m\]&quot; # white
# empahsized (bolded) colors
MK=&quot;\[\033[1;30m\]&quot;
MR=&quot;\[\033[1;31m\]&quot;
MG=&quot;\[\033[1;32m\]&quot;
MY=&quot;\[\033[1;33m\]&quot;
MB=&quot;\[\033[1;34m\]&quot;
MM=&quot;\[\033[1;35m\]&quot;
MC=&quot;\[\033[1;36m\]&quot;
MW=&quot;\[\033[1;37m\]&quot;
# background colors
BGK=&quot;\[\033[40m\]&quot;
BGR=&quot;\[\033[41m\]&quot;
BGG=&quot;\[\033[42m\]&quot;
BGY=&quot;\[\033[43m\]&quot;
BGB=&quot;\[\033[44m\]&quot;
BGM=&quot;\[\033[45m\]&quot;
BGC=&quot;\[\033[46m\]&quot;
BGW=&quot;\[\033[47m\]&quot;
```
```
## 文件 $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 '&gt;' turns red
export PS1=&quot;\e[42m\t\e[m$N $W&quot;'$(__git_ps1 &quot;(%s) &quot;)'&quot;$N$PROMPT_COLOR\u@\H$N:$C\w$N\n\[&quot;'$CURSOR_COLOR'&quot;\]&gt;$W &quot;
export PROMPT_COMMAND='if [ $? -ne 0 ]; then CURSOR_COLOR=`echo -e &quot;\033[0;31m&quot;`; else CURSOR_COLOR=&quot;&quot;; 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=&quot;powerlevel9k/powerlevel9k&quot;
```
命令行提示符以外的其他配置主要是通过安装和使用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 &lt;theme&gt;
omf theme &lt;theme&gt;
## 我使用的是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@&lt;server-ip-or-name&gt;
```
下面这个录屏演示的是我日常工作中使用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的最大区别是什么是否有什么场景我们必须使用其中的一个吗
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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 &quot;node_modules&quot; -f | grep -C3 index.md
```
可以得到当前文件夹中的所有index.md文件。
命令中tree的参数-I表示排除文件夹node_modulestree的参数-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我常用的一个替代品是RipGreprg。跟fd类似它也很适合开发者有如下4个特点
- 默认忽略.gitignore文件里指定的文件
- 默认忽略隐藏文件;
- 默认递归搜索所有子目录;
- 可以指定文件类型。
比如使用rg tags就可以方便地查找当前目录下所有包含tags的文件。它的查找速度非常快显示也比grep要漂亮
```
&gt; rg tags
package-lock.json
2467: &quot;common-tags&quot;: {
2469: &quot;resolved&quot;: &quot;https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz&quot;,
5306: &quot;common-tags&quot;: &quot;^1.4.0&quot;,
5446: &quot;common-tags&quot;: &quot;^1.4.0&quot;,
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&lt;回车&gt; 直接跳转。</tab>
<img src="https://static001.geekbang.org/resource/image/65/3e/65630c3d48a262195d63fad17e4e133e.gif" alt="">
对于第二种情况,即快速定位某个子文件夹,我介绍一个**超级酷的工具fzf**。本质上讲fzf是一个对输入进行交互的模糊查询工具。它的使用场景非常多文件夹的跳转只是一个应用。所以我还在再后面文章做更多的详细讨论。
安装好fzf之后你就可以使用Ctrl+T进行文件夹的交互式查询或者使用Alt+C进行文件夹跳转。
比如我想跳转到src/component文件夹中可以输入Alt+Cfzf就会列出当前文件夹下的所有文件夹。比如我输入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会打开VIMVIM里面列举该文件夹中所包含的文件和子文件夹然后使用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叫做TUITerminal UI。十多年前的Borland终端IDE就是这一类工具的翘楚使用熟练之后效率会很高。
TUI的文件管理器我用过3个Midnight Commander (以下简称mc)、Ranger和nnn。
mc是两个窗口的文件管理器。如果你使用过Windows CommanderTotal 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
{ &quot;id&quot;: { &quot;bioguide&quot;: &quot;E000295&quot;, &quot;thomas&quot;: &quot;02283&quot;, &quot;fec&quot;: [ &quot;S4IA00129&quot; ], &quot;govtrack&quot;: 412667, &quot;opensecrets&quot;: &quot;N00035483&quot;, &quot;lis&quot;: &quot;S376&quot; }, &quot;name&quot;: { &quot;first&quot;: &quot;Joni&quot;, &quot;last&quot;: &quot;Ernst&quot;, &quot;official_full&quot;: &quot;Joni Ernst&quot; }, &quot;bio&quot;: { &quot;gender&quot;: &quot;F&quot;, &quot;birthday&quot;: &quot;1970-07-01&quot; }, &quot;terms&quot;: [ { &quot;type&quot;: &quot;sen&quot;, &quot;start&quot;: &quot;2015-01-06&quot;, &quot;end&quot;: &quot;2021-01-03&quot;, &quot;state&quot;: &quot;IA&quot;, &quot;class&quot;: 2, &quot;state_rank&quot;: &quot;junior&quot;, &quot;party&quot;: &quot;Republican&quot;, &quot;url&quot;: &quot;http://www.ernst.senate.gov&quot;, &quot;address&quot;: &quot;825 B&amp;C Hart Senate Office Building Washington DC 20510&quot;, &quot;office&quot;: &quot;825 B&amp;c Hart Senate Office Building&quot;, &quot;phone&quot;: &quot;202-224-3254&quot; } ] }
```
可以方便地使用cat person.json | jq .”对JSON进行格式化输出
```
$ cat people.json | jq .
{
&quot;id&quot;: {
&quot;bioguide&quot;: &quot;E000295&quot;,
&quot;thomas&quot;: &quot;02283&quot;,
&quot;fec&quot;: [
&quot;S4IA00129&quot;
],
&quot;govtrack&quot;: 412667,
&quot;opensecrets&quot;: &quot;N00035483&quot;,
&quot;lis&quot;: &quot;S376&quot;
},
&quot;name&quot;: {
&quot;first&quot;: &quot;Joni&quot;,
&quot;last&quot;: &quot;Ernst&quot;,
&quot;official_full&quot;: &quot;Joni Ernst&quot;
},
&quot;bio&quot;: {
&quot;gender&quot;: &quot;F&quot;,
&quot;birthday&quot;: &quot;1970-07-01&quot;
},
&quot;terms&quot;: [
{
&quot;type&quot;: &quot;sen&quot;,
&quot;start&quot;: &quot;2015-01-06&quot;,
&quot;end&quot;: &quot;2021-01-03&quot;,
&quot;state&quot;: &quot;IA&quot;,
&quot;class&quot;: 2,
&quot;state_rank&quot;: &quot;junior&quot;,
&quot;party&quot;: &quot;Republican&quot;,
&quot;url&quot;: &quot;http://www.ernst.senate.gov&quot;,
&quot;address&quot;: &quot;825 B&amp;C Hart Senate Office Building Washington DC 20510&quot;,
&quot;office&quot;: &quot;825 B&amp;c Hart Senate Office Building&quot;,
&quot;phone&quot;: &quot;202-224-3254&quot;
}
]
}
```
以及使用jq ".terms[0].office"命令查询他的第一个工作任期的办公室地址。
```
$ cat person.json | jq &quot;.terms[0].office&quot;
&quot;825 B&amp;c Hart Senate Office Building&quot;
```
jq存在的最大问题是它有一套自己的查询处理语言。如果使用jq的频次没那么高的话很难记住每次都要去查帮助才可以。
针对这种情况有人设计了另一种类似的工具直接使用JavaScript作为查询处理语言典型代表是fx和jq.node。这就大大方便了使用JavaScript的开发者因为可以使用已经熟悉了的语法。
比如对于上个案例的JSON文件我可以方便地在fx工具中使用JavaScript的函数filter()进行过滤查询。
```
$ cat person-raw.json| fx 'json =&gt; json.terms.filter(x =&gt; x.type == &quot;top&quot;)'
[
{
&quot;type&quot;: &quot;top&quot;,
&quot;office&quot;: &quot;333 B&amp;c Hart CIrcle Building&quot;,
&quot;phone&quot;: &quot;202-224-3254&quot;
}
]
```
### 查找、关闭进程
通常情况下我们使用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的工具。你知道它是什么作用吗
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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&gt;2的效果。
所以接下来,我会从工具集成和提高工具一致性两个方面,与你介绍如何把多个工具组合成为高效的工具环境。
## 工具的集成
工具的集成最值得优化的情况包括两种一是使用管道Pipe对命令行工具进行集成二是对集成开发工具环境IDE进行配置让IDE和周边工具集成。
### 使用管道Pipe对命令行工具进行集成
其实,我在前面的文章中已经使用过管道很多次了,只是使用场景比较简单而已。
比如,在[第24篇文章](https://time.geekbang.org/column/article/150779)中我使用命令curl -s &lt;github_url&gt; | vim -来查看GitHub上某个用户的代码仓情况通过管道把curl命令的输出传给VIM以方便我在VIM中查看较长的输出。
```
&gt; curl -s https://api.github.com/users/jungejason/repos | vim -
Vim: Reading from stdin...
## 进入VIM窗口显示所有repo的array
[
{
&quot;id&quot;: 213849635,
&quot;node_id&quot;: &quot;MDEwOlJlcG9zaXRvcnkyMTM4NDk2MzU=&quot;,
&quot;name&quot;: &quot;counter-redux-sample&quot;,
&quot;full_name&quot;: &quot;jungejason/counter-redux-sample&quot;,
&quot;private&quot;: false,
&quot;owner&quot;: {
&quot;login&quot;: &quot;jungejason&quot;,
...
```
这个案例使用了一次管道,比较简单。
而高效使用管道,首先是**使用多个管道灵活地把多个工具连接起来**。一个常见的场景是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>
上面管道每一步的执行输出,如下所示:
```
&gt; 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
...
&gt; 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
&gt; ps aux | grep vim | grep -v grep
jasonge 31383 0.0 0.0 4310340 4740 s001 S+ 9:57AM 0:00.11 vim
&gt; ps aux | grep vim | grep -v grep | awk '{print $2}'
31383
&gt; 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命令把进程列表发送给fzffzf首先会列举出所有进程供我搜索过滤。随着我的输入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 &quot;fzf&quot; | 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很多工作都通过ArcanistPhabricator 的命令行工具)的各种子命令来完成。这样就实现了命令使用的一致性,降低了学习成本。
在日常工作中你也可以尝试开发一个命令行工具对常用工作进行封装。之前我在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%的操作,进行集成和优化。从我个人的体验来看,这样的平衡点是比较合适的。
总之,我们时刻要牢记,配置环境的目的是,配置出一个强大而灵活的环境,让一切工作流畅,从而提高生产效率,一定要注意投入产出比。
## 思考题
在浏览器作为工作入口的例子中,如果一个任务描述搜索到的任务不止一条时,我们可以怎么处理呢?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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出现障碍Athen使用计划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/)专门讲这个话题,很有意思,推荐你阅读下。
## 小结
好了,以上就是今天的主要内容了,希望能对你梳理目标和提高沟通能力有所帮助。如果有你希望深入了解还未涉及的话题,那就直接给我留言吧。
接下来,我们就会开启新的模块了,进入管理和文化的讨论。在下一个模块,我会重点与你介绍硅谷的管理、文化,以及在国内的一些落地实践,希望帮助你了解如何利用管理推动高效研发的落地,以及如何使用文化来激发团队成员的内驱力和创造力,提高团队的研发效能。
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!

View 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)上提问怎么退出VIM6年以来的阅读量已经接近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表示将光标向右移动到当前单词最后一个字符
- 敲击符号*,表示在当前文件搜索光标所在的单词。
另外,在命令模式中输入“:&lt;命令&gt;回车键”,可以执行一些命令行命令以及进行系统配置。比如:
- 输入: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的前身是VIVI的前身是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教徒的争吵吗
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!