Python 中的 namespace

略微标题党一下,Python 中有 namespace 这个特性吗?就看你如何定义 namespace 了。

背景

近期参与一个项目,用 Python 写,设计规划上有点小规模,然后就按出的架构拆分为多个子模块项目了。但这几个子模块项目又关联性比较大,所以看起来要在一个总项目中。

考虑

题外话,一直觉得 Python 的模块导入是个挺麻烦是事情,什么绝对导入、相对导入,一定程度提高了使用者的理解成本,以及项目的维护成本。记得 Python 开发者的哲学不是“用一种方法,最好是只有一种方法来做一件事” [1] 嘛,貌似遵守这个哲学不容易。

最初反应是不拆分项目,直接把各个子模块的代码放在一个总项目中,比如:这个总项目叫 Star Wars [2] 吧,有 4 个子模块,分别叫 Yoda,Force,Skywalker,R2-D2。

可能源码的目录结构就是这样了

starwars/
├── force
│   └── __init__.py
├── __init__.py
├── r2d2
│   └── __init__.py
├── skywalker
│   └── __init__.py
└── yoda
    └── __init__.py

其实这样开始项目也没什么大关系,只是基于以下几点考虑,后来还是拆分这个项目为多个子模块项目了:

  • 按设想的架构展开,这个项目还是有点规模的
  • 项目中的某些子模块其实是很基础通用的模块,是可以被外部项目所用的,而外部项目要引入整个项目源码,没这个必要
  • 拆分为子项目后,方便由多个小组协同研发
  • 对后期项目的软件更新更有利,只更新某个子模块的 Python 模块包就行

问题

对 Python 来说,拆分子项目也方便,基本上就是把各个子模块目录作为独立的模块包,以 skywalker 子模块为例就是这样了:

skywalker/
├── anakin.py
├── __init__.py
├── luke.py
└── shmi.py

然后使用起来就是:

import skywalker

现在问题是可能有很多模块都叫 skywalker,如何避免模块名冲突?

解决

很简单,想当然会用 starwars 这个所谓的 namespace,然后使用起来就是:

from starwars import skywalker

对应的,目录结构就是:

starwars/
├── __init__.py
└── skywalker
    ├── anakin.py
    ├── __init__.py
    ├── luke.py
    └── shmi.py

然后写个 setup.py 就可以,目录差不多就是这样:

starwars.skywalker/
├── setup.py
└── starwars
    ├── __init__.py
    ├── __init__.pyc
    └── skywalker
        ├── anakin.py
        ├── __init__.py
        ├── luke.py
        └── shmi.py

其它子模块也类似处理,这样就拆分成多个模块包了。

更优

严格来说,上面的模块包,其实是 starwars 的模块包,而不是拆分后的各个子模块的模块包,也就是说安装完毕后的 starwars 可以没有 __init__.py,但也能用这种导入:

from starwars import skywalker

可以参考 Zope [3] 这个大型的 Python 项目,其实就是把它的子项目拆分为多个模块包,但共享了 zope 这个 namespace。

修改 setup.py

增加 namespace_packages 就行,例如:

#!/usr/bin/env python

from setuptools import setup, find_packages

setup(
    name='starwars.skywalker',
    version='0.0.1',
    packages=find_packages(),
    namespace_packages=['starwars']
)

修改 __init__.py

在 starwars/__init__.py 中增加:

__import__('pkg_resources').declare_namespace(__name__)

需要注意的是,除了这一行,不能有别的代码了。

安装

这个时候安装完毕 starwars.skywalker 这个模块包,可以发现安装完毕后的 starwars 是没有 __init__.py 的,但会在 starwars 平级目录多一个类似 starwars.skywalker-0.0.1-py2.7-nspkg.pth 的文件,作用相当于是在 starwars 目录中有了个 __init__.py 一样。

示例

说了这么多,直接看代码估计更容易理解,示例代码:

或者,直接接安装下亲自感受下:

pip install starwars.skywalker starwars.yoda

可以看下第三方包的安装目录的实际安装情况,在 starwars 目录没有 __init__.py,但可以导入想要的子模块库

from starwars import skywalker, yoda

再或者,随便找个 Zope 的子项目,看下实际的项目是如何做的:

本质

问题本质其实算是 Python 不允许模块包,在多个位置来进行导入 [4] ,所以只能放在比如 starwars 这一个目录下,无论是 starwars 目录下放个 __init__.py 还是严格声明下 namespace 是 starwars 这种方式,最后都是把模块包放在一个位置下来处理。

简单说:

  • 就是 Python 不支持所谓的 namespace 这种语法吧。
  • 或者说不支持,允许多位置模块包,却共享一个 namespace 这个特性。

参考

参考清单

[1]https://zh.wikipedia.org/zh/Python
[2]http://www.starwars-tw.com/story/character/character.htm
[3]https://github.com/zopefoundation
[4]https://pythonhosted.org/setuptools/setuptools.html#namespace-packages

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

软件开发过程杂谈

这里特别说是“软件开发过程”,而没有特别用“软件工程”?

  • 一是,软件工程涵盖的范围太大了,所以直接取其中的开发过程来重点说明,并且大家更喜欢关注,软件工程中的具体开发过程。
  • 二是,软件“工程”的所谓工程,和其它的工程实在有些差异,而更接近于是艺术品的创作过程,比如:画、音乐等艺术形式的创作。

虽然有差异,但被冠上“工程”的名字也有道理,其本身也强调“如何做”(于工程师而言),而有别于“为什么”和“是什么”(于科学家而言)。

下面就按常见的瀑布模型来说明下软件开发过程,说是瀑布模型,切忌把下列的各个阶段划分得层级很清楚,其实这些都是前后连贯的,有的过程甚至是贯穿全程的。

我想要一个东西?!

../../../_images/idea_bulb.jpg

对用户(或者说客户)来说,软件开发就是这么简单:

我有一个想法 -> 一个哆啦A梦(就是所谓神奇的工厂,最好再加上个时光机器) -> 这就是我想要的

但真正从事过软件开发的人知道,哪有这么简单。

“银弹”

很多软件开发的说上都提到过什么“银弹”,那么有没有“银弹”呢?自然没有,这也是“软件工程”很难达到所谓“工程”的表现。我对此的理解是:事实上,开发软件根本没有所谓“绝对正确的方法”,倒是有很多错误的方法。

“务实”

所以没有“银弹”咋办?最有效的方法,那就是说了没说一样的说法:“务实”,或者说具体问题具体分析,具体项目具体安排,具体团队具体战法。也就是说非常依赖于“人”,所以对于一个广义上的软件来说可能得包括:

  • 平时我们所说的“软件”。
  • 软件运行在的实际“硬件”。
  • 能让上述两者实际结合的“湿件”,也就是你,“人”,或者说人的脑细胞或思维过程。

这里再提个问题,为什么软件有别于其它我们所见的物质,为什么没有质量(这里特指重量那种意思)?如果思考过这个问题,你就会觉得软件开发过程,的确更接近于画、音乐等艺术形式的创作:好比用画料涂在纸上,乐曲谱在纸上或其它介质上类似,软件也只不过是装在了硬件上。

需求分析

../../../_images/right_requirement.jpg

这是是一个很重要的环节,同时也是一个很难的环节。

但貌似很多软件工程师或者说程序员,觉得这个环节很没有技术含量,但这的确也是一个必须要掌握的技术之一(如果说编程语言等计算机相关知识算是“硬技术”的话,那么这个需求分析环节你可以认为是“软技术”)。

另外,很多程序员往往会将“需求”和“解决方案”这两个概念混在一块,导致最后做出的软件南辕北辙或不尽如人意,看下两个例子吧:

例子一:

  • 产品将易于使用,是需求。
  • 产品将有一个图形用户界面,是解决方案。

例子二:

  • 产品将在菜单条上有一个时钟,是解决方案。
  • 产品将使用户意识到当前的时间,是需求。

为什么容易混呢?因为很多程序员直接把用户说的话当成是“需求”,而用户往往说出来的话只是他所要的“需求的解决方案”,只不过以他的知识范围内想出来的而已。而你应该要做的是挖掘其背后的真正需求,也就是上面说的,用户只是想让产品易于使用和想意识到时间而已。而作为程序员,你的作用就是将需求用你天才般的脑袋想出一个不错的解决方案,也许会比用户替你想的更好(也就是上面提到的“工程”强调“如何做”)。

最后,需求分析后:

  • 切记需求 txt 化。一个意思就是,有记录,条目化;另一个意思就是,需求十有八九是会有变更的,是很容易随时被编辑修改的。
  • 最好需求 exe 化。一个意思就是,需求具备技术上的可执行性;另一个意思就是,理想上最好需求能固定下来,当然如果做不到这点,那么有需求变更了,也得有个编译成 exe 的过程,也就是需求变更要有变更流程,或者说要让大家都意识到变更会有一定的代价。

再回到刚才出现的图,漏斗所要表达的意思是,需求分析最好能到达的效果就是,将一堆杂乱的信息,通过这个漏斗后降噪过滤,最后得到的信息是更加有序的。相反的效果就如下图:

../../../_images/wrong_requirement.jpg

底下那个人就是另一个悲催的程序员,看起来慢悠悠的沙漏好像在过滤这需求,但最后还是那些信息,让人一头雾水。

然后要记清楚上面那2个例子表达的“需求”和解决方案的区别,切记南辕北辙。虽说条条大路通罗马,但建立罗马帝国往往就一条路!

再看下一个很流行的软件开发笑话的图。这种笑话时刻发生在我们平时软件开发的过程中,应该要避免出现这种情况。

../../../_images/joke_requirement.jpg

概要设计

