碎片

记录点点滴滴

使用gitlab和gitlab_ci进行持续集成

Git是越来越火了,根据聪明的程序猿都是随大流的原则,我也开始使用Git了。当然,一个人用不如大家一起用,所以还需要一个团队能共享的git仓库,有朋友推荐了gitlab,看后感觉很好很强大,而且还有gitlab_ci进行持续构建。不过只是持续构建还不够,如果能持续部署就更好了:)

持续部署其实并不难,只要在持续构建的单元测试通过后,可以自动将最新的代码部署到测试环境,并重启测试环境的服务,我们就可以在测试服务器上访问到最新的内容了。

安装gitlab很简单,只要按照官方文档一步一步做就好了,有一点需要注意的就是,官方文档的步骤只适合Ubuntu和Debian系统。

GitLab安装指南

安装gitlab之后,我把gitlab_ci装在了另外一台服务器上,当然也可以装在一起。安装时需要注意第二步,文档中使用RVM方式安装的Ruby,我测试时对后续步骤有影响,就干脆按照GitLab安装指南的做法,直接安装的Ruby。如果装在同一台服务器上,还要注意nginx的端口配置,两个不能都使用80端口。

GitLab CI安装指南

安装一下nose,一会儿需要执行测试。

1
sudo pip install nose

系统装后好,先在GitLab上创建一个项目,叫examples,我的服务器IP是192.168.8.202,项目的SSH地址是git@192.168.8.202:yachuan.chen/examples.git。

创建本地仓库:

1
2
3
4
5
6
7
8
mkdir examples
cd examples
git init
touch README
git add README
git commit -m 'first commit'
git remote add origin git@192.168.8.202:yachuan.chen/examples.git
git push -u origin master

接下来在GitLab CI(以后简称CI)添加一个持续构建的项目,我的地址是http://192.168.8.201,这里会出现一个错误提示“Project path is not a git repository”,持续构建的目录必须是一个Git仓库,其实就是在CI服务器上要有一份Examples的Clone,现在SSH到CI服务器上去创建Project path。 由于CI是使用gitlab_ci用户运行的,所以也是用该用户进行clone。

1
2
3
4
5
$ cd /home/gitlab_ci/workspace
$ sudo -u gitlab_ci -H git clone git@192.168.8.202:yachuan.chen/examples.git
<div class=""></div>
FATAL: R any yachuan.chen/examples deploy_ab12176da49cec6816daf762dbe6e76d DENIED by fallthru
...

直接这么做会报错,因为CI服务器还没有权限使用ssh进行clone,需要本地生成一个ssh key。

1
2
3
$ sudo -u gitlab_ci -H ssh-keygen -q -N '' -t rsa -f /home/gitlab_ci/.ssh/id_rsa
$ sudo -u gitlab_ci -H cat /home/gitlab_ci/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3rFoK7n+EYolWlLmPTWQxHiAsv7xFCuVVgEeSqstMolzcBOShIdh2sYj0fsfqmllMCqt6C52V66xCV50xsu4YkUA8Zo1MIVLwwpzAI0NeYuGVWjquQaeuLl+KKqmk7fq3NRCXc7/Ce8wb8VvWPxr89uhKuakhKXd6UylpY0DjhZHqQ5XSTBjjY/CMzv9rA6he+aofS5biJF2d5DxIvOkWjXzfBzrR1cGxtnkYYvG9dYJU8A4876LjqrHLY++hS6QtCXD9gSOk0aCQ9qzvNV+lvmg1919wMfGjRKd1CnXLfkGS45EjZxq8O/gzvxDxGNTXV42Zx/72WDl4y5jaCam7 gitlab_ci@smile-1

在GitLab>Project>Examples>Deploy Keys中,点击Add Delpoy Key按钮,将id_rsa.pub的内容复制到Key中,再起个名字,再执行刚才的Clone命令就之后,可以就完成Add Project的操作了。注意,Scripts是必填项,这里先只填一个/usr/local/bin/nosetests,后续在补充。

进入CI系统的Project: examples → Details页面: Details页面 我使用的GitLab是4.0版本,按照提示,将Project URL和Project Token复制到GitLab → Project → Services中的GitLab CI中。 这时,我们进行git push后,将会触发gitlab ci执行scripts中的脚本。

