罗泽轩

Results 142 issues of 罗泽轩

在解决git merge的冲突时,有时我总忍不住吐槽git实在太不智能了,明明仅仅是往代码里面插入几行,没想到合并就失败了,只能手工去一个个确认。真不知道git的合并冲突是怎么判定的。 在一次解决了涉及几十个文件的合并冲突后(整整花了我一个晚上和一个早上的时间!),我终于下定决心,去看一下git merge代码里面冲突判定的具体实现。正所谓冤有头债有主,至少下次遇到同样的问题时就可以知道自己栽在谁的手里了。于是就有了这样一篇文章,讲讲git merge内部的冲突判定机制。 ## recursive three-way merge和ancestor [git的源码](github.com/git/git) 先用`merge`作关键字搜索,看看涉及的相关代码。 找了一段时间,找到了git merge的时候,比较待合并文件的函数入口:[ll_merge](https://github.com/git/git/blob/74301d6edeb0e081a4ef864057952b6a7ff2b4be/ll-merge.c)。另外还有一份[文档](https://github.com/git/git/blob/master/Documentation/technical/api-merge.txt),它也指出`ll_merge`正是合并实现的入口。 从函数签名可以看到,mmfile_t应该就代表了待合并的文件。有趣的是,这里待合并的文件并不是两份,而是三份。 ``` int ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char...

Git

绝大部分日常使用Linux和OS X的程序员都会选择zsh作为自己的shell环境,毕竟对比于bash,zsh的便利性/可玩性要胜出很多,同时它又能兼容bash大多数的语法。不过相对而言,zsh补全脚本要比bash补全脚本要难写。zsh提供了非常多的补全的API,而且这些API功能有不少重叠的地方,掌握起来并不容易。不像bash,你只需记住三个API(`compgen`,`complete`,`compopt`)就能实现整个补全脚本。 这篇的任务跟上一篇的一样,需要实现一个针对`pandoc`的补全脚本,囊括下面三个目标: 1. 支持主选项(General options) 2. 支持子选项(Reader options/General writer options) 3. 支持给选项提供参数值来源 ## 支持主选项 还是跟上一篇一样,先解释一个实现第一个目标的程序,带各位入门: ``` zsh #compdef pandoc _arguments \ {-f,-r}'[-f FORMAT, -r FORMAT, Specify input format]' \...

Shell

