鄢倩

(conj clojurians me)

为什么要面向接口编程?


背景提要

莱恩是一名程序员,混迹Java界三载有余。从刚接触这门语言起,就被前辈告知必须遵循面向接口编程这一原则。之后,反复翻阅《设计模式》,面向接口编程的理念便深入脑髓。当然,他也不是没有思想的小白,“不好的东西不用”这种基本常识还是有的。人类这种动物,最擅长地就是提问,莱恩也会问“为什么我要面向接口编程呐?”。有经验的老人告诉过他,这样有利于解除耦合,根据依赖倒置原则——高层次的模块不应该依赖低层次的模块,抽象不应该依赖实现,实现应该依赖抽象。换句话说,就是要面向抽象编程。这顿论调,老生常谈了,但是高度概括的东西没有实践的基石,就如同空中楼阁,虚幻飘渺,能否理解,需要看小沙弥的悟性。

IoC中谁的控制权被反转?

莱恩实践过Java界家喻户晓的framework spring,被它强大的IoC容器深深吸引。好莱坞经典语录:“Don’t call me, I’ll call you”。它揭示的是这样一个道理:我需要你自然会找你。莱恩不禁问自己:IoC容器究竟和这个道理有什么关系呢?
大师说:获取和管理对象的方式被倒转了。

接下来,通过一个实际的例子探索IoC容器的意义所在。

面向接口编程=定义行为的标准

以开关和灯泡为例:

最简单的实现:开关依赖(use)灯泡,然后控制。

这个例子中,开关创建并维护了灯泡的实例。不过有一天,灯泡要延伸出多种类型了。没关系,简单实现:

开关依赖灯泡这个抽象东西,其余的灯泡派生自灯泡。

好像问题也不大。然而,实际上开关不会只控制灯泡,还有冰箱,电视,空调呢?我们终于发现,其实开关的职责不是去开关灯泡,而是开合电源。but who cares the electrinic? 所以我们假设电源就是一只信号,某位ruby大牛把所有对象的调用,都看作是向这些对象发送message。用在这里,倒是很合理。

那么现在我们运用接口隔离原则,提升一个最小接口。

依赖又不是只有聚合、组合,还有继承和实现(implement)。这样的话,我们把灯泡依赖到一个具有开合行为的接口。开关此时就定义了电源开合的接口。

这里,灯泡并不知道哪只开关让自己或亮或暗,而开关也只知道开合某个电源接口。言而总之,所谓的面向接口编程等同于定义行为的标准。不过问题来了,谁为开关连上灯泡呢?我们现在只知道开关持有一个开合接口,不能在开关里新建一个灯泡的对象,否则好不容易获得依赖抽象的好处便会付诸东流了。

怎么办?记住计算机中的任何问题都能通过构造一个抽象层解决。IoC容器就是干这件事的,根据各个实例之间的依赖关系创建和管理对象的实例。

所以说,IoC中被反转控制权的是获取和管理对象的方式,它被反转到IoC容器当中了。“I’ll call you.”中的I也就是IoC容器。

谈谈IoC和DI

大部分书上,都把这两者混为一谈。

DI是IoC的子集。IoC是个泛泛之论,通俗来讲,任何有利于程序高内聚低耦合的框架都具有IoC的特征。在OO里,Dependency被反转;在基于Event的编程模型里,Event Loop被反转了;在Observer Pattern中,Subject通知Observer的方式被反转了。

方法级别上的IoC

Template Method方法具有IoC的特点:

  • 父类方法决定了必要的流程和某些不变的部分;
  • 子类继承父类,重写可变的方法;
  • 子类只关心职责,而不关心流程。

所以是父类定义了标准,让子类实现之。

总结


  • IoC不等于DI;
  • IoC不是对象的专利,方法级别一样适用。

Vim Practice

<leader>

Vim have a extra key to activate mappings, and call this “prefix” key the “leader”.

*Mapping keys is one of the places where Vim comments does’t work.

1
2
3
let mapleader=','
nnoremap <leader>ev :vsplit $MYVIMRC<cr>
nnoremap <leader>sv :source $MYVIMRC<cr>

Abbrevation

Vim has a feature called “abbreviations” that feel similar to mappings but are meant for use in insert, replace, and command modes.

1
2
3
iabbrev what what
when you type *what<space>*
were substituted with *what<space>*

Disable old keys

This effectively disables the escape key in insert mode by telling Vim to perform (no operation) instead.