使用Backbone重构拖拽式任务白板

上次用jqueryui实现了一个可以拖拽的任务白板,实际项目中随着功能逐渐增多,传统的js组织形式就很凌乱了,这个时候要引入前端js框架,来保证代码的质量,尤其是清晰度和扩展性。前端js的框架可选的很多,比如backbone.js、ember.js等,本文采用的是backbone。使用backbone先要引入两个js文件,一个是underscore.js,这个文件是backbone框架依赖的基础库,另外一个是backbone.js本身。

1
2
  <script src="http://backbonejs.org/test/vendor/underscore.js"></script>
  <script src="http://backbonejs.org/backbone.js"></script>

重构之前先分析一下页面内容,需要有以下几个元素:

  • 模型:
    • 任务
    • 任务集合
  • 视图:
    • 任务卡
    • 任务栏

使用继承Backbone.Model创建Task类作为模型,继承Bankbone.View创建TaskCard作为视图。

1
2
3
var Task = Backbone.Model.extend({});

var TaskCard = Backbone.View.extend({});

利用javascript是动态语言的特性,模型暂时不需要添加属性和方法,现在需要定义TaskCard视图的属性,一个Card是一个li元素,视图渲染时,会使用Model填充内容,填充的内容定义为一个模板(template)。

1
2
3
4
5
6
7
8
9
10
var TaskCard = Backbone.View.extend({
    tagName: "li",
    //卡片模板,这里可以更复杂。
    template: _.template('<h5><%-title%></h5>'),
    //使用模板和模型渲染卡片成html。
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    }
});

现在可以使用Task和TaskCard在页面上显示任务卡片了,我们可以动态的创建任务卡片。

1
2
3
4
5
6
7
8
9
10
11
//创建任务对象
var task1 = new Task({'title':'在弹出窗口中输入任务信息,点击save按钮保存到数据库。'});
var task2 = new Task({'title':'任务卡片可以在状态栏之间拖拽。'});

//创建卡片对象
var taskCard1 = new TaskCard({model:task1});
var taskCard2 = new TaskCard({model:task2});

//将卡片放到NEW状态的任务栏中
$('#taskcards_ul').append(taskCard1.render().$el);
$('#taskcards_ul').append(taskCard2.render().$el);

下一步将任务栏设计为一个视图类,方便进行动态扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var TaskBoard = Backbone.View.extend({
  tagName: "div",
  className:"cardbox",
  //使用预先定义的模板
  template: _.template($('#taskboard-template').html()),
  initialize:function(){
    this.render();
  },
  render: function() {
    //使用一个对象渲染模板
    this.$el.html(this.template({name:this.options.name}));
    return this;
  }
});
//创建一个名字为“New”的任务栏。
var newTaskBoard = new TaskBoard({
  name:'New'
});
//将任务栏显示到页面中
$('#taskboards').append(newTaskBoard.$el);

现在页面上只有一个New状态的任务栏了。这里用到了一个模板taskboard-template,是预先在html中定义好的一段html代码,underscore.js使用JSON格式的Object对模板进行渲染,<%%>之间用Object的属性填充。Taskboard的模板如下:

1
2
3
4
<script type="text/template" id="taskboard-template">
    <h4 class="page-header"><%-name%></h4>
    <ul id="taskcards_ul" class="ui-helper-reset"></ul>
</script>

再添加两个任务栏,分别是Progress和Done。

1
2
3
4
5
6
7
8
9
10
11
var progresssTaskBoard = new TaskBoard({
  name:'Progress'
});

var doneTaskBoard = new TaskBoard({
  name:'Done'
});

//将任务栏显示到页面中
$('#taskboards').append(progresssTaskBoard.$el);
$('#taskboards').append(doneTaskBoard.$el);

现在三个状态栏都是动态创建的,加入任务卡的代码显得很不协调,因为是直接通过jquery将TaskCard放入到状态栏中,现在需要引入Collection类了。

1
2
3
var TaskList = Backbone.Collection.extend({
  model: Task
});