../../../_images/design_summary.jpg

这个阶段可以认为是整个软件的顶层设计,有的人喜欢叫构架设计或架构设计,不管叫什么吧。完成这个设计后:

  • 关注业务的人看着这个业务构架图,听着你的讲解能了解这个业务是干嘛的,业务间是如何协作的;
  • 关注技术的人看着另一个技术构架图,听着人你的解释了解这个系统是如何运作的。

这个阶段,一般也会确定技术选型,比如:用哪些编程语言、编程框架、类库、操作系统、数据库等等技术层面的组件。

如果是个已经有成熟参考经验的系统,可能可以搭建出一个大致的框架,后续基于这个框架就可以进行具体实现了。

另外,这个阶段很重要的一点就是要进行下原型验证,无论这个原型是很原始的白纸黑字;还是低保证的原型草图;亦或是高保证的原型图(比如:系统本身有 UI 的话),基于这个原型,你能给别人很好的讲解清楚,帮助别人更好理解最后做出来的东西,这样就可以尽早确认完毕这个东西是不是别人想要的。

详细设计

../../../_images/design_detail.jpg

很多时候和写代码一起了,一般来说会重点关照,核心模块、重要模块、难度高的模块,可能不同人有不同风格吧:

  • 有些人会更加仔细推敲设计而不会急于写代码。
  • 当然对有些人来说,写出一个模块的接口层和主线运行逻辑,更为实际。

设计/编码/调试/测试

这块过程对很多人来说应该算是最熟悉的了,也是最喜欢的,但往往其实花的时间也是最少的一个环节。

一旦进入具体实现阶段时候,日常工作大部分时间都是集中在这几块了。这里特地还把设计提出来,作为其中一部分,就是想表达设计的重要性,对于程序员来说编码只是实现你的设计的一个工具而已,工具用得熟练不表示设计思路足够好。

../../../_images/code1.jpg

有不少人喜欢有足够单元测试覆盖的编码过程,日常编码时候,能让代码跑一会儿,又让测试跑一会儿。这样能保证写的代码能有足够的保险,无论是增加新特性、改进功能、重构代码、修复缺陷的时候都能信心满满的让代码运行在正式上线的系统中。

../../../_images/code2.jpg

如果没有看过这张图中的那本书,建议可以看下,全书其实就是讲了一个悲剧的软件开发故事。看完后对比下自己经历的项目,就能发现各种雷同的现象,让自己以后避免这种经历。

../../../_images/code3.jpg

新手

../../../_images/code4.jpg

老手

上面 2 张图就是表达了新手和老手具体干活时候的状态,谁都是从新手过来的,所以新手的痛苦应该都有所体会,但只要把自己的基础牢牢打扎实了,就逐渐能变成老手一样会得心应手,随时运用手头的各种工具、技能、必杀解决各种问题。

../../../_images/code5.jpg

效率!很重要的一个词。

有人说过,“很多效率低下的程序员,可以归结为基本功不牢。”,所以无论是新手阶段还是已经成为老手了,基本功必须扎实。

一个程序员得有构建能力,而且是足够好的构建能力,比如:从写下第一行代码,到编写功能代码、编写测试代码、编写技术文档、编写构建脚本、发布软件包,这些都能一气呵成。

  • 0 -> 1。软件是个虚无的东西,相当于是程序员脑中得到设计,能转化成别人能看得见用得了的软件。即:思考 -> 软件。
  • 自动化。能做到所谓一气呵成,如果没有足够高的自动化是很难的。
../../../_images/code6.jpg

如果你是个高效的程序员,那么你的价值一定要得到体现。即:show me the code, show me the money!

当然写出好的代码到成为一个好的软件或好的产品还是有一段距离的,可能不一定是技术层面的事情了,但技术是必备的一部分。如果足够幸运,你的软件产品能做到苹果产品那么成功,就可以让别人,show me your kidney, show you the apple

../../../_images/code7.jpg

最后,大家要注意保养啊!可以得花买本《程序员健康指南》看看吧。

test and test

再说下测试,其实前面已经涉及到测试了,编写代码边测试,那个测试算是细粒度的测试,或者我们平时说的单元测试。这里说的测试粒度更大。

集成测试

很多人觉得做集成测试这么痛苦,代码集成、模块集成、项目集成,十分让人头大,然后暴露各种问题,怎么解决?

解决办法很简单:更频繁地集成,从而减少潜在的冲突,让问题提前跑路出来。也就是说把你觉得困难的事情提前,到后面来看这都不是事了。这里看起来有点心理效应上的味道,但其实也有很多技术操作方面的方式来降低集成的难度。

../../../_images/test_all_in_one.jpg

痛苦的集成测试

../../../_images/test_all_in_one_nice.jpg

幸福的集成测试

系统测试

这里重点说下测试的目的是啥?反正不是为了发现 bug 而测试,引用我觉得合理的话:

“简言之,测试的目的应该是验证需求,bug(预期结果与实际结果之间的差别)是这个过程中的产品而非目标。测试人员应该象工兵一样,在大部队(客户)预期前进的方向上探雷、扫雷(bug),而不需要去关心那些根本没有人会去碰的地雷。”

../../../_images/test.jpg

发布/交付

这就是我想要的东西!

到达这里,算是一个里程碑,但一般来说不是终点。

交付、验收完毕,往往进入下个迭代周期,进行日常维护、系统升级等等,又不断重复着上述过程。直到这个软件不在维护,逐渐消亡、报废处理。

../../../_images/i_need.jpg

坑爹!这哪是我想要的东西啊?!

这个是我们要避免的情况,运气好,回头重走一遍上述过程,顶多就亏本付出些额外代价;运气不好,直接跟你说再见了!

../../../_images/not_i_need.jpeg

想象的和现实的差距

为了避免出现这种情况,一定要记得时刻问一句:这是客户/用户想要的吗?

../../../_images/before_and_after.jpg

完结

银弹是否存在?存在吗?不存在吗?如果真有的话,那一定是一个靠谱的团队!

../../../_images/team.jpg

这里有个个人觉得不错的检查条目(这个翻译可能是台湾版本的翻译)

《Joel 衡量法则》

  • 你们用不用源文件管理系统?
  • 你们可以把整个系统从源码到CD映像文件一步建成吗?
  • 你们每天白天都把从系统源码到CD映像做一遍吗?
  • 你们有软件虫管理系统吗?
  • 你们在写新程序之前总是把现有程序里已知的虫解决吗?
  • 你们的产品开发日程安排是否反映最新的开发进展情况?
  • 你们有没有软件开发的详细说明书?
  • 你们的程序员是否工作在安静的环境里?
  • 你们是否使用现有市场上能买到的最好的工具?
  • 你们有没有专职的软件测试人员?
  • 你们招人面试时是否让写一段程序?
  • 你们是否随便抓一些人来试用你们的软件?

后续主题会逐渐展开说下几种常见的软件开发流,当然还是那句话:“具体问题具体分析。。”

参考

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

项目如何从 Subversion 迁移到 Git

很多有点历史的项目,都是用的 Subversion 作为版本控制工具的,随着项目需要,很多团队就打算采用 Git 作为替代工具了。好,现在问题来了:项目如何平滑的从 Subversion 迁移到 Git。

这里所谓的迁移是按照版本控制要求来迁移,包括:

  • 尽可能完整的由谁提交的代码、做出的代码变更记录,提交日志等。
  • 尽可能完整的分支、标签等。

因为毕竟是不同的版本控制工具,转化过程难免会有瑕疵。

Step 0 - 准备环境

安装用到的工具的软件包,这里以 Ubuntu 为例

$ sudo apt-get install subversion git
$ sudo apt-get install git-core libsvn-perl perl libterm-readkey-perl

Step 1 - 规范 Subversion

确认项目的 Subversion 地址:

# 后面统一用 $PROJECT 表示项目的 Subversion 地址
# 这里的示例项目名称是 west
https://scms.example.com/svn/projects/west/

规范项目在 Subversion 的目录结构:

  • 标准的 trunk、branches、tags 目录布局
  • branches 和 tags 目录下的分支和标签保持平级,例如:
    • tags/v1.0.0 可以。
    • tags/1.x/v1.0.0 多了层目录就不可以。
  • 如果不是平级,以 tags 为例,先执行 svn mv 操作

方式 1 - 远程 svn mv

$ svn mv $PROJECT/tags/1.x/v1.0.0 $PROJECT/tags/v1.0.0

方式 2 - 本地 svn mv

$ svn co $PROJECT west_subversion
$ cd west_subversion
$ svn mv tags/1.x/v1.0.0 tags/v1.0.0

最后规范后的目录示例如下:

west
├── trunk
│   ├── docs
│   ├── west
│   ├── setup.py
│   └── README.rst
├── branches
│   ├── hotfix_add_user_error
│   ├── hotfix_issuse_9527
│   ├── feature_unittest4app
│   └── feature_multi_add_user
└── tags
    ├── v1.0.0
    ├── v1.0.1
    ├── v2.0.0
    └── v2.1.0

Step 3 - 生成提交者 ID 和邮箱

  • example.com代表组织的邮箱,比如:knownsec.com
  • 但如果个人邮箱不是统一的组织的话,就需要手工编辑 users.txt 了
svn log $PROJECT --xml | grep -P "^<author" | \sort -u | perl -pe 's/<author>(.*?)<\/author>/$1 = $1 \<$1\@example.com\>/' > users.txt

Step 4 - 迁出项目代码(git svn)

git svn clone $PROJECT --authors-file=users.txt --no-metadata --localtime --stdlayout
  • --authors-file 是得到的 git log 提交记录映射好提交者的信息
  • --no-metadata 是得到的 git log 不带上对应的 Subversion 信息了,更干净
  • --localtime 是得到的 git log 以本地时间为准,建议用上
  • --stdlayout 是先前准备的按规范目录风格来迁出代码

