网络寻租

Programmer, Gamer, Hacker

如何度过每天的24小时

| Comments

昨天, 某个朋友给我推荐了一本书: 书名是 悠游度过一天的24小时.

这本书不错. 如果你觉得自己每天虚度光阴, 可以看这本书, 这本书提供了一些改变的方法, 简单易行.

上周在 primesplus 里面和他们聊天, 提到一个问题: 中国的程序员大多没有生活. 每天上班下班两点一线, 晚上在住的地方宅起, 要么就是一直都钻在技术里面, 长此以往, 人会憋坏的. 如果你觉得自己就是这样的人, 可以读读这本书.

下面是我整理出来的一些要点, 如果你不想被剧透, 那么就不要往下看了, 直接去买本来看吧.

  • 我们每个人都拥有同样的时间: 每天24小时
  • 但是我们一般来说, 都很难利用好这些时间.
  • 开始前提醒: 这个过程很艰难, 不要期望过高, 要有心理准备, 不要被失败压垮了.
  • 一个示例: 人们往往没有利用好每天的时间, 在时间处理上态度不对, 工作的时候有抵触情绪, 下班后宝贵的自由时间以休息的理由, 随意消耗掉了.
  • 除去上班8小时, 休息8小时, 损耗2小时, 人还有大约6小时的自由时间, 好好利用.
  • 上下班的时间不要消磨掉, 利用好这段时间.
  • 上班途中的时间用来锻炼心智, 约束自己的大脑想特定的事情.
  • 下班途中的时间用来反省.
  • 下班后承认自己并不累, 好好计划下班后的时间, 过真正充实的生活.
  • 幸福并非源于肉体或精神上的享乐, 而在于充实自己的理性思考, 并采取符合自己原则的行为方式.
  • 诗歌可能是最高形式的文艺形式, 给人以最大的愉悦并予人以最深邃的智慧.

Git学习总结

| Comments

image

虽然我的主力版本控制系统是hg, 但是既然在开源社区里面混, git是绕不掉的. 平时接触到git的项目, 为了能够正常使用不出篓子, 还是要系统地学习一下git. 这里推荐 git官方教程. 基础如何使用就不多说了, 网上漫天都是, 这里面整理一下使用CVS的一些经验, 绝大多数是从 其他人 那里抄过来的. 抄是学习的一大利器, 只要会抄, 什么事情都好办, 全世界那么多顶级的开源项目, 抄会一个, 吃穿不愁了—-有点离题了.

首先, CVS不是备份工具, 如果你一股脑地把所有修改过的文件commit上去, 那么还不如去用 dropbox. 我们要让版本库里面呈现的东西, 恰如其分地反应出开发流程, 方便流程控制, 阅读和处理. 那么CVS的使用, 应该符合专业的软件开发流程:

  • 每次提交代码, 应该是完成一项特定的工作, 提交的内容, 提交者应该能够回溯跟踪.
  • 代码分为开发版本, 以及发布版本.
  • 对于重要的阶段, 需要记录下来, 比如不同的发布版本号对应阶段的代码.

那么我们在使用的时候应该如何做呢? 根据 gitworkflows:

拆分变更

每次我们修改代码, 可能同时根据无数的需求改了非常多的地方, 我们在提交的时候, 需要尽量按照逻辑拆分出来, 分别commit它们. 这样出来的历史才有足够的可读性. 没有可读性我们还记录它们干什么?

分支管理

我们需要有至少2个分支: 开发分支和发布分支, 平时在开发分支干活, 需要发布的时候, 提交到发布分支上面去, 并且根据版本加tag. 注意, 提交到发布分支上面的代码是通过流程保证稳定性的.

关于上面的流程管控, 有个神器可以使用: git flow, 这里面的教程看一遍就能够体会到威力了.

用shiboken做python绑定

| Comments

image

pyside的项目已经beta了一段时间了, 它采用的方法是开发一个名为shiboken的绑定生成工具来做处理. 我们也可以利用这个工具来简化做python绑定的工作.

具体工作流程

上面的图代表了整个体系工作的方法. 用户提供2个信息: 需要绑定的库的头文件, 以及建立一个需要绑定的库的描述信息文件(xml), 以及在这个描述文件里面手动对生成内容做一些修改. 如果一切顺利的话, 只需要在描述文件里面说明需要绑定到python里面的类/枚举等信息即可.

如何用?

具体可以见 shiboken官方教程, 走到这一步的同学应该对英文没有压力的吧, 我就不再整理成中文了, 毕竟作者他们也都不是英语母语的.

一般情况下, 我们只需要直接下载里面附带的示例, 然后修改一番, 就可以用来给我们自己的库来做绑定了. 我现在就是这样做的.

如何实现的?