1
inoremap <esc> <nop> "no operation

Autocommands

Autocommands are the way to tell Vim to run commands whenever certain events happen.

1
2
3
4
5
6
7
8
9
10
11
12
:autocmd BufNewFile * :write
^ ^ ^
| | |
| | The command to run.
| |
| A "pattern" to filter the event.
|
The "event" to watch for.

:autocmd BufWritePre,BufRead *.html :normal gg=G
:help autocmd-events

System clipboard

1
2
3
4
5
BundleInstall fakeclip
1. Normal mode
"+Y or "+yy (use system register<+> to copy)
2. Visual mode
"+Y

当夜幕已近闭合,沉睡的晚灯张起结界,昏黄中没半点柔情,仿佛在告诉行人:非诚勿扰

夜晚11:11分,注定孤独的时分。Carl倚靠窗台失神了半晌,回过来就顺手拉紧了窗帘。拧开台灯,桌上静静地躺着一本书,是日本人西尾太和的《代码之髓》。他翻开扉页,签上自己的大名,满足地撇撇嘴,开始看起来。

Lisp是这个世界上最精练,最美观的语言


1
2
3
(setq f
(let ((x 1))
(lambda (y) (* x y))))

直观上,这个函数f的结果就是(* 1 y),也就是y的值。那么实际调用的结果如何呢?

1
2
(let ((x 2))
(funcall f 2))

即f(2)的结果是2?实际运行的结果让人大跌眼镜,是4!

Carl揉揉被蹂躏多时的眼球,问自己:为什么是这样,像梦一场?

行文到这里,我们的黑暗主角慢慢走向台前——作用域(Scope)。作用域在程序的世界里,见怪不怪,谁都知道它的第一要务就是防止命名冲突!

Carl点点头,道:嗯,教材里是这么说的。他抬头看了一下光,阴影里有种烦躁的情绪在跳动。

其实,作用域是分类别的。Lisp中用到了其中之一的动态作用域(Dynamic Scope),动态作用域最根本的特点是从时间维度上判断进入和离开某个函数具有独立的作用域。上面Lisp的例子当中,(let ((x 2)) (funcall f 2))表示一个函数,也就是独立的作用域。它调用了f这个函数,但从时间上看,调用点依旧处于原来的作用域当中,所以x=2这样的变量在f函数里是有效的,最后的结果是(* 2 y),等于4也就理所应当了。

Carl接受了这样的解释,他想:既然存在,那么一定合理。闭目养神的时间里,他的思维遨游了很久,有溺水的迹象,仿佛得到了神启。他想到了人生,理想,家人,婚姻和python2

python2里面,这个世界里有怪物存在——嵌套的函数和外部作用域的再绑定。


1
2
3
4
5
6
7
x = "global"
def foo():
x = "foo"
def bar():
print x
bar()
foo()

答案是global,而不是想当然的foo。python2里头使用了静态作用域(Static Scope), 与动态作用域相对。静态作用域的显著特点是源码级别上拥有独立的作用域,简单地可以理解成每一个函数都是独立的作用域。然后,2011年发布的python2.1修改了逻辑,最终返回了foo,他们显然认为嵌套的函数不是函数,而是形同if/else这样的嵌套结构体。感觉上,又回到了当初动态作用域的年代。

Carl咽了口水,他讨厌动态作用域如同父母窥视了孩子狭小私人空间,这让他很不舒服。有一首诗:阴郁的日子总会过去。接下来,事情总算不太坏。

1
2
3
4
5
6
7
def foo():
x = "old"
def bar():
x = "new"
bar()
print x
foo()

这回得到的还是old。

Carl笑了,这才像话。

1
2
3
4
5
6
7
8
def foo():
x = "old"
def bar():
nonlocal x
x = "new"
bar()
print x
foo()

这回得到的却是new。

Carl哭了。他想起来了,java里的类,private, protected修饰符,还有静态变量。他触摸到了作用域的本质,如同一直看不见的手,抚摸那薄膜面纱背后吹弹可破的脸蛋。至于有没有被赐予洪亮的耳光,那已经是后话了。

背景