Step 5 - 转化成Git的仓库格式(tags 和 branches)

处理 tag:

git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | while read tagname; do git tag "$tagname" "tags/$tagname"; git branch -r -d "tags/$tagname"; done

处理 branch:

git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; git branch -r -d "$branchname"; done

Step 6 - 一些清理工作

由于这个转化转化会将及历史上的 branches 和 tags 也都生成一个 Git 的分支和标签,所以还是得清理下你认为不用的分支和标,可能包括:

  • Subversion 历史上错误的 tags。
  • Subversion 历史上临时的 branches。
  • 冗余的 trunk 分支(其实跟转化后的 Git master 分支一样)。

Step 7 - 添加到远程 Git 仓库

  • 比如:GitHub 上创建项目 west
  • 添加本地 Git 项目到刚创建的远程 Git 仓库
git remote add origin git@github.com:akun/west.git
git push origin --all
git push origin --tags

Step 8 - 完成迁移

这样,可以直接在 Git 下来继续你的项目了。

git clone git@github.com:akun/west.git

有关 Git 日常的使用,可以参考 Git 常用命令用法:程序员的场景

遗留问题

上述方式转化其实还有瑕疵,比如:

  • Subversion 允许空目录,转化 Git 用 git svn,处理空目录带上 --preserve-empty-dirs 可能会报错,不处理,可能项目的程序原先依赖空目录的处理就得修改。
  • 类似 svn:externals,svn:ignore,svn:merge 等属性丢失

不过问题不大,可以接受,Subversion 迁移 Git 算是基本平滑迁移。

Git 常用命令用法:程序员的场景

Git 相比 Subversion,无论概念上还是使用上,复杂度其实是高出一个等级的。为什么这么说?分别看下 git help -asvn help 命令清单的对比,单按这个来看,就如果要掌握所有命令的用法,Git 的学习曲线绝对是比 Subversion 高的。尽管如此,但还是有越来越多项目开始用 Git 来做源码管理了。

实际中,我们用到的的 Git 命令还是很有限的,可能也就 git help 中那些而已。下面就类似源代码的管理和发布:以SVN为例一样,结合实际场景说下 Git 的常用命令用法。

“新人报道”

你刚入职一家公司,或新加入某个团队,立马参与到一个项目中,那么就得获取项目代码,开始你的项目生涯。这个时候一般你需要克隆一份项目代码,下面都以 GitHub 上的项目地址为例:

$ git clone git@github.com:akun/pm.git

之后就进入项目目录,运行项目中的构建脚本,然后就可以熟悉代码,展开具体工作了。

当然,有的时候,有一个新项目是由你发起的,你要将初始化的项目工程放到 Git 版本仓库中:

$ mkdir pm
$ cd pm
$ git init
$ touch README.md
$ git add README.md
$ git commit

Git 是分布式的版本控制系统,所以刚才的操作,算是已经在你本地版本控制起来了,为了推送本地仓库到远程仓库,就还得执行:

$ git remote add origin git@github.com:akun/pm.git
$ git push -u origin master

一般这个时候都会设置下 ~/.gitconfig.git/config 中的配置,最基本的就是用户名和邮箱

确认当前的 Git 配置信息:

$ git config --list

设置用户名和邮箱:

$ git config user.name akun
$ git config user.email admin@example.com

刚才的命令只是对 .git/config 生效,如果想全局生效,也就是 ~/.gitconfig,就得加上 --global 参数,比如:

$ git config --global user.name akun
$ git config --global user.email admin@example.com

日常工作

当你已经逐渐融入了一个项目,可能一天的工作场景或完成某个任务的工作周期是这样的:

更新

无论是清早或下午或晚上,开始了你的一天工作,你首先会更新你的工作目录:

$ cd ~/projects/pm
$ git checkout develop  # 我想在 develop 分支上开始一天的工作

更新方式一:

$ git fetch --all  # 从远程仓库获取所有分支的代码变更
$ git merge

更新方式二:

$ git fetch --all
$ git rebase  # 默认就衍合 develop 分支的代码了

更新方式三,可以认为是 fetch 和 merge 的合集:

$ git pull  # 懒得理解 fetch 和 merge 就直接 pull 吧

这样你就可以在最新的项目代码基础上工作了。

注解

  • git pull --rebase 相当于是前面的方式二的合集
  • 有关 “fetch + merge” VS “fetch + rebase” VS pull 的差异后续单独写一篇文章说明
  • 这里说的三种方式,可能每个人或团队都有自己的习惯吧
  • 想了解 Git 中的“衍合”,可以实践下这个文档:Git-分支-分支的衍合

修改

可能你写了一个新的模块,需要纳入项目的版本控制:

$ git add tools.py

可能你发现某个模块已经陈旧了,不再使用了:

$ git rm utils.py

可能你发现一个模块的命名不太合理,需要改名:

$ git mv model.py models.py

可能你要创建一个新的较大的模块,需要归档为目录的方式:

$ mkdir groups
$ touch groups/__init__.py
$ git add groups/__init__.py

注解

Git 不支持空文件加加入版本控制,非得必要咋办,后续的其它场景会简单说明下

可能你发现要写的模块代码布局类似于旧的模块,直接复制个代码模版:

$ cp users/tests.py groups/tests.py
$ git add groups/tests.py

注解

Git 没有自带的所谓 cp 命令

当然,其实最常见的情况其实还是打开编辑器,比如 Vim,修改已经存在的代码,这个就跟 Git 命令无关了。

检查

忙碌的一天过去了,或者一个任务完成了,这个时候一般会将你的工作成果,也就是代码更新到版本仓库(分为本地版本仓库和远程版本仓库)。

习惯上会先检查下修改状态:

$ git status

看到一些 Git 状态信息,确认是修改了哪些文件,之后一般会自己 code review 一下代码的改动,可能有的人会习惯直接用 Git 方式来查看:

$ git diff

这里的 diff 只是查看其中“工作目录”和“暂存区域”的区别。要查看“暂存区域”和“本地仓库”的区别,可以用:

$ git diff --staged  # 或 git diff --cached

注解

最好理解下三个区的概念,以代码角度来理解:

  • 工作目录:git clone 后获得的一份本地的代码,也包括新编辑的,尚未加入版本控制的代码
  • 暂存区域:git add 后暂存起来,尚未 git commit 的代码
  • 本地仓库:git commit 后正式被版本控制记录起来的代码

可以看下图,能更好的理解这三个区

../../../_images/git_3_kingdom.png

然后本地运行下相关的单元测试,确认是否有问题。一般来说这个时候,没有什么特殊情况,就直接进入“提交”甚至是“推送”阶段了,然后结束一个工作日或工作周期,但难免会有些特殊情况出现。

取消修改

当你 code review 完后,发现有些改动不满意;或者运行完单元测试,发现有些测试用例没通过,你可能会进行取消这些修改的操作。

如果还没 add,那么可以:

$ git checkout -- main.py

为了避免刚好跟分支名重合,所以加了两个斜杠(虽然概率很低),如果已经 add 了,但还没 commit,那么可以:

$ git reset HEAD main.py

万一刚提交完毕,也就是已经 commit 了,才发现代码有问题,比如:忘记把某个文件提交了,这个时候咋办?Git 好处是可以覆盖上一次提交,那么可以:

$ git add tests.py
$ git commit --amend

上面还只是简单的撤销操作,Git 还能支持更高级的重写历史功能,想掌握高级技能的可以实践下这个文档:Git-工具-重写历史

解决冲突

有时候同别人合作写一个模块的代码,会把对方代码合并或衍合过来,比如:对方修复了某个缺陷,你刚好也需要这个修复;再比如:对方完成了某个特性,你也刚好需要用下这 个特性等等各种情况。

大多数情况,代码的合并或衍合不会冲突,但也有冲突的情况,分两种情况说明,第一种是合并操作时候有冲突:

$ git fetch --all
$ git merge bugfix/remove_error
# 这个时候就提示你代码冲突了,处理完冲突的代码后
$ git diff  # code review 下代码
$ git add remove.py
$ git commit
# 日志中就多了一条合并操作的日志了

另一种是衍合操作时有冲突:

$ git fetch --all
$ git rebase bugfix/remove_error
# 这个时候就提示你代码冲突了,处理完冲突的代码后
$ git diff  # code review 下代码
$ git rebase --continue  # 有时候会 git rebase --skip
# 直到不用再 rebase 为止

提交到本地版本仓库

最后,一切确认没问题了:code review 完毕,自己觉得代码满意了;有可能也合并完别人的修改并且没有冲突了;运行单元测试也通过了。那么就提交代码吧:

$ git commit

推送到远程版本仓库

Git 中的 commit 只是提交到自己本地的版本控制仓库,如果想分享你的代码提交,还需要推送到远程的版本控制仓库:

$ git push

在分支工作

Git 分支很灵活,用 Git 的合作开发模式方式也很灵活,如何更好得使用 Git 分支来合作开发,可以参考这篇文章:

可能后续也会写一篇专门的以 Git 为例的源代码的管理和发布相关主题的文章。

下面说下在分支工作的常见的实际场景,按顺序:

创建新的本地分支

确定要新开个分支来写代码,这里以贡献新特性为例子:

$ git checkout -b features/batch_remove
$ git branch -a  # 确认已经在新分支中工作了
$ git log  # 可以确认是基于刚才的分支新分出来的

这里已经隐含了自动切换到新分支的动作了。

在新的本地分支工作