上面都是具体的做法, 在实际的使用过程中还会遇到各种各样的问题, 我们可能还需要对shiboken机制有一定的了解.

编译过程

我们写了xml描述文件之后, 实际执行的命令是generatorrunner, 它会按照xml文件, 以及引入的库头文件, 生成wrapper的cpp/h文件. 之后, 我们把这些文件编译成动态链接库, 这个链接库能够直接被python调用. 如下图:

global.h             generatorrunner                       gcc
typesystem_foo.xml -------------------> wrapper.h/cpp ---------> libfoo.so

generatorrunner运作机制

这里是 generator运作机制 的介绍.

image 如上图, 分成几个模块, api extractor获取头文件的信息, typesystem和injected code就是那个xml描述文件. shiboken就是generatorrunner后台调用的cpp文件生成工具了.

结论

具体shiboken是如何处理的, 以及为了调试方便, 如何获取中间生成的类信息, 这个我还需要时间去了解. 他们的作者也是很开放的, 如果发现问题, 可以往: pyside@lists.openbossa.org 邮件列表上面发邮件提问, 作者基本上是会给回应的(毕竟他们是nokia雇来做这个事情的).

Pysideqwt开源经验

| Comments

我现在的项目采用pyqt开发, 利用到了pyqwt, 但是pyqt闭源的话是要授权费用的. nokia他们因此弄出了lgpl的pyside. 我现在一直在跟进. 不过缺少qwt对pyside的绑定, 我的项目跑不起来.

与其等待有人来做这件事, 不如我自己弄吧. 于是我自己就建了一个: http://gitorious.org/pysideqwt#more 本来预期很难的, 不过实际做起来还是比较顺利的, 现在还在开发中. 期间, 遇到了不少问题, 也得到了pyside开发小组的帮助. 甚至还提交了我一生中第一个patch.

项目本身不说明什么, 我只想说一下这个过程是怎么思考的, 感觉这个才是开源的本质:

我们在项目开发中, 往往会发现某些问题非常通用, 值得很多企业外部的人一起来解决, 或者自己的解决方案可以被很多外部人士用到. 这样的话, 选择开源, 对自己的商业模式没有影响, 反而可以造福他人, 也可以给自己带来无形的收益. 我想, 这才是开源生存的重要支柱.

使用纯文本方式写文档

| Comments

大家用什么方式写文档?

为什么要用纯文本

WYSIWYG

自由

专注

word

image

xml

image

应该是这样的

Book:
    name: pyqt book
chapter:
    - python introduce
    - qt introduce

reStructuredText

rst2s5

sphinx

开发经验

| Comments

这里面整理了一些零散的开发经验, 方便提醒自己, 以及给其他人灵感.

  • 写代码本质上来讲是把信息量体现在代码上面, 最好写下来的代码正好反应出项目的信息. 比如代码本质上是实现一个状态机, 那么就需要写清楚, 然人一看就知道是状态机(有向图)
  • 要让代码复杂度反映在代码行数上面. no一行流
  • C语言是lisp的底层抽象?
  • C语言每个语句的性能是平衡的, 能够从C代码中看出程序性能消耗?
  • python函数边界不做类型检查, 运行时刻再去判断类型, 造成性能下降, 但是也带来足够强大的抽象能力?

箴言

| Comments

这里收集了一些我的语录, 以及我转述其他人的语录(会注明, 大多数不记得转述谁的了), 如果一股读者味道不要怪我. 如果我没有按照我说的话做也不要怪我. 我也只是一个心灵脆弱的人.

新的东西添加在前面:

  • 如果一个人活得和别人一样, 那么他给这个世界带来了什么信息量呢?
  • 编程的快乐, 本质上是创造的快乐: 你以自己的方式完成了工作, 工作本身刻有你自己的烙印.
  • 婚姻是一个合约, 任何超过十年的合约, 在这个时代都是带有巨大风险的. 但是什么时候我们能够有渐进的婚姻呢?
  • 人活着需要的东西其实很少, 即使你在大城市里面, 即使你工资不多.
  • 精神上的乐趣比物质上的乐趣更有持久性.
  • 人工作的目标是需要工作的时间越来越少, 而不是越来越多. (转述)
  • 人活着本身没有意义, 如果你产生了这样的问题, 那么期望的结果是不会再问这个问题了. (转述维特根斯坦)

为什么要用纯文本

| Comments

什么是纯文本

我们所说的纯文本, 有几层含义:

任何信息, 都采用文本的方式来展示

我们拒绝任何形式的二进制格式, 拒绝任何形式的私有格式, 我们只支持纯文本.

比如: Word, PPT, Excel, 他们都是只能用微软的工具打开的, 并且都是二进制的, 不能被文本编辑工具修改.

文本的编码必须是统一的标准编码

要么ASCII, 要么UTF-8, 拒绝任何其他的编码格式. 比如gbk等.