Collection是一组Model的集合,并提供了add、remove等集合操作的方法,接下来创建一个New状态的TaskList,并且和New状态的任务栏关联起来。修改之前的代码,让Taskboard可以和TaskList绑定,并监听TaskList的add事件,当add事件被触发时,调用Taskboard的addOne方法,创建一个任务卡并放到状态栏中。同时删除创建任务卡的代码,修改之前添加任务卡的逻辑,不需要在最外层创建TaskCard了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    var TaskBoard = Backbone.View.extend({
      tagName: "div",
      className:"cardbox",
      template: _.template($('#taskboard-template').html()),
      initialize:function(){
        this.render();
        //绑定一个任务集合
        this.tasks = this.options.tasks||new TaskList;
        //监听任务集合的add事件
        this.tasks.on('add',this.addOne,this);
      },
      render: function() {
        this.$el.html(this.template({name:this.options.name}));
        this.taskcards = this.$('#taskcards_ul');
        return this;
      },
      addOne: function(task){
        var taskcard = new TaskCard({model:task});
        taskcard.render().$el.appendTo(this.taskcards).fadeIn();
      }
    });
    //创建任务集合
    var newTasks = new TaskList;

    var newTaskBoard = new TaskBoard({
      name:'New',
      //设置任务栏关联的任务集合
      tasks:newTasks
    });

    //将卡片放到NEW状态的任务栏中
    newTasks.add(task1);
    newTasks.add(task2);

页面布局调整完后加入拖拽效果,先套用之前的代码逻辑,只是将其转移到TaskCard和TaskBoard中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    var TaskCard = Backbone.View.extend({
      ... ...
      //类构造函数
      initialize:function(){
        //设置任务卡可以被拖拽,不需要每次render时重复设置,在这里只会设置一次。
        this.$el.draggable({
            revert: "invalid",
            containment: "document",
            helper: "clone",
            cursor: "move"
        });
      },
      ... ...
    });

    var TaskBoard = Backbone.View.extend({
      ... ...
      initialize:function(){
        ... ...
        //使用闭包保持现在的上下文
        var that = this;
        //设置当前状态栏被拖入时的行为。
        this.$el.droppable({
            activeClass: "ui-state-highlight",
            drop: function( event, ui ) {
                var $card = ui.draggable;
                $card.fadeOut(function() {
                    $card.appendTo( that.taskcards ).fadeIn();
                });
                //输出拖拽之后,任务集合的变化。
                console.log(that.tasks.length);
                console.log(newTasks.length);
            }
        });
      },
      ... ...
    });

现在页面上可以实现拖拽效果了,但我们观察浏览器的console发现,拖拽之后,任务集合的状态并没有变,只是视图变了,模型并没有变。我们希望拖拽结束后,被拖拽的Task从原来的集合中删除,并加入到拖入栏目关联的集合中。在drop函数中无法直接获取Task对象,可以利用模型的cid是唯一特点,将cid保存在li元素的id属性中,然后在drop时取出id属性,根据这个id遍历所有任务集合,获取Task对象。修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    //创建任务集合,三个都要在外部创建,并初始化到对应的TaskBoard中。
    var newTasks = new TaskList;
    var progressTasks = new TaskList;
    var doneTasks = new TaskList;

    var TaskCard = Backbone.View.extend({
      ... ...
      render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        //把li元素的id属性设置为模型的cid
        this.$el.attr('id', this.model.cid);
        return this;
      }
    });

    var TaskBoard = Backbone.View.extend({
      ... ...
      initialize:function(){
        ... ...

        var that = this;
        this.$el.droppable({
            activeClass: "ui-state-highlight",
            drop: function( event, ui ) {
                var cid = $(ui.draggable).attr('id');
                ui.draggable.fadeOut(function() {
                    //遍历所有任务集合
                    _.each([newTasks,progressTasks,doneTasks],function(from){
                      //跳过当前状态栏的任务集合
                      if(that.tasks!=from){
                        var draggableTask = from.get(cid);
                        if(typeof draggableTask != 'undefined') {
                          from.remove(draggableTask);
                          that.tasks.push(draggableTask);
                        }
                      }
                    });
                    console.log(that.tasks.length);
                    console.log(newTasks.length);
                });
            }
        });
      },
      ... ...
    });