假设某种Web App的业务场景是这样的:每个页面仅仅只有简单的表单,包括文字提醒label, 单选radio button, 问题列表dropdown menu, 处理页面前后跳转的button, 复杂一点的如价格输入input,时间选择date picker。而且这些可以独立展现业务价值,也就是说任何一个或者多个这样的页面元素可以经过简单组合,甚至无需组合就能反映一个独立的业务提问(具备评估填写人能力的价值),那么我们就可以将这些页面元素及其简单的组合体作为一个独立的component。这些component最大的好处就是恰如其分地反映了它本身的业务,但是这也不能成为封装成component的理由——因为我们完全可以通过一个良好名称的id来反映。独立出来的好处无非两个:可插拔,能复用。

首先,来讨论一下可插拔这个问题。假如BA美女告诉说某个业务提问T不应该再出现在A页面了,它应该出现在B页面。若不是component,问题来了:我们需要删掉本页面的T,连带着需要删除分散的js和css文件。麻烦!若是独立一个component,简单了先解除该页面对T的使用(在AngularJS中是删除对应的指令),然后在B页面导入进来。因为是component,且每个页面的结构都是表单,代码结构也类似,所以改动的量少。方便!

再来讨论一下能复用这个问题。我刚才提到过,因为是反映一个独立的业务提问,所以很可能在其他同类的产品上,这东西也是存在的,可能唯一不同的就是问法不同,以及提示信息有别。换句话说这些问句和提示是可编辑的。有人说那简单呐,component的所有提问和提示信息弄成可编辑的不就行了?component是可变的(可编辑),但是具体在哪个页面给出什么提问和提示总是不变的吧。我原来也是这样想的,但是仔细想想我发现这里有一个矛盾之处:我们想复用业务,而不是组件!这世界上最不可信的就是业务,最可能变的也是业务,但我们还异想天开地复用业务。任何一个独立的component,在具体的业务上,都很可能不是简单地替换显示文本,它完全会增加一小条额外的询问条目!那么原来的component就只能乖乖地被copy/paste成另一个可怜的component——因为它们是不同的业务啊!BA如是说。作为程序员,我讨厌重复的代码,这也是我们不断使用重构的原因。但是就因为业务不同,哪怕代码相似度是99%,我也得重新创建一个,这个实在不值得!

而且,更奇葩地是这些可编辑信息居然被写入了后端的properties文件当中,使用Java这样静态的语言来抽象出一个给这些component提供文本信息的接口。试想一下,哪天某个BA告诉我:我想为某个业务X,添加一个subTitle。我的回答:不行呐,我们的接口只提供了一个label,而且这里已经使用了,我如果加一个新的,会影响其他的所有component的。BA:!&^$%@$. 我接着说:我可以提供一个sub.label的property key,这样前端可以写成 [X.sub].label,如何?BA:呵呵……

探索


简单得出几个结论

  • 不用妄图复用业务,即使它们在UI表现上极其相似;
  • 业务和UI的关系,等同于饮料和瓶子的关系,瓶子一个模子就够了,饮料随便换;
  • 饮料要换个瓶模子了,那换就是,具体是从原来的模子继承,还是组合,饮料不需要关心。

如何做瓶子,如何换饮料,如何组合成卖品

做瓶子简单。作为程序员,我使用AngularJS的Directive构建基础的common components,如radio button, dropdown menu, search box. 这些仅仅和UI相关。当然这些还仅仅是瓶子的组成成分。

那么接下来,如何换饮料呢?饮料有自己的骄傲——它不仅口味各异,还分品牌!每种品牌下的饮料自然不同。那我们只好先做一下分类。先从品牌开始,先分成X, Y, Z这几类,然后开始分口味了,又分成A, B, C这几类。这个季度,老板说我们要推出X类的A, B, C饮料!怎么做?我们把A, B, C写入配置文件(properties),拿A举例子:A有颜色,甜度,气味三个属性,分别对应如下:

1
2
3
4
A:
color: red
sweet: middle
smell: nice

Ok,接下来我们来构造A这款饮料的业务的directive

1
2
3
4
5
6
7
8
9
10
(function(global, angular){
'use strict';
angular.module('a.b.c',[])
.directive('A', function(){
return {
restrict: 'AE',
templateUrl: 'views/A.html'
};
});
})(this, angular);

好了,我们有业务细节A了,又有业务A的directive了。我们理所应当地认为这个directive和A应该是一体的,为什么不直接将A细节写入directive的scope中呢?答案是X, Y, Z在这里是可以复用这个directive的,所以不能硬编码。不能硬编码,我首先的直觉就是变量替换,又要求复用,那么就立马想到了继承或者组合。又因为组合优于继承(更现实的是我不知道如何在AngularJS中实现继承),我决定使用组合。接下来这个过程的产出物就是我们的卖品,构造如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# define in common module
A:{
color: red,
sweet: middle,
smell: nice
}
##

