我与Vim

我的Vim使用经历 我使用Vim的时间并不长。上大学的时候有一门课讲计算机体系架构,那是我第一次了解Ubuntu和Linux,那个时候习惯了各种现代编程工具的我(我是EE出身,很多是硬件专属,比如Keil、Xilinx/Quartus系列的,当时这两家都还没被收购呢…)。我认为Vim这个编辑器实在过于反直觉,很难用,在简单完成课程大作业后,我便再没使用Vim了,会的操作也只有i进入Insert Mode,:wq 退出而己。工作后一开始由于是在Windows环境下做C++工作,VS是我的主力代码编程器,后来进入后端领域后,才发现周围同事真的有不少是用Vim或者Vim插件进行编码的,加之常常要进服务器查看日志修改配置,我便强迫自己更进一步地学习Vim。一年多后,Vim已经成了我的主要编辑器。 为什么后来坚持使用Vim 可以说我正式用Vim的时间并不长,也就一年多,为什么我现在会使用Vim作为我的主编辑器呢,和很多人的理由可能不一样,我的主要理由并不是效率,或者是其超高的可定制性。我想有以下几个主要原因吧: 在绝大多数场景下获得相同的体验。我在工具的使用上倾向于一种“确定感”,也许不会是效率最高或者操作最快的,但这种感觉会在主观上让我很舒服。我现在是一个后端工程师,经常需要看一些服务器日志或者改一些配置,虽然公司可能提供了一些平台,让我们可以使用前端的Web页面进行搜索,但在频繁切换搜索关键字的时候总归是不习惯。而Vim在几乎所有Linux服务器上都有预装,再不济也有vi,这让我的查找工作流基本上是确定和一致的,对我来说效率得到了不小的提升。此外,由于有时我也会在远程的开发机上写代码(有时因为本机负载重,有时是懒得同步等等),同步一下Vim的配置后也可以几乎获得一样的体验,这种可以说是另一种层面的“开箱即用”的确定性,让我十分舒适。顺带提一句,在全拼输入法因为云输入法流行变得非常好用的今天,我也依然在使用看上去已经落伍过时的86版五笔输入法,就是因为我在几乎所有可以安装五笔输入法的环境里都能获得基本一致的体验,五笔本身重码率低,也加强了这种确定感。 Vim是非常好用的TUI编辑器,这意味着我在阅读代码或者编写代码的时候几乎不需要去够鼠标(当然查阅文档很多时候还是免不了,但更连续)。用鼠标并不一定就意味着低效,但对我来说如果单纯敲代码的时候要频繁去够鼠标定位我的光标,频繁地在键盘和鼠标之间移动会让我非常烦躁。我不确定我的“心流”会不会因此被打断,但情绪一定是受到影响了。这一点也许Emacs或者很多IDE默认也有快捷键可以做到,但我要记住很多奇奇怪怪的快捷键组合,而且对于IDE来说,我一旦更换了,就意味着我要重新学习一套快捷键,这真的是会造成一定的学习和切换负担的。在这一点上,Emacs可能也和Vim一样,当有自己熟悉的工作流之后可以用很久,但我并不喜欢Emacs那种极度依赖功能键的操作方式。关于这两者的对比后面再稍微展开聊聊,这里先放放。接触或者熟悉Vim的人可能知道,Vim在Normal Mode下的操作真的很像’speaking a language’,其operator + motion / text object 的设计在习惯之后真的非常自然和高效,而默认的Emacs键位对我来说稍有些别扭。 Vim是UNIX “组合”理念的经典体现,Vim作为UNIX环境下最为经典的TUI编程器之一,可以和许多命令行工具相结合,而我本人是非常喜欢UNIX这种理念的,每个组件可能都不大,但组合起来就能发挥无穷无尽的可能性。写一个测试脚本时,你可以写个Makefile,然后简单地调用make实现临时运行,不需要另开一个shell(虽然也可以这么做)。在Command Mode下也可以通过 !{cmd}调用几乎所有命令行工具,并和r/w结合与buffer互动。Vim的这种高度的可“组合”性(在这个语境下相对于Emacs的高“扩展”性)让Vim在默认配置下就已经非常好用。 Vim非常轻量。我日常工作里可能要同时开几个代码工程进行参考或者编辑,如果都使用IDE,我的电脑内存可能会爆,而且有时只是临时打开某个文件查看一下。这种场景下Vim就非常适合我了,配合tmux/iterm2等工具,即使在配置很低的电脑上,我的工作体验也不会受到什么影响。 Vim对于我的缺陷 当然Vim也不是尽善尽美的,对于我来说,Vim目前至少有以下几个缺陷: 中文输入。这可以说是让Vim成为我的通用编辑器的路上最大的阻碍。虽然大多数编辑代码的情况下我都不太需要中文输入法,但是在写一些简单文章,比如这篇文章的时候,在Insert Mode下使用中文输入法,切回Normal Mode的时候需要手动切回英文(Vim的使用者会相当频繁地切到Normal Node进行移动和修改),每次我都要按系统的中文切换快捷,有时忘了还得退格重新操作,确实让我苦恼。我也曾经在网上搜索到过一些解决方案,但后来出于各种考虑还是放弃了:1. 大段输入中文的场景一般都是特定场景或者特定格式需要,那我为何不选择更合适的工具呢,比如Typora写中文markdown, 用CTex写LaTex,甚至是用Word写有特定排版要求的文章,毕竟我只是需要更适合的工具,并不强求“Everything in Vim"。但我得承认,在用Vim写下这篇文章的过程当中,体验并不好。 Vimscript 非常晦涩,而且并不通用。这个可能也是很多初中级的Vim都感同身受的一点,Vim自己的脚本语言和很多现代语言差距比较明显,甚至比Emacs Lisp这门古老的语言都难看懂。在没有一些基础知识或者读过足够插件源码的话,很难一眼看懂一个函数是在做什么,或者细节是怎么实现的。这门奇怪的语言确实造成了一定的插件开发或者自定义的高门槛。好在Vim也支持和Python、Rudy等语言进行交互,Vimscript9也在发展中,更是有NeoVim这个项目,引领插件生态往更常规的lua语言环境去迁移。作为一个相当初级的用户,这对我来说并不是不可接受的。 会有种疯狂折腾配置的冲动。这可能是可定制编辑器的通病了,我支持适度演化和更新自己的配置文件,但有的时候会走火入魔,导致磨刀磨太多,在上面花了太多的时间,导致耽误了真正重要的事。这应该算是我自己的问题,并不是编辑器本身的过错。我相信磨刀不误砍柴工,但也要把握度,重复造轮子性价比并不高。但如果目的就是学了学习,就是为了折腾(程序员的浪漫),那么多踩踩坑,花时间将铁杵磨成针也未尝不可。 读复杂代码或者重构的时候有时不如IDE。我承认也许我们可以将Vim配置的具备IDE大部分功能,但这要花费不少精力。Vim始终只是一个文本编辑器,在代码跳转/引用查询/代码重构等专门功能上可能或多或少存在一些缺陷。也许我们可以解决所有问题,让它像IDE一样工作,但对于精力和时间有限的,像我这样的一般程序员,为何不让它做自己最擅长的事情呢。现代IDE都基本都可以配置Vim插件,甚至可以将IDE的功能与Vim的键位在语义上进行绑定,如果会基础的Vim操作的话迁移成本非常低,比如ideaVim,基本满足我对Vim编辑移动的要求,所以我在看一些陌生项目代码、或是重构、写单元测试时,也时常会开启我的Goland/VS/Pycharm等,毕竟商业公司对这些典型需求做了非常好的适配,也非常好用。正如我上面所说的,我并不期望“Everything in Vim”。 Vim, Emacs 和 其他编辑器/IDE 这里我无意引起战争(笑)。它们都仅仅是工具而已,我用Vim只是因为它适合当前的我,适合我现在的工作流,满足我大多数的需求。 相比Emacs,我是因为先接触了Vim,而且Vim在大多数类UNIX环境里都有预装而选择了它,加之Emacs对功能键依赖大(有的终端模拟器需要额外配置Meta键)。Emacs我了解有限,它更像是一个Lisp解释器和运行环境,因此它的功能可以更丰富(甚至可以在里面实现一个Vim,比如Evil-Mode)。Vim和Emacs作为编辑器界的老前辈,带来的不仅仅是编辑器本身,更是文化、设计理念以及如今在命令行环境下几乎约定俗成的操作习惯。在terminal里常用ctrl-a/ctrl-e/ctrl-u/ctrl-k/alt-w/alt-b等快捷键,或者经常在less/man/git diff等页面进行移动搜索的同学应该知道我在说什么。如果有机会,我可能还是会学习一下Emacs和Lisp编程,因为各类EmacsTalk和使用者的体会让我对它充满了好奇心。 至于其他编辑器,像VSCode、Sublime Text或者是IDE,我认为他们与Vim/Emacs并不是竞争者(严格来说Emacs和Vim也并不是一个赛道上的),而是相辅相成的。IDE在现代编程工作里面多数情况下确实有着更高的生产力,但这和使用高效/舒服的编辑手段并不冲突。就像我之前所说,很多现代编辑器或IDE都有Vim插件,而macOS下很多图形界面的光标移动快捷键也和Emacs一脉相承,它们完全可以组合起来,来实现满足每一个人特定的需求(比如煮咖啡^_^) 写在后面 如果有机会,我可能考虑录个Vim相关的入门视频,会比文字直观许多。虽然网上已经有很多了,但我也许能以一个初学者、一个非高级用户的视角,给大家介绍一下一个“简陋”的编辑器是怎么满足我日常工作的工作流的。而Vim也是一个常学常新,可以一直学习的东西。生命不息,学习不止。Peace~。