类似,“日常工作”中的工作周期操作,这个时候,你就可以在新分支中进行大刀阔斧的工作了,直到分支中代码符合要求。

推送成为作为远程分支

如果想把分支分享给别人,可以推送到远程版本库,这样别人可以根据需要来把你的分支代码更新到他自己的本地仓库,例如:

$ git push origin features/batch_remove

合并或衍合远程分支

在分支中工作一段时间后,确认相关的功能代码、测试代码、文档等都提交完毕了,单元测试通过,大家 code review 一致认为没问题,审核通过,最后该分支的持续集成(CI)完整 build 通过。这个时候,就可以进行合并的操作了。

其实前面也提过类似操作,这里再类似重复一遍,如果用合并:

$ git fetch --all
$ git merge features/batch_remove
# 如果没提示冲突,那就合并成功
# 如果这个时候就提示你代码冲突了,处理完冲突的代码后
$ git diff  # code review 下代码
$ git add batch.py
$ git commit
# 日志中就多了一条合并操作的日志了

如果用衍合:

$ git fetch --all
$ git rebase features/batch_remove
# 如果没提示冲突,那就衍合成功
# 如果这个时候就提示你代码冲突了,处理完冲突的代码后
$ git diff  # code review 下代码
$ git rebase --continue  # 有时候会 git rebase --skip
# 直到不用再 rebase 为止

这里也提下直接合并本地分支,有时候你创建的分支只是自己用用,没有共享给别人,因为本地已经有了这份分支代码了,那么就省去 git fetch 操作,类似上述方式合并或衍合代码就行。

对比 Subversion 的分支合并操作,实在是简化不少。

删除分支

如果确认工作完毕的分支不再需要了,那就记得及时清理掉,删除远程分支:

$ git push origin :features/batch_remove

删除本地分支:

$ git branch -d features/batch_remove

顺便说下,一段时间后,一定有一堆别人的分支,然后你 git fetch 下来了,这样就出现在本地的分支清单中,但远程版本库中已经删除了,如果想本地分支清单干净些,可以在 git fetch 时候这样执行:

$ git fetch --all -p

Ship it

可能在平时的研发分支工作一段时间后,并且测试完毕,大家觉得符合发布条件了。终于可以进入到版本发布阶段的工作了。

创建发布分支

一般来说这个时候已经将在某个发布分支上工作了,比如:

$ git checkout -b release-1.2 develop  # develop 就是平时的研发分支
$ release.sh 1.2  # 比如有个执行发布脚本
$ git commit

打标签

确定可以发布了,就开始打标签吧,比如:

$ git checkout master
$ git merge --no-ff release-1.2
$ git tag -a v1.2
$ git tag  # 确认下打上了标签了
$ git push origin v1.2  # 推送标签到远程版本库

正式发布

发布又是一个比较复杂的主题,比如:能快速发布、快速回滚(包括数据回滚)、灰度发布等等,在构建发布工具中会详细进行介绍,这里就简单罗列下。

一般来说,根据实际情况,可以记录下来发布相关的操作过程。很多环节可以写脚本将来的人工操作改成自动化操作。以后只要执行发布脚本执行一键发布就可以了。

其它场景

可能还有很多别的场景,比较零散,但也算经常用到。

code review 查看代码,要知道对应代码是由谁写的,好询问了解具体代码的思路:

$ git blame

跟踪问题时候,会查看日志,更方便历史代码定位:

$ git log

觉得完整的 Git 命令太长,想用类似 Subversion 的缩写命令,可以用 alias,比如配置文件中可以写上:

[alias]
    br = branch
    ci = commit
    co = checkout
    diffs = diff --staged
    st = status
    lg = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all

有时候合并或衍合代码,但本地有修改了一半的代码没有提交,可以先暂存起来:

$ git stash
# 合并或衍合完毕代码后
$ git stash pop  # 恢复刚才修改了一半的代码

原来的一个项目想拆分多个项目,又想保留版本仓库记录,可以用下 git subtree split,例如:

$ git subtree split --prefix=plugins/sqli

Git 不支持空文件夹加入版本控制,变通方式:

$ mkdir downloads
$ vim downloads/.gitignore  # 增加 * 和 !.gitignore 这两条规则

永远别忘了 help

对于习惯命令行下编程的程序员来说,多看帮助总是好的,直接执行

$ git help

可以看到 Git 的常用命令,如果想看到更全的 Git 命令,可以执行

$ git help -a

单独查看某个命令的帮助,可以执行

$ git help add  # 比如 add 命令

会发现更多的命令,这个相比 Subversion 的命令更多,所以看起来也更复杂些,不过Git 本身也比 Subversion 更灵活、更好,比如:分支的使用、历史提交修改等。

好习惯

这里顺带说下几个使用 Git 的好习惯,但有的其实跟 Git 联系也不算大,只是顺带提下:

  • 保持工作目录干净。或者说工作目录中的代码变更就为了完成一个任务,即一次只做一件事。完成任务后,就直接 git commit 提交到本地版本仓库的某个分支中,而不用担心其它任务作出的代码变更无提交。并且,对于分支切换更方便,而不用担心代码被覆盖或冲突的问题。
  • Git 的日志信息足够有效。足够有效的意思,是说这次提交作出的变更摘要,只要别人阅读了日志就能知道大概,如果为了深入了解变更细节才会去查看具体代码变更。
  • git commit 前 code review。code review 本身就是个好习惯,提交前确认是一种更为严谨的方式,如果觉得自己 code review 发现不了什么问题,那么随便从身边抓个会代码的,跟别人讲解下代码变更的内容,说不定会发现你没考虑到的问题。
  • git commit 前跑单元测试。写单元测试本身也是个不错的习惯,如果项目本身已经有了完备的单元测试覆盖了,那么你对代码的修改,应该能通过单元测试,所以提交前执行一遍是否通过。如果没通过,就得看下是功能代码写的有问题,还是测试代码有问题,比如:功能需求或接口设计有变化,而测试代码没有同步更新。
  • 有代码变更及时提交。有 Git 这种版本控制工具,本身就是为了记录研发过程,避免意外导致代码丢失,如果为了完成某个任务需要很长时间,代码也很久没有提交,风险太高。这个时候,一般会自己开个分支,而将代码提交到分支中,既解决代码要及时提交的问题,又解决代码提交频繁,可能造成代码不稳定影响别人的问题,因为那个分支只有你自己在工作。而这一点,Git 分支的功能更为强大,更加鼓励多开分支。

最后

这些场景覆盖的 Git 命令其实很有限,如果要完整的熟悉,那就 git help 以及阅读下《Git Pro》这本官方推荐的入门书,有个系统的学习,基础才会更加牢固。

后续

另外,这里只是以程序员的场景来简单介绍 Git 使用,对于系统管理员,可能有一部分职责是作为 Git 版本仓库管理员,日常也会遇到的各种场景吧,后续也会简单介绍。

参考

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

不开会

不开会?!起这个标题,算是一个标题党行为。完整点说法应该是:项目过程中,你应该避免参加不需要你解决问题或参与决策的会议。

如果你是个程序员

这里不一定特指程序员或者说研发工程师啦,项目过程中会参与的其他一线角色,比如:设计师、构架师、测试工程师也包括其中。

请问你的最大价值是在哪?当然是最直接的产出设计、代码,以及检查这些设计和代码是否符合预期等等这些东西了。

相信很多人都参加过“状态报告”之类的会议吧,回想下自己在这种会议中,除去自己发言的时间外:

  • 你会仔细听取每个人的发言吗?
  • 你会记录每个人的发言吗?
  • 会后你能回忆起跟你工作相关人的发言不?

可以先心里回答这3个问题,后面会简单说明。那你都在干嘛呢?回答可能是:

  • 我在调试代码、推敲设计、查找资料、测试功能。不错嘛,居然还在赶项目进度啊。
  • 我在上上网、刷刷微博、看看朋友圈。这也正常,正好拿会议时间调剂下。

既然是这种情况,身为程序员或者刚才说的其他角色的“XX师”们会问,那是否还要参加一个只需要我发言2-3分钟,但会议时间却长达2-3分钟乘以N个人的会议呢?

答案当然是:Absofuckinglutely NOT

回到刚才3个问题:

  • 会仔细听。那么你一定是新来的,自然会了解下大家都在干嘛,虽然结果是你听完还是不知道大家在干嘛。
  • 会记录。那么你一定是个专门负责会议记录的。
  • 居然还能想起来。好吧,如果包括前2个问题的回答都是会的话,那你就是项目经理了,或者说你有这种意向或潜质。

当然,这个绝对不是戏谑或讽刺项目经理啦,现实的确如此。

如果你是个项目经理

刚才说到项目经理,如果你是项目经理,下次开会的时候你可以环顾下周围的人,心里默默计算下这个会议的成本。例如:

  • 有8个人参加,每人每年的人力成本12万RMB,相当于每人参会1小时差不多40RMB吧。那么8个人就是320RMB,这还不包括会前会后每人的时间耗费。
  • 如果不参加会议,而是去工作能创造多少价值?相当于参会丢失了这些价值的机会成本如果也算在内,那么就更多了。

不知道是不是每个项目经理都会这么思考?所以下次开会前一定要问一句:真的有必要开这个会吗?

再回到刚才程序员那小结说的“状态报告”之类的会议,可能除了项目经理外,团队成员的确需要互相知道各自在干嘛。但按照不知道靠不靠谱的经验来看:

  • 一个小项目,也就那么几个人,每个人都已经知道别人在干嘛了。
  • 对于一个大项目,具体做事的人,可能不会再分精力去关心别人的细枝末节的在干嘛了,往往关心的是别人的工作会不会影响自己的进度,是否满足自己的工作需要。