(function(global, angular){
'use strict';
angular.module('a.b.c', ['common'])
.directive('A', ['me', function(){
return {
restrict: 'AE',
scope: {
self: '='
},
link: function($scope) {
$scope.self = me;
},
templateUrl: 'views/A.html'
};
}]);
})(this, angular);

从代码中很容易看出,我们把A这个业务,组成了一个叫做common的module,可以类比为装着众多饮料的机器,然后对准一个叫做A的瓶子注入,注意A这个瓶子用到了很多common components。这整个过程就是Angular完成这个A的directive的过程,也就是饮料和瓶子组装的过程。然后这个A的directive就是我们的卖品。

实际成效


事实验证,上述的方式是不合理的。理由有下面三点:

  • 依赖过剩。我们将会有很多的components,如果每次都得手动注入common依赖的话,量实在是太大了。
  • 非独立。一个纯净的业务组件不应该有第三方的依赖,有依赖,必然导致无法独立使用。
  • 不可扩展设备。这个最大的怪兽就是我们将业务的东西全部放到了前端,如果要兼容其他设备,我们后端的这块业务功能是缺失的,不能给其他设备提供支持。

我的疑问


面向对象编程和函数式编程的最大不同点在什么地方?

  • 数据从何而来?面向对象编程中的数据都是对象本身产生的,也就是说对象的基础建立在它本身的属性上,没有属性,那么这个对象什么都不能做。函数式(闭包)的过程数据来自于外部,也就是说闭包本身是不维护数据的。这样的好处是只管逻辑,而不用维护数据本身复杂的状态。

  • 数据的状态如何?面向对象编程中数据(也就是属性)是可以改变的,而函数式中数据是不可以改变的。

  • 没有属性的对象,还是面向对象嘛?我认为controller, service, DAO(repository)其实都是没有数据的对象!也就是说这些分层结构其实都是函数式编程可以应用的地方。有人会说奇怪了,controller中明明有service属性,service中明明有DAO属性啊,怎么能说没有数据呢?其实,这个只是调用链(我喜欢把它们看作是filter),真正的数据(大概被称为model)是被一层层传递下去的。可能又有人会反驳,函数式中最大的特点就是数据不可变,这些真正的数据(model)在传递过程中其实是会被改变的,所以怎么能说这些地方可以应用函数式编程?我认为,这些数据在各个层上应该是不同类的。展现给view的,和存储到database中的model是很难相同的,所以也就存在一种模型之间转化的可能,一个模型变成另一个新的模型,模型本身是不会变的。

  • 没有方法的值对象呢?就是数据吧。

AOP和函数式编程有没有关系?


AOP是什么?

AOP是面向切面编程。我们平时编写web程序的时候,通常都会使用controller->service->dao这样的层次结构的调用链。事实上,有些逻辑不是纵向延伸的,对于单个的层级,如service,一个典型的应用就是Log处理:我们需要对service中涉及写入数据库的操作进行记录,这样的逻辑对于service上的所有需要log记录的方法而言是通用的。所以没有必要在各个不同的services上手工记录log,我们只需要将这部分log的逻辑抽出来作为独立的方法,然后很形象地表述成在services上的方法打上一个个洞,将这部分独立的方法像插槽(阀门)一样插入这些洞里,流过services上这些被记录的方法上的数据及其本身就会被完整的记录下来。

AOP的概念也很有趣。事实上,AOP中的Joinpoint,Pointcut,Advice可以分别类比为钩子,晾衣杆和晾衣服这个动作。Joinpoint就是要拿来鼓捣的方法,Pointcut则是将这些方法串起来,Advice是统一处理这些方法的逻辑操作,同时也包括了它自身的执行时机,是在Pointcut之前、之后或是其中,当然更有甚者,是在方法返回之后,还是抛出异常之后呢?

那么接下来,我们来看看AOP和函数式编程到底有没有关系。第一个方面,AOP其实是间接地拿函数作为自己的参数了。举个例子,拦截controller的方法:

1
2
3
4
5
6
7
@AfterReturning(value = "execution(* controller.*.*(..)) && args(customer)",
returning = "result")
public void aMethod(Customer customer, String result) {
System.out.println("before");
System.out.println(result);
System.out.println("after");
}