February 23, 2022 · 1 min · ChaosNyaruko

Go中的内存可见性与happens-before

什么是内存模型 Go的内存模型特指在并发的场景下,一个goroutine所写的变量在另一个goroutine能在哪些情况下被观察到 在计算中,内存模型描述了多线程如何通过内存的交互来共享数据 官方建议 程序可能会并发地访问/修改一些变量,多个goroutine的并发访问一定要保证“可串行化”,尤其是绝对不允许出现数据竞争的场景下 为了保证数据访问的串行化访问,没有数据竞争,在Go语言中,尽量通过channel或者各种同步原语对数据的并发访问进行保护,例如sync 、sync/atomic等 强烈推荐阅读官方文档 If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don’t be clever. C语言中的内存可见性 内存可见性是一个通用性质的问题,类似于 c/c++,golang,java 都存在相应的策略。作为比较,我们先思考下 c 语言,在 c 里面却几乎没有 happens-before 的理论规则,究其原因还是由于 c 太底层了,常见 c 的内存可见性一般用两个比较原始的手段来保证: volatile 关键字(很多语言都有这个关键字,但是含义大相径庭,这里只讨论 c ) memory barrier volatile volatile 声明的变量不会被编译器优化掉,在访问的时候能确保从内存获取,否则很有可能变量的读写都暂时只在寄存器。但是,c 里面的 volatile 关键字并不能确保严格的 happens-before 关系,也不能阻止 cpu 指令的乱序执行,且 volatile 也不能保证原子操作。 以一个常见的c代码为例: // done 为全局变量 int done = 0; while ( ! done ) { /* 循环内容 */ } // done 的值,会在另一个线程里会修改成 1; 这段代码,编译器根据自己的优化理解,可能在编译期间直接展开翻译成(或者每次都直接取寄存器里的值,寄存器里永远存的是 0 ): ...