作为项目经理,你必须要掌握的一个技能就是,不开这种“状态报告”之类的会议,也能了解每个人的状态和进展。怎么掌握?

  • 作为同事,你们上下班总时不时碰面吧?
  • 午餐或休息总得聊天吧?
  • 总能看到代码提交吧?
  • 再不行,总还有每日站立会议或一对一会议或邮件周报吧。这里说的每日站立会议或一对一会议,算是“状态报告”会议中比较推荐的,如果用的好的话。

最后

本文,虽然特定角色说明,为了表述方便,都是以程序员或项目经理作为例子,但一定程度上,也同样适用于其他一线干活人员和中高层管理人员。

  • 不开会的意思是,避免陷入过多的“状态报告”等进度汇报之类的会议。
  • 但还是有不少项目过程中的会议,还是有价值和必要的,但前提是也得会组织,有价值的会,没组织好,那还是没价值。这个后续会议相关主题再具体展开说明。

参考

  • 《项目管理修炼之道》

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

如何理解版本控制系统

版本控制系统在当今的软件开发中,被认为是理所当然的配备工具之一。应该现在很少有人对于“版本控制系统”的好处的理解还停留在所谓的代码备份那么简单吧。这里说下个人对版本控制系统的理解。

版本控制系统的历史

版本控制系统作为软件开发中的一个必备工具,回顾下这个工具的发展历史,也有助于理解这个工具都解决了软件开发中的什么问题。

版本控制的历史最早可以追朔到20世纪60年代[1],总结来说就是:

  • 借助人工来进行版本控制
  • 本地版本控制系统
  • 集中式的版本控制系统
  • 分布式的版本呢控制系统

这里顺带提下,版本控制系统提供的3个能力[2]:可逆、并行、注解。也就是可以回到任何一个软件版本、大家可以进行协作开发、可以详细记录代码的变更情况。

使用版本控制系统

从版本控制系统的发展历史可以看出,版本控制的重要性,远远不是单纯用于备份那么简单。还包括:

尽可能版本化所有的东西

不要仅仅将项目的源代码纳入到版本控制下,也应该包括网页、文档、FAQ、设计注释和任何人们希望编辑的内容[3]

但这个可能又会被滥用,所以要特别说明:

  • 一般是将会变化的纳入版本控制,比如:源码、文档等。
  • 而不会变化的就进行归档即可,而非版本化,比如:邮件。
  • 由于当前版本控制系统对二进制文件的变更展现支持不是太好,所以有些文档建议用纯文本方式管理,比如:reStructuredText、MarkDown。
  • 生成的文件就不要纳入版本控制了,比如:源码编译出来的二进制文件。

尽可能方便团队协作

  • 在CVS流行前,还只能对单个文件进行版本控制,后来的Subversion的流行,又是对CVS的进化。
  • 善用分支,避免开发过程中的瓶颈,比如:Git等分布式版本控制系统的流行,对分支的处理更加自如。
  • 项目的版本库应该能够通过Web浏览。这不仅是意味着浏览最新修订的能力,也包括回到过去查看早先的版本,查看修订之间的区别,以及阅读针对特定变更的日志信息等等[3]。很多版本控制系统都有周边的软件支持,比如:Trac、GitHub、GitLab等。
  • 让大家都知晓版本变化的细节。
  • 有一定的权限或授权控制。

尽可能详细记录历史

  • 可以知道哪个版本的哪个文件的哪行代码的原作者是谁。
  • 谁在什么时间进行哪些改动。
  • 按一定约定规范记录变更日志。上面2条,可以由版本控制系统本身来处理,这一条就得看大家项目内部的规范执行力了。

尽可能多的记录软件开发中产生的常见行为

是第一条,“尽可能版本化所有的东西”的扩展,也就是广义上的配置管理,包括:

  • 需求分析、构架设计行为。常见的就转化为对应文档
  • 开发行为。就是大多数程序员喜欢编写的,常见的编写项目代码、产品代码、功能代码。并且,最终用户/客户日常用的软件或服务,也是运行这些代码。
  • 测试行为。随着TDD等开发哲学的流行,逐渐被重视。常见的就转化为对应的单元测试代码、功能测试代码(为了将人工测试行为代码自动化,这样这个行为也被记录到版本控制中了)
  • 配置/部署行为。所谓DevOps的复古的开发哲学流行,也慢慢被接受。常见的就转化为配置文件、部署脚本、运维脚本(更重视配置文件,推行约定大于配置,行为脚本化、工具化、自动化,这样这个行为也被记录到版本控制中了)

参考

注解

[1]http://blog.jobbole.com/14489/
[2]http://www.catb.org/~esr/writings/version-control/version-control.htm
[3](1, 2) http://producingoss.com/zh/vc.html

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

做开源软件项目会用到的服务简介

一般,程序员做公司的商业软件项目都会用到各种工具,这里的工具是指,源代码的版本控制、持续集成等工具。但世界上很大一部分软件项目其实都是开源的(没有统计过,猜想而已),而开源软件项目,参与的程序员一般都分散在世界各地,没有集中的工作场所,自然也不会有统一的涉及软件配置管理等工具的托管场所。

那么,有没有好的解决方案呢,自然是有。即使没有的话,也会有一帮程序员做出类似的工具和服务的。比如,下面会介绍的几个部分服务,完全可以满足大多数开源项目的要求(Python项目就更多满足了)。这些服务都是免费的,当然要更好服务,那就得掏出一些美刀了。

当然,如果信任这些服务的话,很多公司的商业项目或初创公司而言,也完全可以用这些服务起步,只要支付一定的美刀就行,就能省去很多额外的麻烦。

注意:部分服务的网站可能需要翻墙。

源码托管

GitHub:https://github.com/

就不废话了。如果不想折腾成Mantis、Bugzilla、Trac、Redmine那种更为复杂的issues跟踪管理,也完全可以用来做开源项目的issues跟踪管理。

../../../../_images/small-GitHub.png

持续集成

Travis CI:https://travis-ci.org/

很好结合到了GitHub,可以用GitHub帐号统一注册登录。

只要开源项目中,写好相关配置,就会自动按配置执行持续集成的相关过程,比如:单元测试,真正做到让你的每一次代码提交,都能得到测试。当然,前提是你得写单元测试代码啦。

有关单元测试的内容,具体可以看 单元测试

../../../../_images/small-Travis-CI.png

代码质量检查

Landscape:https://landscape.io/

也是很好结合到了GitHub,可以用GitHub帐号统一注册登录。

只要开源项目中,写好相关配置,就会自动按配置执行代码质量检查,看看代码有没有错误啦,有没有“坏味道”啦,以及有没有违反约定风格之类的东西。

不过,这个服务只针对Python作为编程语言的项目,其它编程语言估计也有类似的服务吧(没有的话,估计也有一帮程序员正做着吧)。

../../../../_images/small-Landscape.png

测试覆盖率检查

Coveralls:https://coveralls.io/

同样,很好结合到了GitHub,可以用GitHub帐号统一注册登录。

只要开源项目中,写好相关配置,就会自动按配置,根据持续集成中单元测试结果,得到测试覆盖率。

../../../../_images/small-Coveralls.png

文档托管

Read the Docs:https://readthedocs.org/

这个帐号不能像上面一样用GitHub帐号通用得注册登录了,需要自己单独注册一个。

项目的文档只是得用Sphinx编写(对Python项目而言,Sphinx这个算是标配工具了),就可以很好的用这个服务了。

有关Sphinx的编写,具体可以看 用Sphinx编写技术文档

../../../../_images/small-ReadTheDocs.png

最后

具体如何使用,这里就不介绍了,各个服务的网站上都有足够的文档说明。并且,这只是一片水文而已,所以不会有太多内容的。

上面几个服务,大多都提供徽章了服务,比如,可以直接放到GitHub项目中的README中,这样别人就可以看到你的项目持续集成是否通过、代码质量是否足够好、测试覆盖率是否足够高。例如,这个我正在写的一个简单的类似Hacker News网站的开源项目:https://github.com/akun/PyHackerNews

README中再放上一个项目文档的链接,就给自己的项目配备了很好的文档服务了(特别是很多类库性质的项目)。比如,这个《软件构件实践》系列文章就是用了这个文档托管服务:http://pm.readthedocs.org/

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

如何进行code review?

code reivew是保障代码质量的实用方法之一,这里简单分享下个人code review的经验。建议只当案例来看,因为不同项目、不同团队所做的事情、所具备的技术背景也是不同的,当然也会有些通用的点。

现实情况

先问下,你所在的公司有code review的习惯不?你所在的团队呢?你个人呢?如果没有的话,为啥会忽略这个环节呢?

来说下现实情况,大多数公司做的项目,如果按项目规模来看,都是很小的项目,即使是很大的项目,正确的做法也必然会拆分成多个小项目,由多个团队同时合作完成。在这个前提下,实际每天每个程序员提交的代码量是很少的,对这么少的代码变更进行code review其实是花不了个人多少时间的。

我觉得完全每天可以抽出半小时,乃至一小时的时间来对别人的代码进行review,比如:

  • 或每天上班正式开始自己的工作前,边吃早点边code review别人昨天的几次提交。
  • 或中午大桌聚餐在大液晶屏,边吃边评论互相的代码
  • 或每天下班前花半个小时review下别人代码

只是随便举几个场景的例子,code review形式不限、时间、地点更无所谓。

不同的团队,不同的项目,在不同的时期,可能对代码发布的要求不同:

  • 有的团队,对代码质量控制很严格,会规定凡是提交到主干的代码都必须经过review,严格保持主干的代码历史提交都是足够干净的;
  • 但现实中也有很多团队,根据实际情况采用事后review,review后随时修正,不过这样看起来代码历史看起来不那么干净,但最后结果总还是不错的。