其实这样的方式——既拿到了输入参数,又拿到了输出参数,就等于已经拿到了整个方法,何况还有牛掰的@Around,直接拿人家的方法到自己的方法体里执行。而函数式第一要点就是函数是一等公民,函数可以作为参数传递。所以说从获取参数这个层面说,两者实在太相像了。

第二个方面,抽取共有逻辑。函数式中有惰性求值,假设我们在函数式中预留一个Function作为钩子,任何想使用这部分函数逻辑的在需要的时候,通过某种机制传递真正的Function到我们的预留参数中,是不是也能实现抽取共有逻辑呢?还有,我们之所以有很多services,是因为model(domain)存在差异,假设函数式中没有OO的概念,model似乎就趋同了,那么或许我们根本不需要services。所以函数式是可以抽取共有逻辑的,或者说这部分逻辑本来就不应该分散到各个services中。

结论处,我大胆地猜测:AOP其实就是OO被掣肘之后,利用函数式缝补自身的妥协方案。AOP本来就是Functional programming.

Functional Programming的要点


  • 声明式编程
  • 不变量
  • 无副作用的函数
  • 不会依附于任何类或者对象,这点“functional programming for java developer”中有清楚描述。在java中写的大部分函数式代码都是静态的,但逻辑上,它不属于这个类。

Mysql 5.5 CharacterSet Configuration

Client Configuration

  • #1 No need to restart server for setting client

When

1
2
[client]
default-character-set = utf8

Then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql  Ver 14.14 Distrib 5.5.38, for debian-linux-gnu (x86_64) using readline 6.2

Connection id: 40
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: latin1
Db characterset: latin1
Client characterset: utf8
Conn. characterset: utf8
  • #2 It will change character_set_client & character_set_connection & character_set_results without client’s setting.

When

1
2
[client]
#default-character-set = utf8

Then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Connection id:		37
Current database: mall
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: latin1
Conn. characterset: latin1
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 14 min 4 sec

And Then

1
2
3
4
5
6
7
8
9
10
11
12
13
For a special DB.
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+

Server Configuration

  • #1 Restart mysql server without character-set-server=utf8

When

1
2
[mysqld]
#character-set-server=utf8

Then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql  Ver 14.14 Distrib 5.5.38, for debian-linux-gnu (x86_64) using readline 6.2

Connection id: 42
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: latin1
Db characterset: latin1
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 8 min 47 sec
  • #2 Create a new database newa without character-set-server=utf8

When

1
2
[mysqld]
# character-set-server=utf8

Then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Connection id:		36
Current database: newa
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: latin1
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 12 sec
  • #3 Restart server with character-set-server=utf8

When

1
2
[mysqld]
character-set-server=utf8

Then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Connection id:		38
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 23 min 46 sec

But For newa DB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CConnection id:		38
Current database: newa
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.5.38-0ubuntu0.12.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: ***latin1***
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /var/run/mysqld/mysqld.sock
Uptime: 22 min 36 sec

So That Means

The character set used by the default database. The server sets this variable whenever the default database changes. If there is no default database, the variable has the same value as character_set_server.

conclusion

  • For client, connection, results, you should add
    [client] default-character-set = utf8

  • For server and DB, you should add
    [mysqld] character-set-server=utf8

  • You should drop original DB when created in error character set, just like newa DB

Sublime Text3 Plugins

jshint for sublime installation

  1. brew install nodejs
  2. npm install -g jshint
  3. open sublime & package control install jshint
  4. that’s ok

install SublimeOnSaveBuild

  1. open sublime & package control install SublimeOnSaveBuild
  2. that will build jshint once save

expand region

  1. open sublime & package control install Expand_region
  2. change the default-keymap
    { “keys”: [“super+w”], “command”: “expand_region”}

jshint gutter

  1. open sublime & package control install jshint gutter
  2. ctrl+shit+j to lint code!

PackageResourceViewer

  1. install PackageResourceViewer by package control
  2. ctrl+shift+p to use ‘open resource’
  3. find tm.theme, then add something end
1
2
3
4
5
6
7
8
9
10
11
<dict>
<key>name</key>
<string>Bracket Curly</string>
<key>scope</key>
<string>brackethighlighter.curly</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CC99CC</string>
</dict>
</dict>

BracketHighter

  1. must have packageresourceviewer at first
  2. clear the annotation of color, and then save

Trailing Spaces

  1. install Trailing Spaces by package control
  2. configure keymap