文本本身的数据, 反映出文本需要展示的信息量

比如:

<elementType id="Book" name="pyqt book">
    <elementType id="chapter" title="python introduce"> 
    </elementType>
    <elementType id="chapter" title="qt introduce"> 
    </elementType>
</elementType>

虽然格式是纯文本的(xml), 但是不能很好地被人理解, 所以我们认为是坏的, 不符合纯文本的要义.

我们应该这样:

Book:
    name: pyqt book
chapter:
    - python introduce
    - qt introduce

一个示例

本文就是采用纯文本方式编辑的, 原始文件可以在 这里 看到.

为什么要用纯文本

我们为什么要使用纯文本?

  • 纯文本是方便人阅读和修改的 纯文本反映了本文所承载的信息, 我们阅读和修改都能够针对具体的信息, 而不像某些专有软件, 需要熟悉软件的特殊功能, 才能获取我们想要的信息.

  • 纯文本是自由的, 不依赖任何第三方工具 如果你把纯文本发给其他人, 其他人可以直接获取到纯文本中的信息, 而不像二进制的文件, 必须要安装指定的软件. 如果该软件无法获取, 专有格式文件中的信息就遗失了.

注意点

  • 我们不反对程序自动生成的文件(不需要人阅读的部分)是其他格式的. 我们只关心人有可能看的部分. 如果你用纯文本生成pdf等格式展示给其他人看, 我们不要求pdf本身是纯文本的.. 因为这只是个中间产物, 如果有需要, 我们直接去看源码, 而源码 必须 是纯文本的.

纯文本工具链

为了能够实践我们纯文本主义, 我们需要有强大的工具作为支持:

  • reStructuredText 这是我们的主要文档编写工具. 适合人编写和阅读, 也可以很容易地转变为其他需要的格式.
  • S5 文本方式写幻灯片. S5本身是html的, 为了能够深刻实践纯文本主义, 我们建议你采用 rst2s5.
  • graphviz 文本方式画图. 文本方式编写图的结构和显示, 没有再适合的了.
  • csv 表格. 单一的符号以及回车符分割开具体的数据, csv非常适合阅读和修改.

Sphinx介绍

| Comments

什么是sphinx?

image

它有什么特性?

  • python文档所使用的系统
  • 输出HTML, LaTeX, manual pages, 纯文本..
  • 层级结构, 内链
  • 自动索引
  • 语法高亮
  • 扩展: graphivz, docstrings, 等等..

让我们开始尝试一下

安装 ~ easy_install -U Sphinx

教程 ~ http://sphinx.pocoo.org/tutorial.html

创建一个项目

sphinx-quickstart

目录

$ find
.
./Makefile               # 工具帮我们生成的makefile
./build                  # 生成文档后放置的目录
./source                 # 文档源码的位置
./source/index.rst       # 文档的入口
./source/conf.py         # 项目的一些设置, 上面quickstart设置的部分内容可以在里面修改
./source/_static         # 静态文档存放目录
./source/_templates      # 模板存放目录

开写哈

Contents:

.. toctree::
   :maxdepth: 2

# 我们在这里加内容
intro
tutorial

文档对象

.. py:function:: enumerate(sequence[, start=0])

Return an iterator that yields tuples of an index and an item of the
*sequence*. (And so on.)

这里是连接 :py:func:`enumerate`

自动导入代码中的文档

.. autofunction:: io.open

.. automodule:: io
   :members:

生成我们需要的格式

sphinx-build

$ sphinx-build -b html sourcedir builddir

make

$ make html

资源

官方文档 http://sphinx.pocoo.org

程序如何作在线更新

| Comments

问题

image

我们的客户端程序庞大, 笨重, 更为严重的是, 当我们的程序发布给用户之后, 如果没有问题还好, 遇到了bug, 修改很成问题. 如何把更新后的程序发布给用户呢? 很多项目在做架构的时候, 选择采用web方式来绕过这样的问题. 但是很多时候还是非用客户端程序不可. 如何让客户端程序实现在线更新功能? 这里提供我的一个非常简单的解决方案.

思路

更新的业务逻辑很简单. 只需要下面几步:

  • 去某个服务器, 下载一个记录有版本信息的文档.
  • 与本地程序的版本号做比较, 如果不是最新的, 提示用户更新程序.
  • 更新程序的方法: 去服务器下载一个压缩包, 解压覆盖本地程序.

然后是具体实现上面的逻辑.

要点

  • 服务器必须能够被客户访问到.

    如果是在外网的话, 必须有一个存放更新文件的公共服务器. 我采用 google code 存放文件. 一个项目上传文件的限额是2G, 足够用了. 你也可以选择自己搭建一个http/ftp服务器, 或者用dropbox(被墙掉了), everbox(现在还不支持共享文件).

  • 更新程序. 下载程序后, 需要覆盖当前程序目录, windows下面, 开启后的程序会加锁执行文件, 所以需要运行另外的一个程序来做更新. 下面是具体的逻辑.