不管如何,我觉得做项目本身都没有十足的铁则,实用就好。

code review的目的

团队中为什么要有code review的文化呢?或者说,code review有哪些好处吗?

从项目角度,很直接,可以提高代码质量。从人性弱点上来看,人类总是乐于去发现别人不好的地方的,所以多一双眼睛,更容易发现代码中的坏味道;另一方面,大家内心深处都是乐于追求真、善、美,所以总是希望写出来的代码更加优美。间接上,代码质量提高,对于一个项目的成功,以及一个软件的生命周期的延长,是个挺必要的因素,至少微观上是这样。

从个人角度,可以大致分两种。对于初学者学习,从看有经验人的代码过程中,可以学到好的代码实践;对于有经验的人自省,自然能发现各种不好的,时刻提醒让自己避免这种不好的代码,顺便可以帮助别人更好更快地提高,毕竟大家都是从初学者过来的。

从团队角度,逐渐拉近团队成员间的水平。尽管团队中成员各有所长,但在代码层面会有共同标杆,共同进步,让团队整体实力得到提高。

code review的形式

code review的形式各种各样,这里按人数规模来分类。

1自审

这个其实是大家都在做的方式,但可能不一定每个人都能做得好,或者说人类总是很难自觉做到从自己身上发现问题,经过一定训练后逐渐会学会限制这种本能。一种方式可以有所帮助,就是找个木偶讲解代码,比如:放在桌上的瓦力小机器人或盆栽之类的东西。讲出你写的代码的变化,也许就会发现一些问题。

当然,一般都会借助一些工具来帮忙检查代码,比如:各种语言的lint工具或者很多IDE本身的功能,在代码风格这些偏静态层面的东西,可以辅助发现一些问题。

另外,随着程序员编程经验的增长,逐渐会注意到代码的各种坏味道,这也是给别人做code review的基础。

这种形式的code review,发现了问题基本上就是立马纠正了。或者说,自己已经处理了一大半的代码坏味道了,剩下可能真的自己都很难发现了,毕竟不是每个人的自省能力都是很高的,所以才有了后面的几种形式。

p2p互相

这种形式其实也很常见,特别是两个小伙伴一起协作编程的时候,比如:所谓的结对编程。当然,也可以不是结对编程,比如:两个人挨着坐,一个人完成一块逻辑复杂或稍有难度的代码后,需要别人来把把关,这个时候就可以叫上旁别的人帮忙过下代码或自己给他讲解下代码。

如果是结对编程,个人认为还是需要两人水平相当,一个人在一个显示器前写代码,另一个人在另一个复制的显示器前观察写的代码,有问题随时沟通,也许两人各有所长,一人完成某块他擅长的部分后,两人交换角色。这个时候,其实是个互相审核的过程。

当然,也有不在少数的人不习惯所谓结对编程,那么就各自干活,谁觉得需要另一个人帮忙,就进行code review。这种形式,个人觉得就不必要求两人水平相当,也可以一个老手+新手的组合。

另外,个人建议这种形式的code review,最好两人负责的任务是比较相似或有交集,效果会更好些,不然一个人不理解另一个人的业务逻辑,效果上还是会打些折扣的。

这种形式的code review,也是发现了问题基本上就是立马纠正了。如果这个时候提交代码,至少2个人看过了,或者说有2个人对这块代码负责了,也能从一定程度上对这块代码所具备的知识有了一份复制,软件这种东西的背后知识看不见摸不着,知识的复制备份尤其重要。

2-3单向

这种形式随着各种code review的工具出现,越来越方便,自然也就越来越流行了。这里的“2-3”意思并不是只有2个人或3个人,根据需要也可以再增加人数,特别是有很多code review工具辅助的时候。

这里特别强调是单向,说白了就是你提交了代码,别人来评论,赞一下或踩一下,当然写代码的人也不要把这些评论太过心,过脑即可,意思是说别人的评论都是善意的,别人好的建议或意见可以采纳,如果要拒绝可以向对方说明理由,就是觉得不爽时候别太放在心上。如果不习惯用各种code review辅助工具,可能就是比如:有2个人站在你后面,你指着显示器上的代码给他俩各种讲解,他们有疑问随时提问,你负责说明,也就是一问一答。如果有code review工具辅助,那就更方便了,大家直接看代码,然后在线对代码进行评论,比如:如果有2+以上人允许通过,那就可以提交,如果有人反对或有改进建议,就直接评论,各自的邮箱也能收到需要code review的任务和结果,那块代码的作者也可以根据评论来改进自己的代码。

当然,这个时候个人建议那2个人中,有至少一人是个老手,最好是那种什么扫地阿姨,背后一站,扫一眼就看出你这有个溢出那种。当然这是玩笑了,意思是得有个有经验的程序员把关。

另外,至于是否对这块代码有了解或熟悉,就可以不用像上一种“p2p互相”那种形式要求的那样,大家最好都要对这块代码熟悉了。

这种形式的code review,一般是由写代码的人负责记录问题,当然有code review工具辅助,那就工具自动给你记录了,省去很多麻烦。这种形式也是最省事的,特别是有了工具辅助,也就没有时间、地点的限制了。

>3展示

这种形式有点会议形式,更接近“代码审查”了,而不是“代码评审”,一堆人圆桌+大屏。如果有场景,的确要用到这种形式的code review那就要慎用了,大家都知道一旦变成开会形式了,那就必然会有会议的各种缺点出现,所以要注意。

  • 首先,得有个主持人,无特定人员要求,也可以是写代码的那个人,没有关系,关键这个主持人能控场,别让偏题了,毕竟人多口杂。
  • 其次,必须举手发言,不然容易变成聊天。
  • 最后,避免争论或者叫吵架吧,可以讨论或叫沟通吧,互相不要对着人说话,而是对着某个物件来说话,比如:前面提到的那个瓦力机器人和盆栽,这样能一定程度上避免出现争论或吵架的情况。

当然,一旦开会形式了,针对某个人写的代码,容易变成批评、批斗,所以还得要注意:

  • 不要去关注谁写的代码
  • 对代码不对人
  • 重点在业务逻辑或代码设计实现
  • 弱化代码风格的评价
    • 除非展示目的是为了大家了解代码风格
    • 除非代码风格让人没法进行代码的审查

另外,如果没有强烈的特别的需要,其实还是不要这种形式的code review了,主持人不好或大家意识上不到位,容易浪费时间,效果不一定好。

这种形式的code review,可以指定一个记录人,当然就和主持人不是一个人了,不然主持人即当爹又当妈,功能不要太全哦!当然最好可以是写代码的那个人。

其它

这里说些其它形式的code review。可以引入某方面的专业人士,比如:安全专家,对代码进行安全层面的审计,虽然程序员必须具备安全意识,但安全层面的思路有时候还是会有所差异的,毕竟每个人的安全意识水平也参差不齐;再比如,需要对数据库或操作系统等的特别操作,可以让这方面的专家帮忙把关,当然如果团队中有这些类型角色的成员,就再好不过了。

最后,提几个建议,可能上面说过:

  • 针对代码,不针对人。但另一方面代码毕竟人写的,所以需要注意评论的方式。
  • 提出评论的人,可以采用发问的形式评论,比如:是不是XXXX这样改,这块代码执行起来就会更加OOOO了?类似这样的语气
  • 被评论代码的人,别有抵触心理,要有空杯心态,就是上面说的过脑但别过心了。
  • 程序员这个群体,并不是所有人都是天才级别的,所以当你看到别人写的烂代码时候,也得知道自己当初也可能经历过那个阶段。

code review的重点

本来想说下,哪些地方需要重点code review,但发现场景不同,项目不同,采用的技术不同,大家的关注点都不一样,如果说成通用的,反而没有重点了。比如:大段代码、大函数、大类、长SQL、复杂存储过程、使用频率高的功能、代码嵌套太深、核心算法实现、核心接口实现、函数参数过多、用户输入校验是否有安全隐患、异常处理、日志记录、业务是否需要事物等等太多了。

另外,不同编程语言、编程框架还有不同规范性质的编程建议,实在不好说重点。

如果要看重点,估计还是自己去翻一遍《代码大全》比较实际。

code review的工具

code review有很多工具,最原始的莫过于人肉了,这里简单列举下几个工具,看各自实际情况使用了。一般对这种类型工具或软件的要求可能有:

  • 可以评论,跟代码关联程度高,然后最好能跟网易评论似的各种嵌套。
  • 可以邮件提醒。
  • 可以很好支持Git或SVN等版本控制工具。
  • 可以很好支持Jenkins等CI工具。
  • 可以支持命令行,方便写脚本集成其它工具。

列举下我知道的工具:

  • Trac插件:Code Comments。如果用的Trac管理项目,可以用下,但没有上面说的具备的要求那么全。
  • Review Board。一个Python写的Web类型工具。
  • Facebook Phabricator。小有名气,Facebook出品,应该人家内部自己做项目都用的这个,功能有点太全了,可能如果单做code review,会有点晕。
  • Gerrit。一个Java写的Web工具,可以和Jenkins很好结合,也算好用。
  • Code:豆瓣出品,没用过,但据说不错。

另外,用了工具后,别忘了还有最实用的面对面交流这个人肉工具,也许用惯了冷冰冰的工具外,小伙伴互相看完代码后,温暖的会心一笑还是不错的。

最后

  • code review多在平时的零散时间。
  • 贵在坚持、积累,让code review成为呼吸一样理所当然的事情吧。
  • 对代码质量有更多提高效果的其实是“code review的形式”说的平时的前3种情况。
  • 某种角度说,代码是产品、项目、软件的基石,还是需要关注下的。