现在观察控制台输出,已经达到我们预期的效果了。其实代码还有重构空间,fadeOut行为应该绑定到任务卡上,为TaskCard自定义一个事件叫dropped,from.remove会触发TaskList的remove事件,在响应remove事件时,再触发一次task的dropped事件,就可以将fadout行为和TaskBorad解耦。重构后的最终代码参考:Gist

Bootstrap+jquery+jquerui实现拖拽式任务白板

bootstrap是一套Html+CSS+Javascript的轻量框架,可以很容易的实现美观的UI界面和动态效果,最近需要实现个拖拽效果,只用bootstrap没有现成的办法,就引入了jqueryui+jquery。基础页面样式引用了bootstrap starter

首先,把页面中引入的javascript和css都改成完整的URL,并加入jqueryui和jquery。

1
2
3
4
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
<link href="http://twitter.github.com/bootstrap/assets/css/doc.css" rel="stylesheet">
<link href="http://code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css" rel="stylesheet"/>
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css" rel="stylesheet">

注意js文件放在页面的最后,这样可以使页面加载更快。这里我只引入了一个了bootstrap.js。

1
2
3
<script src="http://twitter.github.com/bootstrap/assets/js/jquery.js"></script>
<script src="http://code.jquery.com/ui/1.9.1/jquery-ui.js"></script>
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap.js"></script>

然后删除页面中Container原来的内容,换成我们需要的白板栏,共有三个状态栏:New、Progress和Done。

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
    <div id="new_tasks" class="cardbox">
      <h4 class="page-header">New</h4>
    </div>
    <div id="progress_tasks"  class="cardbox">
      <h4 class="page-header">Progress</h4>
    </div>
    <div id="done_tasks"  class="cardbox">
      <h4 class="page-header">Done</h4>
    </div>
</div>

cardbox是自定义的样式表,用来显示一个状态栏位。