更新程序解决方案

比如旧程序在dir_a目录下面, 可执行文件是a.exe. 星号括起来的是当前正在执行的文件.

dir_a: **a.exe**

我们把dir_a拷贝出一份, 到dir_backup, 最新的程序保存到dir_backup里面, 保存为new.tar.gz. 下载完毕后, 我们在dir_b里面建立一个文件, 叫need_update, 作为标识.

dir_a: **a.exe**
dir_backup: a.exe, new.tar.gz, need_update

execl (c/c++/python/都可以用类似的函数调用), 关闭当前程序, 跳到dir_backup环境里面, 执行dir_backup/a.exe.

dir_a: a.exe
dir_backup: **a.exe**, new.tar.gz, need_update

dir_backup/a.exe启动的时候检查当前目录下面是否有need_update, 有的话, 就解压new.tar.gz, 覆盖dir_a. 这个时候程序已经更新完成了.

dir_a: new.exe
dir_backup: **a.exe**, new.tar.gz, need_update

然后再用 `execl`, 放弃当前程序, 执行dir_a/new.exe.

dir_a: **new.exe**
dir_backup: a.exe, new.tar.gz, need_update

步骤虽然有点复杂, 但是顶用, 也不需要另外加一个更新程序. 对于python发布出去的代码来说, 一个可执行文件就上M了, 少一个是一个.

下面是实际代码, 因为是从实际项目中挖出来的, 保证不能用. 都是update.py这一个源文件的:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from qtlib import *
from qtlib.download import download
from config import add_config, conf
from tools.progresser import Progresser

import tarfile
from distutils.dir_util import copy_tree
SERVER = "http://project.googlecode.com/files/"

def update(program, version):
    """具体更新的方法"""
    if not confirm(None, '开始准备更新, 是否继续?'):
        return

    # 检查是否有更新
    if not check_update(program, version):
        return

    # 复制program
    temp_path = '../%s.backup' % program
    shutil.rmtree(temp_path, True)
    shutil.copytree(os.path.join('..', program),
                    temp_path)

    # 下载程序
    tar_file = program + '.tar.gz'
    local_tar_file = os.path.join(temp_path, tar_file)

    with Progresser("下载中, 比较忙, 你可以离开位置休息一下..","退出",0,100, None) as p:
        result = download(SERVER + tar_file,
                          local_tar_file,
                          stepper = p)
        if result == False:
            showMsg("无法连接到服务器, 请检查是否连上外部网络!")
            return
        elif result == None:
            showMsg('取消下载, 不好意思, 下次还得从头开始下载..')
            return

    showMsg('下载完成, 现在安装新程序.')
    os.chdir(temp_path)
    # 解压
    unzip(tar_file)
    # 标示一下是新程序
    with open('need_update', 'w+') as f:
        f.write(program)
    # 执行新程序
    program_name = program+'.exe'
    os.execl(program_name, program_name)

def check_update(program, version):
    """检查是否有更新"""
    # 下载versions文件
    if download(SERVER+'versions', 'temp/versions') == False:
        showMsg("无法连接到服务器, 请检查是否连上外部网络!")
        return False

    # 读取里面的信息
    data = open('temp/versions').read()
    # 格式是: program + 空格 + 版本号
    for line in data.split('\n'):
        if line.startswith(program):
            new_version = line.split(' ')[1]
            # 把版本号当作浮点来检查
            if float(version) < float(new_version):
                return True
            else:
                showMsg("程序已经是最新版本: %s, %s" % (program, version))


def check_finish_update():
    # 检查当前是否是需要更新的临时文件
    if not os.path.isfile('need_update'):
        return
    # 读取程序名称
    with open('need_update') as f:
        program = f.read()
    os.remove('need_update')
    # 然后把新程序复制回去
    old_path = '../%s'%program
    copy_tree(program, old_path)
    # 执行程序
    os.chdir(old_path)
    program_name = program + '.exe'
    os.execl(program_name, program_name)

# 加载update.py模块的时候, 判断当前是否为在backup过程中执行的..
check_finish_update()

其他

基于上面的更新逻辑, 我们还可以根据需求, 补充一些功能:

  • 启动的时候检查更新. 只需要用threading.Thread(target=check_update, …)来用一个线程跑更新检查就可以了.
  • 后台更新. 也可以用一个线程跑下载, 下载完毕后, 再通知主线程.
  • 增量更新, 减少下载时间. 可以考虑用diff之类工具.

如果有人觉得这样的更新脚本有价值的话, 可以联系我, 让我整理出一份不依赖其他模块的代码出来.