November 4, 2021 · 7 min · ChaosNyaruko

如何看别人的代码(转载)

本文系转载 关于看别人的代码 自己曾是过来人,经常遇到刚毕业的同学很反感看别人的代码,也很反感使用别人的代码,甚至与他人协作开发还有点小抗拒 据我个人总结,出现这种问题的根本原因,是因为经验不足,无法瞬间秒懂别人写的代码(其他的原因可能根本原因都是这个),以前我觉得自己太菜了感觉分享此类心得会被人笑话,但是现在,我阅码无数,因工作原因腾讯几大最热门游戏源码我都瞄过,且因本人目前工作关系,几乎查阅过字节80%游戏的反编译代码,我有多年的逆向分析经验,如何快速定位感兴趣的代码也有些许心得。然后我来分享几点,如何超高速秒懂别人代码的几个心得。 在讨论心得之前,先讲一个理论原则,这个原则应该是科班《数据结构》里的内容,大意是指一个程序的输出仅与输入(输入不仅包括UI输入,控制台输入,也包括网络输入,甚至随机数也算是输入,任何外部的数据都算输入)有关。好,记住这点,将大大加快你阅码速度。 首先,第一点,最简单,也是最吃经验的一点,就是面对一份庞大的代码,如何快速定位自己感兴趣的代码,这最简单的一点就是全局搜索字符串和猜测函数名,这点应该都是大家的共识,函数名猜测,这是共识,就跟公理一样不需要证明了(当然也吃经验和英语水平)。另外是字符串搜索,一个程序,目光所及固定可见字符串,90%都可以在源代码中找到,关键代码基本上就在附近。 其次,是找关键代码的思维,不好描述,我举个例子: 比如你想找一个游戏代码里面发包相关的函数,通过Send关键字搜索发现没有相关函数的时候,此时可以换种思维,思考一下发包上下游关系,发包可能要对包进行压缩,加密,可以尝试搜索compress,encrypt等关键字,交叉引用看看是否有你感兴趣的代码,也可以通过上游的Login看看有没有关键的函数,Logon啊这些,甚至是相近的 Message,Msg啊这些都可以是关键字进行搜索,如果百般尝试最终无果的话,动用我们linux系统调用的“原子技术”,从send一步步反推吧~。 有一些好的项目,基本上从文件名就可以知道这个文件做了什么功能的。如果以上,可能比较吃经验,那么以下这个,我应该可以把这个过程讲清楚 找到了代码之后如何看懂(这里的看懂,指的是了解这个函数哪些做了什么事情,输出了什么东西)呢? 看懂一个函数,无非是看懂这个函数对哪些数据做了什么处理,最终对外输出了什么数据,因为函数的本质就是这个,函数无法再做其他事情了,所以,此时,你需要一个能将某个变量高亮的IDE,选中所有参数,看看函数对参数做了什么处理,应该是一目了然,秒懂应该没有问题,接下来以C讲几个特殊case分支(以下讲解仅限值传递类语言,比如C。引用传递类语言例如java,C#等的除外,看完应该也应该知道为什么要除外了) 参数进入了另一个函数,如果此参数前没有&,那么直接忽略 参数进入了另一个函数,如果此参数前面有&,那么跟进此函数,循环本过程看懂该子函数 高亮返回值和指针进来的参数,看如何构造的返回值以及有没有对指针参数指向内容进行修改,因为不高亮的地方,不用黑科技是改不了的 如果中间有赋值的地方,高亮赋值后的变量继续看 注意看一下,此函数有没有对全局变量做处理 如果是引用传递类的语言,比如java,C++(C++属于显式引用传递,看一下子函数形参里有没有&就知道了),C#这类语言就麻烦一点,可能还要跟进每个子函数看下,有没有做处理 如果是面向对象类的语言,可能还要麻烦点,因为有个this指针,本函数可能无形当中还会对本对象进行一些修改,注意那些凭空多出来的没有定义就拿来用的变量就行(所以,你们现在清楚将成员变量命名为m_开头是多么重要了吧?因为能让别人秒懂!) 好了,讲完了,讲完了,之后,也许有人已经知道了 linus大神为何如此不待见C++了吧,因为C++的确是一门自己用起来很爽,但是要让别人看懂你写的代码,的确可以让人口吐芬芳。据我这么多年的经验,在看懂别人写的代码难易程度上,C最简单,C#次之,C++最难懂(仅根据语言特性来分,无关我对这些语言的熟悉程度,特性一样的语言效果等同)。 当你看懂了一个函数的输入输出以后,你基本上已经认定了这个写法是没问题的,不然如果代码有bug,你在查阅的时候应该就已经发现了。所以,此时此刻,你再使用别人的代码,或者修改别人的代码,还有那么抗拒吗?

September 1, 2021 · 1 min · ChaosNyaruko

KMP算法中DFA的构造

以下内容转载自https://stackoverflow.com/questions/30548170/dfa-construction-in-knuth-morris-pratt-algorithm,帮助理解KMP算法中根据模式串构造DFA,以及与LPS的关系 Question I am referring to the outline of the Knuth-Morris-Pratt (KMP) algorithm for substring search in Sedgewick’s book “Algorithms” (4th ed.). The KMP algorithm uses a backup in substring search based on a deterministic finite automaton (DFA). I understand how the DFA enters the algorithm, however I don’t understand how to construct the DFA, which is done by the following code snippet: dfa[pat.charAt(0)][0] = 1; for (int X = 0; j = 1; j< M; j++) { for (int c = 0; c < R; c++) { dfa[c][j] = dfa[c][X]; } dfa[pat.charAt(j)][j] = j+1; X = dfa[pat.charAt(j)][X]; } where M is the the length of the pattern pat and R the size of the alphabet. The charAt() function returns the integer value of the character at the respective position. ...

April 20, 2021 · 3 min · ChaosNyaruko

稳定婚姻问题及其在CDN调度分发中的应用

稳定婚姻问题 Stable Marriage Problem定义 两个集合men、women数量相等,每个人持有对异性的好感度顺序表。现在假定要对这n对进行匹配,如果某种匹配存在同时满足以下三个条件的m-w对(blocking-pair),则认为此匹配是不稳定的:1)m和w目前没有订婚(意味着它们的订婚对象是别人) 2) 在m的优先级列表中,w的级别比当前订婚对象高 3) 在w的优先级列表中,m的优先级比当前订婚对象优先级高。 反之则称这个匹配是稳定的。在这样一个场景中求解稳定订婚关系的问题,称之为稳定婚姻问题 常见变种 SMI:(SM with Incomplete lists) SMT:(SM with Ties) 按照稳定条件依次减弱 super-stability strong stability weak stability: 总是存在解,并能在多项式时间内解决 SMTI(SM with Ties and Incomplete lists) 三种稳定关系定义,同上 super/strong关系,存在算法可以确定一个问题是否有解,以及求出一个解,super stability时间复杂度O(a), strong stabilityO(na), a表示所有优先级列表的整体长度(2n^2如果所有优先级列表都是完整的),且所有的解都具有相同的大小 weak stablity,任何情况下都存在一个稳定匹配,并且可以在O(a)时间内找到,但是解的大小可以有多个,求解其中最大的那个是NP-Hard的 经典解法 Gale-Shapley Algorithm algorithm stable_matching is Initialize all m ∈ M and w ∈ W to free while ∃ free man m who still has a woman w to propose to do w := first woman on m's list to whom m has not yet proposed if w is free then (m, w) become engaged else some pair (m', w) already exists if w prefers m to m' then m' becomes free (m, w) become engaged else (m', w) remain engaged end if end if repeat 按轮次进行 ...

April 16, 2021 · 2 min · ChaosNyaruko