1
2
3
4
5
6
7
8
9
10
11
12
{
"command": "toggle_trailing_spaces",
"keys": [
"ctrl+shift+d"
]
},
{
"command": "delete_trailing_spaces",
"keys": [
"ctrl+shift+t"
]
}
  1. configure trailing_spaces.sublime-settings
1
2
3
4
5
## color: invaild, comment, error
{
"trailing_spaces_highlight_color": "comment",
"trailing_spaces_trim_on_save": true
}

Centurion theme

  1. install theme Centurion first,
  2. open Preferences->Settings - User,
  3. insert {"theme": "Centurion.sublime-theme"},
  4. every time, subl3 use .sublime-theme as suffix.

install SyncedSideBar (keep sync between sidebar and opening file)

  1. install SyncedSideBar,
  2. open Preference->Key Bindings - User,
  3. configure keymap
1
2
3
4
{
"command": "reveal_in_side_bar",
"keys": ["alt+f1"]
}

Rake Version Conflicts

1
bundle exec rake CMD.

Invalid byte sequence in US-ASCII

1
2
3
4
5
6
7
8
*Excpetion*
bundle exec rake generate
(in /Users/qianyan/Dropbox/octopress)
Generating Site with Jekyll
identical source/stylesheets/screen.css
Configuration from /Users/qianyan/Dropbox/octopress/_config.yml
Building site: source -> public
YAML Exception reading 2013-12-10-hello-world.markdown: invalid byte sequence in US-ASCII

Resolve this problem

1
2
3
4
> vim ~/.zshrc
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
> source ~/.zshrc

  1. Find your theme file in ~/.oh-my-zsh/themes/, then edit it;

  2. Add local svn_info='$(svn_prompt_info)';

  3. Append %{$reset_color%}${svn_info} to Prompt variable;

  4. Add followings to END-OF-FILE.

1
2
3
4
ZSH_THEME_SVN_PROMPT_PREFIX="%{$fg_bold[blue]%}svn:("
ZSH_THEME_SVN_PROMPT_SUFFIX="%{$fg_bold[blue]%})"
ZSH_THEME_SVN_PROMPT_DIRTY="%{$fg[red]%} ✘ %{$reset_color%}"
ZSH_THEME_SVN_PROMPT_CLEAN="%{$FG[040]%} ✔"

And you can refer the theme (awesomepanda)

修改Vagrantfile


1
2
3
4
5
6
# 把客户机器(ubuntu)的port 3306 映射到宿主机器上的port 3306
config.vm.network :forwarded_port, guest: 3306, host: 3306

# 执行安装程序
config.vm.provision :shell :path => "install_mysql.sh"

mysql安装程序


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
echo "Setup mysql-server-5.5 if not exists.."
sudo debconf-set-selections <<< 'mysql-server-5.5
mysql-server/root_password password root'
sudo debconf-set-selections <<< 'mysql-server-5.5
mysql-server/root_password_again password root'
sudo apt-get update
sudo apt-get -y install mysql-server-5.5

if [ ! -f /var/log/databasesetup ];
then
echo "CREATE USER 'codeeker' IDENTIFIED BY 'codeeker'" | mysql -uroot -proot
echo "CREATE DATABASE mall" | mysql -uroot -proot
echo "GRANT ALL ON mall.* TO 'codeeker'@'%'" | mysql -uroot -proot
echo "flush privileges" | mysql -uroot -proot

touch /var/log/databasesetup #为了不重复执行创建用户的操作

if [ -f /vagrant/data/initial.sql ];
then
mysql -uroot -proot codeeker < /vagrant/data/initial.sql
fi
fi

陷阱

错误一

  • 报错误Lost connection to MySQL server at 'reading initial communication packet', system error: 0
  • 原因是 /etc/mysql/my.cnf中有bind-address = 127.0.0.1,会拒绝非localhost的访问,包括vagrant映射port到的宿主主机上
  • 解决的方法是修改bind-address = 0.0.0.0,表示接受本网络中所有的ip登录

错误二

echo "CREATE USER 'codeeker' IDENTIFIED BY 'codeeker'" | mysql -uroot -proot

如果写成了

echo "CREATE USER 'codeeker'@'localhost' IDENTIFIED BY 'codeeker'" | mysql -uroot -proot

那么在宿主的机器上,会报access deny错误。

  • 原因是这样创建的用户,只能在localhost上登录
  • 解决的方法是去掉@'localhost'
0%