1
2
.cardbox { border:1px solid #AAA; float: left; width: 31%; min-height: 18em; padding: 0.5em;}
.cardbox h4 {margin: 0px;}

加上样式后是这样的:

效果图

增加两个个卡片,这里使用html中的ul和li元素实现,不过要调整一下样式表,li元素用了样式ui-helper-reset,这是jqueryui支持的,目的是不显示列表前的原点儿。

1
2
3
4
5
6
7
<div class="cardbox">
    <h4 class="page-header">New</h4>
    <ul class="ui-helper-reset">
        <li><h5>在弹出窗口中输入任务信息,点击save按钮保存到数据库。</h5></li>
        <li><h5>任务卡片可以在状态栏之间拖拽。</h5></li>
    </ul>
</div>

要增加一些css,控制任务卡片大小、内外边距以及拖拽时的鼠标图标。custom-state-active一会儿会用到,当状态栏允许放入时会变色。:

1
2
3
4
5
6
7
8
.cardbox {border: 1px solid #AAA;float: left;margin-left: 10px;min-height: 46em;padding-left: 0.2em; width: 31%; }
.cardbox h4 { line-height: 16px; margin: 0 0 0.4em; }
.cardbox h5 { margin: 0.1em 0.1em 0 0.1em; }

.cardbox.custom-state-active { background: #eee; }
.cardbox li {background-color: white;border: 1px solid #AAA;color: #222;float: left;height: 84px;list-style: none;margin: 0.3em;padding: 0.2em;width: 164px;}
.cardbox li a { cursor: move; margin: 0 0 0.4em; }
.cardbox li p { float: right; margin: 0 10px 10px; }

样式加好后,可以写js了,在页面的最后加上javascript块,先允许New中的任务卡片可以拖拽,具体参数参考jqueryui文档

1
2
3
4
5
6
7
8
9
10
var $new_tasks = $( "#new_tasks" ),
    $progress_tasks = $( "#progress_tasks" ),
    $done_tasks = $( "#done_tasks" );

$( "li", $new_tasks ).draggable({
    revert: "invalid",
    containment: "document",
    helper: "clone",
    cursor: "move"
});

现在任务卡片可以拖动了,但是还不能放到其他的状态栏目里,还需要设置其他栏目允许放置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$progress_tasks.droppable({
    accept: "#new_tasks li",
    activeClass: "ui-state-highlight",
    drop: function( event, ui ) {
        move_item( ui.draggable , $progress_tasks);
    }
});

function move_item( $item, $tasks) {
    $item.fadeOut(function() {
        var $list = $( "ul", $tasks ).length ?
            $( "ul", $tasks ) :
            $( "<ul class='ui-helper-reset'/>" ).appendTo( $tasks );
        $item.appendTo( $list ).fadeIn();
    });
}

accept参数指定了那些元素可以被放入该栏目,这里允许New状态的任务卡放入。move_item函数负责将拖拽的元素从原栏目放入新栏目,如果新栏目中不存在ul元素,则创建一个,move_item函数在将卡片拖拽到Progress栏目中,释放鼠标时触发调用drop。按照这个逻辑,设置Done状态栏,只允许拖入Progress状态栏中的任务卡。此时拖拽New状态时,将不能放入Done状态栏。 为了可以将人物卡片重新放回New状态,需要设置New状态栏允许放置,并设置Progress状态和Done状态都可以拖入。

1
2
3
4
5
6
7
$new_tasks.droppable({
    accept: "#progress_tasks li,#done_tasks li",
    activeClass: "ui-state-highlight",
    drop: function( event, ui ) {
        move_item( ui.draggable , $new_tasks);
    }
});

完整代码在这里Gist

OOBootcamp Season One 01

有幸请到了姜志辉老师来到公司帮助我们实施《OOBootcamp》这项培训计划。这项计划将持续四期,每期四次课,课程将采用训练营的方式,以实战为主,从动手中提炼和总结知识,讲师只负责引导和指点,与传统的填鸭式培训有很大的不同。第一季我们采用的语言是javascript,用一种大家都不熟悉的语言,来理解面向对象(OO)的本质。

创建对象

javascript中创建对象的两种方式,使用{}和new Object方式(objects.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var assert = require('assert')

describe('object', function() {
    it('{}', function() {
        var obj = {
            name: 'obj',
            des: 'des'
        };
        assert.equal('obj', obj.name);
        assert.equal('des', obj.des);
    });

    it('Object', function() {
        var obj2 = new Object();
        obj2.name = 'obj2';
        obj2.des = 'des';
        assert.equal('obj2', obj2.name);
        assert.equal('des', obj2.des);
    });
});

代码中使用了nodejs中的单元测试框架mocha,使用之前需要先用npm安装,安装到全局环境中需要加入参数“-g”:

$ npm install -g mocha

使用require引入mocha:

var assert = require('assert')

在describe的function中编写测试方法,第一个参数是一组测试的名称:

describe('object', function() {
    //这里写测试。
});

在it的function中编写一个测试方法,第一个参数是单个测试的名称:

it('{}', function() {
    //测试写在这里。
});

运行测试使用mocha,而不是node:

$ mocha  objects.js 
․․
✔ 2 tests complete (2 ms)

函数和作用域

函数function是javascript的内建类型之一,javascript中的作用域分为全局作用域和局部作用域,局部作用域只体现在function中,即变量的作用域在声明变量的function中,未声明的变量默认使用全局作用域。

1
2
3
4
5
6
7
var Person = function(name){
    var age=45;
    this.name = name;
    this.say_age=function(){
        return age;
    };
};

在Person这个function中,age相当与一个似有变量,他只能在function内部访问到,而name和say_age都可以在外部访问,同时say_age相当于封装了对age的访问,这就是使用function模拟类的定义。使用代码如下:

1
2
3
var jobs = new Person('jobs');
console.log(jobs.name);
console.log(jobs.say_age());

执行结果:

$ node functions.js 
jobs
45

如果不使用new来创建Person对象,如下代码:

1
2
3
4
var iverson = Person('iverson');
console.log(typeof(iverson));
console.log(name);
console.log(say_age());

不使用new的话,相当于只是执行了Person函数,由于函数没有返回值,因此iverson变量是undefined,而name和say_age则被赋值在全局作用域上。执行结果如下:

$ node functions.js 
undefined
iverson
45

这一节演示的是使用function模拟类,使用function的作用域模拟类的私有属性。

callback和工厂模式

因为function也是对象,所以function也可以被当做参数或返回值处理。被当做参数时,就是callback方式,在函数内部可以执行传入的callback参数(callback.js):

1
2
3
4
5
6
7
8
var foo = function(func){
    var name = 'jobs';
    func(name);
};

foo(function(name){
    console.log(name);
});
$ node callback.js 
jobs

下面定义一个普通的类Model(models.js):

1
2
3
4
5
6
7
8
9
10
11
12
var Model = function(id,name){
    var id = id;
    var name = name;

    this.say = function(){
        console.log(id+':'+name);
    };

};

var todo = new Model(1,'todo1');
todo.say();
$ node models.js 
1:todo1

修改为工厂模式是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Model = function(id,name){
    var id = id;
    var name = name;

    return {
        say:function(){
            console.log(id+':'+name);
        }
    }
};

var todo = new Model(1,'todo1');
todo.say();
$ node models.js 
1:todo1

使用工厂模式时有一个好处,当忘记使用new创建对象的时候,创建的对象行为和使用new时是一样的。但也有一个缺点,看以下代码:

1
2
3
4
var todo = new Model(1,'todo1');
todo.say();
console.log(todo instanceof Model);
console.log(typeof(todo));
$ node models.js 
1:todo1
false
object

使用工厂模式创建的对象,类型不是Model,而是object。当使用工厂模式时,要考虑应用场景是否依赖对象的类型。

类的继承

继承是面向对象的一个重要特性,使用javascirpt模拟继承的代码(class.js)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var Class = function(){
    var inner = function(name){
         var name = name;
         this.show = function(){
             console.log('show ' + name);
         };
    };
    inner.inherts = function(c){
        for (i in c){
            inner.prototype[i] = c[i] ;
        };
    };
    return inner;
};

var obj = {
    info:function(){
        console.log('info');
    }
};

var obj2 = {
    save:function(){
        console.log('save');
    }
};

var Person = new Class(); // class Person()
Person.inherts(obj);
Person.inherts(obj2);
var jobs = new Person('jobs');
jobs.show();
jobs.info();
jobs.save();

Class函数相当与定义类的语法,Class中的inner函数相当于类本身的定义,而inner函数中的name属性相当于类的私有属性。类的继承是通过在子类的prototype中增加父类的方法来实现。执行结果如下:

$ node class.js 
show jobs
info
save

突破瓶颈,突破自己

一口气读完了《突破瓶颈》,忽略关于销售、组织架构方面的内容,还是有些内容能吸引我。对谈到所谓的精神力量嗤之以鼻,向来反感鸡汤类型的说教,总是觉得说得都是正确的废话。吸引我的内容之一是度假管理系统这章,重点是系统,这从某种角度与戴明博士的理论很相似,要从系统的角度去分析和解决问题,而不是某个具体的环节。从小旅馆的管理系统中发现,员工的工作是自己主动选择的,哈哈,这不就是极限编程中的做法吗;每项工作都有完整的验收标准,并且有专门的服务监督人员去校验并纠正工作中的错误,这不就是code review吗,敏捷开发过程本身就是一套高效率的工作系统。

另外吸引我的一部分是工作中的本性,管理者不可能让员工按照他的本意工作,那些制定的条条框框、各类规章制度,其实都是摆设,管理者真正可以做的,就是创造和维持一个环境,在这个环境下,员工的技能会得到提升,员工的心情会舒畅,在这个过程中所做的一切,就是工作。反过来说,一个人工作中的表现,从中反映出的也是他的本性,如果我们对待工作马虎,那我们本身就是个马虎懒散的人。

旅馆老板的经营理念也很触动我,第一条,顾客并非永远都是正确的,不过,无论其观点是否正确,我们都应该努力满足他们的需求。按照我的脾气,不正确的是绝对不会去做的,这是今后需要改进的地方,发现客户的真实需求和对客户的价值,而不是纠结于知否正确这个结论。第二条,每个员工应该竭尽所能将自己负责的工作做到最好,假如他做不到,也应该按照这一标准来要求自己,直到他能做到为之。如果他不愿意用这样的标准要求自己,那她就应该离开去找一份更适合自己的工作。这是态度的问题,态度决定一切,正确的态度是对一个员工的基本要求,至少我招聘时是这样认为的。第三条,在旅馆里所有我们会做的事情都要经受检测,而检测的标准就是那些我们还不会做的事情。由此暴露出来的矛盾,就是旅馆成长和发展的动力。这个理念能够让我们不断的前进,正像我们现在做的,每个人心中有对自己工作满分的标准,给现在的自己打个分,衡量出差距,然后在工作中不断改进。我觉得老板的比喻很形象,他觉得公司就是一个武馆,员工就是这里的习武之人,而习武之人最大的对手就是自己。作者总结到高效的工作系统是个竞赛系统,一定不要理解错了,竞赛不是员工之间的竞赛,是关于个人行为的竞赛。

模式,模式,还是模式

《突破瓶颈》读到第六章,就开始阐述一个重要的概念:模式。书中的背景是作者和一家馅饼店老板娘的对话,并一直用麦当劳作为榜样。麦当劳作为馅饼店的榜样很合适,毕竟卖馅饼和卖汉堡包没啥本质区别,卖咖啡、卖炸鸡的也一样,可我从事的IT行业和他们是有本质的区别的呀,看得我心烦不已,硬着头皮读到了第十一章。书中反复称赞特许经营模式,甚至称其为转折性革命,的确这些采用特许经营模式的公司中很多都取得了成功,但很少见到有成功的IT企业在用这种模式啊?

转念一想,作者的本意应该不会这么浅薄,他不是想引导读者去学习如何采用特许经营模式去办公司,重点应该是在“模式”上,无论是特许经营模式,还是XX模式,对于创业者来讲,重要的是要找到适合公司发展的模式。麦当劳的成功,是模式的成功,不是因为他的汉堡物美价廉,也不是因为他的薯条比别人更加香脆,而是因为麦当劳的公司运作模式可以保证任何一家店的产品都是一样的,模式是可复制的,并且在这个模式下,公司是高效运作的。难道成功IT公司的模式也是可复制的吗?这里首先要明确模式是什么,他不是具体的规章制度,不是作息时间,不是办公装潢,不是统一着装;模式是一种做事的态度,一种文化氛围,甚至一种价值观,当公司所有员工的模式保持一致时,公司有了适合自己的模式。

模式不是固定不变的,不同行业、不同环境的模式用法也不是固定的,模式是可以自我进化的,只有具备了自我完善的功能,公司才会持续发展,才不会依赖某个具体的管理者,而管理者们所要做的就是,保证模式的正常的运行,能够及时的修正错误。

使用doctest测试重载dict.sefdefault方法

为了避免字典中取出的值为None,通常会使用dict的setdefult方法:

1
2
task = dict()
urls = task.setdefult('urls',[])

当字典中存在key时,无论value是否为None,setdefault方法都会忽略default值,直接返回value。为了避免这种情况,引入了一段重复代码逻辑,每次取一个key都要这样写:

1
2
urls = task.get("urls") if task.get("urls") else []
dirs = task.get("dirs") if task.get("dirs") else []

代码可读性下降了,决定采用采用子类化内建类型的方式重构。重构后代码如下:

1
2
3
task = alwaysdefaultdict(task)
urls = task.setdefault('urls',[])
dirs = task.setdefault('dirs',[])

重构过程中,先写doctest,alwaysdefaultdict.py文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#-*- coding:utf-8 -*-
'''
重载setdefault方法,当key存在,并且value为空字符串或None时,也会设置为defaultvalue。
>>> d = alwaysdefaultdict()
>>> d.setdefault('key','')
''
>>> d.setdefault('key','default')
'default'
>>> d.get('key')
'default'
>>> d.setdefault('key','newvalue')
'default'
>>> d.get('no_key')
>>> d['newkey']='newvalue'
>>> d.get('newkey')
'newvalue'
>>> d['newkey']
'newvalue'  
'''
class alwaysdefaultdict(dict):
    pass

这时候执行测试的结果是:

$ nosetests --with-doctest alwaysdefaultdict.py
F
......
Traceback (most recent call last):
......
Failed example:
    d.setdefault('key','default')
Expected:
    'default'
Got:
    ''
......
Failed example:
    d.get('key')
Expected:
    'default'
Got:
    ''
......
Failed example:
    d.setdefault('key','newvalue')
Expected:
    'default'
Got:
    ''
----------------------------------------------------------------------
Ran 1 test in 0.035s

FAILED (failures=1)

然后实现代码逻辑,最后代码如下:

执行doctest的结果:

$ nosetests --with-doctest alwaysdefaultdict.py
.
----------------------------------------------------------------------
Ran 1 test in 0.096s

OK

三重身份

被迫读书虽然很不爽,但也没啥坏处,坐车随便翻了一下《突破瓶颈》,还不错,一口气读到了第五章。

书中提到想要创业的人必须集三种身份于一身,创业者、管理者和专业人士,我自己的理解,这三种身份并不仅仅是想创业的人才有,而是每个人都有,只不过比例不同罢了。创业者代表理想、管理者代表意志、专业人士代表着现实。这三种身份不仅影响着创业的成功率,对工作角色以及能否在当前角色中高效工作也有着很大的影响。

创业者,我经常会幻想,理想的状态是什么?理想的团队、理想的工作环境、理想的公司、理想的系统等等。有些不切实际,有些触手可得,这些往往都在脑子里昙花一现,极少能被付诸实施,也许是创业者这个身份所获得的权力很低,经常被专业人士和管理者打败。要想做出成绩,要不断增强创业者的实力才行。

管理者,自从带团队以来,管理的职责越来越多,对沟通和计划能力的要求越来越高,在缺少创业者的指导下,管理者也很难做出正确的判断,往往会对专业人士听之任之,这也是导致创业者长期被打压的原因之一。不过管理者有他的强项,看待问题很淡定,会冷静的思考事情的前因后果,不会武断,并且能接受其他人的意见和建议。沟通时会采用平易近人的态度,引导其他团队成员采用更加高效的工作方式,找到问题、分析问题、解决问题,然后总结,避免重复犯错。

专业人士,这是个很棘手的家伙。脾气暴躁,以自我为中心,只要遇到看不顺眼的人和事,就会暴跳如雷,拨开旁人,独自战斗。喜欢与人争论,只是为了争论而已。喜欢深入了解细节,而忽略了大环境和上下文。这个家伙力量强大,估计我70%都被他控制着。当然现在不是:)

要想在当前的工作角色中获得成功,必须要调整这三种身份的力量比例,创业者20%,管理者50%,专业人士30%。需要创业者指明方向,又不至于目标太多;需要管理者落实计划,带领团队稳步前行;专业人士有他的优势,不能彻底打压,需要他对细节的了解,为管理者和创业者提供具体的反馈信息。

MacOS下安装ruby-1.9.3-p286

在小峰同学的帮助下,终于配好了Octopress,装ruby的时候遇到了问题。因为mac之前装的是ruby1.87,而Octopress需要1.9以上的版本,只好升级了。但不知为何,使用rvm安装ruby奇慢无比,不知道什么时候能装完,实在忍受不了了,就自己下载了ruby(1.9.3)的源代码,configure make & make install之后,执行gem的时候会报异常:

It seems your ruby installation is missing psych (for YAML output).

google了一下,需要安装yaml的包,于是下载之:

wget http://pyyaml.org/download/libyaml/yaml-0.1.4.tar.gz

安装之后,有重新ruby,但执行gem仍然报同样的错误,又仔细搜索了一下google,找到一篇文章《Install Ruby 1.9.3 with libyaml on CentOS》,发现做法是一样的,只是执行configure时需要指定参数,而我使用的都是默认值,虽然指定的和默认值是一样的。按照文中的做法操作之后,成功了!

$ wget http://pyyaml.org/download/libyaml/yaml-0.1.4.tar.gz
$ tar xzvf yaml-0.1.4.tar.gz
$ cd yaml-0.1.4
$ ./configure --prefix=/usr/local
$ make
$ make install

$ wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p286.tar.gz
$ tar xzvf ruby-1.9.3-p286.tar.gz
$ cd ruby-1.9.3-p286
$ ./configure --prefix=/usr/local --enable-shared --disable-install-doc --with-opt-dir=/usr/local/lib
$ make
$ make install