今天写前端页面的时候发现了一个有趣的事情,或者说bug吧。点击一个按钮,会导致页面跳转到404。查了下浏览器请求,发现是一个POST请求导致的。这个页面包含了一个`form`,确实是想让它POST数据给后台。不过按照设定,应该是通过AJAX实现的,而不是直接重新加载页面。 看来不知道是什么触发了表单的submit事件。仔细阅读下相关的代码,这个按钮被按下时,会去检查表单是否正确,如果正确,则通过AJAX去进行POST。注意,这个按钮只是普通的``,并不是``。然而在页面跳转的同时,检查表单正确性部分的代码也不能工作了。 因为浏览器在POST之后会重新加载内容,不会保留之前的log,这给调试带来了麻烦。花了我不少时间,终于把bug揪出来了。原来是某一个分支中,`event.preventDefault()`被绕过了,导致原来的事件没有拦住。一开始发现这一点时,我是感到很奇怪的,原来的事件不只是`click`么?怎么会是`submit`呢? [查了下](http://w3c.github.io/html-reference/button.html),原来`button`元素默认是`type="submit"`,也即在`form`中,点击`button`自然会触发`submit`事件。 ``` don't submit ``` 即使这个`button`已经标明是“don't submit”了,呵呵。 解决之道很简单,就是指定`type="button"`,这样它就是人畜无害、天真无邪的好button了。 如下: ``` submit don't submit just click it ``` 如果这个`button`在`form`之外,那么就不会触发`submit`事件。然而在html5标准中,你可以通过设置`form="#form_id"`属性来给`button`指定一个非直系祖先的`form`。 这篇debug小文本来打算发到SegmentFault上的,不过考虑到它实在太短,干货还不够多,所以就放到这里了。

TIL

备份自:http://segmentfault.com/a/1190000004213733 呃,标题有点耸人听闻,不过我并不是标题党。考虑到谈论大而虚的东西(比如最好的语言)容易引起争论,所以还请诸君带着看戏而不是庭辩的心态来看待本文。 依我个人所见,后端框架,类似于MVC这样的组织方式已经显得过气了。 过去,在创建应用时通常会按MVC各建一个文件夹,每个文件夹就是一个模块。MVC三者的职责是这样的: 1. Controller绑定到某个路由上,接着处理请求参数,然后创建在整个请求中可见的对象,并进行一些业务逻辑上的工作。期间会从数据库中构造Model,也有可能新建/修改Model后将它们保存入数据库。最后,Controller会通过View响应用户的请求,或者返回重定向的报文。基本上业务逻辑都是实现在Controller里面的。(下文为了阐述方便,都以“业务逻辑”特指Controller中的业务逻辑) 2. 每个Model往往对应数据库的一个实体。Model类除了装数据之外,还提供了跟自己身份相关的一些方法,比如`User`类提供`authenticate`方法以用于验证密码。Model对象通常还会负责检验构造自己的参数是否正确。 3. View负责使用给定的参数渲染模板,并作为响应返回给用户。View一般是由该框架提供的模板语言写成。 现在,一个后端应用如果还是按MVC的方式划分,似乎有些不适应了。 ## 后端MVC的历史 我们先从后端MVC的历史开始讲起吧。MVC最初发端于客户端程序的开发,旨在用Controller在Model和View之间进行解耦。如果我没记错的话,gang of four的《设计模式》里面提到MVC,就是以客户端程序作为例子。 跟客户端开发类似的,后端程序一开始也只有View这一层。在很久很久以前,后端程序都是这样运行的:用户一个HTTP请求进来,服务器通过cgi调用一个程序生成文本作为响应。这时期大部分后端程序,看上去都像是模板语言(见过初学者写过的JSP/PHP吗?)。因为它们主要做的事情,就是从用户输入和数据库中获取数据,并拼接字符串生成文本。后来后端程序开始演化得越来越复杂,单单一层View已经不适应了。由于需要把逻辑从View中分割开来,后端程序开始走上客户端程序走过的路,进行MVC的分离。于是,负责路由和业务逻辑处理的部分变成了Controller,负责数据处理和持久化的部分变成了Model。 虽然后端程序也是做了MVC的拆分,但是它跟客户端的MVC其实是不同的。 在客户端里,Controller把View和Model分离开来,实现View和Model的解耦合。View上的变化,通过Controller传递给Model,然后再将Model最新的数据通过Controller传递回View。View:Controller:Model的比例通常是N:1:N,其中每个View基本对应一个Model。 然而,在后端程序里面,View和Model通常没有很强的对应关系。一般意义上的CRUD,基本上是Controller(业务逻辑)围绕着Model(数据层)在转。View扮演的往往是跑龙套的角色。 ## 还需要V层吗 在客户端MVC中,View扮演的是跟其他二者三足鼎立的角色。用户的输入经过View,底层数据的变更通过View反馈给用户。 然而后端MVC中,View的地位摇摇欲坠、可有可无。前文提到,Controller绑定在路由上,接收请求;Controller渲染模板,发送响应。跟客户端不同的是,Controller只有在渲染模板时才用上View。View的戏份一下子被砍掉了一半。祸不单行,Controller并不一定需要渲染模板来发送响应,它可能直接就重定向了;或者更常见的是,Controller直接把一个对象JSON化,并把它响应给用户。这么一来,View的戏份还剩多少? 有些后端框架提供了JSON格式的模板,多多少少试图挽救View的没落地位。可惜并没有什么用。 过去,View通常由三部分组成:html代码,控制流程语句,渲染时的上下文。如果提供的是JSON格式的模板,那么View的前两部分基本不需要了,只需要渲染时的上下文。可是这么一来,为什么我还需要渲染一个JSON模板,直接用上下文的数据创建出个实例,由它生成JSON字符串,不也行吗?虽然我多了个用于响应的类,但是少了个JSON模板啊,而且说不定就不用给View留个文件夹。 最近我参与开发的几个后端应用,根本没有View的容身之地。所有的响应都是JSON格式,都是由特定的类JSON化出来的。 很多情况下,后端应用要么仅仅是大后端系统中的一个组件,要么需要跟多种来源的客户端打交道。通常它们只是作为数据的守护者,API的执行人,仅响应以JSON数据。至于接收者想用这些数据做什么,那是它们的事了。也许是拿来渲染前端页面,也许是保存在客户端的数据库里,也许是拿去进一步分析处理。View已经退化到算不上一个层了。 ## M负责数据实体还是负责数据的访问...

本篇是“dash/zeal添加新文档”的姐妹篇,讲讲如何在另一个类似的文档应用——devdocs——中添加文档。 如果你之前没有用过类似的文档应用,建议现在就用起来。它们可以显著地提高文档查找的效率,而查文档又是日常编程中常做的事情之一。 ## devdocs 介绍 [devdocs](https://github.com/Thibaut/devdocs)是一个开源的文档浏览应用。跟dash一样,它把HTML源文档转换成带索引的docset规格,以供用户查看。不过devdocs是一个用rails写的web应用而非本地应用,文档作为静态资源由服务器提供,交互则是通过浏览器页面完成。你可以在devdocs.io使用该应用,也可以在自己的电脑上部署它。注意devdocs提供了offline的选项,可以把数据写入到浏览器的indexeddb中,避免了每次从服务器获取数据带来的延迟。 在制作docset方面,devdocs跟dash最为显著的不同,在于devdocs把制作docset的功能集成到了应用当中。跟dash不同,devdocs把docset规格留作黑箱,取代它的是开发出一组制作docset的接口,通过这些接口制作的docset才能被识别。这么做的好处在于,官方会提供了制作docset的通用的辅助函数,减少了自己的开发量。坏处呢,就是限制了制作docset的发挥空间。另外,由于docset的静态资源代码是放在应用代码里面的,导致分发docset时也要将对应的静态资源代码一并分发。考虑到devdocs主要是一个web应用而非本地应用,这么做也不难理解。 还是跟上一篇讲dash的一样,本篇依旧采用官方文档解读加示例的形式。由于devdocs假定docset都位于同一个入口地址下(比如xxx/docs/),而上一篇的OpenResty文档不符合这一假设,所以换个示例。这次我们选择用devdocs里面的PHP作为示例。注意devdocs是一个Rails应用,所以要用它提供的接口自然只能用Ruby。不过据我观察,要制作docset并不需要什么Ruby编程知识。即使不懂Ruby,你也可以拿起现有的代码改改看,然后搜一下相关的语法知识,就能完事。 ## devdocs添加新文档(PHP示例) 官方文档见:https://github.com/Thibaut/devdocs/wiki/Adding-documentations-to-DevDocs 创建一个文档需要以下几步(假设需要生成的文档名为`docset_name`,下面的路径都是相对于devdocs项目代码根目录的相对路径,再强调一次,给devdocs添加文档需要在项目中写拓展代码): - 在`./lib/docs/scrapers/`下创建一个scraper类,起名为`docsetName`(注意这里用的是驼峰命名法)。让它继承自`Docs::UrlScraper`(用于下载在线文档)或`Docs::FileScraper`(用于处理本地文档,仅当文档页面过多时推荐)。 - 填写该类的以下属性。 ``` ruby # https://github.com/Thibaut/devdocs/blob/master/lib/docs/scrapers/php.rb module Docs class Php < FileScraper include FixInternalUrlsBehavior self.name =...

工具
小技巧

## dash介绍 dash是一个收费的文档浏览应用。什么是文档浏览应用呢?就是将文档的HTML源格式打包成docset,该docset对文档中的条目加了索引,然后用户就可以在应用内查询相关的条目内容。当然dash的功能不仅止于浏览文档,如果感兴趣的话可以去[官网](https://kapeli.com)接受下安利。 如果你之前没有用过类似的文档应用,建议现在就用起来。它们可以显著地提高文档查找的效率,而查文档又是日常编程中常做的事情之一。 如果你不是OS X用户,可以试下[zeal](zealdocs.org),一个开源的dash实现(支持Windows和Linux)。如果觉得dash的价格太贵,也可以试下`devdocs`,我会在下一篇文章中讲讲devdocs相关的内容。 ## 如何生成dash docset docset包含三部分:源文档的HTML文件、跟文档展示相关的静态资源、用于索引的sqlite表。 生成docset,简单来说就是处理源文档的HTML文件,然后提取出条目,并写入sqlite。具体的生成方式见https://kapeli.com/docsets 。如果需要生成的文档是由`godoc`之类的生成器生成的,由于它们遵循同样的格式,可以直接用别人写好的工具生成。否则的话,需要自己解析源文档的HTML文件。下面我将以openresty项目为例,阐述下dash docset的生成步骤。 创建一个文档需要以下几步(假设需要生成的文档名为`docset_name`): - 创建`.docset/Contents/Resources/Documents/`文件夹。 - 复制文档的HTML源文件到上面的文件夹中。 注意其中包括HTML中引用的css/image等外部资源,如果需要的话,修改HTML中对这些资源的引用路径。 ``` python resources = set() rewritten_head = '%s\n' % metadata.name for...

工具
小技巧

因为需要在Go语言里格式化输出时间,所以看了下对应的文档。看了之后我整个人都囧掉了,感觉就像是在唐诗精选里面读到一首打油诗。 Go里面格式化时间的方式实在太奇怪了……一般情况下,我们都是使用类似于`%Y-%m-%D`或者`yyyy-mm-dd`的格式字符串来格式化输出时间。Go也不例外。只是Go里面不是用y啊m之类抽象的符号,而是直接使用数字和英语单词…… 先记住这个时间点:`2006-01-02T15:04:05Z07:00`,这是个星期一。 如果你把上面的时间点作为格式化字符串放到`Time.Parse`里面,它就会以“年-月-日T时:分:秒”的格式输出时间。对的,你没看错,上面那串时间就是合法的、正规的、受到推荐的Go里面的时间格式化字符串!Go通过琅琅上口(打油诗般)的字词,解决了其它语言里面格式符号难懂、难记、~~难写~~的困难,值得我们颁发年度最佳设计奖! 一开始我以为Go里面这么奇葩的设计,是为了纪念2006年1月2日下午3点4分5秒,那个改变世界的时刻。虽然我托着脸颊,沉思了一分钟,依然想不起那一天发生了什么事。也许是Go设计出来的日子?不过Go的年纪还不致于这么大……于是搜索一下,在这个帖子里面发现了那一天的真相 : https://www.quora.com/Why-is-the-Golang-reference-time-Jan-2-2006-at-3-04pm-MST 原来是出于`Explicit is better than implicit`的考虑。 1表示月份,2表示天,3是那天下午,4是分,5是秒,6是年(2006),最后有一个7表示时区。我终于明白了这一切。据说这种表示方法连老奶奶也看得懂……顿时我就汗颜了,看来我不如老奶奶啊。也许老奶奶只是看得懂英文字符串,却不可能猜到它可以用来表示时间 :sob: 比起其它语言的`yy`和`dd`,Go这种时间格式字符串不用担心混淆`MM`和`mm`哪个是月哪个是分,也不用担心表示年份该用`yy`还是`YY`。不过Go的问题是,`3`到底是小时呢,还是分钟呢?也许从1开始算起,我们就可以弄懂`3`到底是哪个时间单位。月、天、时,拇指、食指、中指,嗯,是第三个。不过是上午还是下午呢?本来想用24小时的,一不小心写成了12小时了…… :disappointed: 不管怎么样,为了避免犯错,这里把Go的时间格式字符整理如下: | 时间 | Go中的表示法 | | --- | --- | | 年 |...

Go

原文链接:http://techblog.netflix.com/2015/11/linux-performance-analysis-in-60s.html 作者是Brendan Gregg, Oracle/Linux系统性能分析方面的大牛。 # Linux性能分析的前60000毫秒 为了解决性能问题,你登入了一台Linux服务器,在最开始的一分钟内需要查看什么? 在Netflix我们有一个庞大的EC2 Linux集群,还有非常多的性能分析工具来监控和调查它的性能。其中包括用于云监控的[Atlas](http://techblog.netflix.com/2014/12/introducing-atlas-netflixs-primary.html),用于实例按需分析的[Vector](http://techblog.netflix.com/2015/04/introducing-vector-netflixs-on-host.html)。即使这些工具帮助我们解决了大多数问题,我们有时还是得登入Linux实例,运行一些标准的Linux性能工具来解决问题。 在这篇文章里,Netflix Performance Engineering团队将使用居家常备的Linux标准命令行工具,演示在性能调查最开始的60秒里要干的事, ## 最开始的60秒...... 运行下面10个命令,你可以在60秒内就对系统资源的使用情况和进程的运行状况有大体上的了解。无非是先查看错误信息和饱和指标,再看下资源的使用量。这里“饱和”的意思是,某项资源供不应求,已经造成了请求队列的堆积,或者延长了等待时间。 ``` uptime dmesg | tail vmstat 1 mpstat -P ALL 1 pidstat 1 iostat -xz...

Linux

有一天闲着无聊的时候,脑子里突然冒出一个Magic Method的有趣用法,可以用`__getattr__`来实现Python版的`method_missing`。 顺着这个脑洞想下去,我发现Python的Magic Method确实有很多妙用之处。故在此记下几种有趣(也可能有用的)Magic Method技巧,希望可以抛砖引玉,打开诸位读者的脑洞,想出更加奇妙的用法。 如果对Magic Method的了解仅仅停留在知道这个术语和若干个常用方法上(如`__lt__`,`__str__`,`__len__`),可以阅读下这份[教程](http://www.diveintopython3.net/special-method-names.html),看看Magic Method可以用来做些什么。 ## Python method_missing 先从最初的脑洞开始吧。曾几何时,Ruby社区的人总是夸耀Ruby的强大的元编程能力,其中`method_missing`更是不可或缺的特性。通过调用`BaseObject`上的`method_missing`,Ruby可以实现在调用不存在的属性时进行拦截,并动态生成对应的属性。 Ruby例子 ``` ruby # 来自于Ruby文档: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-method_missing class Roman def roman_to_int(str) # ... end def method_missing(methId) str = methId.id2name...

Python

如果你是一个重度shell用户,一定会关注所用的shell的补全功能。某款shell的补全强弱,也许就是决定你的偏好的第一要素。 shell里面补全的影子无处不在,输入命令的时候可以有补全,敲打选项的时候可以有补全,选择文件的时候可以有补全。有些shell甚至支持通过补全来切换版本控制的分支。由于shell里面可以运行的程序千差万别,shell一般不会内置针特定对某个工具的补全功能。与之相对的,shell提供了一些补全用的API,交由用户编写对应的补全脚本。 在这里,我想向大家介绍如何利用提供的API,来编写一个shell补全脚本。由于需要覆盖的内容较多,所以分为Bash和Zsh两篇。也许有fish用户会抱怨,fish又一次被忽略了:D。之所以只有Bash和Zsh的内容,是因为:1. 这两种shell的用户占了shell用户的绝大多数。2. 我没有用过fish,所以对这方面也不了解。希望有人能够锦上添花,写一个fish版本的补全脚本教程。 既然想要写一个shell补全脚本,那么接下来要决定待补全的对象了。这里我选择[pandoc](http://pandoc.org/)作为目标。`pandoc`是文档转换器中的瑞士军刀,支持主流的各种标记语言,甚至对于PDF和MS Word也有一定程度上的支持。`pandoc`支持的选项琳琅满目,如果都要实现确实很花时间。所以这里就只实现General options,Reader options,General writer options大部分的内容。不管怎么说,这将会是一个“既不至于简单到让人丧失兴趣,又不至于困难到让人丧失信心”的任务。 安装`pandoc`的方式见官网上的说明,这里就不赘述了。安装完了之后,`man pandoc`就能看到各个选项的说明。大体上我们需要实现以下几个目标: 1. 支持主选项(General options) 2. 支持子选项(Reader options/General writer options) 3. 支持给选项提供参数值来源。比如在敲`pandoc -f`之后,能够补全`FORMAT`的内容。 好,让我们开始给`pandoc`写补全脚本吧! 下一篇:[跟我一起写shell补全脚本(Bash篇)](http://segmentfault.com/a/1190000002968878)

Shell