有关code review的趣评(来自网上,出处不详):

  • 如果代码变更在几十行内,能写出一堆评论
  • 如果代码变更成百上千了,基本都一个评论,“你的代码不错~”

其实想表达的意思是,递交的代码让别人review不要太爆炸了,别人会很有压力的。

后续,有关code review的主题,会具体介绍下几个常用的code review工具的安装和使用。

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

用Sphinx编写技术文档

大家会发现,如果一个项目主要是用Python写的,其文档都很类似,比如:Python在线的HTML官方手册。这些项目的文档都来源于一个很不错的项目:Sphinx。这个Sphinx特指Sphinx doc这个项目(另一个也叫Sphinx的search的项目,虽然都叫一个名字)。

官网:http://sphinx-doc.org/

以下出现Sphinx的地方,都特指Sphinx doc这个项目

使用场景

  • 很多开源Python库项目的API手册都是用这个写的,可以看Sphinx官网给的链接:http://sphinx-doc.org/examples.html
  • 如果是用Python写的商业软件,也可以用这个来写技术文档,纯文本管理研发文档,保证功能代码、测试代码、相关文档同时更新
  • 直接拿来写在线书。比如,这个《软件构建实践系列》就是:https://github.com/akun/pm
  • 直接用来做slide等演示幻灯片,从一定程度上替代PowerPoint。比如,http://example.zhengkun.info/slide.html

功能

这里就列举个人关心的几个特性:

  • 文本是rst格式语法
  • 生成HTML、PDF、Slide(需要插件)等格式的文档
  • 支持文档、代码等引用
  • 支持自定义样式
  • 支持插件扩展
  • 直接看官网手册了解更多:http://sphinx-doc.org/contents.html

语法简介

就是rst的语法,这里就列举几个常用的:

标题等级

rst如下:

一级标题
========

二级标题
--------

三级标题
^^^^^^^^

效果如下:

习惯上,可以用以下字符:“= - ` : ‘ ” ~ ^ _ * + # < >”。最好能约定个依次标题等级。

列表

rst如下:

* 列表1
* 列表2
* 列表3

效果如下:

  • 列表1
  • 列表2
  • 列表3

列表写法除了用“*”,还可以用:“-”,“+”,最后效果一样。

上面说的是无序列表,如果想用有序列表,可以用“#.”。

rst如下:

#. 列表1
#. 列表2
#. 列表3

效果如下:

  1. 列表1
  2. 列表2
  3. 列表3

表格

rst如下:

=====  =====  =====
第1列  第2列  第3列
=====  =====  =====
8      1      6
3      5      7
4      9      2
=====  =====  =====

效果如下:

第1列 第2列 第3列
8 1 6
3 5 7
4 9 2

插入图片

rst如下:

.. image:: images/ball1.gif

效果如下:

../../../../_images/ball1.gif

插入代码

展示代码示例,经常会用到:

默认

rst如下:

::

   print 'Hello World!'

效果如下:

print 'Hello World!'

自定义

rst如下:

.. code-block:: python
   :linenos:

   print 'Hello World!'

效果如下:

1
print 'Hello World!'

引用代码文件

rst如下:

.. literalinclude:: code/example.js
   :language: javascript
   :linenos:

效果如下:

提供下载文件链接

直接下载该RST本身。

rst如下:

:download:`sphinx.rst <sphinx.rst>`

效果如下:

目录索引

example1对应sphinx.rst所在目录下的example1.rst文件,example2类似。

rst如下:

.. toctree::
   :maxdepth: 2

   example1
   example2

效果如下:

  • 二级标题1
  • 二级标题2

引用

可以用于跨rst文档间的内容互相引用。这里以本文档内为例。

rst如下:

.. _my-reference-label:

用Sphinx编写技术文档
====================

很长的文字内容

点击回到顶部, :ref:`my-reference-label`.

效果如下:

点击回到顶部,  用Sphinx编写技术文档 .

文字效果

斜体

rst如下:

*斜体*

效果如下:

斜体

粗体

rst如下:

**粗体**

效果如下:

粗体

下标

斜杠是为了空格转义,最后显示无空格。

rst如下:

H\ :sub:`2`\ O

效果如下:

H2O

上标

rst如下:

E = mc\ :sup:`2`

效果如下:

E = mc2

参见

Hello World

根据上面的介绍,其实常用的语法不多,现在直接用下,自己感受下吧!

安装 & 初始化

常用Python安装方式,创建个文件夹,执行命令,按提示自己选择即可。

pip install Sphinx
mkdir docs
cd docs
sphinx-quickstart

根据提示输入相应参数即可,可以一路默认。

尝试编辑

编辑index.rst,只写入以下内容

用Sphinx编写技术文档
====================

使用场景
--------

生成HTML

很简单,默认支持就很好。

make html
python -m SimpleHTTPServer 9527

直接浏览器访问9527端口,就可以看到类似Python官方文档的效果。

生成PDF

麻烦些,需要依赖库,且需要简单修改下配置。

  1. 安装依赖库
pip install rst2pdf
  1. 编辑conf.py,增加或修改如下配置:
  2. 编辑Makefile,增加如下代码:

Linux下的Makefie:

Windows下的批处理:

  1. 执行生成PDF
make pdf
python -m SimpleHTTPServer 9527

参见

有关PDF的更多配置,可以阅读这个文档:http://ralsina.me/static/manual.pdf

生成Slide

Slide就是我们常说的演示文档,如:Windows下的PowerPoint(PPT);Mac下Keynote等等。这里用Sphinx生成在线的HTML5形式的Slide,操作也相对简单,也是需要依赖库和简单修改下配置。

  1. 安装依赖库
pip install hieroglyph
  1. 编辑conf.py,修改如下配置:
  2. 编辑Makefile,增加如下代码:

Linux下的Makefie:

  1. 执行生成Slides
make slides
python -m SimpleHTTPServer 9527

参见

有关Slide的更多信息,可以直接查看这个项目:https://github.com/nyergler/hieroglyph

自定义样式

直接拿来主义,直接用别人写的Trac的样式

  1. 复制样式文件到静态资源目录,比如,这里是:
cp tracsphinx.css _static/
  1. 编辑conf.py,增加或修改如下配置:
  2. 执行生成HTML
make html
python -m SimpleHTTPServer 9527

直接浏览器访问9527端口,就可以看到类似Trac的官方样式效果。

汇总到一块

可以直接看Python项目模板:https://github.com/akun/aproject/只看docs目录即可。

这里提到的几个核心文件示例如下:

  • index.rst
  • conf.py
  • Makefile
  • css

另外推荐一个服务:https://readthedocs.org/

如果你的项目研发文档用Sphinx写的,可以用来做文档的持续集成,相当方便。

这个《软件构建实践系列》就是用的这个服务。

最后

这是一篇很简单的项目推广文章,在自己的Python项目中把Sphinx用起来吧!

当然Sphinx不仅支持Python源码的Domain,而且支持C、C++、JavaScript等Domain,即使没有你所用的语言的Domain,它本身还支持写插件扩展,所以其它类型语言的项目也不妨用一下。

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm

SVN命令用法:程序员的场景

SVN有不少命令,其实常用的也就那么几个,可以结合下实际的使用场景,来说明下SVN的命令用法。

当然可能对很多人来说,最实用的熟悉方式,就是直接运行

svn help (?, h)

就入门了,但为了更好的记忆,有个实际场景也是个不错的选择。

注解

括号中的是该命令的缩写或别名,有的可以少打几个字母,后面也有类似描述。

“新人报道”

你刚入职一家公司,或新加入某个团队,立马参与到一个项目中,项目代号Norther,那么就得获取项目代码,开始你的项目生涯。这个时候一般你需要签出项目代码:

svn checkout (co) https://scms.ship.it/svn/norther/trunk norther

确认工作目录的SVN信息,说明已经纳入版本控制了:

cd ~/projects/norther
svn info

确认没问题了,就运行项目中的构建脚本,然后就可以熟悉代码,展开具体工作了。

当然,有的时候,有一个新项目是由你发起的,你要将初始化的项目工程放到SVN版本仓库中:

svn import souther https://scms.ship.it/svn

确认项目已经在版本仓库中了:

svn list (ls) https://scms.ship.it/svn/souther/trunk

应该就可以看到Souther项目的根目录结构了。

日常工作

当你已经逐渐融入了一个项目,可能一天的工作场景或完成某个任务的工作周期是这样的:

更新

无论是清早或下午或晚上,开始了你的一天工作,你首先会更新你的工作目录:

cd ~/projects/norther
svn update (up)

这样你就可以在最新的项目代码基础上工作了。

修改

可能你写了一个新的模块,需要纳入项目的版本控制:

svn add tools.py

可能你发现某个模块已经陈旧了,不再使用了:

svn delete (del, remove, rm) utils.py

可能你发现一个模块的命名不太合理,需要改名:

svn move (mv) model.py models.py

可能你要创建一个新的较大的模块,需要归档为目录的方式:

svn mkdir groups

可能你发现要写的模块代码布局类似于旧的模块,直接复制个代码模版:

svn copy (cp) users/tests.py groups/tests.py

当然,其实最常见的情况其实还是打开编辑器,比如Vim,修改已经存在的代码,这个就跟SVN命令无关了。

检查

忙碌的一天过去了,或者一个任务完成了,这个时候一般会将你的工作成果,也就是代码更新到版本仓库。

习惯上会先检查下修改状态:

svn status (stat, st)

看到一些SVN状态位信息,确认是修改了哪些文件,之后一般会自己code review一下代码的改动,可能有的人会习惯直接用SVN方式来查看:

svn diff (di)

然后本地运行下相关的单元测试,确认是否有问题。一般来说这个时候,没有什么特殊情况,就直接进入“提交”阶段了,然后结束一个工作日或工作周期,但难免会有些特殊情况出现。

取消修改

当你code review完后,发现有些改动不满意,你可能又会取消这些修改:

svn revert main.py

解决冲突

当你打算提交时候,习惯上一般会再次更新自己的工作目录,现在合并下别人的工作成果(如果有的话):

svn update (up)
  • 可能这个时候更新完代码,你对某个模块的代码有改动,别人也改动了同一个模块的代码,可能就会产生代码冲突。
  • 也可能有的人没这习惯,就直接提交代码,发现提交没有成功,一看,原来是别人提交的代码刚好也改动了你提交的代码,也产生了冲突。

无论哪种情况,就是代码冲突了,需要解决冲突,一般会人工确认代码合并,处理冲突的代码,是选择别人的处理,还是自己的处理,还是要额外处理,处理完毕后,执行命令,比如:

svn resolve main.py --accept working

另外,也有个resolved命令,用来删除“冲突”状态,但官方说被上面命令替换了,不推荐使用了:

svn resolved main.py

提交

最后,一切确认没问题了:code review完毕,自己觉得代码满意了;然后也合并完别人的修改并且没有冲突了;运行单元测试也通过了。那么就提交代码吧:

svn commit (ci)

在分支工作

源代码的管理和发布:以SVN为例 这篇文章中,介绍的SVN开发模式中,涉及分支的概念,一般来说会有以下3种情况:

  • 贡献新特性。也就是说,为了增加新的功能,或者对旧功能的改进等等。
  • “除虫”。就是日常说的缺陷修复。
  • 发布阶段(发布分支)->旧版本维护(旧版本维护分支)。这个概念稍微复杂,trunk研发到某个阶段,代码符合某个版本发布条件了,就会新建1个发布分支,测试没问题了,就在这个分支上进行发布;发布完成后,这个版本的维护就在这个维护分支上进行了;这个时候trunk已经进行最新版本的研发了,所以说这个分支是个旧版本维护分支。

上述说的3种分支情况,前两个分支的生命周期比较短,新特性搞定或“除虫”完毕,合并代码到trunk后就结束自己的生命周期了。

最后一种情况,生命周期相对较长,如果这个分支需要维护的版本还要支持,那么就得一直存在,直到不再维护为止。

下面说下在分支工作的实际场景,按顺序:

创建新分支

当上述3种场景发生,确定要新开个分支来写代码,先复制trunk到分支,这里以贡献新特性为例子:

svn copy (cp) https://scms.ship.it/svn/norther/trunk https://scms.ship.it/svn/norther/branches/feature1

切换到新分支

一般来说这个时候本地的工作目录是trunk,确定本地工作目录是干净的,为后续在分支工作,以及合并分支做好准备,避免可能的各种代码冲突或工作成果代码被覆盖等情况出现。

确认当前所在的SVN工作目录,比如,可能是在trunk的SVN路径:

svn info

确认工作目录干净:

svn status (st)

切换到刚才新创建的分支:

svn switch (sw) https://scms.ship.it/svn/norther/branches/feature1

确认切换后的SVN工作目录,应该就是在刚才新创建的分支的SVN路径了:

svn info

在新分支工作

类似,“日常工作”中的工作周期操作,这个时候,你就可以在新分支中进行大刀阔斧的工作了,直到分支中代码符合合并到trunk的的条件了。

合并分支到trunk

在分支中工作一段时间后,确认相关的功能代码、测试代码、文档等都提交完毕了,单元测试通过,大家code review一致认为没问题,审核通过,最后该分支的持续集成(CI)完整build通过。这个时候,就可以进行合并到trunk的操作了。

确保下面操作是在工作目录的根目录下进行

cd ~/projects/norther/

确认分支工作目录干净,没有需要提交的代码了:

svn status (st)

切换工作目录回trunk,如果由于代码变动大有冲突,就解决冲突,特别如果有目录变动很可能有目录冲突:

svn switch (sw) https://scms.ship.it/svn/norther/trunk

确认切换后的SVN工作目录是trunk:

svn info

先在本地合并分支的代码,合并过程可能会有代码冲突,解决冲突,合并会指定版本范围,一般都是分支建立时候的版本号到分支工作完毕时候最后一次提交的版本号:

svn merge -r9527:9549 https://scms.ship.it/svn/norther/branches/feature1 .

确认本地代码变更,code review一下,执行下单元测试:

svn status (st)
svn diff (di)

确认代码没问题,正式提交代码到trunk,SVN的提交日志说明下合并信息:

svn commit (ci)

删除分支

如果确认工作完毕的分支不再需要了,那就记得及时清理掉:

svn delete (del, remove, rm) https://scms.ship.it/svn/norther/branches/feature1

Ship it

在上面说的发布分支工作一段时间后,并且测试完毕,大家觉得符合发布条件了。终于可以进入到版本发布阶段的工作了。

具体故事场景可以看 源代码的管理和发布:以SVN为例 这篇文章,有对“发布分支”的介绍。

一般来说这个时候已经将trunk复制一份到了发布分支了:

svn copy (cp) https://scms.ship.it/svn/norther/trunk https://scms.ship.it/branches/1.0.x

打标签

复制最新的发布分支为标签:

svn copy (cp) https://scms.ship.it/svn/norther/branches/1.0.x https://scms.ship.it/svn/norther/tags/1.0.0

正式发布

发布又是一个比较复杂的主题,比如:能快速发布、快速回滚(包括数据回滚)、灰度发布等等,在 构建发布工具 中会详细进行介绍,这里就简单罗列下。

情况1:完整包。导出代码,然后执行打包命令,进行完整安装:

svn export https://scms.ship.it/svn/norther/tags/1.0.0 norther

情况2:补丁升级包。相对复杂,可能会综合运用下列命令,制作补丁安装升级包:

svn status (st)
svn diff (di)
svn patch

情况3:线上更新。一般不太推荐,需要注意不要泄露“.svn”,特别是旧版本的SVN,每个目录下都有“.svn”。可能会用到下列命令:

svn update (up)
svn switch (sw) https://scms.ship.it/svn/norther/tags/1.0.0

一般来说,根据实际情况,可以记录下来发布相关的操作过程。很多环节可以写脚本将原来的人工操作改成自动化操作。以后只要执行发布脚本执行一键发布就可以了。

其它场景

可能还有很多别的场景,比较零散,但也算经常用到。

code review查看代码,要知道对应代码是由谁写的,好询问了解具体代码的思路:

svn blame (praise, annotate, ann)

跟踪问题时候,会查看日志,更方便历史代码定位:

svn log

经常碰到代码锁定或很多诡异情况:

svn cleanup

编辑特定属性,比如:定义忽略规则;依赖其它SVN路径等等

svn propedit (pedit, pe) svn:ignore .
svn propedit (pedit, pe) svn:externals .

SVN客户端更新,使用新的SVN客户端了,有时候会发现本地工作目录SVN相关信息陈旧,会提示你升级:

svn upgrade

好习惯

这里顺带说下几个使用SVN的好习惯,但有的其实跟SVN联系也不算大,只是顺带提下:

  • 保持工作目录干净。或者说工作目录中的代码变更就为了完成一个任务,即一次只做一件事。完成任务后,就直接svn commit (ci)提交到版本仓库,而不用担心其它任务作出的代码变更无提交。并且,对于分支和trunk间切换更方便,而不用担心代码被覆盖或冲突的问题。
  • SVN的日志信息足够有效。足够有效的意思,是说这次提交作出的变更摘要,只要别人阅读了日志就能知道大概,如果为了深入了解变更细节才会去查看具体代码变更。
  • svn commit (ci)前先svn update (up)。可能不更新提交也没问题,但也有可能会相关代码被别人改动了,而提交不了,为了避免这种情况,先本地更新完毕,确保别人的改动不影响你对代码修改的意图。
  • svn commit (ci)前code review。code review本身就是个好习惯,提交前确认是一种更为严谨的方式,如果觉得自己code review发现不了什么问题,那么随便从身边抓个会代码的,跟别人讲解下代码变更的内容,说不定会发现你没考虑到的问题。
  • svn commit (ci)前跑单元测试。写单元测试本身也是个不错的习惯,如果项目本身已经有了完备的单元测试覆盖了,那么你对代码的修改,应该能通过单元测试,所以提交前执行一遍是否通过。如果没通过,就得看下是功能代码写的有问题,还是测试代码有问题,比如:功能需求或接口设计有变化,而测试代码没有同步更新。
  • 有代码变更及时提交。有SVN这种版本控制工具,本身就是为了记录研发过程,避免意外导致代码丢失,如果为了完成某个任务需要很长时间,代码也很久没有提交,风险太高。这个时候,一般会自己开个分支,而将代码提交到分支中,既解决代码要及时提交的问题,又解决代码提交频繁,可能造成代码不稳定影响别人的问题,因为那个分支只有你自己在工作。

最后

这些场景覆盖的SVN命令其实很有限,如果要完整的熟悉,那就svn help以及阅读下SVN的官方手册,有个系统的学习,基础才会更加牢固。

后续

另外,这里只是以程序员的场景来简单介绍SVN使用,对于系统管理员,可能有一部分职责是作为SVN版本仓库管理员,日常也会遇到的各种场景吧,后续也会简单介绍。

参考

注解

这篇是个人总结的《软件构建实践》系列的一篇文章,更多更新内容,可以直接在线查看:http://pm.readthedocs.org。并且部分内容已经公布在GitHub上:https://github.com/akun/pm