├── .gitignore ├── Makefile ├── README.md ├── docs ├── Makefile ├── _static │ └── tracsphinx.css ├── changelog │ └── index.rst ├── codereview │ ├── astng_django.py │ ├── astng_django.pyc │ ├── error.py │ ├── example.py │ ├── howto.rst │ ├── index.rst │ ├── plugin.rst │ ├── pylint.rst │ ├── tools.rst │ └── why.rst ├── conf.py ├── construction │ ├── build_opensource.rst │ ├── index.rst │ ├── namespace.rst │ ├── onepiece.png │ └── tox_result.log ├── continuous │ └── index.rst ├── contribute │ └── index.rst ├── doc │ ├── code │ │ ├── example.js │ │ ├── pdf.Makefile │ │ ├── pdf.bat │ │ ├── pdf.py │ │ ├── slides.Makefile │ │ ├── slides.py │ │ └── style.py │ ├── example1.rst │ ├── example2.rst │ ├── images │ │ └── ball1.gif │ ├── index.rst │ └── sphinx.rst ├── index.html ├── index.rst ├── intro │ └── index.rst ├── make.bat ├── meeting │ ├── index.rst │ └── no_meeting.rst ├── note │ └── index.rst ├── process │ ├── index.rst │ ├── loop.rst │ └── static │ │ ├── before_and_after.jpg │ │ ├── code1.jpg │ │ ├── code2.jpg │ │ ├── code3.jpg │ │ ├── code4.jpg │ │ ├── code5.jpg │ │ ├── code6.jpg │ │ ├── code7.jpg │ │ ├── design_detail.jpg │ │ ├── design_summary.jpg │ │ ├── i_need.jpg │ │ ├── idea_bulb.jpg │ │ ├── joke_requirement.jpg │ │ ├── not_i_need.jpeg │ │ ├── right_requirement.jpg │ │ ├── team.jpg │ │ ├── test.jpg │ │ ├── test_all_in_one.jpg │ │ ├── test_all_in_one_nice.jpg │ │ └── wrong_requirement.jpg ├── reference │ └── index.rst ├── tao │ ├── index.rst │ ├── opensource.rst │ ├── static │ │ ├── small-Coveralls.png │ │ ├── small-GitHub.png │ │ ├── small-Landscape.png │ │ ├── small-ReadTheDocs.png │ │ └── small-Travis-CI.png │ └── who_is_user.rst ├── unittest │ ├── concept_relationship.png │ ├── django │ │ ├── example.py │ │ ├── index.rst │ │ └── test_example.py │ ├── example.py │ ├── example_test_suite.py │ ├── hello.rst │ ├── index.rst │ ├── javascript.rst │ ├── lizi.jpg │ ├── mock.rst │ ├── order.png │ ├── py2_py3_with_tox.rst │ ├── python.rst │ ├── test_doctest.py │ ├── test_order.py │ ├── test_random.py │ ├── test_set_up_error.py │ ├── test_tear_down_always.py │ ├── tox.ini │ └── tox_result.log └── vcs │ ├── branch.rst │ ├── git │ ├── contrastive.rst │ ├── git_3_kingdom.png │ ├── index.rst │ ├── management.rst │ ├── subversion2git.rst │ └── usage.rst │ ├── index.rst │ ├── svn │ ├── SVN_normal.png │ ├── SVN_small.png │ ├── SVN_strict.png │ ├── SVN_tree_example.png │ ├── index.rst │ ├── management.rst │ ├── usage.rst │ └── usage_admin.rst │ └── understanding.rst ├── pm ├── onepiece │ ├── nose.cfg │ ├── onepiece │ │ ├── __init__.py │ │ ├── example.py │ │ └── main.py │ ├── setup.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_example.py │ │ └── test_main.py │ └── tox.ini ├── slide │ ├── Makefile │ ├── _static │ │ └── tracsphinx.css │ ├── conf.py │ ├── index.rst │ └── make.bat ├── starwars.skywalker │ ├── setup.py │ └── starwars │ │ ├── __init__.py │ │ └── skywalker │ │ ├── __init__.py │ │ ├── anakin.py │ │ ├── luke.py │ │ └── shmi.py └── starwars.yoda │ ├── setup.py │ └── starwars │ ├── __init__.py │ └── yoda │ ├── __init__.py │ ├── be.py │ ├── force.py │ ├── may.py │ ├── the.py │ ├── with.py │ └── you.py ├── requirements.txt └── slides ├── Makefile ├── _static ├── auto_test.png ├── custom.css ├── dict.png ├── git_3_kingdom.png ├── git_log.png ├── git_simple.png ├── github_blog.png ├── google_developers_icon_128.png ├── logo.png ├── rst_editor_qt1.png ├── space.png ├── start.png └── triangle.png ├── conf.py ├── git_part1.rst ├── how_to_run_a_successful_software_project.rst ├── index.rst └── subversion2git.rst /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *.swp 4 | *env 5 | .coverage 6 | .tox 7 | _build 8 | bin 9 | build 10 | dist 11 | include 12 | lib 13 | local 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKE = make 2 | DOC_DIR = docs 3 | SLIDE_DIR = slides 4 | ONEPIECE_DIR = pm/onepiece 5 | 6 | .PHONY: all install test html slide ui clean 7 | 8 | all: 9 | make install 10 | make html 11 | make slide 12 | make test 13 | 14 | install: 15 | pip install -r requirements.txt 16 | cd $(ONEPIECE_DIR) && pip install -e .[test] 17 | 18 | test: 19 | cd $(ONEPIECE_DIR) && tox 20 | 21 | html: 22 | cd $(DOC_DIR) && $(MAKE) html 23 | 24 | slide: 25 | cd $(SLIDE_DIR) && $(MAKE) slides 26 | 27 | ui: 28 | cd $(DOC_DIR) && python -m SimpleHTTPServer 29 | 30 | clean: 31 | cd $(DOC_DIR) && $(MAKE) clean 32 | cd $(SLIDE_DIR) && $(MAKE) clean 33 | cd $(ONEPIECE_DIR) && rm -rf *.egg-info .tox 34 | find $(ONEPIECE_DIR) -name '*.pyc' | xargs rm -f 35 | find $(ONEPIECE_DIR) -name '__pycache__' | xargs rm -rf 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 在线阅读:http://pm.readthedocs.org/ 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = pm 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | slides: 16 | $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides 17 | @echo 18 | @echo "Build finished. The slides are in $(BUILDDIR)/slides." 19 | 20 | .PHONY: help slides Makefile 21 | 22 | # Catch-all target: route all unknown targets to Sphinx using the new 23 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 24 | %: Makefile 25 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 26 | -------------------------------------------------------------------------------- /docs/_static/tracsphinx.css: -------------------------------------------------------------------------------- 1 | /* Trac specific styling */ 2 | 3 | @import url("sphinxdoc.css"); 4 | 5 | /* Structure */ 6 | 7 | div.footer { 8 | background-color: #4b4d4d; 9 | text-align: center; 10 | } 11 | 12 | div.bodywrapper { 13 | border-right: none; 14 | } 15 | 16 | /* Sidebar */ 17 | 18 | div.sphinxsidebarwrapper { 19 | -moz-box-shadow: 2px 2px 7px 0 grey; 20 | -webkit-box-shadow: 2px 2px 7px 0 grey; 21 | box-shadow: 2px 2px 7px 0 grey; 22 | padding: 0 0 1px .4em; 23 | } 24 | 25 | div.sphinxsidebar h3 a, 26 | div.sphinxsidebar h4 a { 27 | color: #b00; 28 | } 29 | 30 | div.sphinxsidebar h3, 31 | div.sphinxsidebar h4 { 32 | padding: 0; 33 | color: black; 34 | } 35 | 36 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 37 | background: none; 38 | border: none; 39 | border-bottom: 1px solid #ddd; 40 | } 41 | 42 | div.sphinxsidebar input { 43 | border: 1px solid #d7d7d7; 44 | } 45 | 46 | p.searchtip { 47 | font-size: 90%; 48 | color: #999; 49 | } 50 | 51 | /* Navigation */ 52 | 53 | div.related ul li a { color: #b00 } 54 | div.related ul li a:hover { 55 | color: #b00; 56 | } 57 | 58 | /* Content */ 59 | 60 | body { 61 | font: normal 13px Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; 62 | background-color: #4b4d4d; 63 | border: none; 64 | border-top: 1px solid #aaa; 65 | } 66 | h1, h2, h3, h4 { 67 | font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; 68 | font-weight: bold; 69 | letter-spacing: -0.018em; 70 | page-break-after: avoid; 71 | } 72 | 73 | h1 { color: #555 } 74 | h2 { border-bottom: 1px solid #ddd } 75 | 76 | div.body a { text-decoration: none } 77 | a, a tt { color: #b00 } 78 | a:visited, a:visited tt { color: #800 } 79 | 80 | :link:hover, :visited:hover, 81 | a:link:hover tt, a:visited:hover tt { 82 | background-color: #eee; 83 | color: #555; 84 | } 85 | 86 | a.headerlink, a.headerlink:hover { 87 | color: #d7d7d7 !important; 88 | font-size: .8em; 89 | font-weight: normal; 90 | vertical-align: text-top; 91 | margin: 0; 92 | padding: .5em; 93 | } 94 | a.headerlink:hover { 95 | background: none; 96 | } 97 | 98 | div.body h1 a, div.body h2 a, div.body h3 a, 99 | div.body h4 a, div.body h5 a, div.body h6 a { 100 | color: #d7d7d7 !important; 101 | } 102 | 103 | dl.class { 104 | -moz-box-shadow: 1px 1px 6px 0 #888; 105 | -webkit-box-shadow: 1px 1px 6px 0 #888; 106 | box-shadow: 1px 1px 6px 0 #888; 107 | padding: .5em; 108 | } 109 | dl.function { 110 | margin-bottom: 24px; 111 | } 112 | 113 | dl.class > dt, dl.function > dt { 114 | border-bottom: 1px solid #ddd; 115 | } 116 | 117 | th.field-name { 118 | white-space: nowrap; 119 | font-size: 90%; 120 | color: #555; 121 | } 122 | 123 | td.field-body > ul { 124 | list-style-type: square; 125 | } 126 | 127 | td.field-body > ul > li > strong { 128 | font-weight: normal; 129 | font-style: italic; 130 | } 131 | 132 | /* Admonitions */ 133 | 134 | div.admonition p.admonition-title, div.warning p.admonition-title { 135 | background: none; 136 | color: #555; 137 | border: none; 138 | } 139 | 140 | div.admonition { 141 | background: none; 142 | border: none; 143 | border-left: 2px solid #acc; 144 | } 145 | 146 | div.warning { 147 | background: none; 148 | border: none; 149 | border-left: 3px solid #c33; 150 | } 151 | 152 | /* Search */ 153 | 154 | dl:target, dt:target, .highlighted { background-color: #ffa } 155 | 156 | -------------------------------------------------------------------------------- /docs/changelog/index.rst: -------------------------------------------------------------------------------- 1 | 变更日志 2 | ======== 3 | 4 | 0.1 5 | --- 6 | -------------------------------------------------------------------------------- /docs/codereview/astng_django.py: -------------------------------------------------------------------------------- 1 | from logilab.astng import MANAGER 2 | from logilab.astng.builder import ASTNGBuilder 3 | 4 | def hashlib_transform(module): 5 | if module.name == 'django.utils.translation': 6 | fake = ASTNGBuilder(MANAGER).string_build(''' 7 | 8 | def ugettext_lazy(value): 9 | return u'' 10 | 11 | def ugettext(value): 12 | return u'' 13 | 14 | ''') 15 | for hashfunc in ('ugettext_lazy', 'ugettext'): 16 | module.locals[hashfunc] = fake.locals[hashfunc] 17 | 18 | def register(linter): 19 | """called when loaded by pylint --load-plugins, register our tranformation 20 | function here 21 | """ 22 | MANAGER.register_transformer(hashlib_transform) 23 | -------------------------------------------------------------------------------- /docs/codereview/astng_django.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/codereview/astng_django.pyc -------------------------------------------------------------------------------- /docs/codereview/error.py: -------------------------------------------------------------------------------- 1 | fake_function = 1 2 | fake_function() 3 | -------------------------------------------------------------------------------- /docs/codereview/example.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy as _ 2 | 3 | print _('damn it!') 4 | -------------------------------------------------------------------------------- /docs/codereview/howto.rst: -------------------------------------------------------------------------------- 1 | 如何进行code review? 2 | ===================== 3 | 4 | code reivew是保障代码质量的实用方法之一,这里简单分享下个人code review的经验。建议只当案例来看,因为不同项目、不同团队所做的事情、所具备的技术背景也是不同的,当然也会有些通用的点。 5 | 6 | 现实情况 7 | -------- 8 | 9 | 先问下,你所在的公司有code review的习惯不?你所在的团队呢?你个人呢?如果没有的话,为啥会忽略这个环节呢? 10 | 11 | 来说下现实情况,大多数公司做的项目,如果按项目规模来看,都是很小的项目,即使是很大的项目,正确的做法也必然会拆分成多个小项目,由多个团队同时合作完成。在这个前提下,实际每天每个程序员提交的代码量是很少的,对这么少的代码变更进行code review其实是花不了个人多少时间的。 12 | 13 | 我觉得完全每天可以抽出半小时,乃至一小时的时间来对别人的代码进行review,比如: 14 | 15 | * 或每天上班正式开始自己的工作前,边吃早点边code review别人昨天的几次提交。 16 | * 或中午大桌聚餐在大液晶屏,边吃边评论互相的代码 17 | * 或每天下班前花半个小时review下别人代码 18 | 19 | 只是随便举几个场景的例子,code review形式不限、时间、地点更无所谓。 20 | 21 | 不同的团队,不同的项目,在不同的时期,可能对代码发布的要求不同: 22 | 23 | * 有的团队,对代码质量控制很严格,会规定凡是提交到主干的代码都必须经过review,严格保持主干的代码历史提交都是足够干净的; 24 | * 但现实中也有很多团队,根据实际情况采用事后review,review后随时修正,不过这样看起来代码历史看起来不那么干净,但最后结果总还是不错的。 25 | 26 | 不管如何,我觉得做项目本身都没有十足的铁则,实用就好。 27 | 28 | code review的目的 29 | ----------------- 30 | 31 | 团队中为什么要有code review的文化呢?或者说,code review有哪些好处吗? 32 | 33 | 从项目角度,很直接,可以提高代码质量。从人性弱点上来看,人类总是乐于去发现别人不好的地方的,所以多一双眼睛,更容易发现代码中的坏味道;另一方面,大家内心深处都是乐于追求真、善、美,所以总是希望写出来的代码更加优美。间接上,代码质量提高,对于一个项目的成功,以及一个软件的生命周期的延长,是个挺必要的因素,至少微观上是这样。 34 | 35 | 从个人角度,可以大致分两种。对于初学者学习,从看有经验人的代码过程中,可以学到好的代码实践;对于有经验的人自省,自然能发现各种不好的,时刻提醒让自己避免这种不好的代码,顺便可以帮助别人更好更快地提高,毕竟大家都是从初学者过来的。 36 | 37 | 从团队角度,逐渐拉近团队成员间的水平。尽管团队中成员各有所长,但在代码层面会有共同标杆,共同进步,让团队整体实力得到提高。 38 | 39 | code review的形式 40 | ----------------- 41 | 42 | code review的形式各种各样,这里按人数规模来分类。 43 | 44 | 1自审 45 | ~~~~~ 46 | 47 | 这个其实是大家都在做的方式,但可能不一定每个人都能做得好,或者说人类总是很难自觉做到从自己身上发现问题,经过一定训练后逐渐会学会限制这种本能。一种方式可以有所帮助,就是找个木偶讲解代码,比如:放在桌上的瓦力小机器人或盆栽之类的东西。讲出你写的代码的变化,也许就会发现一些问题。 48 | 49 | 当然,一般都会借助一些工具来帮忙检查代码,比如:各种语言的lint工具或者很多IDE本身的功能,在代码风格这些偏静态层面的东西,可以辅助发现一些问题。 50 | 51 | 另外,随着程序员编程经验的增长,逐渐会注意到代码的各种坏味道,这也是给别人做code review的基础。 52 | 53 | 这种形式的code review,发现了问题基本上就是立马纠正了。或者说,自己已经处理了一大半的代码坏味道了,剩下可能真的自己都很难发现了,毕竟不是每个人的自省能力都是很高的,所以才有了后面的几种形式。 54 | 55 | p2p互相 56 | ~~~~~~~ 57 | 58 | 这种形式其实也很常见,特别是两个小伙伴一起协作编程的时候,比如:所谓的结对编程。当然,也可以不是结对编程,比如:两个人挨着坐,一个人完成一块逻辑复杂或稍有难度的代码后,需要别人来把把关,这个时候就可以叫上旁别的人帮忙过下代码或自己给他讲解下代码。 59 | 60 | 如果是结对编程,个人认为还是需要两人水平相当,一个人在一个显示器前写代码,另一个人在另一个复制的显示器前观察写的代码,有问题随时沟通,也许两人各有所长,一人完成某块他擅长的部分后,两人交换角色。这个时候,其实是个互相审核的过程。 61 | 62 | 当然,也有不在少数的人不习惯所谓结对编程,那么就各自干活,谁觉得需要另一个人帮忙,就进行code review。这种形式,个人觉得就不必要求两人水平相当,也可以一个老手+新手的组合。 63 | 64 | 另外,个人建议这种形式的code review,最好两人负责的任务是比较相似或有交集,效果会更好些,不然一个人不理解另一个人的业务逻辑,效果上还是会打些折扣的。 65 | 66 | 这种形式的code review,也是发现了问题基本上就是立马纠正了。如果这个时候提交代码,至少2个人看过了,或者说有2个人对这块代码负责了,也能从一定程度上对这块代码所具备的知识有了一份复制,软件这种东西的背后知识看不见摸不着,知识的复制备份尤其重要。 67 | 68 | 2-3单向 69 | ~~~~~~~ 70 | 71 | 这种形式随着各种code review的工具出现,越来越方便,自然也就越来越流行了。这里的“2-3”意思并不是只有2个人或3个人,根据需要也可以再增加人数,特别是有很多code review工具辅助的时候。 72 | 73 | 这里特别强调是单向,说白了就是你提交了代码,别人来评论,赞一下或踩一下,当然写代码的人也不要把这些评论太过心,过脑即可,意思是说别人的评论都是善意的,别人好的建议或意见可以采纳,如果要拒绝可以向对方说明理由,就是觉得不爽时候别太放在心上。如果不习惯用各种code review辅助工具,可能就是比如:有2个人站在你后面,你指着显示器上的代码给他俩各种讲解,他们有疑问随时提问,你负责说明,也就是一问一答。如果有code review工具辅助,那就更方便了,大家直接看代码,然后在线对代码进行评论,比如:如果有2+以上人允许通过,那就可以提交,如果有人反对或有改进建议,就直接评论,各自的邮箱也能收到需要code review的任务和结果,那块代码的作者也可以根据评论来改进自己的代码。 74 | 75 | 当然,这个时候个人建议那2个人中,有至少一人是个老手,最好是那种什么扫地阿姨,背后一站,扫一眼就看出你这有个溢出那种。当然这是玩笑了,意思是得有个有经验的程序员把关。 76 | 77 | 另外,至于是否对这块代码有了解或熟悉,就可以不用像上一种“p2p互相”那种形式要求的那样,大家最好都要对这块代码熟悉了。 78 | 79 | 这种形式的code review,一般是由写代码的人负责记录问题,当然有code review工具辅助,那就工具自动给你记录了,省去很多麻烦。这种形式也是最省事的,特别是有了工具辅助,也就没有时间、地点的限制了。 80 | 81 | >3展示 82 | ~~~~~~ 83 | 84 | 这种形式有点会议形式,更接近“代码审查”了,而不是“代码评审”,一堆人圆桌+大屏。如果有场景,的确要用到这种形式的code review那就要慎用了,大家都知道一旦变成开会形式了,那就必然会有会议的各种缺点出现,所以要注意。 85 | 86 | * 首先,得有个主持人,无特定人员要求,也可以是写代码的那个人,没有关系,关键这个主持人能控场,别让偏题了,毕竟人多口杂。 87 | * 其次,必须举手发言,不然容易变成聊天。 88 | * 最后,避免争论或者叫吵架吧,可以讨论或叫沟通吧,互相不要对着人说话,而是对着某个物件来说话,比如:前面提到的那个瓦力机器人和盆栽,这样能一定程度上避免出现争论或吵架的情况。 89 | 90 | 当然,一旦开会形式了,针对某个人写的代码,容易变成批评、批斗,所以还得要注意: 91 | 92 | * 不要去关注谁写的代码 93 | * 对代码不对人 94 | * 重点在业务逻辑或代码设计实现 95 | * 弱化代码风格的评价 96 | 97 | + 除非展示目的是为了大家了解代码风格 98 | + 除非代码风格让人没法进行代码的审查 99 | 100 | 另外,如果没有强烈的特别的需要,其实还是不要这种形式的code review了,主持人不好或大家意识上不到位,容易浪费时间,效果不一定好。 101 | 102 | 这种形式的code review,可以指定一个记录人,当然就和主持人不是一个人了,不然主持人即当爹又当妈,功能不要太全哦!当然最好可以是写代码的那个人。 103 | 104 | 其它 105 | ~~~~ 106 | 107 | 这里说些其它形式的code review。可以引入某方面的专业人士,比如:安全专家,对代码进行安全层面的审计,虽然程序员必须具备安全意识,但安全层面的思路有时候还是会有所差异的,毕竟每个人的安全意识水平也参差不齐;再比如,需要对数据库或操作系统等的特别操作,可以让这方面的专家帮忙把关,当然如果团队中有这些类型角色的成员,就再好不过了。 108 | 109 | 最后,提几个建议,可能上面说过: 110 | 111 | * 针对代码,不针对人。但另一方面代码毕竟人写的,所以需要注意评论的方式。 112 | * 提出评论的人,可以采用发问的形式评论,比如:是不是XXXX这样改,这块代码执行起来就会更加OOOO了?类似这样的语气 113 | * 被评论代码的人,别有抵触心理,要有空杯心态,就是上面说的过脑但别过心了。 114 | * 程序员这个群体,并不是所有人都是天才级别的,所以当你看到别人写的烂代码时候,也得知道自己当初也可能经历过那个阶段。 115 | 116 | code review的重点 117 | ----------------- 118 | 119 | 本来想说下,哪些地方需要重点code review,但发现场景不同,项目不同,采用的技术不同,大家的关注点都不一样,如果说成通用的,反而没有重点了。比如:大段代码、大函数、大类、长SQL、复杂存储过程、使用频率高的功能、代码嵌套太深、核心算法实现、核心接口实现、函数参数过多、用户输入校验是否有安全隐患、异常处理、日志记录、业务是否需要事物等等太多了。 120 | 121 | 另外,不同编程语言、编程框架还有不同规范性质的编程建议,实在不好说重点。 122 | 123 | 如果要看重点,估计还是自己去翻一遍《代码大全》比较实际。 124 | 125 | code review的工具 126 | ----------------- 127 | 128 | code review有很多工具,最原始的莫过于人肉了,这里简单列举下几个工具,看各自实际情况使用了。一般对这种类型工具或软件的要求可能有: 129 | 130 | * 可以评论,跟代码关联程度高,然后最好能跟网易评论似的各种嵌套。 131 | * 可以邮件提醒。 132 | * 可以很好支持Git或SVN等版本控制工具。 133 | * 可以很好支持Jenkins等CI工具。 134 | * 可以支持命令行,方便写脚本集成其它工具。 135 | 136 | 列举下我知道的工具: 137 | 138 | * Trac插件:Code Comments。如果用的Trac管理项目,可以用下,但没有上面说的具备的要求那么全。 139 | * Review Board。一个Python写的Web类型工具。 140 | * Facebook Phabricator。小有名气,Facebook出品,应该人家内部自己做项目都用的这个,功能有点太全了,可能如果单做code review,会有点晕。 141 | * Gerrit。一个Java写的Web工具,可以和Jenkins很好结合,也算好用。 142 | * Code:豆瓣出品,没用过,但据说不错。 143 | 144 | 另外,用了工具后,别忘了还有最实用的面对面交流这个人肉工具,也许用惯了冷冰冰的工具外,小伙伴互相看完代码后,温暖的会心一笑还是不错的。 145 | 146 | 最后 147 | ---- 148 | 149 | * code review多在平时的零散时间。 150 | * 贵在坚持、积累,让code review成为呼吸一样理所当然的事情吧。 151 | * 对代码质量有更多提高效果的其实是“code review的形式”说的平时的前3种情况。 152 | * 某种角度说,代码是产品、项目、软件的基石,还是需要关注下的。 153 | 154 | 有关code review的趣评(来自网上,出处不详): 155 | 156 | * 如果代码变更在几十行内,能写出一堆评论 157 | * 如果代码变更成百上千了,基本都一个评论,“你的代码不错~” 158 | 159 | 其实想表达的意思是,递交的代码让别人review不要太爆炸了,别人会很有压力的。 160 | 161 | 后续,有关code review的主题,会具体介绍下几个常用的code review工具的安装和使用。 162 | -------------------------------------------------------------------------------- /docs/codereview/index.rst: -------------------------------------------------------------------------------- 1 | code review 2 | =========== 3 | 4 | 有关code review的趣评(来自网上,出处不详): 5 | 6 | * 如果代码变更在几十行内,能写出一堆评论 7 | * 如果代码变更成百上千了,基本都一个评论,“你的代码不错~” 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | why 13 | howto 14 | tools 15 | pylint 16 | plugin 17 | -------------------------------------------------------------------------------- /docs/codereview/plugin.rst: -------------------------------------------------------------------------------- 1 | Pylint插件:忽略Django的国际化代码调用报错 2 | ========================================== 3 | 4 | **NOTICE:如果更新到Django1.4+以上版本后,就不会报类似Error级别的提示了。** 5 | 6 | 用Django写Web开发类型的程序,经常会用到程序的国际化操作,例如,保存如下代码为example.py: 7 | 8 | .. literalinclude:: example.py 9 | 10 | 11 | 如果项目中用到Pylint来检查代码,执行如下: 12 | 13 | :: 14 | 15 | pylint -E example.py 16 | 17 | 这个时候会显示一个Error级别的提示,如下: 18 | 19 | :: 20 | 21 | No config file found, using default configuration 22 | ************* Module example 23 | E: 3,6: _ is not callable 24 | 25 | 熟悉Django的人,一定会觉得很奇怪,怎么可能函数不能被调用,这里就不具体展开说明原因了: 26 | 27 | * 可以认为是Pylint不够智能 28 | * 也可以认为Python本身太灵活,Django中调用的国际化模块,刚好用了一些动态语言的特性 29 | 30 | 怎么办? 31 | -------- 32 | 33 | * Pylint提供了灵活的配置设置,过了一遍配置,发现没有自定义设置可以忽略这个情况 34 | * Google之,但很多是说直接忽略那条规则,这样虽然可以达到忽略的效果,但是同样也忽略了不应该忽略的检查,比如:a = 1; a()这种语法错误 35 | * 看Pylint官方文档,发现Pylint扩展性还是不错的,提供自定义插件机制,--load-plugin参数指定插件即可,那么就自己写个插件来处理这种情况吧! 36 | * 继续Google,发现有人写过类似插件,虽然是处理别的问题,但很类似:http://www.logilab.org/blogentry/78354 37 | 38 | 写个插件! 39 | ---------- 40 | 41 | 代码很简单,原理就是写个假的函数调用,在Pylint执行该模块的函数检查时候,直接调用了假的函数,从而绕过该报错,但又不会影响这条规则检查其它代码时候报告Error级别信息。保存如下代码为astng_django.py 42 | 43 | .. literalinclude:: astng_django.py 44 | 45 | 这个时候执行命令(需要PYTHONPATH能找到astng_django.py) 46 | 47 | :: 48 | 49 | pylint -E example.py --load-plugins=astng_django 50 | 51 | 输出如下: 52 | 53 | :: 54 | 55 | No config file found, using default configuration 56 | 57 | 就没有刚才的Error级别的提示了。 58 | 59 | 保存如下代码为error.py,再验证下报错的情况是否被忽略: 60 | 61 | .. literalinclude:: error.py 62 | 63 | 执行命令: 64 | 65 | :: 66 | 67 | pylint -E error.py --load-plugins=astng_django 68 | 69 | 输出如下: 70 | 71 | :: 72 | 73 | No config file found, using default configuration 74 | ************* Module error 75 | E: 2,0: fake_function is not callable 76 | 77 | 这样就算初步解决这个问题了,即可以不让Django程序报干扰的Error级别信息,又不会忽略的确需要报的Error级别信息。 78 | 79 | 占个坑~ 80 | ------- 81 | 82 | 照例,GitHub上先占个坑:https://github.com/akun/pylint_plugin 83 | 84 | 后续TODO: 85 | 86 | * 前面有说明必须指定PYTHONPATH,有点麻烦,但作为临时解决问题是够了。后续需要做个完整的Python库,然后发布到PyPI,然后作为正式的安装包,就更方便别人使用了。 87 | * 另外,这个插件还是相对简单,其实Django中的国际化处理有好多函数,需要后续补充。 88 | * 将上述的相关验证转化为对应的测试代码。 89 | -------------------------------------------------------------------------------- /docs/codereview/pylint.rst: -------------------------------------------------------------------------------- 1 | Pylint协助审查代码 2 | ================== 3 | -------------------------------------------------------------------------------- /docs/codereview/tools.rst: -------------------------------------------------------------------------------- 1 | code review工具介绍 2 | =================== 3 | -------------------------------------------------------------------------------- /docs/codereview/why.rst: -------------------------------------------------------------------------------- 1 | 为什么进行code review 2 | ===================== 3 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pm documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Feb 7 00:07:17 2018. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | from __future__ import unicode_literals 25 | 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ['hieroglyph', 'sphinx.ext.autodoc'] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = 'pm' 52 | copyright = '2018, akun' 53 | author = 'akun' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.1' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '0.1' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | # If true, `todo` and `todoList` produce output, else they produce nothing. 80 | todo_include_todos = False 81 | 82 | 83 | # -- Options for HTML output ---------------------------------------------- 84 | 85 | # The theme to use for HTML and HTML Help pages. See the documentation for 86 | # a list of builtin themes. 87 | # 88 | html_theme = 'sphinxdoc' 89 | html_style = 'tracsphinx.css' 90 | 91 | # Theme options are theme-specific and customize the look and feel of a theme 92 | # further. For a list of options available for each theme, see the 93 | # documentation. 94 | # 95 | # html_theme_options = {} 96 | 97 | # Add any paths that contain custom static files (such as style sheets) here, 98 | # relative to this directory. They are copied after the builtin static files, 99 | # so a file named "default.css" will overwrite the builtin "default.css". 100 | html_static_path = ['_static'] 101 | 102 | # Custom sidebar templates, must be a dictionary that maps document names 103 | # to template names. 104 | # 105 | # This is required for the alabaster theme 106 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 107 | html_sidebars = { 108 | '**': [ 109 | 'relations.html', # needs 'show_related': True theme option to display 110 | 'searchbox.html', 111 | ] 112 | } 113 | 114 | 115 | # -- Options for HTMLHelp output ------------------------------------------ 116 | 117 | # Output file base name for HTML help builder. 118 | htmlhelp_basename = 'pmdoc' 119 | 120 | 121 | # -- Options for LaTeX output --------------------------------------------- 122 | 123 | latex_elements = { 124 | # The paper size ('letterpaper' or 'a4paper'). 125 | # 126 | # 'papersize': 'letterpaper', 127 | 128 | # The font size ('10pt', '11pt' or '12pt'). 129 | # 130 | # 'pointsize': '10pt', 131 | 132 | # Additional stuff for the LaTeX preamble. 133 | # 134 | # 'preamble': '', 135 | 136 | # Latex figure (float) alignment 137 | # 138 | # 'figure_align': 'htbp', 139 | } 140 | 141 | # Grouping the document tree into LaTeX files. List of tuples 142 | # (source start file, target name, title, 143 | # author, documentclass [howto, manual, or own class]). 144 | latex_documents = [ 145 | (master_doc, 'pm.tex', 'pm Documentation', 146 | 'akun', 'manual'), 147 | ] 148 | 149 | 150 | # -- Options for manual page output --------------------------------------- 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, 'pm', 'pm Documentation', 156 | [author], 1) 157 | ] 158 | 159 | 160 | # -- Options for Texinfo output ------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | (master_doc, 'pm', 'pm Documentation', 167 | author, 'pm', 'One line description of project.', 168 | 'Miscellaneous'), 169 | ] 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/construction/build_opensource.rst: -------------------------------------------------------------------------------- 1 | 如何快速构建一个 Python 模块项目(开源方案) 2 | ============================================ 3 | 4 | 这里只是用一个 Hello World 级别的模板类型项目,来说明下如何快速构建起来一个 \ 5 | Python 项目。且是在开源场景下发布,如果是闭源场景,再单独写一篇文章类似说明。 6 | 7 | 先看结果 8 | -------- 9 | 10 | 我们最终要完成的就是一个 Python 模块安装包。这里“完成”的定义是:实现功能、\ 11 | 通过测试、发布到网上供别人使用。 12 | 13 | 比如:我们最后发布的是一个叫 onepiece 的 Python 库。 14 | 15 | 因为这里是允许开源的场景,所以就可以直接发布到 PyPI \ 16 | https://pypi.python.org/pypi 了,如图: 17 | 18 | .. image:: onepiece.png 19 | 20 | 然后就可以直接用了,比如用 pip 方式安装: 21 | 22 | :: 23 | 24 | $ pip install onepiece 25 | 26 | onepiece 库中演示模块的功能很简单,就是打印一个字符串“One Piece”就完事,如下: 27 | 28 | :: 29 | 30 | >>> from onepiece.example import hello_world 31 | >>> hello_world() 32 | >>> 'One Piece' 33 | 34 | 项目的源码直接查看 GitHub:https://github.com/akun/onepiece。 35 | 36 | 下面我们来看下,如何快速构建这么一个 Python 模块项目。 37 | 38 | first commit 39 | ------------ 40 | 41 | 直接用项目模板来初始化项目,后续再单独写一篇文章,详细解释由模板生成的模块项目\ 42 | 中各个文件的作用。因为用了模板,这里所谓的“快速构建”模板的历史积累起到很大的作\ 43 | 用,我们要遵循 DRY(Don't Repeat Yourself)这个原则。 44 | 45 | 安装模板工具 46 | ~~~~~~~~~~~~ 47 | 48 | 你得先安装一个模板工具,后续会用到,如下: 49 | 50 | :: 51 | 52 | $ pip install cookiecutter 53 | 54 | 或者如果你用的是 Ubuntu 的话,也可以: 55 | 56 | :: 57 | 58 | $ apt-get install python-cookiecutter 59 | 60 | 初始化项目 61 | ~~~~~~~~~~ 62 | 63 | 然后,就可以用现成的 Python 项目模板初始化了,比如: 64 | 65 | :: 66 | 67 | $ cookiecutter https://github.com/akun/aproject.git # 按提示输入内容即可 68 | $ cd onepiece 69 | $ virtualenv onepiece_venv 70 | $ source onepiece_venv/bin/activate 71 | $ make 72 | 73 | 初始化 Git 本地仓库 74 | ~~~~~~~~~~~~~~~~~~~ 75 | 76 | :: 77 | 78 | $ git init 79 | $ git add . 80 | $ git commit # 比如日志是:chore: init project 81 | 82 | 推送到 Git 远程仓库 83 | ~~~~~~~~~~~~~~~~~~~ 84 | 85 | 我们直接用 GitHub,直接在上面新建个项目,就叫 onepiece,然后把 Git 本地仓库推\ 86 | 送到 Git 远程仓库: 87 | 88 | :: 89 | 90 | $ git remote add origin https://github.com/akun/onepiece.git 91 | $ git push origin master 92 | 93 | DONE 94 | ~~~~ 95 | 96 | 然后从 GitHub 上查看第一次提交的结果吧:https://github.com/akun/onepiece 97 | 98 | 就这样 first commit 就包含了一个完整的初始项目了,然而还没“完成”。 99 | 100 | 做好配置管理工作 101 | ---------------- 102 | 103 | 下列原则可以参考下: 104 | 105 | * 用项目模板协助初始化常见的配置管理; 106 | * 代码未动,CI/CD 先行:因为这里是允许开源的场景,我们选用 Travis-CI 这个服务; 107 | * 优先考虑安装部署脚本:立马发布一个空壳项目; 108 | * 尽可能多的记录软件开发中产生的常见行为,参考::doc:`/vcs/understanding`\。 109 | 110 | 实现功能并测试 111 | -------------- 112 | 113 | 因为是个示例,所以这里实现的功能就很简单了,就是打印一个字符串,代码如下: 114 | 115 | .. literalinclude:: ../../pm/onepiece/onepiece/example.py 116 | 117 | 同样,测试代码也很简单,代码如下: 118 | 119 | .. literalinclude:: ../../pm/onepiece/tests/test_example.py 120 | 121 | 测试 122 | ---- 123 | 124 | 独立一节来说,就是为了说明构建中测试是必须的环节,在这里,测试也很简单: 125 | 126 | :: 127 | 128 | $ make test 129 | 130 | 可以看到所有测试用例在 Python 2 和 Python 3 下都通过,测试覆盖率是 100%,如下: 131 | 132 | .. literalinclude:: tox_result.log 133 | 134 | 这里演示的是一个很简单的单元测试,Python 的单元测试可以详见:\ 135 | :doc:`/unittest/python`\。 136 | 137 | 文档 138 | ---- 139 | 140 | 独立一节来说,也是为了说明构建中文档是必须的环节,对于一个初始的项目,文档也很\ 141 | 简单,大致包括: 142 | 143 | * License:开源项目,一般会有个开源许可证书声明,这里选择的是相对自由的 \ 144 | MIT License。关于各种开源证书的选择,可以参考文章:\ 145 | https://choosealicense.com/,或懒得想太多就参考:\ 146 | http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html; 147 | * README:说明项目是干什么的、如何安装、如何使用、如何开发、如何发布,前三者是\ 148 | 对于使用项目的人来说的,后两个一般是你的项目协作者需要关心的。如果内容很多,\ 149 | 就不要都放在 README 了,可以拆分为多个文档; 150 | * Changelog:一开始也可以写在 README 中,当然后续维护的版本多了后,可以拆分为\ 151 | 独立的文件维护; 152 | * Credits:可能有的人会写上贡献者的荣誉信息; 153 | * 有的还会加上一堆有用的第三方服务的 **badges** 来体现你的项目是一个靠谱的项目\ 154 | ,比如:这里 onepiece 示例项目中的 CI/CD 构建是否通过、代码健康程度,以及测\ 155 | 试覆盖率等等。 156 | 157 | 推荐用 Sphinx 写技术文档,尤其你写的是 Python 项目。当然,MarkDown 也很流行,\ 158 | 很多人习惯用这个写文档。用 Sphinx 写技术文档,详见::doc:`/doc/sphinx`\。 159 | 160 | Ship it! 161 | -------- 162 | 163 | 到了最重要的一个环节了,发布 Python 模块库到 PyPI,直接: 164 | 165 | :: 166 | 167 | $ make sdist 168 | 169 | 想了解 make sdist 中具体命令,后续再写一篇如何发布到 PyPI,这里不展开说明。 170 | 171 | 为什么说发布很重要,原因很简单,你不发布,那前面那些对用户来说相当于没有发生。 172 | 173 | 现实是复杂的 174 | ------------ 175 | 176 | * 上述案例也就是个简化的项目,或者说是一个很小的类库级别的微型项目; 177 | * 项目规模大,单一的项目模板必然不适用,比如:多语言的项目; 178 | * 大项目必然会拆分成各个小项目,各个项目集成起来必然复杂; 179 | * 所谓的一键发布脚本以及 CI/CD 脚本,需要持续维护,可能逻辑随着项目复杂度上升\ 180 | ,也会越来越复杂,一般一个团队都会让 0.5 个人维护这些东西,如果项目规模略大\ 181 | ,那么就来个完整的 1 个人甚至是 1 个团队来维护也不夸张。 182 | 183 | 总结 184 | ---- 185 | 186 | 我们用到的工具或服务有这些: 187 | 188 | * 配套工具:cookiecutter + tox + nose + coverage + Sphinx + EditorConfig + \ 189 | prospector 190 | * 配套服务:GitHub + Travis CI + Landscape + Coveralls + Read the Docs 191 | 192 | 配套工具和配套服务,前面提过,后续的文章中详细讲解模板里的文件时候再一并讲解,\ 193 | 可以先简单看下这里::doc:`/tao/opensource`\,对配套服务有简单介绍。 194 | 195 | 最后记住,大家一定要根据自己的需要形成自己的模板。 196 | 197 | 参考 198 | ---- 199 | 200 | * http://www.oschina.net/translate/open-sourcing-a-python-project-the-right-way 201 | -------------------------------------------------------------------------------- /docs/construction/index.rst: -------------------------------------------------------------------------------- 1 | 构建发布工具 2 | ============ 3 | 4 | Shell 5 | ----- 6 | 7 | Make 8 | ---- 9 | 10 | Fabric 11 | ------ 12 | 13 | Scons 14 | ----- 15 | 16 | Ant 17 | --- 18 | 19 | Python 模块发布 20 | --------------- 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | build_opensource 26 | namespace 27 | 28 | Distutils 29 | ~~~~~~~~~ 30 | 31 | Setuptools 32 | ~~~~~~~~~~ 33 | 34 | easy_install 35 | ~~~~~~~~~~~~ 36 | 37 | pip install 38 | ~~~~~~~~~~~ 39 | 40 | 用 devpi 搭建私有的 PyPI 服务 41 | ----------------------------- 42 | -------------------------------------------------------------------------------- /docs/construction/namespace.rst: -------------------------------------------------------------------------------- 1 | Python 中的 namespace 2 | ===================== 3 | 4 | 略微标题党一下,Python 中有 namespace 这个特性吗?就看你如何定义 namespace 了。 5 | 6 | 背景 7 | ---- 8 | 9 | 近期参与一个项目,用 Python 写,设计规划上有点小规模,然后就按出的架构拆分为多个子模块项目了。但这几个子模块项目又关联性比较大,所以看起来要在一个总项目中。 10 | 11 | 考虑 12 | ---- 13 | 14 | 题外话,一直觉得 Python 的模块导入是个挺麻烦是事情,什么绝对导入、相对导入,一定程度提高了使用者的理解成本,以及项目的维护成本。记得 Python 开发者的哲学不是“用一种方法,最好是只有一种方法来做一件事” [#f1]_ 嘛,貌似遵守这个哲学不容易。 15 | 16 | 最初反应是不拆分项目,直接把各个子模块的代码放在一个总项目中,比如:这个总项目叫 Star Wars [#f2]_ 吧,有 4 个子模块,分别叫 Yoda,Force,Skywalker,R2-D2。 17 | 18 | 可能源码的目录结构就是这样了 19 | 20 | :: 21 | 22 | starwars/ 23 | ├── force 24 | │   └── __init__.py 25 | ├── __init__.py 26 | ├── r2d2 27 | │   └── __init__.py 28 | ├── skywalker 29 | │   └── __init__.py 30 | └── yoda 31 | └── __init__.py 32 | 33 | 其实这样开始项目也没什么大关系,只是基于以下几点考虑,后来还是拆分这个项目为多个子模块项目了: 34 | 35 | * 按设想的架构展开,这个项目还是有点规模的 36 | * 项目中的某些子模块其实是很基础通用的模块,是可以被外部项目所用的,而外部项目要引入整个项目源码,没这个必要 37 | * 拆分为子项目后,方便由多个小组协同研发 38 | * 对后期项目的软件更新更有利,只更新某个子模块的 Python 模块包就行 39 | 40 | 问题 41 | ---- 42 | 43 | 对 Python 来说,拆分子项目也方便,基本上就是把各个子模块目录作为独立的模块包,以 skywalker 子模块为例就是这样了: 44 | 45 | :: 46 | 47 | skywalker/ 48 | ├── anakin.py 49 | ├── __init__.py 50 | ├── luke.py 51 | └── shmi.py 52 | 53 | 然后使用起来就是: 54 | 55 | :: 56 | 57 | import skywalker 58 | 59 | 现在问题是可能有很多模块都叫 skywalker,如何避免模块名冲突? 60 | 61 | 解决 62 | ---- 63 | 64 | 很简单,想当然会用 starwars 这个所谓的 namespace,然后使用起来就是: 65 | 66 | :: 67 | 68 | from starwars import skywalker 69 | 70 | 对应的,目录结构就是: 71 | 72 | :: 73 | 74 | starwars/ 75 | ├── __init__.py 76 | └── skywalker 77 | ├── anakin.py 78 | ├── __init__.py 79 | ├── luke.py 80 | └── shmi.py 81 | 82 | 然后写个 setup.py 就可以,目录差不多就是这样: 83 | 84 | :: 85 | 86 | starwars.skywalker/ 87 | ├── setup.py 88 | └── starwars 89 | ├── __init__.py 90 | ├── __init__.pyc 91 | └── skywalker 92 | ├── anakin.py 93 | ├── __init__.py 94 | ├── luke.py 95 | └── shmi.py 96 | 97 | 其它子模块也类似处理,这样就拆分成多个模块包了。 98 | 99 | 更优 100 | ---- 101 | 102 | 严格来说,上面的模块包,其实是 starwars 的模块包,而不是拆分后的各个子模块的模块包,也就是说安装完毕后的 starwars 可以没有 __init__.py,但也能用这种导入: 103 | 104 | :: 105 | 106 | from starwars import skywalker 107 | 108 | 可以参考 Zope [#f3]_ 这个大型的 Python 项目,其实就是把它的子项目拆分为多个模块包,但共享了 zope 这个 namespace。 109 | 110 | 修改 setup.py 111 | ~~~~~~~~~~~~~ 112 | 113 | 增加 **namespace_packages** 就行,例如: 114 | 115 | :: 116 | 117 | #!/usr/bin/env python 118 | 119 | from setuptools import setup, find_packages 120 | 121 | setup( 122 | name='starwars.skywalker', 123 | version='0.0.1', 124 | packages=find_packages(), 125 | namespace_packages=['starwars'] 126 | ) 127 | 128 | 修改 __init__.py 129 | ~~~~~~~~~~~~~~~~ 130 | 131 | 在 starwars/__init__.py 中增加: 132 | 133 | :: 134 | 135 | __import__('pkg_resources').declare_namespace(__name__) 136 | 137 | 需要注意的是,除了这一行,不能有别的代码了。 138 | 139 | 安装 140 | ~~~~ 141 | 142 | 这个时候安装完毕 starwars.skywalker 这个模块包,可以发现安装完毕后的 starwars 是没有 __init__.py 的,但会在 starwars 平级目录多一个类似 starwars.skywalker-0.0.1-py2.7-nspkg.pth 的文件,作用相当于是在 starwars 目录中有了个 __init__.py 一样。 143 | 144 | 示例 145 | ---- 146 | 147 | 说了这么多,直接看代码估计更容易理解,示例代码: 148 | 149 | * https://github.com/akun/pm/tree/master/pm/starwars.skywalker 150 | * https://github.com/akun/pm/tree/master/pm/starwars.yoda 151 | 152 | 或者,直接接安装下亲自感受下: 153 | 154 | :: 155 | 156 | pip install starwars.skywalker starwars.yoda 157 | 158 | 可以看下第三方包的安装目录的实际安装情况,在 starwars 目录没有 __init__.py,但可以导入想要的子模块库 159 | 160 | :: 161 | 162 | from starwars import skywalker, yoda 163 | 164 | 再或者,随便找个 Zope 的子项目,看下实际的项目是如何做的: 165 | 166 | * https://github.com/zopefoundation/zope.annotation 167 | 168 | 本质 169 | ---- 170 | 171 | 问题本质其实算是 Python 不允许模块包,在多个位置来进行导入 [#f4]_ ,所以只能放在比如 starwars 这一个目录下,无论是 starwars 目录下放个 __init__.py 还是严格声明下 namespace 是 starwars 这种方式,最后都是把模块包放在一个位置下来处理。 172 | 173 | 简单说: 174 | 175 | * 就是 Python 不支持所谓的 namespace 这种语法吧。 176 | * 或者说不支持,允许多位置模块包,却共享一个 namespace 这个特性。 177 | 178 | 参考 179 | ---- 180 | 181 | .. rubric:: 参考清单 182 | .. [#f1] https://zh.wikipedia.org/zh/Python 183 | .. [#f2] http://www.starwars-tw.com/story/character/character.htm 184 | .. [#f3] https://github.com/zopefoundation 185 | .. [#f4] https://pythonhosted.org/setuptools/setuptools.html#namespace-packages 186 | -------------------------------------------------------------------------------- /docs/construction/onepiece.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/construction/onepiece.png -------------------------------------------------------------------------------- /docs/construction/tox_result.log: -------------------------------------------------------------------------------- 1 | GLOB sdist-make: /home/kun/projects/pm/pm/onepiece/setup.py 2 | py27 inst-nodeps: /home/kun/projects/pm/pm/onepiece/.tox/dist/onepiece-0.1.0.zip 3 | py27 installed: coverage==4.5.1,future==0.16.0,httpretty==0.8.14,nose==1.3.7,onepiece==0.1.0,pkg-resources==0.0.0 4 | py27 runtests: PYTHONHASHSEED='1283052189' 5 | py27 runtests: commands[0] | nosetests -c nose.cfg 6 | ...E.. 7 | ====================================================================== 8 | ERROR: test_do_print_example (tests.test_main.MainTestCase) 9 | ---------------------------------------------------------------------- 10 | Traceback (most recent call last): 11 | File "/home/kun/projects/pm/pm/onepiece/tests/test_main.py", line 16, in test_do_print_example 12 | text = main.do_print_example() 13 | File "/home/kun/projects/pm/pm/onepiece/onepiece/main.py", line 15, in do_print_example 14 | print(text) 15 | UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128) 16 | 17 | Name Stmts Miss Cover 18 | ------------------------------------------ 19 | onepiece/__init__.py 1 0 100% 20 | onepiece/example.py 2 0 100% 21 | onepiece/main.py 22 0 100% 22 | ------------------------------------------ 23 | TOTAL 25 0 100% 24 | ---------------------------------------------------------------------- 25 | Ran 6 tests in 0.097s 26 | 27 | FAILED (errors=1) 28 | ERROR: InvocationError: '/home/kun/projects/pm/pm/onepiece/.tox/py27/bin/nosetests -c nose.cfg' 29 | py3 inst-nodeps: /home/kun/projects/pm/pm/onepiece/.tox/dist/onepiece-0.1.0.zip 30 | py3 installed: coverage==4.5,future==0.16.0,httpretty==0.8.14,nose==1.3.7,onepiece==0.1.0,pkg-resources==0.0.0 31 | py3 runtests: PYTHONHASHSEED='1283052189' 32 | py3 runtests: commands[0] | nosetests -c nose.cfg 33 | ...... 34 | Name Stmts Miss Cover 35 | ------------------------------------------ 36 | onepiece/__init__.py 1 0 100% 37 | onepiece/example.py 2 0 100% 38 | onepiece/main.py 22 0 100% 39 | ------------------------------------------ 40 | TOTAL 25 0 100% 41 | ---------------------------------------------------------------------- 42 | Ran 6 tests in 0.056s 43 | 44 | OK 45 | 海贼王(One Piece) 46 | ___________________________________ summary ____________________________________ 47 | ERROR: py27: commands failed 48 | py3: commands succeeded 49 | -------------------------------------------------------------------------------- /docs/continuous/index.rst: -------------------------------------------------------------------------------- 1 | 持续输出 2 | ======== 3 | 4 | 缩短生产者输入,输出到消费者,得到反馈的周期 5 | 6 | 反馈 7 | ---- 8 | 9 | 你要的是快速准确的反馈,从而来改进你的软件、服务品质! 10 | 11 | 持续集成 12 | -------- 13 | 14 | 持续交付 15 | -------- 16 | -------------------------------------------------------------------------------- /docs/contribute/index.rst: -------------------------------------------------------------------------------- 1 | 贡献/致谢 2 | ========= 3 | -------------------------------------------------------------------------------- /docs/doc/code/example.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | console.log('ok!'); 3 | }); 4 | -------------------------------------------------------------------------------- /docs/doc/code/pdf.Makefile: -------------------------------------------------------------------------------- 1 | pdf: 2 | $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) _build/pdf 3 | @echo 4 | @echo "Build finished. The PDF files are in _build/pdf." 5 | -------------------------------------------------------------------------------- /docs/doc/code/pdf.bat: -------------------------------------------------------------------------------- 1 | if "%1" == "pdf" ( 2 | %SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf 3 | echo. 4 | echo.Build finished. The PDF files are in %BUILDDIR%/pdf 5 | goto end 6 | ) 7 | -------------------------------------------------------------------------------- /docs/doc/code/pdf.py: -------------------------------------------------------------------------------- 1 | extensions = ['rst2pdf.pdfbuilder'] 2 | 3 | # -- Options for PDF output -------------------------------------------------- 4 | # Grouping the document tree into PDF files. List of tuples 5 | # (source start file, target name, title, author, options). 6 | # 7 | # If there is more than one author, separate them with \\. 8 | # For example: r'Guido van Rossum\\Fred L. Drake, Jr., editor' 9 | # 10 | # The options element is a dictionary that lets you override 11 | # this config per-document. 12 | # For example, 13 | # ('index', u'MyProject', u'My Project', u'Author Name', 14 | # dict(pdf_compressed = True)) 15 | # would mean that specific document would be compressed 16 | # regardless of the global pdf_compressed setting. 17 | pdf_documents = [ 18 | ('index', u'WebSOC', u'WebSOC Documentation', u'WebSOC Team Knownsec'), 19 | ] 20 | # A comma-separated list of custom stylesheets. Example: 21 | #pdf_stylesheets = ['sphinx','kerning','a4'] 22 | pdf_stylesheets = ['chinese','a4'] 23 | # A list of folders to search for stylesheets. Example: 24 | pdf_style_path = ['.', '_styles'] 25 | # Create a compressed PDF 26 | # Use True/False or 1/0 27 | # Example: compressed=True 28 | #pdf_compressed = False 29 | # A colon-separated list of folders to search for fonts. Example: 30 | # pdf_font_path = ['/usr/share/fonts', '/usr/share/texmf-dist/fonts/'] 31 | # Language to be used for hyphenation support 32 | #pdf_language = "en_US" 33 | # Mode for literal blocks wider than the frame. Can be 34 | # overflow, shrink or truncate 35 | #pdf_fit_mode = "shrink" 36 | # Section level that forces a break page. 37 | # For example: 1 means top-level sections start in a new page 38 | # 0 means disabled 39 | #pdf_break_level = 0 40 | # When a section starts in a new page, force it to be 'even', 'odd', 41 | # or just use 'any' 42 | #pdf_breakside = 'any' 43 | # Insert footnotes where they are defined instead of 44 | # at the end. 45 | #pdf_inline_footnotes = True 46 | # verbosity level. 0 1 or 2 47 | #pdf_verbosity = 0 48 | # If false, no index is generated. 49 | #pdf_use_index = True 50 | # If false, no modindex is generated. 51 | #pdf_use_modindex = True 52 | # If false, no coverpage is generated. 53 | #pdf_use_coverpage = True 54 | # Name of the cover page template to use 55 | #pdf_cover_template = 'sphinxcover.tmpl' 56 | # Documents to append as an appendix to all manuals. 57 | #pdf_appendices = [] 58 | # Enable experimental feature to split table cells. Use it 59 | # if you get "DelayedTable too big" errors 60 | #pdf_splittables = False 61 | # Set the default DPI for images 62 | #pdf_default_dpi = 72 63 | # Enable rst2pdf extension modules (default is only vectorpdf) 64 | # you need vectorpdf if you want to use sphinx's graphviz support 65 | #pdf_extensions = ['vectorpdf'] 66 | # Page template name for "regular" pages 67 | #pdf_page_template = 'cutePage' 68 | # Show Table Of Contents at the beginning? 69 | #pdf_use_toc = True 70 | # How many levels deep should the table of contents be? 71 | pdf_toc_depth = 9999 72 | # Add section number to section references 73 | pdf_use_numbered_links = False 74 | # Background images fitting mode 75 | pdf_fit_background_mode = 'scale' 76 | -------------------------------------------------------------------------------- /docs/doc/code/slides.Makefile: -------------------------------------------------------------------------------- 1 | slides: 2 | $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides 3 | @echo 4 | @echo "Build finished. The slides are in $(BUILDDIR)/slides." 5 | -------------------------------------------------------------------------------- /docs/doc/code/slides.py: -------------------------------------------------------------------------------- 1 | extensions = ['hieroglyph'] 2 | -------------------------------------------------------------------------------- /docs/doc/code/style.py: -------------------------------------------------------------------------------- 1 | html_style = 'tracsphinx.css' 2 | 3 | # The theme to use for HTML and HTML Help pages. See the documentation for 4 | # a list of builtin themes. 5 | html_theme = 'sphinxdoc' 6 | -------------------------------------------------------------------------------- /docs/doc/example1.rst: -------------------------------------------------------------------------------- 1 | 一级标题1 2 | ========= 3 | 4 | 二级标题1 5 | --------- 6 | -------------------------------------------------------------------------------- /docs/doc/example2.rst: -------------------------------------------------------------------------------- 1 | 一级标题2 2 | ========= 3 | 4 | 二级标题2 5 | --------- 6 | -------------------------------------------------------------------------------- /docs/doc/images/ball1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/doc/images/ball1.gif -------------------------------------------------------------------------------- /docs/doc/index.rst: -------------------------------------------------------------------------------- 1 | 编写技术文档 2 | ============ 3 | 4 | 文档也是代码! 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | sphinx 10 | -------------------------------------------------------------------------------- /docs/doc/sphinx.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: rst 2 | 3 | .. _my-reference-label: 4 | 5 | 用Sphinx编写技术文档 6 | ==================== 7 | 8 | 大家会发现,如果一个项目主要是用Python写的,其文档都很类似,比如:Python在线的HTML官方手册。这些项目的文档都来源于一个很不错的项目:Sphinx。这个Sphinx特指Sphinx doc这个项目(另一个也叫Sphinx的search的项目,虽然都叫一个名字)。 9 | 10 | 官网:http://sphinx-doc.org/ 11 | 12 | 以下出现Sphinx的地方,都特指Sphinx doc这个项目 13 | 14 | 使用场景 15 | -------- 16 | 17 | * 很多开源Python库项目的API手册都是用这个写的,可以看Sphinx官网给的链接:http://sphinx-doc.org/examples.html 18 | * 如果是用Python写的商业软件,也可以用这个来写技术文档,纯文本管理研发文档,保证功能代码、测试代码、相关文档同时更新 19 | * 直接拿来写在线书。比如,这个《软件构建实践系列》就是:https://github.com/akun/pm 20 | * 直接用来做slide等演示幻灯片,从一定程度上替代PowerPoint。比如,http://example.zhengkun.info/slide.html 21 | 22 | 功能 23 | ---- 24 | 25 | 这里就列举个人关心的几个特性: 26 | 27 | * 文本是rst格式语法 28 | * 生成HTML、PDF、Slide(需要插件)等格式的文档 29 | * 支持文档、代码等引用 30 | * 支持自定义样式 31 | * 支持插件扩展 32 | * 直接看官网手册了解更多:http://sphinx-doc.org/contents.html 33 | 34 | 语法简介 35 | -------- 36 | 37 | 就是rst的语法,这里就列举几个常用的: 38 | 39 | 标题等级 40 | ~~~~~~~~ 41 | 42 | rst如下: 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 | rst如下: 73 | 74 | :: 75 | 76 | * 列表1 77 | * 列表2 78 | * 列表3 79 | 80 | 效果如下: 81 | 82 | * 列表1 83 | * 列表2 84 | * 列表3 85 | 86 | 列表写法除了用“*”,还可以用:“-”,“+”,最后效果一样。 87 | 88 | 上面说的是无序列表,如果想用有序列表,可以用“#.”。 89 | 90 | rst如下: 91 | 92 | :: 93 | 94 | #. 列表1 95 | #. 列表2 96 | #. 列表3 97 | 98 | 效果如下: 99 | 100 | #. 列表1 101 | #. 列表2 102 | #. 列表3 103 | 104 | 表格 105 | ~~~~ 106 | 107 | rst如下: 108 | 109 | :: 110 | 111 | ===== ===== ===== 112 | 第1列 第2列 第3列 113 | ===== ===== ===== 114 | 8 1 6 115 | 3 5 7 116 | 4 9 2 117 | ===== ===== ===== 118 | 119 | 效果如下: 120 | 121 | ===== ===== ===== 122 | 第1列 第2列 第3列 123 | ===== ===== ===== 124 | 8 1 6 125 | 3 5 7 126 | 4 9 2 127 | ===== ===== ===== 128 | 129 | 插入图片 130 | ~~~~~~~~ 131 | 132 | rst如下: 133 | 134 | :: 135 | 136 | .. image:: images/ball1.gif 137 | 138 | 效果如下: 139 | 140 | .. image:: images/ball1.gif 141 | 142 | 插入代码 143 | ~~~~~~~~ 144 | 145 | 展示代码示例,经常会用到: 146 | 147 | 默认 148 | """" 149 | 150 | rst如下: 151 | 152 | :: 153 | 154 | :: 155 | 156 | print 'Hello World!' 157 | 158 | 效果如下: 159 | 160 | :: 161 | 162 | print 'Hello World!' 163 | 164 | 自定义 165 | """""" 166 | 167 | rst如下: 168 | 169 | :: 170 | 171 | .. code-block:: python 172 | :linenos: 173 | 174 | print 'Hello World!' 175 | 176 | 效果如下: 177 | 178 | .. code-block:: python 179 | :linenos: 180 | 181 | print 'Hello World!' 182 | 183 | 引用代码文件 184 | """""""""""" 185 | 186 | rst如下: 187 | 188 | :: 189 | 190 | .. literalinclude:: code/example.js 191 | :language: javascript 192 | :linenos: 193 | 194 | 效果如下: 195 | 196 | .. literalinclude:: code/example.js 197 | :language: javascript 198 | :linenos: 199 | 200 | 提供下载文件链接 201 | ~~~~~~~~~~~~~~~~ 202 | 203 | 直接下载该RST本身。 204 | 205 | rst如下: 206 | 207 | :: 208 | 209 | :download:`sphinx.rst ` 210 | 211 | 效果如下: 212 | 213 | :download:`sphinx.rst ` 214 | 215 | 目录索引 216 | ~~~~~~~~ 217 | 218 | example1对应sphinx.rst所在目录下的example1.rst文件,example2类似。 219 | 220 | rst如下: 221 | 222 | :: 223 | 224 | .. toctree:: 225 | :maxdepth: 2 226 | 227 | example1 228 | example2 229 | 230 | 效果如下: 231 | 232 | .. toctree:: 233 | :maxdepth: 2 234 | 235 | example1 236 | example2 237 | 238 | 引用 239 | ~~~~ 240 | 241 | 可以用于跨rst文档间的内容互相引用。这里以本文档内为例。 242 | 243 | rst如下: 244 | 245 | :: 246 | 247 | .. _my-reference-label: 248 | 249 | 用Sphinx编写技术文档 250 | ==================== 251 | 252 | 很长的文字内容 253 | 254 | 点击回到顶部, :ref:`my-reference-label`. 255 | 256 | 效果如下: 257 | 258 | 点击回到顶部, :ref:`my-reference-label`. 259 | 260 | 文字效果 261 | ~~~~~~~~ 262 | 263 | 斜体 264 | """" 265 | 266 | rst如下: 267 | 268 | :: 269 | 270 | *斜体* 271 | 272 | 效果如下: 273 | 274 | *斜体* 275 | 276 | 粗体 277 | """" 278 | 279 | rst如下: 280 | 281 | :: 282 | 283 | **粗体** 284 | 285 | 效果如下: 286 | 287 | **粗体** 288 | 289 | 下标 290 | """" 291 | 292 | 斜杠是为了空格转义,最后显示无空格。 293 | 294 | rst如下: 295 | 296 | :: 297 | 298 | H\ :sub:`2`\ O 299 | 300 | 效果如下: 301 | 302 | H\ :sub:`2`\ O 303 | 304 | 上标 305 | """" 306 | 307 | rst如下: 308 | 309 | :: 310 | 311 | E = mc\ :sup:`2` 312 | 313 | 效果如下: 314 | 315 | E = mc\ :sup:`2` 316 | 317 | .. seealso:: 318 | 319 | * 更多说明,详见rst文档:http://docutils.sourceforge.net/rst.html 320 | * 另外,这本书本身就是个示例:https://github.com/akun/pm 321 | 322 | Hello World 323 | ----------- 324 | 325 | 根据上面的介绍,其实常用的语法不多,现在直接用下,自己感受下吧! 326 | 327 | 安装 & 初始化 328 | ~~~~~~~~~~~~~ 329 | 330 | 常用Python安装方式,创建个文件夹,执行命令,按提示自己选择即可。 331 | 332 | :: 333 | 334 | pip install Sphinx 335 | mkdir docs 336 | cd docs 337 | sphinx-quickstart 338 | 339 | 根据提示输入相应参数即可,可以一路默认。 340 | 341 | 尝试编辑 342 | ~~~~~~~~ 343 | 344 | 编辑index.rst,只写入以下内容 345 | 346 | :: 347 | 348 | 用Sphinx编写技术文档 349 | ==================== 350 | 351 | 使用场景 352 | -------- 353 | 354 | 生成HTML 355 | ~~~~~~~~ 356 | 357 | 很简单,默认支持就很好。 358 | 359 | :: 360 | 361 | make html 362 | python -m SimpleHTTPServer 9527 363 | 364 | 直接浏览器访问9527端口,就可以看到类似Python官方文档的效果。 365 | 366 | 生成PDF 367 | ~~~~~~~ 368 | 369 | 麻烦些,需要依赖库,且需要简单修改下配置。 370 | 371 | #. 安装依赖库 372 | 373 | :: 374 | 375 | pip install rst2pdf 376 | 377 | #. 编辑conf.py,增加或修改如下配置: 378 | 379 | .. literalinclude:: code/pdf.py 380 | :language: python 381 | :linenos: 382 | 383 | #. 编辑Makefile,增加如下代码: 384 | 385 | Linux下的Makefie: 386 | 387 | .. literalinclude:: code/pdf.Makefile 388 | :linenos: 389 | 390 | Windows下的批处理: 391 | 392 | .. literalinclude:: code/pdf.bat 393 | :linenos: 394 | 395 | #. 执行生成PDF 396 | 397 | :: 398 | 399 | make pdf 400 | python -m SimpleHTTPServer 9527 401 | 402 | .. seealso:: 403 | 404 | 有关PDF的更多配置,可以阅读这个文档:http://ralsina.me/static/manual.pdf 405 | 406 | 生成Slide 407 | ~~~~~~~~~ 408 | 409 | Slide就是我们常说的演示文档,如:Windows下的PowerPoint(PPT);Mac下Keynote等等。这里用Sphinx生成在线的HTML5形式的Slide,操作也相对简单,也是需要依赖库和简单修改下配置。 410 | 411 | #. 安装依赖库 412 | 413 | :: 414 | 415 | pip install hieroglyph 416 | 417 | #. 编辑conf.py,修改如下配置: 418 | 419 | .. literalinclude:: code/slides.py 420 | :language: python 421 | :linenos: 422 | 423 | #. 编辑Makefile,增加如下代码: 424 | 425 | Linux下的Makefie: 426 | 427 | .. literalinclude:: code/slides.Makefile 428 | :linenos: 429 | 430 | #. 执行生成Slides 431 | 432 | :: 433 | 434 | make slides 435 | python -m SimpleHTTPServer 9527 436 | 437 | .. seealso:: 438 | 439 | 有关Slide的更多信息,可以直接查看这个项目:https://github.com/nyergler/hieroglyph 440 | 441 | 自定义样式 442 | ~~~~~~~~~~ 443 | 444 | 直接拿来主义,直接用别人写的Trac的样式 445 | 446 | #. 复制样式文件到静态资源目录,比如,这里是: 447 | 448 | :: 449 | 450 | cp tracsphinx.css _static/ 451 | 452 | #. 编辑conf.py,增加或修改如下配置: 453 | 454 | .. literalinclude:: code/style.py 455 | :language: python 456 | :linenos: 457 | 458 | #. 执行生成HTML 459 | 460 | :: 461 | 462 | make html 463 | python -m SimpleHTTPServer 9527 464 | 465 | 直接浏览器访问9527端口,就可以看到类似Trac的官方样式效果。 466 | 467 | 汇总到一块 468 | ~~~~~~~~~~ 469 | 470 | 可以直接看Python项目模板:https://github.com/akun/aproject/\ 只看docs目录即可。 471 | 472 | 这里提到的几个核心文件示例如下: 473 | 474 | * `index.rst `_ 475 | * `conf.py `_ 476 | * `Makefile `_ 477 | * `css `_ 478 | 479 | 另外推荐一个服务:https://readthedocs.org/ 480 | 481 | 如果你的项目研发文档用Sphinx写的,可以用来做文档的持续集成,相当方便。 482 | 483 | 这个\ `《软件构建实践系列》 `_\ 就是用的这个服务。 484 | 485 | 最后 486 | ---- 487 | 488 | 这是一篇很简单的项目推广文章,在自己的Python项目中把Sphinx用起来吧! 489 | 490 | 当然Sphinx不仅支持Python源码的Domain,而且支持C、C++、JavaScript等Domain,即使没有你所用的语言的Domain,它本身还支持写插件扩展,所以其它类型语言的项目也不妨用一下。 491 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 软件构建实践 |version| 2 | ====================== 3 | 4 | * 项目地址:https://github.com/akun/pm 5 | * 新浪微博:`@阿kun太受欢迎了 `_ 6 | 7 | 注意 8 | ---- 9 | 10 | * 带有Python背景,很多实践是以Python的常见项目为例的。 11 | 12 | 目录 13 | ---- 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | intro/index 19 | vcs/index 20 | codereview/index 21 | unittest/index 22 | doc/index 23 | construction/index 24 | continuous/index 25 | process/index 26 | meeting/index 27 | note/index 28 | tao/index 29 | reference/index 30 | contribute/index 31 | changelog/index 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | -------------------------------------------------------------------------------- /docs/intro/index.rst: -------------------------------------------------------------------------------- 1 | 简介 2 | ==== 3 | 4 | 约定 5 | ---- 6 | 7 | 如何阅读 8 | -------- 9 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pm.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pm.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | if "%1" == "pdf" ( 191 | %SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf 192 | echo. 193 | echo.Build finished. The PDF files are in %BUILDDIR%/pdf 194 | goto end 195 | ) 196 | 197 | :end 198 | -------------------------------------------------------------------------------- /docs/meeting/index.rst: -------------------------------------------------------------------------------- 1 | 如何开会 2 | ======== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | no_meeting 9 | 10 | 每日例会 11 | -------- 12 | 13 | 评审会议 14 | -------- 15 | 16 | 启动会议 17 | -------- 18 | 19 | 总结会议 20 | -------- 21 | -------------------------------------------------------------------------------- /docs/meeting/no_meeting.rst: -------------------------------------------------------------------------------- 1 | 不开会 2 | ====== 3 | 4 | 不开会?!起这个标题,算是一个标题党行为。完整点说法应该是:项目过程中,你应该避免参加不需要你解决问题或参与决策的会议。 5 | 6 | 如果你是个程序员 7 | ---------------- 8 | 9 | 这里不一定特指程序员或者说研发工程师啦,项目过程中会参与的其他一线角色,比如:设计师、构架师、测试工程师也包括其中。 10 | 11 | 请问你的最大价值是在哪?当然是最直接的产出设计、代码,以及检查这些设计和代码是否符合预期等等这些东西了。 12 | 13 | 相信很多人都参加过“状态报告”之类的会议吧,回想下自己在这种会议中,除去自己发言的时间外: 14 | 15 | * 你会仔细听取每个人的发言吗? 16 | * 你会记录每个人的发言吗? 17 | * 会后你能回忆起跟你工作相关人的发言不? 18 | 19 | 可以先心里回答这3个问题,后面会简单说明。那你都在干嘛呢?回答可能是: 20 | 21 | * 我在调试代码、推敲设计、查找资料、测试功能。不错嘛,居然还在赶项目进度啊。 22 | * 我在上上网、刷刷微博、看看朋友圈。这也正常,正好拿会议时间调剂下。 23 | 24 | 既然是这种情况,身为程序员或者刚才说的其他角色的“XX师”们会问,那是否还要参加一个只需要我发言2-3分钟,但会议时间却长达2-3分钟乘以N个人的会议呢? 25 | 26 | 答案当然是:Absofuckinglutely NOT 27 | 28 | 回到刚才3个问题: 29 | 30 | * 会仔细听。那么你一定是新来的,自然会了解下大家都在干嘛,虽然结果是你听完还是不知道大家在干嘛。 31 | * 会记录。那么你一定是个专门负责会议记录的。 32 | * 居然还能想起来。好吧,如果包括前2个问题的回答都是会的话,那你就是项目经理了,或者说你有这种意向或潜质。 33 | 34 | 当然,这个绝对不是戏谑或讽刺项目经理啦,现实的确如此。 35 | 36 | 如果你是个项目经理 37 | ------------------ 38 | 39 | 刚才说到项目经理,如果你是项目经理,下次开会的时候你可以环顾下周围的人,心里默默计算下这个会议的成本。例如: 40 | 41 | * 有8个人参加,每人每年的人力成本12万RMB,相当于每人参会1小时差不多40RMB吧。那么8个人就是320RMB,这还不包括会前会后每人的时间耗费。 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 | -------------------------------------------------------------------------------- /docs/note/index.rst: -------------------------------------------------------------------------------- 1 | 读书笔记 2 | ======== 3 | 4 | 箴言 5 | ---- 6 | 7 | Manage It! 8 | ---------- 9 | 10 | Ship It! 11 | -------- 12 | -------------------------------------------------------------------------------- /docs/process/index.rst: -------------------------------------------------------------------------------- 1 | 开发流 2 | ====== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | loop 8 | 9 | “曳光弹” 10 | -------- 11 | 12 | 瀑布 13 | ---- 14 | 15 | 迭代 16 | ---- 17 | 18 | 敏捷 19 | ---- 20 | -------------------------------------------------------------------------------- /docs/process/loop.rst: -------------------------------------------------------------------------------- 1 | 软件开发过程杂谈 2 | ================ 3 | 4 | 这里特别说是“软件开发过程”,而没有特别用“软件工程”? 5 | 6 | * 一是,软件工程涵盖的范围太大了,所以直接取其中的开发过程来重点说明,并且大家\ 7 | 更喜欢关注,软件工程中的具体开发过程。 8 | * 二是,软件“工程”的所谓工程,和其它的工程实在有些差异,而更接近于是艺术品的创\ 9 | 作过程,比如:画、音乐等艺术形式的创作。 10 | 11 | 虽然有差异,但被冠上“工程”的名字也有道理,其本身也强调“如何做”(于工程师而言)\ 12 | ,而有别于“为什么”和“是什么”(于科学家而言)。 13 | 14 | 下面就按常见的瀑布模型来说明下软件开发过程,说是瀑布模型,切忌把下列的各个阶段\ 15 | 划分得层级很清楚,其实这些都是前后连贯的,有的过程甚至是贯穿全程的。 16 | 17 | 我想要一个东西?! 18 | ------------------ 19 | 20 | .. image:: static/idea_bulb.jpg 21 | 22 | 对用户(或者说客户)来说,软件开发就是这么简单: 23 | 24 | **我有一个想法 -> 一个哆啦A梦(就是所谓神奇的工厂,最好再加上个时光机器) -> \ 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 | .. image:: static/right_requirement.jpg 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 | * 切记需求 txt 化。一个意思就是,有记录,条目化;另一个意思就是,需求十有八九\ 85 | 是会有变更的,是很容易随时被编辑修改的。 86 | * 最好需求 exe 化。一个意思就是,需求具备技术上的可执行性;另一个意思就是,理\ 87 | 想上最好需求能固定下来,当然如果做不到这点,那么有需求变更了,也得有个编译\ 88 | 成 exe 的过程,也就是需求变更要有变更流程,或者说要让大家都意识到变更会有一\ 89 | 定的代价。 90 | 91 | 再回到刚才出现的图,漏斗所要表达的意思是,需求分析最好能到达的效果就是,将一堆\ 92 | 杂乱的信息,通过这个漏斗后降噪过滤,最后得到的信息是更加有序的。相反的效果就如\ 93 | 下图: 94 | 95 | .. image:: static/wrong_requirement.jpg 96 | 97 | 底下那个人就是另一个悲催的程序员,看起来慢悠悠的沙漏好像在过滤这需求,但最后还\ 98 | 是那些信息,让人一头雾水。 99 | 100 | 然后要记清楚上面那2个例子表达的“需求”和解决方案的区别,切记南辕北辙。\ **虽说\ 101 | 条条大路通罗马,但建立罗马帝国往往就一条路!** 102 | 103 | 再看下一个很流行的软件开发笑话的图。这种笑话时刻发生在我们平时软件开发的过程\ 104 | 中,应该要避免出现这种情况。 105 | 106 | .. image:: static/joke_requirement.jpg 107 | 108 | 109 | 概要设计 110 | -------- 111 | 112 | .. image:: static/design_summary.jpg 113 | 114 | 这个阶段可以认为是整个软件的顶层设计,有的人喜欢叫构架设计或架构设计,不管叫什\ 115 | 么吧。完成这个设计后: 116 | 117 | * 关注业务的人看着这个业务构架图,听着你的讲解能了解这个业务是干嘛的,业务间是\ 118 | 如何协作的; 119 | * 关注技术的人看着另一个技术构架图,听着人你的解释了解这个系统是如何运作的。 120 | 121 | 这个阶段,一般也会确定技术选型,比如:用哪些编程语言、编程框架、类库、操作系统\ 122 | 、数据库等等技术层面的组件。 123 | 124 | 如果是个已经有成熟参考经验的系统,可能可以搭建出一个大致的框架,后续基于这个框\ 125 | 架就可以进行具体实现了。 126 | 127 | 另外,这个阶段很重要的一点就是要进行下原型验证,无论这个原型是很原始的白纸黑字\ 128 | ;还是低保证的原型草图;亦或是高保证的原型图(比如:系统本身有 UI 的话),基于\ 129 | 这个原型,你能给别人很好的讲解清楚,帮助别人更好理解最后做出来的东西,这样就可\ 130 | 以尽早确认完毕这个东西是不是别人想要的。 131 | 132 | 详细设计 133 | -------- 134 | 135 | .. image:: static/design_detail.jpg 136 | 137 | 很多时候和写代码一起了,一般来说会重点关照,核心模块、重要模块、难度高的模块,\ 138 | 可能不同人有不同风格吧: 139 | 140 | * 有些人会更加仔细推敲设计而不会急于写代码。 141 | * 当然对有些人来说,写出一个模块的接口层和主线运行逻辑,更为实际。 142 | 143 | 设计/编码/调试/测试 144 | ------------------- 145 | 146 | 这块过程对很多人来说应该算是最熟悉的了,也是最喜欢的,但往往其实花的时间也是最\ 147 | 少的一个环节。 148 | 149 | 一旦进入具体实现阶段时候,日常工作大部分时间都是集中在这几块了。这里特地还把设\ 150 | 计提出来,作为其中一部分,就是想表达设计的重要性,对于程序员来说编码只是实现你\ 151 | 的设计的一个工具而已,工具用得熟练不表示设计思路足够好。 152 | 153 | .. image:: static/code1.jpg 154 | 155 | 有不少人喜欢有足够单元测试覆盖的编码过程,日常编码时候,能让代码跑一会儿,又让\ 156 | 测试跑一会儿。这样能保证写的代码能有足够的保险,无论是增加新特性、改进功能、重\ 157 | 构代码、修复缺陷的时候都能信心满满的让代码运行在正式上线的系统中。 158 | 159 | .. image:: static/code2.jpg 160 | 161 | 如果没有看过这张图中的那本书,建议可以看下,全书其实就是讲了一个悲剧的软件开发\ 162 | 故事。看完后对比下自己经历的项目,就能发现各种雷同的现象,让自己以后避免这种经\ 163 | 历。 164 | 165 | .. image:: static/code3.jpg 166 | 167 | 新手 168 | 169 | .. image:: static/code4.jpg 170 | 171 | 老手 172 | 173 | 上面 2 张图就是表达了新手和老手具体干活时候的状态,谁都是从新手过来的,所以新\ 174 | 手的痛苦应该都有所体会,但只要把自己的基础牢牢打扎实了,就逐渐能变成老手一样会\ 175 | 得心应手,随时运用手头的各种工具、技能、必杀解决各种问题。 176 | 177 | .. image:: static/code5.jpg 178 | 179 | 效率!很重要的一个词。 180 | 181 | 有人说过,“很多效率低下的程序员,可以归结为基本功不牢。”,所以无论是新手阶段还\ 182 | 是已经成为老手了,基本功必须扎实。 183 | 184 | 一个程序员得有构建能力,而且是足够好的构建能力,比如:从写下第一行代码,到编写\ 185 | 功能代码、编写测试代码、编写技术文档、编写构建脚本、发布软件包,这些都能一气呵\ 186 | 成。 187 | 188 | * 0 -> 1。软件是个虚无的东西,相当于是程序员脑中得到设计,能转化成别人能看得见\ 189 | 用得了的软件。即:思考 -> 软件。 190 | * 自动化。能做到所谓一气呵成,如果没有足够高的自动化是很难的。 191 | 192 | .. image:: static/code6.jpg 193 | 194 | 如果你是个高效的程序员,那么你的价值一定要得到体现。即:\ 195 | **show me the code, show me the money!** 196 | 197 | 当然写出好的代码到成为一个好的软件或好的产品还是有一段距离的,可能不一定是技术\ 198 | 层面的事情了,但技术是必备的一部分。如果足够幸运,你的软件产品能做到苹果产品那\ 199 | 么成功,就可以让别人,\ **show me your kidney, show you the apple** 200 | 201 | .. image:: static/code7.jpg 202 | 203 | 最后,大家要注意保养啊!可以得花买本《程序员健康指南》看看吧。 204 | 205 | test and test 206 | ------------- 207 | 208 | 再说下测试,其实前面已经涉及到测试了,编写代码边测试,那个测试算是细粒度的测试\ 209 | ,或者我们平时说的单元测试。这里说的测试粒度更大。 210 | 211 | 集成测试 212 | ~~~~~~~~ 213 | 214 | 很多人觉得做集成测试这么痛苦,代码集成、模块集成、项目集成,十分让人头大,然后\ 215 | 暴露各种问题,怎么解决? 216 | 217 | 解决办法很简单:更频繁地集成,从而减少潜在的冲突,让问题提前跑路出来。也就是说\ 218 | 把你觉得困难的事情提前,到后面来看这都不是事了。这里看起来有点心理效应上的味道\ 219 | ,但其实也有很多技术操作方面的方式来降低集成的难度。 220 | 221 | .. image:: static/test_all_in_one.jpg 222 | 223 | 痛苦的集成测试 224 | 225 | .. image:: static/test_all_in_one_nice.jpg 226 | 227 | 幸福的集成测试 228 | 229 | 系统测试 230 | ~~~~~~~~ 231 | 232 | 这里重点说下测试的目的是啥?反正不是为了发现 bug 而测试,引用我觉得合理的话: 233 | 234 | “简言之,测试的目的应该是验证需求,bug(预期结果与实际结果之间的差别)是这个过\ 235 | 程中的产品而非目标。测试人员应该象工兵一样,在大部队(客户)预期前进的方向上探\ 236 | 雷、扫雷(bug),而不需要去关心那些根本没有人会去碰的地雷。” 237 | 238 | .. image:: static/test.jpg 239 | 240 | 发布/交付 241 | --------- 242 | 243 | 这就是我想要的东西! 244 | ~~~~~~~~~~~~~~~~~~~~ 245 | 246 | 到达这里,算是一个里程碑,但一般来说不是终点。 247 | 248 | 交付、验收完毕,往往进入下个迭代周期,进行日常维护、系统升级等等,又不断重复着\ 249 | 上述过程。直到这个软件不在维护,逐渐消亡、报废处理。 250 | 251 | .. image:: static/i_need.jpg 252 | 253 | 坑爹!这哪是我想要的东西啊?! 254 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 255 | 256 | 这个是我们要避免的情况,运气好,回头重走一遍上述过程,顶多就亏本付出些额外代价\ 257 | ;运气不好,直接跟你说再见了! 258 | 259 | .. image:: static/not_i_need.jpeg 260 | 261 | 想象的和现实的差距 262 | ~~~~~~~~~~~~~~~~~~ 263 | 264 | 为了避免出现这种情况,一定要记得时刻问一句:这是客户/用户想要的吗? 265 | 266 | .. image:: static/before_and_after.jpg 267 | 268 | 完结 269 | ---- 270 | 271 | 银弹是否存在?存在吗?不存在吗?如果真有的话,那一定是一个靠谱的团队! 272 | 273 | .. image:: static/team.jpg 274 | 275 | 这里有个个人觉得不错的检查条目(这个翻译可能是台湾版本的翻译) 276 | 277 | 《Joel 衡量法则》 278 | 279 | * 你们用不用源文件管理系统? 280 | * 你们可以把整个系统从源码到CD映像文件一步建成吗? 281 | * 你们每天白天都把从系统源码到CD映像做一遍吗? 282 | * 你们有软件虫管理系统吗? 283 | * 你们在写新程序之前总是把现有程序里已知的虫解决吗? 284 | * 你们的产品开发日程安排是否反映最新的开发进展情况? 285 | * 你们有没有软件开发的详细说明书? 286 | * 你们的程序员是否工作在安静的环境里? 287 | * 你们是否使用现有市场上能买到的最好的工具? 288 | * 你们有没有专职的软件测试人员? 289 | * 你们招人面试时是否让写一段程序? 290 | * 你们是否随便抓一些人来试用你们的软件? 291 | 292 | 后续主题会逐渐展开说下几种常见的软件开发流,当然还是那句话:“具体问题具体分析\ 293 | 。。” 294 | 295 | 参考 296 | ---- 297 | 298 | * `工程学 `_ 299 | * `没有银弹 `_ 300 | * http://baike.baidu.com/view/19375.htm 301 | * http://chinese.joelonsoftware.com/Articles/TheJoelTest.html 302 | * 《项目管理修炼之道》 303 | * 《软件项目成功之道》 304 | * 《掌握需求过程》 305 | * 图片资源来自网上 306 | -------------------------------------------------------------------------------- /docs/process/static/before_and_after.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/before_and_after.jpg -------------------------------------------------------------------------------- /docs/process/static/code1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code1.jpg -------------------------------------------------------------------------------- /docs/process/static/code2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code2.jpg -------------------------------------------------------------------------------- /docs/process/static/code3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code3.jpg -------------------------------------------------------------------------------- /docs/process/static/code4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code4.jpg -------------------------------------------------------------------------------- /docs/process/static/code5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code5.jpg -------------------------------------------------------------------------------- /docs/process/static/code6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code6.jpg -------------------------------------------------------------------------------- /docs/process/static/code7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/code7.jpg -------------------------------------------------------------------------------- /docs/process/static/design_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/design_detail.jpg -------------------------------------------------------------------------------- /docs/process/static/design_summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/design_summary.jpg -------------------------------------------------------------------------------- /docs/process/static/i_need.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/i_need.jpg -------------------------------------------------------------------------------- /docs/process/static/idea_bulb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/idea_bulb.jpg -------------------------------------------------------------------------------- /docs/process/static/joke_requirement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/joke_requirement.jpg -------------------------------------------------------------------------------- /docs/process/static/not_i_need.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/not_i_need.jpeg -------------------------------------------------------------------------------- /docs/process/static/right_requirement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/right_requirement.jpg -------------------------------------------------------------------------------- /docs/process/static/team.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/team.jpg -------------------------------------------------------------------------------- /docs/process/static/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/test.jpg -------------------------------------------------------------------------------- /docs/process/static/test_all_in_one.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/test_all_in_one.jpg -------------------------------------------------------------------------------- /docs/process/static/test_all_in_one_nice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/test_all_in_one_nice.jpg -------------------------------------------------------------------------------- /docs/process/static/wrong_requirement.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/process/static/wrong_requirement.jpg -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | 参考 2 | ==== 3 | -------------------------------------------------------------------------------- /docs/tao/index.rst: -------------------------------------------------------------------------------- 1 | 杂记 2 | ==== 3 | 4 | 软件开发各种漫谈、杂谈、扯淡 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | who_is_user 10 | opensource 11 | -------------------------------------------------------------------------------- /docs/tao/opensource.rst: -------------------------------------------------------------------------------- 1 | 做开源软件项目会用到的服务简介 2 | ============================== 3 | 4 | 一般,程序员做公司的商业软件项目都会用到各种工具,这里的工具是指,源代码的版本控制、持续集成等工具。但世界上很大一部分软件项目其实都是开源的(没有统计过,猜想而已),而开源软件项目,参与的程序员一般都分散在世界各地,没有集中的工作场所,自然也不会有统一的涉及软件配置管理等工具的托管场所。 5 | 6 | 那么,有没有好的解决方案呢,自然是有。即使没有的话,也会有一帮程序员做出类似的工具和服务的。比如,下面会介绍的几个部分服务,完全可以满足大多数开源项目的要求(Python项目就更多满足了)。这些服务都是免费的,当然要更好服务,那就得掏出一些美刀了。 7 | 8 | 当然,如果信任这些服务的话,很多公司的商业项目或初创公司而言,也完全可以用这些服务起步,只要支付一定的美刀就行,就能省去很多额外的麻烦。 9 | 10 | **注意:部分服务的网站可能需要翻墙。** 11 | 12 | 源码托管 13 | -------- 14 | 15 | GitHub:https://github.com/ 16 | 17 | 就不废话了。如果不想折腾成Mantis、Bugzilla、Trac、Redmine那种更为复杂的issues跟踪管理,也完全可以用来做开源项目的issues跟踪管理。 18 | 19 | .. image:: static/small-GitHub.png 20 | 21 | 持续集成 22 | -------- 23 | 24 | Travis CI:https://travis-ci.org/ 25 | 26 | 很好结合到了GitHub,可以用GitHub帐号统一注册登录。 27 | 28 | 只要开源项目中,写好相关配置,就会自动按配置执行持续集成的相关过程,比如:单元测试,真正做到让你的每一次代码提交,都能得到测试。当然,前提是你得写单元测试代码啦。 29 | 30 | 有关单元测试的内容,具体可以看 :doc:`/unittest/index` 31 | 32 | .. image:: static/small-Travis-CI.png 33 | 34 | 代码质量检查 35 | ------------ 36 | 37 | Landscape:https://landscape.io/ 38 | 39 | 也是很好结合到了GitHub,可以用GitHub帐号统一注册登录。 40 | 41 | 只要开源项目中,写好相关配置,就会自动按配置执行代码质量检查,看看代码有没有错误啦,有没有“坏味道”啦,以及有没有违反约定风格之类的东西。 42 | 43 | 不过,这个服务只针对Python作为编程语言的项目,其它编程语言估计也有类似的服务吧(没有的话,估计也有一帮程序员正做着吧)。 44 | 45 | .. image:: static/small-Landscape.png 46 | 47 | 测试覆盖率检查 48 | -------------- 49 | 50 | Coveralls:https://coveralls.io/ 51 | 52 | 同样,很好结合到了GitHub,可以用GitHub帐号统一注册登录。 53 | 54 | 只要开源项目中,写好相关配置,就会自动按配置,根据持续集成中单元测试结果,得到测试覆盖率。 55 | 56 | .. image:: static/small-Coveralls.png 57 | 58 | 文档托管 59 | -------- 60 | 61 | Read the Docs:https://readthedocs.org/ 62 | 63 | 这个帐号不能像上面一样用GitHub帐号通用得注册登录了,需要自己单独注册一个。 64 | 65 | 项目的文档只是得用Sphinx编写(对Python项目而言,Sphinx这个算是标配工具了),就可以很好的用这个服务了。 66 | 67 | 有关Sphinx的编写,具体可以看 :doc:`/doc/sphinx` 68 | 69 | .. image:: static/small-ReadTheDocs.png 70 | 71 | 最后 72 | ---- 73 | 74 | 具体如何使用,这里就不介绍了,各个服务的网站上都有足够的文档说明。并且,这只是一片水文而已,所以不会有太多内容的。 75 | 76 | 上面几个服务,大多都提供徽章了服务,比如,可以直接放到GitHub项目中的README中,这样别人就可以看到你的项目持续集成是否通过、代码质量是否足够好、测试覆盖率是否足够高。例如,这个我正在写的一个简单的类似Hacker News网站的开源项目:https://github.com/akun/PyHackerNews 77 | 78 | README中再放上一个项目文档的链接,就给自己的项目配备了很好的文档服务了(特别是很多类库性质的项目)。比如,这个《软件构件实践》系列文章就是用了这个文档托管服务:http://pm.readthedocs.org/ 79 | -------------------------------------------------------------------------------- /docs/tao/static/small-Coveralls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/tao/static/small-Coveralls.png -------------------------------------------------------------------------------- /docs/tao/static/small-GitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/tao/static/small-GitHub.png -------------------------------------------------------------------------------- /docs/tao/static/small-Landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/tao/static/small-Landscape.png -------------------------------------------------------------------------------- /docs/tao/static/small-ReadTheDocs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/tao/static/small-ReadTheDocs.png -------------------------------------------------------------------------------- /docs/tao/static/small-Travis-CI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/tao/static/small-Travis-CI.png -------------------------------------------------------------------------------- /docs/tao/who_is_user.rst: -------------------------------------------------------------------------------- 1 | “用户”是谁? 2 | ============ 3 | 4 | ^ 5 | -- 6 | 7 | * 客户就是上帝 8 | * 以用户为中心设计 9 | * 用户第一 10 | * 还有各种补充,你一定想的比我多 11 | 12 | 好话都会说,貌似也都知道,但不知道有多少人口中这么说,心中还骂你老母、七大姨八大姑九大舅,你二大爷的。其实这里想说的“用户”跟上面提到的没太多关系,想说说广义上的“用户”。 13 | 14 | 第0个笑话 15 | --------- 16 | 17 | * 苦逼B:S啊,你写的功能模块有没有啥文档可以看看啊,感觉有点复杂啊。。 18 | * 序员S:B啊,没有啊,直接看代码吧! 19 | * 苦逼B:好吧,那你直接跟我讲讲吧。。 20 | * 序员S:~!@#$%^&*()_+ 21 | 22 | 上述过程在业界俗称“口交”。几个月后.. 23 | 24 | * 序员B:FVCK,谁写的功能模块啊,连个文档说明都没有! 25 | * 序员A:本人已死,有事烧纸。。 26 | 27 | 此时已不能“交”之。。 28 | 29 | 这种场景太常见:想让我写文档,没门;你没文档,你妹! 30 | 31 | 第1个笑话 32 | --------- 33 | 34 | * 小卷的释迦摩尼:耶稣啊,听说产品UI原型图出来了? 35 | * 大卷的耶稣:是啊,你看就是这个! 36 | * 小卷都立起来的释迦摩尼:你妹!这丫的是小鸡啄米图吧! 37 | * 大卷如飘柔般的耶稣:没时间,凑活着用吧,好歹能有个鸟用了。。 38 | 39 | 没第三个笑话了 40 | -------------- 41 | 42 | 所谓没有,其实是想不出来了。 43 | 44 | 回到刚才说的话题,“用户”是谁?在这里,反正不是上帝,上帝啥都能干,还要你服务?类似“有*中*国*特*色*的*社*会*主*义”深刻的定义写不出来,直接摘录一段书里描述来引出说明吧: 45 | 46 | .. note:: 47 | 48 | 软件系统所影响的人群并不仅限于使用它的人。软件系统并不仅仅是被使用:它会被构建并测试,他需要被运维,它还可能需要修复,它经常会改善,当然它会被支付。上述每种活动都涉及很多(可能是大量)用户以外的人。每种人群都有他们自己的需求、兴趣和需要,需要软件系统来满足。 ———— `《软件系统构架》 `_ P14 49 | 50 | 先不管书中原来想表达的意思,毕竟书中有上下文,表达的意思会有所不同。类比软件系统,可能这个软件系统是一个软件的产品,它将“用户”这一群体广义化,整个软件生命周期可能的参与者都是它可能的服务对象,就是这里想说的“用户”,同时也是书中想表达的“利益相关者”。 51 | 52 | * 最直接的,软件做出来给人用,使用它的人必然是“用户”。 53 | * 但你也知道,它是软件,会有很多问题,测试人员需要定期测试,来发现潜在问题。 54 | * 你访问的网站,其背后是大量服务器,而软件又运行在上面,运维人员需要日常运维,来保障服务可用。 55 | * 同样,它是软件,有问题就需要有人来解决,好的软件都是维护出来的,研发人员需要随时维护、开发,来改善软件。 56 | * 当然,钱是大家最关心的,这么好的软件,哪位大爷来给点钱烧烧呗,不然上述几个角色都不好玩了,这个角度看,有钱就是爷还是对的。 57 | 58 | 其实说到这,也就大概能说明白“用户”是谁了,也许有的人也知道如何更好的“用户就是XX”、“以用户为中心XX”、“用户第?”了。所以,以后: 59 | 60 | * 你平时请教别人问题时,准备好问题的上下文,说清楚你想干什么,让帮助你的人更好帮助你,从而心情愉快。如何提问可以看下 `提问的智慧 `_ 。 61 | * 团队协作一起做事,你完成某个工序后,交给下个环节的人的交付物是否有价值,能让合作者觉得你是个靠谱的人,你很专业。 62 | * 你设计、开发、维护功能模块的时候,有没有想过你的下任程序员对你的代码第一反应是FVCK;或者再夸张点,如果下任程序员是个十足的疯子,他可能心血来潮,提着菜刀,大半夜来见你,你还敢不敢这么写代码。 63 | * 总之,让测试你软件的人,查看你程序输出的日志的人,维护你程序的人,阅读你文档的人,部署你软件的人,运维你系统的人,帮你做技术支持的人,是否都能心情愉快了。 64 | * 干活的大家心情愉快了,那么所谓的用户也好,客户也好,掏钱的大爷也好,心情不愉快那还是很有难度的。从这一角度来看“用户第一”真的有这么重要吗? 65 | 66 | $ 67 | -- 68 | 69 | 就以这个链接作为结束吧,对任何人都很有用的“5W1H”,只不过这里是软件开发的场景。 70 | 71 | http://wiki.woodpecker.org.cn/moin/5W1H 72 | -------------------------------------------------------------------------------- /docs/unittest/concept_relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/unittest/concept_relationship.png -------------------------------------------------------------------------------- /docs/unittest/django/example.py: -------------------------------------------------------------------------------- 1 | 2 | from django.http import HttpResponse 3 | from django.utils import simplejson 4 | from django.views.decorators.http import require_GET 5 | 6 | @require_GET 7 | def get_json(request): 8 | 9 | return HttpResponse(simplejson.dumps({ 10 | 'name': u'大卷的耶稣', 'age': 13 11 | }), mimetype='application/json') 12 | -------------------------------------------------------------------------------- /docs/unittest/django/index.rst: -------------------------------------------------------------------------------- 1 | Django中的单元测试 2 | ================== 3 | 4 | ^ 5 | -- 6 | 7 | 上篇文章《:doc:`../python`\》有提到,“TestResult和TextTestRunner这两个很有用的东西”,但并没有展开讲,这篇介绍Django中的单元测试的文章,在后面一小部分会以Django源码为例简单展开下。 8 | 9 | 先进入主要部分,简单讲解Django中单元测试的使用。 10 | 11 | show me the code 12 | ---------------- 13 | 14 | 照例,先来一段简单示例代码作为讲解。 15 | 16 | 比如,有如下一个简单的功能代码,意思是GET方式请求一个URL,200返回需要JSON的响应。代码如下: 17 | 18 | .. literalinclude:: example.py 19 | 20 | 如果平时手工测试,我们可能就访问下浏览器,看下是否返回结果是否符合预期,如果每次测试都这样,其实很烦,所以我们写个测试,比如访问的URL地址是“/get-json/”,那么代码如下: 21 | 22 | .. literalinclude:: test_example.py 23 | 24 | Django有自己一套测试框架,所以Django的项目提供了一个运行单元测试的命令: 25 | 26 | :: 27 | 28 | python manage.py test 29 | 30 | 这只是一个简单的示例,展示下Django里写单元测试然后执行。 31 | 32 | 另外,< Python 2.7的建议安装unitest2 33 | 34 | 常见场景 35 | -------- 36 | 37 | * 测试数据库模拟 38 | * 测试数据构造 39 | * HTTP请求:POST、GET 40 | * response:200/404/30X、content、context 41 | * 表单验证 42 | * 测试模版 43 | * 测试数据定义 44 | 45 | * 需要登录/注销 46 | * 测试cookie/session 47 | * 浏览器:selenium 48 | 49 | * override_settings 50 | 51 | * 执行所有/某个app/某个TestCase/某个测试条目 52 | * 出错了,立马报错,结束测试 53 | 54 | * 忽略某些测试 55 | 56 | * 额外:MongoDB、修改文件 57 | 58 | * 运行 59 | * 测试数据库 60 | * client 61 | * fixtures 62 | * assert,这文章会讲到的Django中的单元测试框架,封装了不少适合Web开发中的assertXXXX,比如:判断是否URL跳转等。 63 | * **MongoDB特殊处理** 64 | 65 | Django单元测试框架源码简析 66 | -------------------------- 67 | 68 | * runner 69 | 70 | * 模拟 71 | 72 | 参考 73 | ---- 74 | 75 | * https://docs.djangoproject.com/en/1.5/topics/testing/ 76 | 77 | $ 78 | -- 79 | 80 | 这里介绍的其实也很简单,更详细的内容,就直接: 81 | * RTFM - Read The Fucking Manual 82 | * RTFS 83 | 84 | 后续 85 | ---- 86 | 87 | 既然讲到了Web开发,那就离不开Web前端开发,而JavaScript又是Web前端开发中的主流,下次就以JavaScript为例来说明下单元测试好了。 88 | 89 | 资源下载 90 | -------- 91 | 92 | 示例代码 93 | -------------------------------------------------------------------------------- /docs/unittest/django/test_example.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.utils import simplejson 3 | 4 | 5 | class ExampleTestCase(TestCase): 6 | 7 | def test_get_json_200(self): 8 | response = self.client.get(reverse('get_json')) 9 | self.assertEqual(response.status_code, 200) 10 | -------------------------------------------------------------------------------- /docs/unittest/example.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class Example3TestCase(unittest.TestCase): 4 | 5 | def setUp(self): 6 | print 7 | print 'I am setUp' 8 | print a 9 | 10 | def tearDown(self): 11 | print 'I am tearDown' 12 | 13 | def test_one(self): 14 | self.assertEqual(1, 1) 15 | 16 | def test_two(self): 17 | self.assertEqual(2, 2) 18 | 19 | if __name__ == '__main__': 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /docs/unittest/example_test_suite.py: -------------------------------------------------------------------------------- 1 | #-*- encoding: UTF-8 -*- 2 | 3 | 4 | import unittest 5 | 6 | 7 | class ExampleTestCase(unittest.TestCase): 8 | 9 | def test_do_somthing(self): 10 | self.assertEqual(1, 1) 11 | 12 | def test_do_somthing_else(self): 13 | self.assertEqual(1, 1) 14 | 15 | 16 | class AnoterExampleTestCase(unittest.TestCase): 17 | 18 | def test_do_somthing(self): 19 | self.assertEqual(1, 1) 20 | 21 | def test_do_somthing_else(self): 22 | self.assertEqual(1, 1) 23 | 24 | 25 | def suite_use_make_suite(): 26 | """想把TestCase下的所有测试加到TestSuite的时候可以这样用 27 | 28 | """ 29 | 30 | suite = unittest.TestSuite() 31 | suite.addTest(unittest.makeSuite(ExampleTestCase)) 32 | return suite 33 | 34 | def suite_add_one_test(): 35 | """想把TestCase下的某个测试加到TestSuite的时候可以这样用 36 | 37 | """ 38 | 39 | suite = unittest.TestSuite() 40 | suite.addTest(ExampleTestCase('test_do_somthing')) 41 | return suite 42 | 43 | def suite_use_test_loader(): 44 | """想用TestLoader方式把测试加到TestSuite的时候可以这样用 45 | 46 | """ 47 | 48 | test_cases = (ExampleTestCase, AnoterExampleTestCase) 49 | suite = unittest.TestSuite() 50 | for test_case in test_cases: 51 | tests = unittest.defaultTestLoader.loadTestsFromTestCase(test_case) 52 | suite.addTests(tests) 53 | return suite 54 | 55 | if __name__ == '__main__': 56 | unittest.main(defaultTest='suite_use_test_loader') 57 | -------------------------------------------------------------------------------- /docs/unittest/hello.rst: -------------------------------------------------------------------------------- 1 | 你好,单元测试! 2 | ================ 3 | 4 | ^ 5 | -- 6 | 7 | 平时面试程序员,如果想起来,经常会问对方2个问题: 8 | 9 | * 平时写代码会进行单元测试不?能聊的话,就再跟对方交流下细节。 10 | * 你们公司或所在的团队进行研发过程中有单元测试不?然后也是能聊的话,就再跟对方交流下细节。 11 | 12 | 其实这2个问题主要出于2个目的: 13 | 14 | * 个人习惯,喜欢收集统计下市面上各个IT公司的研发阶段的单元测试情况。 15 | * 如果面试者有单元测试习惯,当然有总比没有好嘛。 16 | 17 | 回想一下 18 | -------- 19 | 20 | 相信有一定软件研发经历的程序员,对于以下场景或言论应该经常碰到: 21 | 22 | * 坑爹了,还有一周就要交付版本了,还有好多功能还没做完呢,做完的还有好多bug呢。。写单元测试?开玩笑吧 -- 一个苦逼加班的程序员。 23 | * 擦嘞,怎么这个bug又出现了。。 -- Mr.救火员经常说的话。 24 | * 纠结啊,代码好乱,好想重构,但万一该出问题了咋办。。算了就这样堆代码吧 -- 第n个维护该代码的程序员的心理活动。 25 | * 你妹啊,你们开发自己不测试吗,这么多问题根本没法用嘛 -- 一个很爷们的测试姑娘发怒了。 26 | * 你说说,为啥发布版本周期越来越长,bug反而还越来越多了呢?! -- 某个高层管理者的疑惑。 27 | * 还有更多日常用语、抱怨、吐槽,欢迎补充。。 28 | 29 | 当然,导致这些吐槽的原因必然不是没进行单元测试导致的,这里只是为了说明,如果有单元测试的话,对于上面的场景在一定程度上能有所避免。 30 | 31 | 你怎么看? 32 | ---------- 33 | 34 | 不知道各位程序员是如何看待单元测试这个问题的?经常会有类似说法吧: 35 | 36 | * 我太忙了! 37 | * 我没时间啊!类似上面说法。 38 | * 我认为功能代码更重要。 39 | * 测试代码软件中实际不会跑。 40 | * 我写代码时候很仔细,边写边手工执行下测试。这其实也不错,这个执行过程其实从广义上讲,也是单元测试,但是可重复程度较低而已(比如哪天我偷懒,即时那个程序员不偷懒,但换了个程序员维护,你能确保第二个程序员不偷懒?好,即使第二个程序员不偷懒,那么第n个程序员呢?) 41 | 42 | 其实,每个程序员对于单元测试都会有自己的看法吧。就我的观点而言,单元测试是很必要,不然也不会闲的写这篇文章吧。 43 | 44 | 不是什么 45 | -------- 46 | 47 | 说是什么之前,先说单元测试不是什么吧: 48 | 49 | * NOT 万能药 50 | * NOT 老鼠药 51 | 52 | 一种观点认为,这是个很牛X的东西,有了它,软件质量必然会好。这就是很多人觉得有了单元测试保障以后,就万事大吉了,这个想法很危险,说到底任何一种实践仅仅只是程序员工具箱中的一种工具而已,即使是所谓万能钥匙,它也仅仅能用来开锁、开门不是吗(如果你非得想出拿钥匙来抠耳屎,好吧,这也算一种功能)。再好的实践,再厉害的工具,也能让一个不合格的程序员用烂了。 53 | 54 | 另一种保守的观点认为,这个东西不好,为什么不好?无非就浪费时间,增加工作负担,很繁琐这几种理由。但的确是这样吗?很遗憾,是的,不过得换种说法:会花费原来你没花费的时间,提升你的工作质量,是否繁琐是一个习惯成自然的问题。说白了,这里其实是一个熟练度的问题。 55 | 56 | 是什么 57 | ------ 58 | 59 | 再来说下单元测试是什么吧: 60 | 61 | * 润滑剂 62 | * 催化剂 63 | 64 | 好吧,我承认为了对应上面的“不是什么”故意类比出2个是什么,其实可以认为是一类东西,也就是对于保障软件质量来说,单元测试是让软件实际的功能代码能,更正确的运作(润滑剂),更好的进化(催化剂)。也就是更好的校验你编写代码的输入输出预期,重构改进代码时候能更大胆的对现有代码动刀子。 65 | 66 | 单元测试代码 67 | ------------ 68 | 69 | 需要指出的是,这里的单元测试是泛指,也就是至少你写完一段代码或改完一段代码,这段代码运行的主路径或代码被修改的地方要运行一遍,也就是传统的跑一遍程序。当然这个是必须的,因为你得确认程序是按你预期的输入和输出来执行的。 70 | 71 | 但这种形式的单元测试有个缺点就是可重复性太低,这里“重复性”的意思就是,任何人想任何时间可以随时跑一遍程序,等于跑这种形式的单元测试,太依赖于人,谁都有想偷懒的时候对吧。 72 | 73 | 也就是: 74 | 75 | * 人工跑一边函数、模块、功能,也是单元测试。 76 | * 但人总会偷懒(我很忙、我没时间、我病了、我状态不好、必杀绝招:我宣布这坨代码不归我管了。。),造成重复性太低。 77 | 78 | 所以更理想的单元测试方式,还是颤抖吧,把你的测试过程写成代码吧,交给机器去跑单元测试。也许你人工跑一边程序只要1秒钟,但写测试代码可能耗费了你10分钟,貌似是600倍的耗费,但你写的代码的生命周期值得拥有这600倍的耗费,代码可能被频繁改动,每改动一次,都应该测试一次(估计很多程序员都是以自己的人品保证,自己的修改是不会有问题的,从而忽略了这一次测试吧),并且可能维护代码的不是你,这么看,600倍其实不多,对吧。 79 | 80 | 功能代码 + 测试代码 81 | ------------------- 82 | 83 | 这里的测试代码特指单元测试代码。 84 | 85 | * 互相验证 86 | 87 | + 其实对于功能代码和测试代码而言,两者是一个互相验证的关系,也就是“好基友,一辈子+一被子”的关系。 88 | + 经常有人会问,测试代码不也是代码嘛,那谁来测试啊,答案就是功能代码,不至于出现“测试测试代码”之一说法吧,然后无限递归下去吧。这个疑问的担心是,代码是人写的,难免会出问题,测试代码也是代码,所以自然也难免出问题。 89 | + 其实这个担心是对的,消除这个担心,一个是借助成熟的单元测试框架,减少各种细枝末节的考虑,只关注测试验证,必然能减少很多犯错,但这只能减少,想要规避是不可能的,但可以事后补救,比如测试代码的错误,导致不应该测试通过的,竟然测试通过了,那么这个时候功能代码必然也出错,这个时候不管有没有发现,就必然是产生问题了,如果被发现了,那么就知错就改呗,找到绕过测试代码的地方,看看怎么修改下测试代码逻辑,是否能测试到这种情况。 90 | * 先写?后写? 91 | 92 | + 有人经常的疑惑就是,我到底是先写测试代码呢?还是写完功能代码再写测试代码呢?这个不好说,感觉看个人习惯吧,我的习惯一般都会是偏向后写,因为很多时候在功能代码没出来前,有些测试代码的测试用例设计很难。 93 | + 当然一些通用、普遍或者业务逻辑简单的场景写还是可以尝试下所谓“测试先行”的做法的。 94 | + 这里说的前提,貌似就是由程序员自己负责来写对应的测试代码了。可能有的公司会区分出,开发工程师和测试开发工程师,前者更偏重功能实现代码为主,测试代码为辅;后者更偏重测试代码为主。这个时候,先后也许不那么重要。 95 | * 度的问题 96 | 97 | + 还有人的疑惑就是,到底写多少测试代码才算到头啊?我的想法是,别想了,永远到不了头的。合理的做法是够用就行。 98 | + 比如用户注册,最起码测试下注册成功的情况吧,然后测试失败的情况吧,当然失败情况一堆,咋办?挑个主流的失败场景呗。 99 | + 如果那些边边角角测试不到咋办?别急着一口气吃撑胖子,慢慢补呗,毕竟你主要任务还是实现那个注册功能,测试也会服务于这一点的。但如果你非得一天把罗马城建完,那我也无话可说,有钱、有时间的人就是牛X。 100 | + 其实我这说了也白说,度的问题,往往是自己慢慢体验的。 101 | 102 | 测试代码只是零散的测试片段,要把这些测试集合起来,必然还是要配合测试框架、工具自动化之类的。还是那句话,机器不会偷懒。 103 | 104 | 场景 105 | ---- 106 | 107 | 说了这么多,来说个简单的单元测试应用场景吧: 108 | 109 | #. 天哪!我写的代码竟然有一个bug! 110 | #. 吭哧吭哧,调式了半天,终于找到了触发条件,一个很诡异的条件。 111 | #. 咚咚!打断一下,切换到传统场景,这个时候一般就继续吭哧吭哧修复功能代码了,然后提交修复,perfect!感觉很有成就感。 112 | #. 叮叮!忽略刚才的打断,goto到2。这么诡异的条件,太容易被忽略了,还是加段测试代码保险一下吧,免得别人修改代码时候也碰到这种坑爹的情况。 113 | #. 吭哧吭哧写了个单元测试代码,来模拟刚才的条件触发,果然测试不通过! 114 | #. 吭哧吭哧修复完功能代码。 115 | #. 执行单元测试模拟条件触发,测试通过,看来修复还是有效的。 116 | 117 | 其实这个好处就是能以后避免出现这种条件触发的bug被再次引入,至少有了个保障。 118 | 119 | 但是: 120 | 121 | * 以后不再触发该bug! 122 | * 了吗?不一定噢,说不定有个更诡异的条件也能触发该bug呢。。 123 | * 那为什么还要用?其实前面也说了,至少封堵了一个诡异条件了,如果又发现其它诡异条件,那么继续goto到1,周而复始。 124 | 125 | 示例 126 | ---- 127 | 128 | 实际点,用代码说话才是王道。随便找几个开源项目的代码提交,来说明下单元测试的是怎么做的吧: 129 | 130 | * Python 131 | 132 | + Python的某个版本中,SSL库对HTTPS处理有个缺陷:http://bugs.python.org/issue5103 133 | + 看下官方的修复:http://hg.python.org/cpython/rev/ce4916ca06dd/ 134 | + 可以看到有:功能代码 + 测试代码 + 还有必要的文档更新,这样才算一次完整的提交。 135 | * Django 136 | 137 | + https://github.com/django/django/commit/25f2acfed0fc110f88abbfffb5c5c62a76670db0 138 | + 类似,功能代码 + 测试代码 + 还有必要的文档更新。 139 | 140 | 其实这里体现了一个软件配置管理(SCM)的深刻理解,就是: **一切开发行为,统一在版本仓库中进行自动记录。** 141 | 142 | 也就是传统的手工跑一边功能代码的单元测试这个测试行为没有被记录在版本仓库中,而单元测试代码则很好的帮助记录了,这里2个示例更好的还有对应文档的更新。 143 | 144 | 这仅仅是开始 145 | ------------ 146 | 147 | 单元测试其实只是众多环节的一小部分,或者说是一个起点吧,按顺序演变: 148 | 149 | * 测试框架。有了成熟的框架,必然事半功倍。 150 | * 集成测试。其实也是借助各种成熟工具,来帮你做代码层面的集成测试吧。 151 | * 自动测试。机器帮你跑测试了,自然就自动了。 152 | * 持续构建。每次提交代码构建也好,每日构建也好,跑一遍单元测试一般都是很重要的一个步骤吧。 153 | * 持续发布/交付。前面基础打得好,自动化程度足够高,这种层面的持续就不是梦想了。 154 | * 高效高质量迭代。注意不仅仅是高效,而且是高质量。 155 | * 质量可靠的软件、服务、项目、产品。其实这个才是我们的目的不是吗?单元测试只不过是达到我们目的的工具箱中的众多工具之一。 156 | 157 | $ 158 | -- 159 | 160 | 结束语就用这句话吧: **one test a day, keep bugs away..** 161 | 162 | 当然这必然是夸张了,而且有些反了,有时候经常是来了个bug,才多了个test的;-) 163 | 164 | 后续 165 | ---- 166 | 167 | 这篇文章目的,只是希望大家能对单元测试有个感性的认识,也就是: 168 | 169 | * 原来不知道单元测试的,能记住单元测试这个名词。 170 | * 原来听过的,能认可单元测试这种形式。 171 | * 认可了的,能产生去实践单元测试的欲望。 172 | * 有了欲望的,能尝试去实践一把。 173 | * 已经在实践的,那么就把你的实践经验、教训、总结或吐槽各种分享出来。 174 | 175 | 后续将从实际操作层面来讲解,比如以Python为例、以JavaScript为例等等。 176 | -------------------------------------------------------------------------------- /docs/unittest/index.rst: -------------------------------------------------------------------------------- 1 | 单元测试 2 | ======== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | hello 9 | python 10 | py2_py3_with_tox 11 | django/index 12 | javascript 13 | mock 14 | 15 | 持续集成 16 | -------- 17 | 18 | 更多详见持续输出章节 19 | 20 | * Bitten 21 | * GitLab CI 22 | * Travis CI 23 | 24 | 其它编程语言的单元测试简介(可选) 25 | ---------------------------------- 26 | 27 | C 28 | ~ 29 | 30 | Java 31 | ~~~~ 32 | 33 | Go 34 | ~~ 35 | 36 | Erlang 37 | ~~~~~~ 38 | -------------------------------------------------------------------------------- /docs/unittest/javascript.rst: -------------------------------------------------------------------------------- 1 | JavaScript中的单元测试 2 | ====================== 3 | -------------------------------------------------------------------------------- /docs/unittest/lizi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/unittest/lizi.jpg -------------------------------------------------------------------------------- /docs/unittest/mock.rst: -------------------------------------------------------------------------------- 1 | Mock和Fake的场景举例 2 | ==================== 3 | 4 | 更多资源 5 | -------- 6 | 7 | * http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy 8 | -------------------------------------------------------------------------------- /docs/unittest/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/unittest/order.png -------------------------------------------------------------------------------- /docs/unittest/py2_py3_with_tox.rst: -------------------------------------------------------------------------------- 1 | 用 tox 进行 Python 2 和 Python 3 的兼容性测试 2 | ============================================= 3 | 4 | 背景 5 | ---- 6 | 7 | 不少 Python 项目会遇到从 Python 2 到 Python 3 过渡的问题,由于过渡需要持续一段\ 8 | 时间,所以代码得保证在 Python 2 和 Python 3 中都能运行正常。为了解决这个问题,\ 9 | 所以需要引入 tox 这个工具。 10 | 11 | tox 简介 12 | -------- 13 | 14 | tox 项目代码地址:https://github.com/tox-dev/tox 15 | 16 | 按官网描述,该项目目前是为了自动化和标准化 Python 的测试工作。主要整合了 2 块\ 17 | 内容:分别是,virtualenv 管理不同版本 Python,以及结合不同测试工具命令行调用。\ 18 | 19 | tox 有以下用处: 20 | 21 | * 用不同 Python 版本的解释器,来检查 Python 包是否能正确安装; 22 | * 调用你选择的测试工具,在不同 Python 版本的环境下运行测试; 23 | * 更方便集成到 CI(持续集成)中,减少或合并一些可能会产生的重复内容。 24 | 25 | 使用说明 26 | -------- 27 | 28 | 安装 29 | ~~~~ 30 | 31 | :: 32 | 33 | pip install tox 34 | 35 | 可以把 tox 纳入你的工程项目的研发环境常用依赖库。 36 | 37 | 配置 38 | ~~~~ 39 | 40 | 用向导工具生成 tox 配置 41 | 42 | :: 43 | 44 | tox-quickstart 45 | 46 | 或者直接编辑新建 tox.ini 文件,常见配置如下: 47 | 48 | .. literalinclude:: tox.ini 49 | 50 | 相当于声明了会在 Python 2.7、Python 3.4、Python 3.5、Python 3.6 这 4 个不同环\ 51 | 境下进行:发布、安装、测试相关的工作。 52 | 53 | 这里因为包测试需要依赖:coverage 和 nose,分别进行测试覆盖率统计和方便测试的\ 54 | 调用。 55 | 56 | 这里借用了 nose 进行测试,所以执行的对应测试命令就是 nosetests,具体测试配置写\ 57 | 在 nose.cfg 里,后续单独开个主题讲下 nose 的使用。 58 | 59 | 运行 60 | ~~~~ 61 | 62 | :: 63 | 64 | tox 65 | 66 | 没错,就这么简单,如果不想细究更多参数的话。如果运行没问题就会输出类似信息: 67 | 68 | :: 69 | 70 | ... 71 | py27: commands succeeded 72 | py34: commands succeeded 73 | py35: commands succeeded 74 | py35: commands succeeded 75 | congratulations :) 76 | 77 | 表示在这些 Python 解释器环境下能执行通过,说明可以兼容这些版本 Python 解释器。 78 | 79 | 例子 80 | ---- 81 | 82 | 用一个小的 Python 工程项目举例说明并感受下 tox 的使用,具体如下: 83 | 84 | main.py 85 | ~~~~~~~ 86 | 87 | .. literalinclude:: ../../pm/onepiece/onepiece/main.py 88 | 89 | test_main.py 90 | ~~~~~~~~~~~~ 91 | 92 | .. literalinclude:: ../../pm/onepiece/tests/test_main.py 93 | 94 | setup.py 95 | ~~~~~~~~ 96 | 97 | .. literalinclude:: ../../pm/onepiece/setup.py 98 | 99 | tox.ini 100 | ~~~~~~~ 101 | 102 | .. literalinclude:: ../../pm/onepiece/tox.ini 103 | 104 | 运行结果 105 | ~~~~~~~~ 106 | 107 | 命令行下执行 tox,输出示例如下: 108 | 109 | .. literalinclude:: tox_result.log 110 | 111 | 完整示例 112 | ~~~~~~~~ 113 | 114 | 可以实际演练下,更方便理解,可以在这里查看更完整示例:\ 115 | https://github.com/akun/pm/tree/master/pm/onepiece 116 | 117 | 总结 118 | ---- 119 | 120 | 简单总结下: 121 | 122 | * tox 可以让你更轻松地在不同 Python 版本的解释器上进行测试; 123 | * tox 只是个辅助工具,关键还是得有足够单元测试代码覆盖来检验 Python 2 和 \ 124 | Python 3 兼容; 125 | * tox 使用起来,可以把更多重点放到 Python 2 和 Python 3 兼容的写法上。后续单独\ 126 | 开个主题讲下 Python 2 和 Python 3 兼容的写法。 127 | 128 | 参考 129 | ---- 130 | 131 | * https://github.com/tox-dev/tox 132 | * https://tox.readthedocs.io/ 133 | * https://docs.python.org/3/library/unittest.html 134 | * http://python-future.org/quickstart.html 135 | -------------------------------------------------------------------------------- /docs/unittest/python.rst: -------------------------------------------------------------------------------- 1 | Python中的单元测试 2 | ================== 3 | 4 | ^ 5 | -- 6 | 7 | 上篇文章《:doc:`hello`\》闲扯了下个人对单元测试的看法。 8 | 9 | 后续几篇 :doc:`index` 主题的文章,打算先选几个常见的编程语言作为示例来讲解,因为最近个人的主要编程语言是Python,那就必须先以Python为例讲解最省事了;-) 10 | 11 | 举个“栗子” 12 | ---------- 13 | 14 | .. image:: lizi.jpg 15 | 16 | 偷懒,就直接拿Python官方文档中的例子来做说明好了;-) 17 | 18 | .. literalinclude:: test_random.py 19 | 20 | 先简单说下代码的意思,也就是:写1个测试用例(TestCase),用来测试Python自带的random模块,那1个测试用例里面包含了3个测试,分别用来测试random模块的3个函数(看Python源代码,其实在random模块中已经把一个Random类实例化了,所以从外部用法上看起来就是函数调用一样)。 21 | 22 | 再简单解释下代码中出现的几个概念,后续再详细讲解: 23 | 24 | * TestCase,就是测试用例,一个测试用例可以包含多个测试(为了避免混淆可以把“测试”叫“测试项”)。代码中以Python类的形式出现。 25 | * test_xxxx,就是测试项,根据实际的功能代码逻辑来编写对应的测试项。代码中以Python类方法的形式出现。 26 | * assertXXXX,就是检查点,相当于平时执行测试中的,判断测试结果是否符合测试预期结果的代码模拟。 27 | * setUp,就是执行测试项前的准备工作,比如:可以做一些初始化工作,这里就是初始化一个Python列表。另外,和setUp对应的还有个tearDown,后面会讲到。作为一对兄弟,相当于是平时执行测试中的2种行为的代码模拟,分别是:准备测试执行所需要的环境,以及销毁测试过程中产生的垃圾。 28 | 29 | 几个概念 30 | -------- 31 | 32 | 其实这里讲解的就是Python标准库,unittest模块,又叫PyUnit。类似其它编程语言,也都有对应的所谓XUnit,X可以替换为Java等其它编程语言等。 33 | 34 | 这里只是简单讲解下unittest模块中的几个概念,当然其他XUnit也会有类似概念。 35 | 36 | test fixture 37 | ~~~~~~~~~~~~ 38 | 39 | 貌似平时习惯直接就叫fixture了,如果非得翻译个中文名称,叫“装置”不知道合不合适,也就是测试需要的装备。理解上可以直接对应到上面提到过的setUp和tearDown。所谓fixture就是:比如,程序运行的前提需要数据库,还得准备测试用的数据,常见的那些对数据库的操作程序就会如此;又比如,程序运行的前提得访问某个网页,常见的那些爬虫程序就会如此。类似这些,得需要准备好这些fixtures,而这个装置能有所谓清理和还原的功效(tearDown),这样不至于各个测试执行的时候有环境污染造成各种诡异情况。 40 | 41 | test case 42 | ~~~~~~~~~ 43 | 44 | 这个最直白,也听的最多,叫测试用例。理解上可以直接对应到上面提到过的TestCase这个类。对于测试用例来说,就是针对功能代码,模拟一些输入,来验证输出是否符合预期。 45 | 46 | test suite 47 | ~~~~~~~~~~ 48 | 49 | 测试套件,也好理解,就是包含了一堆test case的集合。使用上可以根据具体场景来归类各个test case吧,比如:根据业务逻辑分(模块A、模块B);根据测试逻辑分(全功能测试、冒烟测试)。当然,测试套件也可以包含一堆其它测试套件。 50 | 51 | test runner 52 | ~~~~~~~~~~~ 53 | 54 | 跑测试的家伙,你把各个测试丢给他,他去执行,然后把测试结果形成一份报告让你看。 55 | 56 | 关系 57 | ~~~~ 58 | 59 | 画个示例图,应该可以更好理解这除了test runner外的几个概念的关系吧: 60 | 61 | .. image:: concept_relationship.png 62 | 63 | 程序执行 64 | -------- 65 | 66 | .. image:: order.png 67 | 68 | 上面这个图,就是一个TestCase执行测试代码的时候,程序执行的过程吧,想要了解更直接些,直接运行下面这个程序,看下输出信息应该就明白了。 69 | 70 | .. literalinclude:: test_order.py 71 | 72 | 控制台输入如下: 73 | 74 | :: 75 | 76 | test_do_something (__main__.ExampleOrderTestCase) ... 77 | I am setUp 78 | I am test_do_something 79 | I am tearDown 80 | ok 81 | test_do_something_else (__main__.ExampleOrderTestCase) ... 82 | I am setUp 83 | I am test_do_something_else 84 | I am tearDown 85 | ok 86 | 87 | TestCase 88 | -------- 89 | 90 | 一般来说,日常用Python写单元测试代码,最多的还是跟TestCase打交道。而搭建针对具体项目的测试框架时候,会用到的较多是TestSuite、TestResult、TestLoader这些,一旦项目中的测试框架搭建成体系了,很少会打交道。所以,先单独讲下大众化点的TestCase。 91 | 92 | setUp() 93 | ~~~~~~~ 94 | 95 | 执行某条测试前需要准备的工作,比如:某个文件或目录必须存在、数据库需要初始化好、网络服务要准备好、访问的URL需要登录授权完毕等等。 96 | 97 | 每次调用测试前,都会执行这个方法。如果你运行过上面的程序就应该了解。 98 | 99 | 顺便讲一下2个概念:测试错误(Error)和测试失败(Failure)。 100 | 101 | * 测试错误,可以简单理解成测试代码执行时候报错了,比如:测试代码中print a,而a没有进行变量声明。 102 | * 测试失败,可以简单理解成测试代码执行正常,但没有得到预期的测试结果,比如:测试代码中调用功能代码add(1, 2),但返回结果不是3。 103 | * 另外,从Python 2.7开始支持了skip特性,也可以理解为测试忽略(Ignore),比如:某个测试只想在Windows下才运行,这样在Linux下就会被跳过,也就是忽略。 104 | 105 | 好了,现在可以讲了,如果代码在这个阶段出错,都会认为是测试错误(Error),比如: 106 | 107 | .. literalinclude:: test_set_up_error.py 108 | 109 | 执行python test_set_up_error.py输出: 110 | 111 | :: 112 | 113 | EE 114 | ====================================================================== 115 | ERROR: test_one (__main__.SetUpErrorTestCase) 116 | ---------------------------------------------------------------------- 117 | Traceback (most recent call last): 118 | File "unittest/test_set_up_error.py", line 6, in setUp 119 | self.assertEqual(1, 2) 120 | AssertionError: 1 != 2 121 | 122 | ====================================================================== 123 | ERROR: test_two (__main__.SetUpErrorTestCase) 124 | ---------------------------------------------------------------------- 125 | Traceback (most recent call last): 126 | File "unittest/test_set_up_error.py", line 6, in setUp 127 | self.assertEqual(1, 2) 128 | AssertionError: 1 != 2 129 | 130 | ---------------------------------------------------------------------- 131 | Ran 2 tests in 0.001s 132 | 133 | FAILED (errors=2) 134 | 135 | 结果是2个errors,可以将代码中的setUp的assert修改正确了,再次执行试下,会发现结果是2个failures 136 | 137 | tearDown() 138 | ~~~~~~~~~~ 139 | 140 | 执行某条测试完毕后需要销毁的工作,比如:删除测试生成的文件或目录、销毁测试用的数据库等等。 141 | 142 | 每次调用测试后,都会执行这个方法,即使调用的测试错误(Error)也会调用,比如: 143 | 144 | .. literalinclude:: test_tear_down_always.py 145 | 146 | 执行python test_tear_down_always.py -v输出: 147 | 148 | :: 149 | 150 | test_one (__main__.TearDownAlwaysTestCase) ... ERROR 151 | 152 | I am tearDown 153 | test_two (__main__.TearDownAlwaysTestCase) ... 154 | I am tearDown 155 | ok 156 | 157 | ====================================================================== 158 | ERROR: test_one (__main__.TearDownAlwaysTestCase) 159 | ---------------------------------------------------------------------- 160 | Traceback (most recent call last): 161 | File "unittest/test_tear_down_always.py", line 11, in test_one 162 | print not_defined 163 | NameError: global name 'not_defined' is not defined 164 | 165 | ---------------------------------------------------------------------- 166 | Ran 2 tests in 0.006s 167 | 168 | FAILED (errors=1) 169 | 170 | 这样设计也是为了不让某个测试的错误,影响到下个要执行的测试,所以必须要执行到清理。 171 | 172 | **如果setUp就测试错误(Error)了,那tearDown()会不会执行呢?各位可以自己写代码验证下;-)** 173 | 174 | .. .. literalinclude:: example.py 175 | 176 | 另外,跟setUp类似,如果代码在这个阶段出错,也都会认为是测试错误(Error)。 177 | 178 | assertXXXX() 179 | ~~~~~~~~~~~~ 180 | 181 | XXXX代码Equal、NotEqual等等一堆协助单元测试的判断方法,太多了直接看官方文档最直接了。问题是这么多不经常用难免记不住,所以平时基本上就记了: 182 | 183 | * assertEqual 184 | * assertNotEqual 185 | * assertTrue 186 | * assertFalse 187 | * assertRaises 188 | 189 | 因为大多数都可以根据这些转化出来,当然,如果记住最好了,可以帮你一定程度上简化代码,以及增加代码的可读性。比如:要明确判别一个正则输出是否符合预期,用assertRegexpMatches,一看就知道是验证正则表达式的,就比单纯的assertEqual或assertTrue的可读性强。 190 | 191 | 当然,根据自己项目中实际情况,完全可以基于上述组合,封装出更具项目中的语义表达,提高下代码的可读性,比如:下几篇文章会讲到的Django中的单元测试框架,就封装了不少适合Web开发中的assertXXXX,比如:判断是否URL跳转等。 192 | 193 | 另外,需要说明的是几个failXXXX的判断方法、assertEquals、assert\_,已经不推荐使用了。 194 | 195 | 搭建自己项目中的单元测试框架 196 | ---------------------------- 197 | 198 | 这篇文章就先引出这个主题,暂时不详细展开,后续几篇文章逐渐来展开。 199 | 200 | 下面几个也会用到,但对于一个项目,已经搭建起来了比较完善的测试框架后,这些就不会经常用到或去改动了。组合使用下面几个,就可以根据各自项目中的实际情况,来搭建一个基本的单元测试框架,后来者基于这个框架,按照约定来填充单元测试代码就可以了。 201 | 202 | TestSuite 203 | ~~~~~~~~~ 204 | 205 | 上面也提到了,TestSuite可以认为是一堆TestCase根据需要打个包,实际运行测试还是以TestCase为单位的。看官方文档,可以知道TestSuite有两个常用的方法,addTest和addTests,addTests可以认为是循环调用了多次addTest。这里add的Test可以是TestCase,也可以是TestSuite,反正是一个套一个,大鱼吃小鱼的关系。 206 | 207 | 几个实例,可以修改需要执行的不同suite自己执行下试试: 208 | 209 | .. literalinclude:: example_test_suite.py 210 | 211 | TestLoader 212 | ~~~~~~~~~~ 213 | 214 | 可以看到上面最后一个例子,有用到TestLoader这个类,现在简单介绍下。根据刚才的例子,可以把TestLoader简单理解成辅助TestSuite的工具,用来收集符合要求的测试,或者可以认为是一个可以批量产生TestCase的工具。 215 | 216 | 看官方文档提供了很多方法,用于适应不同的场景,大多数都是类似loadTestsFromXXXX这种方法。 217 | 218 | 默认有个实例化完毕的可以直接拿来用,就是unittest.defaultTestLoader,上面示例代码中也有体现。如果你觉得默认不满足实际使用,那么就自己写个TestLoader也可以。 219 | 220 | 另外,还有TestResult和TextTestRunner这两个很有用的东西,可以在后续介绍Django中的单元测试中来重点说明,顺便也可以简单阅读下Django的单元测试框架代码,了解下还是有好处的。如果以后在项目中,需要自定义自己特殊需求的单元测试框架的时候还是有点参考意义的。 221 | 222 | doctest 223 | ------- 224 | 225 | 这里简单提下,Python中还自带doctest这种形式的单元测试,就是直接把测试写在文档注释。其中一个优点是,看到注释就知道这个模块、函数、类是怎么个用法了;而其中一个缺点是,测试代码的组织上很难模块化。这里就看个简单示例吧: 226 | 227 | .. literalinclude:: test_doctest.py 228 | 229 | 执行python test_doctest.py -v输出: 230 | 231 | :: 232 | 233 | Trying: 234 | print show_me_the_money() 235 | Expecting: 236 | $ 237 | ok 238 | 1 items had no tests: 239 | __main__ 240 | 1 items passed all tests: 241 | 1 tests in __main__.show_me_the_money 242 | 1 tests in 2 items. 243 | 1 passed and 0 failed. 244 | Test passed. 245 | 246 | $ 247 | -- 248 | 249 | 如何来体会Python中的单元测试,直接在自己的项目中写段单元测试代码吧, **show me the code** 最实在了。所谓实践就得,Think -> Do -> Done -> Think 250 | 251 | * Think:就是得有这个意识或者说想法吧,没有意识的话,一切无从谈起。 252 | * Do:在自己参与的项目中,先开始尝试着写上一段单元测试代码吧。比如:修复缺陷的时候,增加新特性的时候等等。 253 | * Done:成为一种习惯,最后就跟呼吸一样,如果停止,你会觉得难受。 254 | * Think:继续Think,实践过后,每个人一定会有自己的感悟和理解。作为一个思考者、改良者、传道者,分享出来你的看法和经验吧。 255 | 256 | 后续 257 | ---- 258 | 259 | 这里只是很简单地介绍了下Python中的单元测试,更详细的其实还是直接把官方手册相关部分完整的读一遍最实在了,当然希望这篇文章不是官方手册的重复就好。 260 | 261 | 这里讲的示例,估计实际项目中用起来,也就能应付个基本的加减乘除那种业务逻辑的场景。实际的项目,根据不同类型的开发项目,会有各种需要模拟的测试场景,这个时候一般需要借助更高级抽象的单元测试框架、模块,比如: 262 | 263 | * 可能你自己的项目中已经积累了适合你项目的单元测试类库,这样就挺好。 264 | * 还有各种成熟的各种开源开发库,比如:Python的Web开发框架Django,它里面就提供了适合Web开发场景的单元测试各种类库。 265 | * 还有需要模拟各种情况的类库,比如:网络请求、数据库存储、读写文件等等,Python中就提供了不少好的模拟的库(可以Google下Python Mock,官方文档给出的这个资源链接也不错:http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy)。 266 | 267 | 接下去打算再简单介绍下Django中的单元测试,算是Web开发类型的场景吧,当然还是Python,有兴趣的话,还可以看下Django源代码中有关单元测试的部分,相信会有更大的收获吧。如果有别的开发类型的场景,各位也可以分享出来,大家一起开开眼界。 268 | -------------------------------------------------------------------------------- /docs/unittest/test_doctest.py: -------------------------------------------------------------------------------- 1 | def show_me_the_money(): 2 | """ 3 | >>> print show_me_the_money() 4 | $ 5 | """ 6 | 7 | return '$' 8 | 9 | if __name__ == '__main__': 10 | import doctest 11 | doctest.testmod() 12 | -------------------------------------------------------------------------------- /docs/unittest/test_order.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class ExampleOrderTestCase(unittest.TestCase): 4 | 5 | def setUp(self): 6 | print 7 | print 'I am setUp' 8 | 9 | def tearDown(self): 10 | print 'I am tearDown' 11 | 12 | def test_do_something(self): 13 | print 'I am test_do_something' 14 | 15 | def test_do_something_else(self): 16 | print 'I am test_do_something_else' 17 | 18 | if __name__ == '__main__': 19 | unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) # Python 2.6 20 | #unittest.main(verbosity=2) # Python 2.7 21 | -------------------------------------------------------------------------------- /docs/unittest/test_random.py: -------------------------------------------------------------------------------- 1 | # http://docs.python.org/2/library/unittest.html?highlight=unittest#basic-example 2 | 3 | import random 4 | import unittest 5 | 6 | class TestSequenceFunctions(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.seq = range(10) 10 | 11 | def test_shuffle(self): 12 | # make sure the shuffled sequence does not lose any elements 13 | random.shuffle(self.seq) 14 | self.seq.sort() 15 | self.assertEqual(self.seq, range(10)) 16 | 17 | # should raise an exception for an immutable sequence 18 | self.assertRaises(TypeError, random.shuffle, (1, 2, 3)) 19 | 20 | def test_choice(self): 21 | element = random.choice(self.seq) 22 | self.assertTrue(element in self.seq) 23 | 24 | def test_sample(self): 25 | with self.assertRaises(ValueError): 26 | random.sample(self.seq, 20) 27 | for element in random.sample(self.seq, 5): 28 | self.assertTrue(element in self.seq) 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /docs/unittest/test_set_up_error.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class SetUpErrorTestCase(unittest.TestCase): 4 | 5 | def setUp(self): 6 | self.assertEqual(1, 2) 7 | 8 | def test_one(self): 9 | self.assertEqual(1, 2) 10 | 11 | def test_two(self): 12 | self.assertEqual(2, 1) 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /docs/unittest/test_tear_down_always.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class TearDownAlwaysTestCase(unittest.TestCase): 4 | 5 | def tearDown(self): 6 | print 7 | print 'I am tearDown' 8 | 9 | def test_one(self): 10 | self.assertEqual(1, 1) 11 | print not_defined 12 | 13 | def test_two(self): 14 | self.assertEqual(2, 2) 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /docs/unittest/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py34, py35, py36 3 | 4 | [testenv] 5 | deps = 6 | coverage 7 | nose 8 | commands = nosetests -c nose.cfg 9 | -------------------------------------------------------------------------------- /docs/unittest/tox_result.log: -------------------------------------------------------------------------------- 1 | GLOB sdist-make: /home/yourname/projects/pm/pm/onepiece/setup.py [0/4158] 2 | py27 create: /home/yourname/projects/pm/pm/onepiece/.tox/py27 3 | py27 installdeps: coverage, httpretty, nose 4 | py27 inst: /home/yourname/projects/pm/pm/onepiece/.tox/dist/onepiece-0.1.0.zip 5 | py27 installed: coverage==4.5,future==0.16.0,httpretty==0.8.14,nose==1.3.7,onepiece==0.1.0,pkg-resources==0.0.0 6 | py27 runtests: PYTHONHASHSEED='3715476856' 7 | py27 runtests: commands[0] | nosetests -c nose.cfg 8 | ..海贼王(One Piece) 9 | ... 10 | Name Stmts Miss Cover 11 | ------------------------------------------ 12 | onepiece/__init__.py 1 0 100% 13 | onepiece/main.py 22 0 100% 14 | ------------------------------------------ 15 | TOTAL 23 0 100% 16 | ---------------------------------------------------------------------- 17 | Ran 5 tests in 0.061s 18 | 19 | OK 20 | py3 create: /home/yourname/projects/pm/pm/onepiece/.tox/py3 21 | py3 installdeps: coverage, httpretty, nose 22 | py3 inst: /home/yourname/projects/pm/pm/onepiece/.tox/dist/onepiece-0.1.0.zip 23 | py3 installed: coverage==4.5,future==0.16.0,httpretty==0.8.14,nose==1.3.7,onepiece==0.1.0,pkg-resources==0.0.0 24 | py3 runtests: PYTHONHASHSEED='3715476856' 25 | py3 runtests: commands[0] | nosetests -c nose.cfg 26 | ..海贼王(One Piece) 27 | ... 28 | Name Stmts Miss Cover 29 | ------------------------------------------ 30 | onepiece/__init__.py 1 0 100% 31 | onepiece/main.py 22 0 100% 32 | ------------------------------------------ 33 | TOTAL 23 0 100% 34 | ---------------------------------------------------------------------- 35 | Ran 5 tests in 0.034s 36 | 37 | OK 38 | _______________________________________________________________________________ summary ________________________________________________________________________________ 39 | py27: commands succeeded 40 | py3: commands succeeded 41 | congratulations :) 42 | -------------------------------------------------------------------------------- /docs/vcs/branch.rst: -------------------------------------------------------------------------------- 1 | 漫谈分支 2 | ======== 3 | -------------------------------------------------------------------------------- /docs/vcs/git/contrastive.rst: -------------------------------------------------------------------------------- 1 | 同SVN进行对比 2 | ============= 3 | -------------------------------------------------------------------------------- /docs/vcs/git/git_3_kingdom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/vcs/git/git_3_kingdom.png -------------------------------------------------------------------------------- /docs/vcs/git/index.rst: -------------------------------------------------------------------------------- 1 | 以Git为例 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | contrastive 8 | usage 9 | management 10 | subversion2git 11 | -------------------------------------------------------------------------------- /docs/vcs/git/management.rst: -------------------------------------------------------------------------------- 1 | 源代码的管理和发布:以Git为例 2 | ============================= 3 | -------------------------------------------------------------------------------- /docs/vcs/git/subversion2git.rst: -------------------------------------------------------------------------------- 1 | 项目如何从 Subversion 迁移到 Git 2 | ================================ 3 | 4 | 很多有点历史的项目,都是用的 Subversion 作为版本控制工具的,随着项目需要,很多\ 5 | 团队就打算采用 Git 作为替代工具了。好,现在问题来了:项目如何平滑的从 \ 6 | Subversion 迁移到 Git。 7 | 8 | 这里所谓的迁移是按照版本控制要求来迁移,包括: 9 | 10 | * 尽可能完整的由谁提交的代码、做出的代码变更记录,提交日志等。 11 | * 尽可能完整的分支、标签等。 12 | 13 | 因为毕竟是不同的版本控制工具,转化过程难免会有瑕疵。 14 | 15 | Step 0 - 准备环境 16 | ----------------- 17 | 18 | 安装用到的工具的软件包,这里以 Ubuntu 为例 19 | 20 | :: 21 | 22 | $ sudo apt-get install subversion git 23 | $ sudo apt-get install git-core libsvn-perl perl libterm-readkey-perl 24 | 25 | Step 1 - 规范 Subversion 26 | ------------------------ 27 | 28 | 确认项目的 Subversion 地址: 29 | 30 | :: 31 | 32 | # 后面统一用 $PROJECT 表示项目的 Subversion 地址 33 | # 这里的示例项目名称是 west 34 | https://scms.example.com/svn/projects/west/ 35 | 36 | 37 | 规范项目在 Subversion 的目录结构: 38 | 39 | * 标准的 trunk、branches、tags 目录布局 40 | * branches 和 tags 目录下的分支和标签保持平级,例如: 41 | 42 | + tags/v1.0.0 可以。 43 | + tags/1.x/v1.0.0 多了层目录就不可以。 44 | 45 | * 如果不是平级,以 tags 为例,先执行 svn mv 操作 46 | 47 | 方式 1 - 远程 svn mv 48 | 49 | :: 50 | 51 | $ svn mv $PROJECT/tags/1.x/v1.0.0 $PROJECT/tags/v1.0.0 52 | 53 | 方式 2 - 本地 svn mv 54 | 55 | :: 56 | 57 | $ svn co $PROJECT west_subversion 58 | $ cd west_subversion 59 | $ svn mv tags/1.x/v1.0.0 tags/v1.0.0 60 | 61 | 最后规范后的目录示例如下: 62 | 63 | :: 64 | 65 | west 66 | ├── trunk 67 | │  ├── docs 68 | │ ├── west 69 | │ ├── setup.py 70 | │ └── README.rst 71 | ├── branches 72 | │ ├── hotfix_add_user_error 73 | │ ├── hotfix_issuse_9527 74 | │ ├── feature_unittest4app 75 | │ └── feature_multi_add_user 76 | └── tags 77 | ├── v1.0.0 78 | ├── v1.0.1 79 | ├── v2.0.0 80 | └── v2.1.0 81 | 82 | Step 3 - 生成提交者 ID 和邮箱 83 | ----------------------------- 84 | 85 | * example.com代表组织的邮箱,比如:knownsec.com 86 | * 但如果个人邮箱不是统一的组织的话,就需要手工编辑 users.txt 了 87 | 88 | :: 89 | 90 | svn log $PROJECT --xml | grep -P "^(.*?)<\/author>/$1 = $1 \<$1\@example.com\>/' > users.txt 91 | 92 | Step 4 - 迁出项目代码(git svn) 93 | -------------------------------- 94 | 95 | :: 96 | 97 | git svn clone $PROJECT --authors-file=users.txt --no-metadata --localtime --stdlayout 98 | 99 | * ``--authors-file`` 是得到的 git log 提交记录映射好提交者的信息 100 | * ``--no-metadata`` 是得到的 git log 不带上对应的 Subversion 信息了,更干净 101 | * ``--localtime`` 是得到的 git log 以本地时间为准,建议用上 102 | * ``--stdlayout`` 是先前准备的按规范目录风格来迁出代码 103 | 104 | Step 5 - 转化成Git的仓库格式(tags 和 branches) 105 | ------------------------------------------------ 106 | 107 | 处理 tag: 108 | 109 | :: 110 | 111 | 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 112 | 113 | 处理 branch: 114 | 115 | :: 116 | 117 | 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 118 | 119 | Step 6 - 一些清理工作 120 | --------------------- 121 | 122 | 由于这个转化转化会将及历史上的 branches 和 tags 也都生成一个 Git 的分支和标签\ 123 | ,所以还是得清理下你认为不用的分支和标,可能包括: 124 | 125 | * Subversion 历史上错误的 tags。 126 | * Subversion 历史上临时的 branches。 127 | * 冗余的 trunk 分支(其实跟转化后的 Git master 分支一样)。 128 | 129 | Step 7 - 添加到远程 Git 仓库 130 | ---------------------------- 131 | 132 | * 比如:GitHub 上创建项目 west 133 | * 添加本地 Git 项目到刚创建的远程 Git 仓库 134 | 135 | :: 136 | 137 | git remote add origin git@github.com:akun/west.git 138 | git push origin --all 139 | git push origin --tags 140 | 141 | Step 8 - 完成迁移 142 | ----------------- 143 | 144 | 这样,可以直接在 Git 下来继续你的项目了。 145 | 146 | :: 147 | 148 | git clone git@github.com:akun/west.git 149 | 150 | 有关 Git 日常的使用,可以参考 :doc:`usage` 151 | 152 | 遗留问题 153 | -------- 154 | 155 | 上述方式转化其实还有瑕疵,比如: 156 | 157 | * Subversion 允许空目录,转化 Git 用 git svn,处理空目录带上 \ 158 | ``--preserve-empty-dirs`` 可能会报错,不处理,可能项目的程序原先依赖空目录的处理就得修改。 159 | * 类似 svn:externals,svn:ignore,svn:merge 等属性丢失 160 | 161 | 不过问题不大,可以接受,Subversion 迁移 Git 算是基本平滑迁移。 162 | 163 | 参考 164 | ---- 165 | 166 | * http://git-scm.com/book/zh/ 167 | * https://www.semitwist.com/articles/article/view/the-better-svn-git-guide 168 | * http://git.661346.n2.nabble.com/PATCH-1-2-git-svn-fix-occasional-quot-Failed-to-strip-path-quot-error-on-fetch-next-commit-td7584266.html 169 | * http://git.661346.n2.nabble.com/git-svn-error-quot-Not-a-valid-object-name-quot-td7579457.html 170 | * http://git.661346.n2.nabble.com/SVN-gt-Git-but-with-special-changes-td6840904.html 171 | -------------------------------------------------------------------------------- /docs/vcs/git/usage.rst: -------------------------------------------------------------------------------- 1 | Git 常用命令用法:程序员的场景 2 | ============================== 3 | 4 | Git 相比 Subversion,无论概念上还是使用上,复杂度其实是高出一个等级的。为什么\ 5 | 这么说?分别看下 ``git help -a`` 和 ``svn help`` 命令清单的对比,单按这个来看\ 6 | ,就如果要掌握所有命令的用法,Git 的学习曲线绝对是比 Subversion 高的。尽管如此\ 7 | ,但还是有越来越多项目开始用 Git 来做源码管理了。 8 | 9 | 实际中,我们用到的的 Git 命令还是很有限的,可能也就 ``git help`` 中那些而已。\ 10 | 下面就类似\ :doc:`../svn/usage`\ 一样,结合实际场景说下 Git 的常用命令用法。 11 | 12 | “新人报道” 13 | ---------- 14 | 15 | 你刚入职一家公司,或新加入某个团队,立马参与到一个项目中,那么就得获取项目代码\ 16 | ,开始你的项目生涯。这个时候一般你需要克隆一份项目代码,下面都以 GitHub 上的项\ 17 | 目地址为例: 18 | 19 | :: 20 | 21 | $ git clone git@github.com:akun/pm.git 22 | 23 | 之后就进入项目目录,运行项目中的构建脚本,然后就可以熟悉代码,展开具体工作了。 24 | 25 | 当然,有的时候,有一个新项目是由你发起的,你要将初始化的项目工程放到 Git 版本\ 26 | 仓库中: 27 | 28 | :: 29 | 30 | $ mkdir pm 31 | $ cd pm 32 | $ git init 33 | $ touch README.md 34 | $ git add README.md 35 | $ git commit 36 | 37 | Git 是分布式的版本控制系统,所以刚才的操作,算是已经在你本地版本控制起来了,为\ 38 | 了推送本地仓库到远程仓库,就还得执行: 39 | 40 | :: 41 | 42 | $ git remote add origin git@github.com:akun/pm.git 43 | $ git push -u origin master 44 | 45 | 一般这个时候都会设置下 ``~/.gitconfig`` 或 ``.git/config`` 中的配置,最基本的\ 46 | 就是用户名和邮箱 47 | 48 | 确认当前的 Git 配置信息: 49 | 50 | :: 51 | 52 | $ git config --list 53 | 54 | 设置用户名和邮箱: 55 | 56 | :: 57 | 58 | $ git config user.name akun 59 | $ git config user.email admin@example.com 60 | 61 | 刚才的命令只是对 ``.git/config`` 生效,如果想全局生效,也就是 ``~/.gitconfig``\ 62 | ,就得加上 ``--global`` 参数,比如: 63 | 64 | :: 65 | 66 | $ git config --global user.name akun 67 | $ git config --global user.email admin@example.com 68 | 69 | 日常工作 70 | -------- 71 | 72 | 当你已经逐渐融入了一个项目,可能一天的工作场景或完成某个任务的工作周期是这样的\ 73 | : 74 | 75 | 更新 76 | ~~~~ 77 | 78 | 无论是清早或下午或晚上,开始了你的一天工作,你首先会更新你的工作目录: 79 | 80 | :: 81 | 82 | $ cd ~/projects/pm 83 | $ git checkout develop # 我想在 develop 分支上开始一天的工作 84 | 85 | 更新方式一: 86 | 87 | :: 88 | 89 | $ git fetch --all # 从远程仓库获取所有分支的代码变更 90 | $ git merge 91 | 92 | 更新方式二: 93 | 94 | :: 95 | 96 | $ git fetch --all 97 | $ git rebase # 默认就衍合 develop 分支的代码了 98 | 99 | 更新方式三,可以认为是 fetch 和 merge 的合集: 100 | 101 | :: 102 | 103 | $ git pull # 懒得理解 fetch 和 merge 就直接 pull 吧 104 | 105 | 这样你就可以在最新的项目代码基础上工作了。 106 | 107 | .. note:: 108 | * ``git pull --rebase`` 相当于是前面的方式二的合集 109 | * 有关 "fetch + merge" VS "fetch + rebase" VS pull 的差异后续单独写一篇文章\ 110 | 说明 111 | * 这里说的三种方式,可能每个人或团队都有自己的习惯吧 112 | * 想了解 Git 中的“衍合”,可以实践下这个文档:\ 113 | `Git-分支-分支的衍合 `_ 114 | 115 | 修改 116 | ~~~~ 117 | 118 | 可能你写了一个新的模块,需要纳入项目的版本控制: 119 | 120 | :: 121 | 122 | $ git add tools.py 123 | 124 | 可能你发现某个模块已经陈旧了,不再使用了: 125 | 126 | :: 127 | 128 | $ git rm utils.py 129 | 130 | 可能你发现一个模块的命名不太合理,需要改名: 131 | 132 | :: 133 | 134 | $ git mv model.py models.py 135 | 136 | 可能你要创建一个新的较大的模块,需要归档为目录的方式: 137 | 138 | :: 139 | 140 | $ mkdir groups 141 | $ touch groups/__init__.py 142 | $ git add groups/__init__.py 143 | 144 | .. note:: 145 | Git 不支持空文件加加入版本控制,非得必要咋办,后续的其它场景会简单说明下 146 | 147 | 可能你发现要写的模块代码布局类似于旧的模块,直接复制个代码模版: 148 | 149 | :: 150 | 151 | $ cp users/tests.py groups/tests.py 152 | $ git add groups/tests.py 153 | 154 | .. note:: 155 | Git 没有自带的所谓 cp 命令 156 | 157 | 当然,其实最常见的情况其实还是打开编辑器,比如 Vim,修改已经存在的代码,这个就\ 158 | 跟 Git 命令无关了。 159 | 160 | 检查 161 | ~~~~ 162 | 163 | 忙碌的一天过去了,或者一个任务完成了,这个时候一般会将你的工作成果,也就是代码\ 164 | 更新到版本仓库(分为本地版本仓库和远程版本仓库)。 165 | 166 | 习惯上会先检查下修改状态: 167 | 168 | :: 169 | 170 | $ git status 171 | 172 | 看到一些 Git 状态信息,确认是修改了哪些文件,之后一般会自己 code review 一下代\ 173 | 码的改动,可能有的人会习惯直接用 Git 方式来查看: 174 | 175 | :: 176 | 177 | $ git diff 178 | 179 | 这里的 diff 只是查看其中“工作目录”和“暂存区域”的区别\ 180 | 。要查看“暂存区域”和“本地仓库”的区别,可以用: 181 | 182 | :: 183 | 184 | $ git diff --staged # 或 git diff --cached 185 | 186 | .. note:: 187 | 最好理解下三个区的概念,以代码角度来理解: 188 | 189 | * 工作目录:git clone 后获得的一份本地的代码,也包括新编辑的,尚未加入版本\ 190 | 控制的代码 191 | * 暂存区域:git add 后暂存起来,尚未 git commit 的代码 192 | * 本地仓库:git commit 后正式被版本控制记录起来的代码 193 | 194 | 可以看下图,能更好的理解这三个区 195 | 196 | .. image:: git_3_kingdom.png 197 | 198 | 然后本地运行下相关的单元测试,确认是否有问题。一般来说这个时候,没有什么特殊情\ 199 | 况,就直接进入“提交”甚至是“推送”阶段了,然后结束一个工作日或工作周期,但难免会\ 200 | 有些特殊情况出现。 201 | 202 | 取消修改 203 | ~~~~~~~~ 204 | 205 | 当你 code review 完后,发现有些改动不满意;或者运行完单元测试,发现有些测试用\ 206 | 例没通过,你可能会进行取消这些修改的操作。 207 | 208 | 如果还没 add,那么可以: 209 | 210 | :: 211 | 212 | $ git checkout -- main.py 213 | 214 | 为了避免刚好跟分支名重合,所以加了两个斜杠(虽然概率很低),如果已经 add 了,\ 215 | 但还没 commit,那么可以: 216 | 217 | :: 218 | 219 | $ git reset HEAD main.py 220 | 221 | 万一刚提交完毕,也就是已经 commit 了,才发现代码有问题,比如:忘记把某个文件提\ 222 | 交了,这个时候咋办?Git 好处是可以覆盖上一次提交,那么可以: 223 | 224 | :: 225 | 226 | $ git add tests.py 227 | $ git commit --amend 228 | 229 | 上面还只是简单的撤销操作,Git 还能支持更高级的重写历史功能,想掌握高级技能的可\ 230 | 以实践下这个文档:\ 231 | `Git-工具-重写历史 `_ 232 | 233 | 解决冲突 234 | ~~~~~~~~ 235 | 236 | 有时候同别人合作写一个模块的代码,会把对方代码合并或衍合过来,比如:对方修复了\ 237 | 某个缺陷,你刚好也需要这个修复;再比如:对方完成了某个特性,你也刚好需要用下这 238 | 个特性等等各种情况。 239 | 240 | 大多数情况,代码的合并或衍合不会冲突,但也有冲突的情况,分两种情况说明,第一种\ 241 | 是合并操作时候有冲突: 242 | 243 | :: 244 | 245 | $ git fetch --all 246 | $ git merge bugfix/remove_error 247 | # 这个时候就提示你代码冲突了,处理完冲突的代码后 248 | $ git diff # code review 下代码 249 | $ git add remove.py 250 | $ git commit 251 | # 日志中就多了一条合并操作的日志了 252 | 253 | 另一种是衍合操作时有冲突: 254 | 255 | :: 256 | 257 | $ git fetch --all 258 | $ git rebase bugfix/remove_error 259 | # 这个时候就提示你代码冲突了,处理完冲突的代码后 260 | $ git diff # code review 下代码 261 | $ git rebase --continue # 有时候会 git rebase --skip 262 | # 直到不用再 rebase 为止 263 | 264 | 提交到本地版本仓库 265 | ~~~~~~~~~~~~~~~~~~ 266 | 267 | 最后,一切确认没问题了:code review 完毕,自己觉得代码满意了;有可能也合并完别\ 268 | 人的修改并且没有冲突了;运行单元测试也通过了。那么就提交代码吧: 269 | 270 | :: 271 | 272 | $ git commit 273 | 274 | 推送到远程版本仓库 275 | ~~~~~~~~~~~~~~~~~~ 276 | 277 | Git 中的 commit 只是提交到自己本地的版本控制仓库,如果想分享你的代码提交,还需\ 278 | 要推送到远程的版本控制仓库: 279 | 280 | :: 281 | 282 | $ git push 283 | 284 | 在分支工作 285 | ---------- 286 | 287 | Git 分支很灵活,用 Git 的合作开发模式方式也很灵活,如何更好得使用 Git 分支来合\ 288 | 作开发,可以参考这篇文章: 289 | 290 | * 中文翻译版本一:\ 291 | http://www.juvenxu.com/2010/11/28/a-successful-git-branching-model/ 292 | * 中文翻译版本二:\ 293 | http://www.oschina.net/translate/a-successful-git-branching-model 294 | * 英文原文:\ 295 | http://nvie.com/posts/a-successful-git-branching-model/ 296 | 297 | 可能后续也会写一篇专门的以 Git 为例的源代码的管理和发布相关主题的文章。 298 | 299 | 下面说下在分支工作的常见的实际场景,按顺序: 300 | 301 | 302 | 创建新的本地分支 303 | ~~~~~~~~~~~~~~~~ 304 | 305 | 确定要新开个分支来写代码,这里以贡献新特性为例子: 306 | 307 | :: 308 | 309 | $ git checkout -b features/batch_remove 310 | $ git branch -a # 确认已经在新分支中工作了 311 | $ git log # 可以确认是基于刚才的分支新分出来的 312 | 313 | 这里已经隐含了自动切换到新分支的动作了。 314 | 315 | 在新的本地分支工作 316 | ~~~~~~~~~~~~~~~~~~ 317 | 318 | 类似,“日常工作”中的工作周期操作,这个时候,你就可以在新分支中进行大刀阔斧的工\ 319 | 作了,直到分支中代码符合要求。 320 | 321 | 推送成为作为远程分支 322 | ~~~~~~~~~~~~~~~~~~~~ 323 | 324 | 如果想把分支分享给别人,可以推送到远程版本库,这样别人可以根据需要来把你的分支\ 325 | 代码更新到他自己的本地仓库,例如: 326 | 327 | :: 328 | 329 | $ git push origin features/batch_remove 330 | 331 | 合并或衍合远程分支 332 | ~~~~~~~~~~~~~~~~~~ 333 | 334 | 在分支中工作一段时间后,确认相关的功能代码、测试代码、文档等都提交完毕了,单元\ 335 | 测试通过,大家 code review 一致认为没问题,审核通过,最后该分支的持续集成(CI\ 336 | )完整 build 通过。这个时候,就可以进行合并的操作了。 337 | 338 | 其实前面也提过类似操作,这里再类似重复一遍,如果用合并: 339 | 340 | :: 341 | 342 | $ git fetch --all 343 | $ git merge features/batch_remove 344 | # 如果没提示冲突,那就合并成功 345 | # 如果这个时候就提示你代码冲突了,处理完冲突的代码后 346 | $ git diff # code review 下代码 347 | $ git add batch.py 348 | $ git commit 349 | # 日志中就多了一条合并操作的日志了 350 | 351 | 如果用衍合: 352 | 353 | :: 354 | 355 | $ git fetch --all 356 | $ git rebase features/batch_remove 357 | # 如果没提示冲突,那就衍合成功 358 | # 如果这个时候就提示你代码冲突了,处理完冲突的代码后 359 | $ git diff # code review 下代码 360 | $ git rebase --continue # 有时候会 git rebase --skip 361 | # 直到不用再 rebase 为止 362 | 363 | 这里也提下直接合并本地分支,有时候你创建的分支只是自己用用,没有共享给别人,因\ 364 | 为本地已经有了这份分支代码了,那么就省去 ``git fetch`` 操作,类似上述方式合并\ 365 | 或衍合代码就行。 366 | 367 | 对比 Subversion 的分支合并操作,实在是简化不少。 368 | 369 | 删除分支 370 | ~~~~~~~~ 371 | 372 | 如果确认工作完毕的分支不再需要了,那就记得及时清理掉,删除远程分支: 373 | 374 | :: 375 | 376 | $ git push origin :features/batch_remove 377 | 378 | 删除本地分支: 379 | 380 | :: 381 | 382 | $ git branch -d features/batch_remove 383 | 384 | 顺便说下,一段时间后,一定有一堆别人的分支,然后你 ``git fetch`` 下来了,这样\ 385 | 就出现在本地的分支清单中,但远程版本库中已经删除了,如果想本地分支清单干净些,\ 386 | 可以在 ``git fetch`` 时候这样执行: 387 | 388 | :: 389 | 390 | $ git fetch --all -p 391 | 392 | Ship it 393 | ------- 394 | 395 | 可能在平时的研发分支工作一段时间后,并且测试完毕,大家觉得符合发布条件了。终于\ 396 | 可以进入到版本发布阶段的工作了。 397 | 398 | 创建发布分支 399 | ~~~~~~~~~~~~ 400 | 401 | 一般来说这个时候已经将在某个发布分支上工作了,比如: 402 | 403 | :: 404 | 405 | $ git checkout -b release-1.2 develop # develop 就是平时的研发分支 406 | $ release.sh 1.2 # 比如有个执行发布脚本 407 | $ git commit 408 | 409 | 打标签 410 | ~~~~~~ 411 | 412 | 确定可以发布了,就开始打标签吧,比如: 413 | 414 | :: 415 | 416 | $ git checkout master 417 | $ git merge --no-ff release-1.2 418 | $ git tag -a v1.2 419 | $ git tag # 确认下打上了标签了 420 | $ git push origin v1.2 # 推送标签到远程版本库 421 | 422 | 正式发布 423 | ~~~~~~~~ 424 | 425 | 发布又是一个比较复杂的主题,比如:能快速发布、快速回滚(包括数据回滚)、灰度发\ 426 | 布等等,在\ :doc:`../../construction/index`\ 中会详细进行介绍,这里就简单罗列\ 427 | 下。 428 | 429 | 一般来说,根据实际情况,可以记录下来发布相关的操作过程。很多环节可以写脚本将来\ 430 | 的人工操作改成自动化操作。以后只要执行发布脚本执行一键发布就可以了。 431 | 432 | 其它场景 433 | -------- 434 | 435 | 可能还有很多别的场景,比较零散,但也算经常用到。 436 | 437 | code review 查看代码,要知道对应代码是由谁写的,好询问了解具体代码的思路: 438 | 439 | :: 440 | 441 | $ git blame 442 | 443 | 跟踪问题时候,会查看日志,更方便历史代码定位: 444 | 445 | :: 446 | 447 | $ git log 448 | 449 | 觉得完整的 Git 命令太长,想用类似 Subversion 的缩写命令,可以用 alias,比如配\ 450 | 置文件中可以写上: 451 | 452 | :: 453 | 454 | 455 | [alias] 456 | br = branch 457 | ci = commit 458 | co = checkout 459 | diffs = diff --staged 460 | st = status 461 | 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 462 | 463 | 464 | 有时候合并或衍合代码,但本地有修改了一半的代码没有提交,可以先暂存起来: 465 | 466 | :: 467 | 468 | $ git stash 469 | # 合并或衍合完毕代码后 470 | $ git stash pop # 恢复刚才修改了一半的代码 471 | 472 | 原来的一个项目想拆分多个项目,又想保留版本仓库记录,可以用下 git subtree split\ 473 | ,例如: 474 | 475 | :: 476 | 477 | $ git subtree split --prefix=plugins/sqli 478 | 479 | Git 不支持空文件夹加入版本控制,变通方式: 480 | 481 | :: 482 | 483 | $ mkdir downloads 484 | $ vim downloads/.gitignore # 增加 * 和 !.gitignore 这两条规则 485 | 486 | 永远别忘了 help 487 | --------------- 488 | 489 | 对于习惯命令行下编程的程序员来说,多看帮助总是好的,直接执行 490 | 491 | :: 492 | 493 | $ git help 494 | 495 | 可以看到 Git 的常用命令,如果想看到更全的 Git 命令,可以执行 496 | 497 | :: 498 | 499 | $ git help -a 500 | 501 | 单独查看某个命令的帮助,可以执行 502 | 503 | :: 504 | 505 | $ git help add # 比如 add 命令 506 | 507 | 会发现更多的命令,这个相比 Subversion 的命令更多,所以看起来也更复杂些,不过\ 508 | Git 本身也比 Subversion 更灵活、更好,比如:分支的使用、历史提交修改等。 509 | 510 | 511 | 好习惯 512 | ------ 513 | 514 | 这里顺带说下几个使用 Git 的好习惯,但有的其实跟 Git 联系也不算大,只是顺带提下\ 515 | : 516 | 517 | * 保持工作目录干净。或者说工作目录中的代码变更就为了完成一个任务,即一次只做一\ 518 | 件事。完成任务后,就直接 ``git commit`` 提交到本地版本仓库的某个分支中,而不\ 519 | 用担心其它任务作出的代码变更无提交。并且,对于分支切换更方便,而不用担心代码\ 520 | 被覆盖或冲突的问题。 521 | * Git 的日志信息足够有效。足够有效的意思,是说这次提交作出的变更摘要,只要别人\ 522 | 阅读了日志就能知道大概,如果为了深入了解变更细节才会去查看具体代码变更。 523 | * ``git commit`` 前 code review。code review 本身就是个好习惯,提交前确认是一\ 524 | 种更为严谨的方式,如果觉得自己 code review 发现不了什么问题,那么随便从身边\ 525 | 抓个会代码的,跟别人讲解下代码变更的内容,说不定会发现你没考虑到的问题。 526 | * ``git commit`` 前跑单元测试。写单元测试本身也是个不错的习惯,如果项目本身已\ 527 | 经有了完备的单元测试覆盖了,那么你对代码的修改,应该能通过单元测试,所以提交\ 528 | 前执行一遍是否通过。如果没通过,就得看下是功能代码写的有问题,还是测试代码有\ 529 | 问题,比如:功能需求或接口设计有变化,而测试代码没有同步更新。 530 | * 有代码变更及时提交。有 Git 这种版本控制工具,本身就是为了记录研发过程,避免\ 531 | 意外导致代码丢失,如果为了完成某个任务需要很长时间,代码也很久没有提交,风险\ 532 | 太高。这个时候,一般会自己开个分支,而将代码提交到分支中,既解决代码要及时提\ 533 | 交的问题,又解决代码提交频繁,可能造成代码不稳定影响别人的问题,因为那个分支\ 534 | 只有你自己在工作。而这一点,Git 分支的功能更为强大,更加鼓励多开分支。 535 | 536 | 最后 537 | ---- 538 | 539 | 这些场景覆盖的 Git 命令其实很有限,如果要完整的熟悉,那就 git help 以及阅读下\ 540 | `《Git Pro》 `_\ 这本官方推荐的入门书,有个系统的\ 541 | 学习,基础才会更加牢固。 542 | 543 | 后续 544 | ---- 545 | 546 | 另外,这里只是以程序员的场景来简单介绍 Git 使用,对于系统管理员,可能有一部分\ 547 | 职责是作为 Git 版本仓库管理员,日常也会遇到的各种场景吧,后续也会简单介绍。 548 | 549 | 参考 550 | ---- 551 | 552 | * http://git-scm.com/book/zh 553 | * http://source.android.com/source/developing.html 554 | * http://www.oschina.net/translate/a-successful-git-branching-model 555 | -------------------------------------------------------------------------------- /docs/vcs/index.rst: -------------------------------------------------------------------------------- 1 | 版本控制系统 2 | ============ 3 | 4 | Version Control System 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | understanding 10 | svn/index 11 | git/index 12 | branch 13 | -------------------------------------------------------------------------------- /docs/vcs/svn/SVN_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/vcs/svn/SVN_normal.png -------------------------------------------------------------------------------- /docs/vcs/svn/SVN_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/vcs/svn/SVN_small.png -------------------------------------------------------------------------------- /docs/vcs/svn/SVN_strict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/vcs/svn/SVN_strict.png -------------------------------------------------------------------------------- /docs/vcs/svn/SVN_tree_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/docs/vcs/svn/SVN_tree_example.png -------------------------------------------------------------------------------- /docs/vcs/svn/index.rst: -------------------------------------------------------------------------------- 1 | 以SVN为例 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | usage 8 | usage_admin 9 | management 10 | -------------------------------------------------------------------------------- /docs/vcs/svn/management.rst: -------------------------------------------------------------------------------- 1 | 源代码的管理和发布:以SVN为例 2 | ============================= 3 | 4 | 前几天在微博吐槽了SVN的几个不爽的地方:.svn文件满天飞、分支管理的麻烦。不爽一般来说都是有过对比后才有如此感觉,比如:相比于Git;或者你对该事物了解不够,比如:.svn的问题,其实早在1.7版本中已经解决了。 5 | 6 | 至于分支管理,如果把控好,再加上项目本身代码质量足够高,其实也没那么麻烦。如果觉得麻烦,可能很大程度上,是项目的代码本身就很糟糕,造成管理上的麻烦,比如:要修改个特性,估计需要一段时间,为了不影响主干代码稳定,需要在分支中开发,到合并代码的时候就各种麻烦,但觉得麻烦其实很多时候是因为代码耦合度太高了。 7 | 8 | 下面有关SVN代码的管理和发布,其描述前提是团队成员往SVN贡献的代码,质量都是足够高的,并且前提是开发商业软件,会有钱、时间、质量这坑爹3角因素的影响,而不同于开源项目模式。 9 | 10 | 目录布局回顾 11 | ------------ 12 | 13 | 先简单回顾下SVN的目录布局吧。 14 | 15 | 项目在版本仓库中的目录 16 | ~~~~~~~~~~~~~~~~~~~~~~ 17 | 18 | trunk、branches、tags,这是一个项目在版本仓库中典型的目录布局。 19 | 20 | * trunk:主干,如果说把一个软件项目从开始到消亡比作一个故事的话,主线情节都在这里被SVN记录着。 21 | * branches:分支,有很多种用法,比如:版本发布维护分支、新特性开发分支,甚至是缺陷修复分支等等。 22 | * tags:标签,或者叫快照,某个版本发布时候,都在这里留档。 23 | 24 | 示例如下图: 25 | 26 | .. image:: SVN_tree_example.png 27 | 28 | 不同版本仓库 29 | ~~~~~~~~~~~~ 30 | 31 | 对于一个比较大的项目,可能会拆分为多个子项目,各个子项目团队,按照约定各自进行开发;或者一个公司有很多项目,每个项目团队都各自进行开发。无论哪种情况,这个时候版本仓库的建立有2种选择,公用和独立,其实对于使用SVN客户端的程序员来说没太大区别,唯一不同的是前者是公用一套SVN版本号,而后者是独立使用各自的SVN版本号的。而在这个问题上,SVN又不同于Git,Git的所谓版本号就可以认为全球都唯一,甚至全宇宙唯一?哪怕不同公司的版本仓库。 32 | 33 | 其实上面说的版本仓库的管理,理解上就可以认为是目录管理,再加上时间这个维度。下面以可能出现的三种情况作为例子,来说明SVN代码的管理和发布。 34 | 35 | 适合小团队情况 36 | -------------- 37 | 38 | 小团队,不同人的理解可能不同吧,这里就定量一下规模,索性特指2-4人水平都一样的小团队吧。在人员较少的情况下,如何来控制trunk、branches、tags这3者的关系?其实最简单的方式是大家都在trunk上贡献代码,觉得某个版本差不多了就tags一下,相当于和branches说拜拜了。 39 | 40 | 但这里其实会有几个问题: 41 | 42 | * 要开发某个特性,会花费挺长时间,这段时间不提交代码不合适,提交了又会影响trunk代码稳定性,怎么办? 43 | * 觉得差不多了是什么概念?能保证发布了某个版本测试没问题?保守点考虑,还是认为会有各种问题,需要严格测试,定量了,如果发现问题还得进行缺陷修复吧。 44 | * 这个时候,另一个问题出现了,发布这个版本的那段时间,团队中的1-2个人完成本次版本的开发了,又想进行下个版本的开发工作,岂不是得停滞了,因为他们也不知道当前发布的版本有没有问题。 45 | 46 | 针对上述几个问题,可以尝试下下图的模式,但需要配合几个其它实践,保证迭代质量。 47 | 48 | .. image:: SVN_small.png 49 | 50 | 上图的意思,还是2-4人的小团队,集中战力在trunk进行新版本研发+旧版本维护,只不过区别就是,到了上面说的觉得差不多了,新的大版本或旧版本的某个小版本代码稳定的时候,分出去一个发布分支,由1-2个人严格配合测试,在该分支上工作,有需要修复的缺陷,有人在trunk上修复了,可以直接把该修复merge到发布分支上。 51 | 52 | 这里约定,新版本主要进行新特性的发布;旧版本进行旧特性的问题修复。但这里有个疑问,新特性的代码和旧特性的代码都在一起,那发布旧版本问题修复版本时候,新特性的代码咋办?这里就得用到其中1个实践,叫做“特性开关”,虽然新特性的代码已经提交到trunk,但发布旧版本的问题修复版本时候,该特性不会开放出来。对于用户来说,这个还是个旧版本的缺陷修复版本。 53 | 54 | 新特性的研发,必然伴随着代码的不稳定,代码质量的下降,这个时候就得用到另外2个实践,“trunk上每次代码提交进行codereview”和“每次代码提交的持续集成”,说白了就是每次代码提交都要有充分的反馈,如果团队的codereview习惯足够好,以及项目本身有了一套成熟的持续集成的话,就能一定程度上避免这个问题,如果发现有问题,立马反馈,及时修正。 55 | 56 | 还有最后一个疑问,都在trunk中,怎么保证新特性的代码和旧特性的代码管理起来足够分离?这个就得考验团队的设计能力时候了,是否能让各个功能模块间的耦合度足够低,从而把影响降低,说白了还得程序员足够厉害。当然这里说的“特性开关”不要太多,2-4人的小团队,2个大的特性开关已经很复杂了。如果这种开关很多咋办?这个时候其实不是你们团队从技术层面或管理层面需要解决的问题了,很有可能是当前的功能必须要更多人维护开发,就是需要扩充团队了;或者,根本不需要这么多功能特性,直接砍功能吧。 57 | 58 | 下面讲一个理想模型,比如项目资金流能撑得起更多的团队人员的情况下,如何更高效的研发。 59 | 60 | 比较流行的、推荐的 61 | ------------------ 62 | 63 | 直接看图,跟上图很类似,区别就是,新特性研发trunk和旧特性维护branches分开来了,并且由2个小组分别负责: 64 | 65 | .. image:: SVN_normal.png 66 | 67 | * 红色外框代表小组1,一开始是新特性开发小组,该特性发布后,就变成旧特性维护小组了。 68 | * 蓝色外框代表小组2,一开始可能是旧特性维护小组,另一个小组进行维护他们开发的特性的时候,这个小组角色就变成新特性开发小组了。 69 | * 当然两组是实力相当的小组,他们来回切换新特性开发和旧特性维护。 70 | * 并且能保障新版本研发和旧版本维护可以同时进行。 71 | 72 | 为了更好说明,直接引用一段SVN官方文档的故事描述 73 | 74 | :: 75 | 76 | http://svndoc.iusesvn.com/svnbook/1.4/svn.branchmerge.commonuses.html#svn.branchmerge.commonuses.patterns 77 | 78 | 发布分支 79 | 80 | 大多数软件存在这样一个生命周期:编码、测试、发布,然后重复。这样有两个问题,第一,开发者需要在质量保证小组测试假定稳定版本时继续开发新特性,新工作在软件测试时不可以中断,第二,小组必须一直支持老的发布版本和软件;如果一个bug在最新的代码中发现,它一定也存在已发布的版本中,客户希望立刻得到错误修正而不必等到新版本发布。 81 | 82 | 这是版本控制可以做的帮助,典型的过程如下: 83 | 84 | * 开发者提交所有的新特性到主干。 每日的修改提交到/trunk:新特性,bug修正和其他。 85 | * 这个主干被拷贝到“发布”分支。 当小组认为软件已经做好发布的准备(如,版本1.0)然后/trunk会被拷贝到/branches/1.0。 86 | * 项目组继续并行工作,一个小组开始对分支进行严酷的测试,同时另一个小组在/trunk继续新的工作(如,准备2.0),如果一个bug在任何一个位置被发现,错误修正需要来回运送。然而这个过程有时候也会结束,例如分支已经为发布前的最终测试“停滞”了。 87 | * 分支已经作了标签并且发布,当测试结束,/branches/1.0作为引用快照已经拷贝到/tags/1.0.0,这个标签被打包发布给客户。 88 | * 分支多次维护。当继续在/trunk上为版本2.0工作,bug修正继续从/trunk运送到/branches/1.0,如果积累了足够的bug修正,管理部门决定发布1.0.1版本:拷贝/branches/1.0到/tags/1.0.1,标签被打包发布。 89 | 90 | 整个过程随着软件的成熟不断重复:当2.0完成,一个新的2.0分支被创建,测试、打标签和最终发布,经过许多年,版本库结束了许多版本发布,进入了“维护”模式,许多标签代表了最终的发布版本。 91 | 92 | 你会发现其实很多开源项目就是这么干的,这种模式很通用,不仅限于使用SVN的情况下。 93 | 94 | 稳定的、干净的trunk 95 | ------------------- 96 | 97 | 再更进一步,如果项目规模很大,需要参与的人员较多,这个时候代码管理需要更为严格,需要对代码提交要求严格,保持trunk足够稳定、足够干净,可能就会有很多分支出现,比如常见的特性分支,甚至是修复一个缺陷,也会开一个缺陷分支,经过严格的codereview后再merge到trunk或某个维护分支,该实践会牺牲一定的灵活性,给团队成员带来繁琐的感觉,并且迭代起来会慢,建议谨慎使用,如果要用,得有足够理由。示意图如下: 98 | 99 | .. image:: SVN_strict.png 100 | 101 | 习惯上,如果项目很大,一般都会拆分为多个子项目,各个子项目的各个团队都分别按小团队作战,来避免上述情况,从而提高迭代速度。 102 | 103 | 或者从工具使用上,可能这个时候使用的是Git,就能更好的解决类似问题,这个在以后的文章中有机会单独说下。 104 | 105 | 参考 106 | ---- 107 | 108 | * http://svndoc.iusesvn.com/svnbook/1.4/ 109 | * http://subversion.apache.org/docs/release-notes/1.7.html 110 | 111 | 结束 112 | ---- 113 | 114 | 来个箴言作为结束吧: 115 | 116 | 童子军规则,“要让离开时的营地比进入时更加干净” 117 | 118 | 类比到写代码,“让模块签入(check in)的时候比签出(check out)的时候更整洁” 119 | 120 | 再装X些,“做人也是,你离开世界的时候能让世界变得更美好,哪怕一点也行!” 121 | -------------------------------------------------------------------------------- /docs/vcs/svn/usage.rst: -------------------------------------------------------------------------------- 1 | SVN命令用法:程序员的场景 2 | ========================= 3 | 4 | SVN有不少命令,其实常用的也就那么几个,可以结合下实际的使用场景,来说明下SVN的命令用法。 5 | 6 | 当然可能对很多人来说,最实用的熟悉方式,就是直接运行 7 | 8 | :: 9 | 10 | svn help (?, h) 11 | 12 | 就入门了,但为了更好的记忆,有个实际场景也是个不错的选择。 13 | 14 | .. note:: 15 | 16 | 括号中的是该命令的缩写或别名,有的可以少打几个字母,后面也有类似描述。 17 | 18 | “新人报道” 19 | ---------- 20 | 21 | 你刚入职一家公司,或新加入某个团队,立马参与到一个项目中,项目代号Norther,那么就得获取项目代码,开始你的项目生涯。这个时候一般你需要签出项目代码: 22 | 23 | :: 24 | 25 | svn checkout (co) https://scms.ship.it/svn/norther/trunk norther 26 | 27 | 确认工作目录的SVN信息,说明已经纳入版本控制了: 28 | 29 | :: 30 | 31 | cd ~/projects/norther 32 | svn info 33 | 34 | 确认没问题了,就运行项目中的构建脚本,然后就可以熟悉代码,展开具体工作了。 35 | 36 | 当然,有的时候,有一个新项目是由你发起的,你要将初始化的项目工程放到SVN版本仓库中: 37 | 38 | :: 39 | 40 | svn import souther https://scms.ship.it/svn 41 | 42 | 确认项目已经在版本仓库中了: 43 | 44 | :: 45 | 46 | svn list (ls) https://scms.ship.it/svn/souther/trunk 47 | 48 | 应该就可以看到Souther项目的根目录结构了。 49 | 50 | 日常工作 51 | -------- 52 | 53 | 当你已经逐渐融入了一个项目,可能一天的工作场景或完成某个任务的工作周期是这样的: 54 | 55 | 更新 56 | ~~~~ 57 | 58 | 无论是清早或下午或晚上,开始了你的一天工作,你首先会更新你的工作目录: 59 | 60 | :: 61 | 62 | cd ~/projects/norther 63 | svn update (up) 64 | 65 | 这样你就可以在最新的项目代码基础上工作了。 66 | 67 | 修改 68 | ~~~~ 69 | 70 | 可能你写了一个新的模块,需要纳入项目的版本控制: 71 | 72 | :: 73 | 74 | svn add tools.py 75 | 76 | 可能你发现某个模块已经陈旧了,不再使用了: 77 | 78 | :: 79 | 80 | svn delete (del, remove, rm) utils.py 81 | 82 | 可能你发现一个模块的命名不太合理,需要改名: 83 | 84 | :: 85 | 86 | svn move (mv) model.py models.py 87 | 88 | 可能你要创建一个新的较大的模块,需要归档为目录的方式: 89 | 90 | :: 91 | 92 | svn mkdir groups 93 | 94 | 可能你发现要写的模块代码布局类似于旧的模块,直接复制个代码模版: 95 | 96 | :: 97 | 98 | svn copy (cp) users/tests.py groups/tests.py 99 | 100 | 当然,其实最常见的情况其实还是打开编辑器,比如Vim,修改已经存在的代码,这个就跟SVN命令无关了。 101 | 102 | 检查 103 | ~~~~ 104 | 105 | 忙碌的一天过去了,或者一个任务完成了,这个时候一般会将你的工作成果,也就是代码更新到版本仓库。 106 | 107 | 习惯上会先检查下修改状态: 108 | 109 | :: 110 | 111 | svn status (stat, st) 112 | 113 | 看到一些SVN状态位信息,确认是修改了哪些文件,之后一般会自己code review一下代码的改动,可能有的人会习惯直接用SVN方式来查看: 114 | 115 | :: 116 | 117 | svn diff (di) 118 | 119 | 然后本地运行下相关的单元测试,确认是否有问题。一般来说这个时候,没有什么特殊情况,就直接进入“提交”阶段了,然后结束一个工作日或工作周期,但难免会有些特殊情况出现。 120 | 121 | 取消修改 122 | ~~~~~~~~ 123 | 124 | 当你code review完后,发现有些改动不满意,你可能又会取消这些修改: 125 | 126 | :: 127 | 128 | svn revert main.py 129 | 130 | 解决冲突 131 | ~~~~~~~~ 132 | 133 | 当你打算提交时候,习惯上一般会再次更新自己的工作目录,现在合并下别人的工作成果(如果有的话): 134 | 135 | :: 136 | 137 | svn update (up) 138 | 139 | * 可能这个时候更新完代码,你对某个模块的代码有改动,别人也改动了同一个模块的代码,可能就会产生代码冲突。 140 | * 也可能有的人没这习惯,就直接提交代码,发现提交没有成功,一看,原来是别人提交的代码刚好也改动了你提交的代码,也产生了冲突。 141 | 142 | 无论哪种情况,就是代码冲突了,需要解决冲突,一般会人工确认代码合并,处理冲突的代码,是选择别人的处理,还是自己的处理,还是要额外处理,处理完毕后,执行命令,比如: 143 | 144 | :: 145 | 146 | svn resolve main.py --accept working 147 | 148 | 另外,也有个resolved命令,用来删除“冲突”状态,但官方说被上面命令替换了,不推荐使用了: 149 | 150 | :: 151 | 152 | svn resolved main.py 153 | 154 | 提交 155 | ~~~~ 156 | 157 | 最后,一切确认没问题了:code review完毕,自己觉得代码满意了;然后也合并完别人的修改并且没有冲突了;运行单元测试也通过了。那么就提交代码吧: 158 | 159 | :: 160 | 161 | svn commit (ci) 162 | 163 | 在分支工作 164 | ---------- 165 | 166 | 在\ :doc:`management`\ 这篇文章中,介绍的SVN开发模式中,涉及分支的概念,一般来说会有以下3种情况: 167 | 168 | * 贡献新特性。也就是说,为了增加新的功能,或者对旧功能的改进等等。 169 | * “除虫”。就是日常说的缺陷修复。 170 | * 发布阶段(发布分支)->旧版本维护(旧版本维护分支)。这个概念稍微复杂,trunk研发到某个阶段,代码符合某个版本发布条件了,就会新建1个发布分支,测试没问题了,就在这个分支上进行发布;发布完成后,这个版本的维护就在这个维护分支上进行了;这个时候trunk已经进行最新版本的研发了,所以说这个分支是个旧版本维护分支。 171 | 172 | 上述说的3种分支情况,前两个分支的生命周期比较短,新特性搞定或“除虫”完毕,合并代码到trunk后就结束自己的生命周期了。 173 | 174 | 最后一种情况,生命周期相对较长,如果这个分支需要维护的版本还要支持,那么就得一直存在,直到不再维护为止。 175 | 176 | 下面说下在分支工作的实际场景,按顺序: 177 | 178 | 创建新分支 179 | ~~~~~~~~~~ 180 | 181 | 当上述3种场景发生,确定要新开个分支来写代码,先复制trunk到分支,这里以贡献新特性为例子: 182 | 183 | :: 184 | 185 | svn copy (cp) https://scms.ship.it/svn/norther/trunk https://scms.ship.it/svn/norther/branches/feature1 186 | 187 | 切换到新分支 188 | ~~~~~~~~~~~~ 189 | 190 | 一般来说这个时候本地的工作目录是trunk,确定本地工作目录是干净的,为后续在分支工作,以及合并分支做好准备,避免可能的各种代码冲突或工作成果代码被覆盖等情况出现。 191 | 192 | 确认当前所在的SVN工作目录,比如,可能是在trunk的SVN路径: 193 | 194 | :: 195 | 196 | svn info 197 | 198 | 确认工作目录干净: 199 | 200 | :: 201 | 202 | svn status (st) 203 | 204 | 切换到刚才新创建的分支: 205 | 206 | :: 207 | 208 | svn switch (sw) https://scms.ship.it/svn/norther/branches/feature1 209 | 210 | 确认切换后的SVN工作目录,应该就是在刚才新创建的分支的SVN路径了: 211 | 212 | :: 213 | 214 | svn info 215 | 216 | 在新分支工作 217 | ~~~~~~~~~~~~ 218 | 219 | 类似,“日常工作”中的工作周期操作,这个时候,你就可以在新分支中进行大刀阔斧的工作了,直到分支中代码符合合并到trunk的的条件了。 220 | 221 | 合并分支到trunk 222 | ~~~~~~~~~~~~~~~ 223 | 224 | 在分支中工作一段时间后,确认相关的功能代码、测试代码、文档等都提交完毕了,单元测试通过,大家code review一致认为没问题,审核通过,最后该分支的持续集成(CI)完整build通过。这个时候,就可以进行合并到trunk的操作了。 225 | 226 | 确保下面操作是在工作目录的根目录下进行 227 | 228 | :: 229 | 230 | cd ~/projects/norther/ 231 | 232 | 确认分支工作目录干净,没有需要提交的代码了: 233 | 234 | :: 235 | 236 | svn status (st) 237 | 238 | 切换工作目录回trunk,如果由于代码变动大有冲突,就解决冲突,特别如果有目录变动很可能有目录冲突: 239 | 240 | :: 241 | 242 | svn switch (sw) https://scms.ship.it/svn/norther/trunk 243 | 244 | 确认切换后的SVN工作目录是trunk: 245 | 246 | :: 247 | 248 | svn info 249 | 250 | 先在本地合并分支的代码,合并过程可能会有代码冲突,解决冲突,合并会指定版本范围,一般都是分支建立时候的版本号到分支工作完毕时候最后一次提交的版本号: 251 | 252 | :: 253 | 254 | svn merge -r9527:9549 https://scms.ship.it/svn/norther/branches/feature1 . 255 | 256 | 确认本地代码变更,code review一下,执行下单元测试: 257 | 258 | :: 259 | 260 | svn status (st) 261 | svn diff (di) 262 | 263 | 确认代码没问题,正式提交代码到trunk,SVN的提交日志说明下合并信息: 264 | 265 | :: 266 | 267 | svn commit (ci) 268 | 269 | 删除分支 270 | ~~~~~~~~ 271 | 272 | 如果确认工作完毕的分支不再需要了,那就记得及时清理掉: 273 | 274 | :: 275 | 276 | svn delete (del, remove, rm) https://scms.ship.it/svn/norther/branches/feature1 277 | 278 | Ship it 279 | ------- 280 | 281 | 在上面说的发布分支工作一段时间后,并且测试完毕,大家觉得符合发布条件了。终于可以进入到版本发布阶段的工作了。 282 | 283 | 具体故事场景可以看\ :doc:`management`\ 这篇文章,有对“发布分支”的介绍。 284 | 285 | 一般来说这个时候已经将trunk复制一份到了发布分支了: 286 | 287 | :: 288 | 289 | svn copy (cp) https://scms.ship.it/svn/norther/trunk https://scms.ship.it/branches/1.0.x 290 | 291 | 打标签 292 | ~~~~~~ 293 | 294 | 复制最新的发布分支为标签: 295 | 296 | :: 297 | 298 | svn copy (cp) https://scms.ship.it/svn/norther/branches/1.0.x https://scms.ship.it/svn/norther/tags/1.0.0 299 | 300 | 正式发布 301 | ~~~~~~~~ 302 | 303 | 发布又是一个比较复杂的主题,比如:能快速发布、快速回滚(包括数据回滚)、灰度发布等等,在\ :doc:`../../construction/index`\ 中会详细进行介绍,这里就简单罗列下。 304 | 305 | 情况1:完整包。导出代码,然后执行打包命令,进行完整安装: 306 | 307 | :: 308 | 309 | svn export https://scms.ship.it/svn/norther/tags/1.0.0 norther 310 | 311 | 情况2:补丁升级包。相对复杂,可能会综合运用下列命令,制作补丁安装升级包: 312 | 313 | :: 314 | 315 | svn status (st) 316 | svn diff (di) 317 | svn patch 318 | 319 | 情况3:线上更新。一般不太推荐,需要注意不要泄露“.svn”,特别是旧版本的SVN,每个目录下都有“.svn”。可能会用到下列命令: 320 | 321 | :: 322 | 323 | svn update (up) 324 | svn switch (sw) https://scms.ship.it/svn/norther/tags/1.0.0 325 | 326 | 一般来说,根据实际情况,可以记录下来发布相关的操作过程。很多环节可以写脚本将原来的人工操作改成自动化操作。以后只要执行发布脚本执行一键发布就可以了。 327 | 328 | 其它场景 329 | -------- 330 | 331 | 可能还有很多别的场景,比较零散,但也算经常用到。 332 | 333 | code review查看代码,要知道对应代码是由谁写的,好询问了解具体代码的思路: 334 | 335 | :: 336 | 337 | svn blame (praise, annotate, ann) 338 | 339 | 跟踪问题时候,会查看日志,更方便历史代码定位: 340 | 341 | :: 342 | 343 | svn log 344 | 345 | 经常碰到代码锁定或很多诡异情况: 346 | 347 | :: 348 | 349 | svn cleanup 350 | 351 | 编辑特定属性,比如:定义忽略规则;依赖其它SVN路径等等 352 | 353 | :: 354 | 355 | svn propedit (pedit, pe) svn:ignore . 356 | svn propedit (pedit, pe) svn:externals . 357 | 358 | SVN客户端更新,使用新的SVN客户端了,有时候会发现本地工作目录SVN相关信息陈旧,会提示你升级: 359 | 360 | :: 361 | 362 | svn upgrade 363 | 364 | 好习惯 365 | ------ 366 | 367 | 这里顺带说下几个使用SVN的好习惯,但有的其实跟SVN联系也不算大,只是顺带提下: 368 | 369 | * 保持工作目录干净。或者说工作目录中的代码变更就为了完成一个任务,即一次只做一件事。完成任务后,就直接svn commit (ci)提交到版本仓库,而不用担心其它任务作出的代码变更无提交。并且,对于分支和trunk间切换更方便,而不用担心代码被覆盖或冲突的问题。 370 | * SVN的日志信息足够有效。足够有效的意思,是说这次提交作出的变更摘要,只要别人阅读了日志就能知道大概,如果为了深入了解变更细节才会去查看具体代码变更。 371 | * svn commit (ci)前先svn update (up)。可能不更新提交也没问题,但也有可能会相关代码被别人改动了,而提交不了,为了避免这种情况,先本地更新完毕,确保别人的改动不影响你对代码修改的意图。 372 | * svn commit (ci)前code review。code review本身就是个好习惯,提交前确认是一种更为严谨的方式,如果觉得自己code review发现不了什么问题,那么随便从身边抓个会代码的,跟别人讲解下代码变更的内容,说不定会发现你没考虑到的问题。 373 | * svn commit (ci)前跑单元测试。写单元测试本身也是个不错的习惯,如果项目本身已经有了完备的单元测试覆盖了,那么你对代码的修改,应该能通过单元测试,所以提交前执行一遍是否通过。如果没通过,就得看下是功能代码写的有问题,还是测试代码有问题,比如:功能需求或接口设计有变化,而测试代码没有同步更新。 374 | * 有代码变更及时提交。有SVN这种版本控制工具,本身就是为了记录研发过程,避免意外导致代码丢失,如果为了完成某个任务需要很长时间,代码也很久没有提交,风险太高。这个时候,一般会自己开个分支,而将代码提交到分支中,既解决代码要及时提交的问题,又解决代码提交频繁,可能造成代码不稳定影响别人的问题,因为那个分支只有你自己在工作。 375 | 376 | 最后 377 | ---- 378 | 379 | 这些场景覆盖的SVN命令其实很有限,如果要完整的熟悉,那就svn help以及阅读下SVN的官方手册,有个系统的学习,基础才会更加牢固。 380 | 381 | 后续 382 | ---- 383 | 384 | 另外,这里只是以程序员的场景来简单介绍SVN使用,对于系统管理员,可能有一部分职责是作为SVN版本仓库管理员,日常也会遇到的各种场景吧,后续也会简单介绍。 385 | 386 | 参考 387 | ---- 388 | 389 | * http://www.subversion.org.cn/svnbook/1.4/ 390 | -------------------------------------------------------------------------------- /docs/vcs/svn/usage_admin.rst: -------------------------------------------------------------------------------- 1 | SVN命令用法:管理员的场景 2 | ========================= 3 | -------------------------------------------------------------------------------- /docs/vcs/understanding.rst: -------------------------------------------------------------------------------- 1 | 如何理解版本控制系统 2 | ==================== 3 | 4 | 版本控制系统在当今的软件开发中,被认为是理所当然的配备工具之一。应该现在很少有人对于“版本控制系统”的好处的理解还停留在所谓的代码备份那么简单吧。这里说下个人对版本控制系统的理解。 5 | 6 | 版本控制系统的历史 7 | ------------------ 8 | 9 | 版本控制系统作为软件开发中的一个必备工具,回顾下这个工具的发展历史,也有助于理解这个工具都解决了软件开发中的什么问题。 10 | 11 | 版本控制的历史最早可以追朔到20世纪60年代\ [#f1]_\ ,总结来说就是: 12 | 13 | * 借助人工来进行版本控制 14 | * 本地版本控制系统 15 | * 集中式的版本控制系统 16 | * 分布式的版本呢控制系统 17 | 18 | 这里顺带提下,版本控制系统提供的3个能力\ [#f2]_\ :可逆、并行、注解。也就是可以回到任何一个软件版本、大家可以进行协作开发、可以详细记录代码的变更情况。 19 | 20 | 使用版本控制系统 21 | ---------------- 22 | 23 | 从版本控制系统的发展历史可以看出,版本控制的重要性,远远不是单纯用于备份那么简单。还包括: 24 | 25 | 尽可能版本化所有的东西 26 | ~~~~~~~~~~~~~~~~~~~~~~ 27 | 28 | 不要仅仅将项目的源代码纳入到版本控制下,也应该包括网页、文档、FAQ、设计注释和任何人们希望编辑的内容\ [#f3]_\ 。 29 | 30 | 但这个可能又会被滥用,所以要特别说明: 31 | 32 | * 一般是将会变化的纳入版本控制,比如:源码、文档等。 33 | * 而不会变化的就进行归档即可,而非版本化,比如:邮件。 34 | * 由于当前版本控制系统对二进制文件的变更展现支持不是太好,所以有些文档建议用纯文本方式管理,比如:reStructuredText、MarkDown。 35 | * 生成的文件就不要纳入版本控制了,比如:源码编译出来的二进制文件。 36 | 37 | 尽可能方便团队协作 38 | ~~~~~~~~~~~~~~~~~~ 39 | 40 | * 在CVS流行前,还只能对单个文件进行版本控制,后来的Subversion的流行,又是对CVS的进化。 41 | * 善用分支,避免开发过程中的瓶颈,比如:Git等分布式版本控制系统的流行,对分支的处理更加自如。 42 | * 项目的版本库应该能够通过Web浏览。这不仅是意味着浏览最新修订的能力,也包括回到过去查看早先的版本,查看修订之间的区别,以及阅读针对特定变更的日志信息等等\ [#f3]_\ 。很多版本控制系统都有周边的软件支持,比如:Trac、GitHub、GitLab等。 43 | * 让大家都知晓版本变化的细节。 44 | * 有一定的权限或授权控制。 45 | 46 | 尽可能详细记录历史 47 | ~~~~~~~~~~~~~~~~~~ 48 | 49 | * 可以知道哪个版本的哪个文件的哪行代码的原作者是谁。 50 | * 谁在什么时间进行哪些改动。 51 | * 按一定约定规范记录变更日志。上面2条,可以由版本控制系统本身来处理,这一条就得看大家项目内部的规范执行力了。 52 | 53 | 尽可能多的记录软件开发中产生的常见行为 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | 是第一条,“尽可能版本化所有的东西”的扩展,也就是广义上的配置管理,包括: 57 | 58 | * 需求分析、构架设计行为。常见的就转化为对应文档 59 | * 开发行为。就是大多数程序员喜欢编写的,常见的编写项目代码、产品代码、功能代码。并且,最终用户/客户日常用的软件或服务,也是运行这些代码。 60 | * 测试行为。随着TDD等开发哲学的流行,逐渐被重视。常见的就转化为对应的单元测试代码、功能测试代码(为了将人工测试行为代码自动化,这样这个行为也被记录到版本控制中了) 61 | * 配置/部署行为。所谓DevOps的复古的开发哲学流行,也慢慢被接受。常见的就转化为配置文件、部署脚本、运维脚本(更重视配置文件,推行约定大于配置,行为脚本化、工具化、自动化,这样这个行为也被记录到版本控制中了) 62 | 63 | 参考 64 | ---- 65 | 66 | .. rubric:: 注解 67 | 68 | .. [#f1] http://blog.jobbole.com/14489/ 69 | .. [#f2] http://www.catb.org/~esr/writings/version-control/version-control.htm 70 | .. [#f3] http://producingoss.com/zh/vc.html 71 | -------------------------------------------------------------------------------- /pm/onepiece/nose.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | nocapture=1 3 | with-coverage=1 4 | cover-package=onepiece 5 | -------------------------------------------------------------------------------- /pm/onepiece/onepiece/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | __version__ = '0.1.0' 6 | -------------------------------------------------------------------------------- /pm/onepiece/onepiece/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | def hello_world(): 6 | return 'One Piece' 7 | -------------------------------------------------------------------------------- /pm/onepiece/onepiece/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | from __future__ import print_function, unicode_literals 6 | 7 | from future import standard_library 8 | standard_library.install_aliases() 9 | 10 | import urllib.request 11 | 12 | 13 | def do_print_example(): 14 | text = '海贼王(One Piece)' 15 | print(text) 16 | return text 17 | 18 | 19 | def do_numliterals_example(): 20 | number = 0o755 21 | return number 22 | 23 | 24 | def do_except_example(): 25 | try: 26 | number = 1 / 0 27 | except ZeroDivisionError as ex: 28 | number = None 29 | return number 30 | 31 | 32 | def do_raise_example(): 33 | raise Exception('I am Exception') 34 | 35 | 36 | def do_urllib_example(): 37 | response = urllib.request.urlopen('http://www.google.com') 38 | return response 39 | -------------------------------------------------------------------------------- /pm/onepiece/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | from setuptools import find_packages, setup 6 | 7 | from onepiece import __version__ 8 | 9 | 10 | setup( 11 | name='onepiece', 12 | version=__version__, 13 | description='One Piece', 14 | author='akun', 15 | author_email='6awkun@gmail.com', 16 | license='MIT License', 17 | url='https://github.com/akun/pm/tree/master/pm/onepiece', 18 | packages=find_packages(), 19 | include_package_data=True, 20 | install_requires=[ 21 | 'future>=0.16.0', 22 | ], 23 | extras_require={ 24 | 'test': [ 25 | 'coverage>=4.5', 26 | 'httpretty>=0.8.14', 27 | 'nose>=1.3.7', 28 | ], 29 | }, 30 | test_suite='nose.collector', 31 | classifiers=[ 32 | 'Programming Language :: Python', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'Programming Language :: Python :: 3.5', 38 | 'Programming Language :: Python :: 3.6', 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /pm/onepiece/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | -------------------------------------------------------------------------------- /pm/onepiece/tests/test_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | from unittest import TestCase 6 | 7 | from onepiece.example import hello_world 8 | 9 | 10 | class HelloWordTestCase(TestCase): 11 | 12 | def test_hello_world(self): 13 | self.assertEqual('One Piece', hello_world()) 14 | -------------------------------------------------------------------------------- /pm/onepiece/tests/test_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | from __future__ import unicode_literals 6 | import unittest 7 | 8 | import httpretty 9 | 10 | from onepiece import main 11 | 12 | 13 | class MainTestCase(unittest.TestCase): 14 | 15 | def test_do_print_example(self): 16 | text = main.do_print_example() 17 | self.assertEqual(text, '海贼王(One Piece)') 18 | 19 | def test_do_numliterals_example(self): 20 | number = main.do_numliterals_example() 21 | self.assertEqual(number, 0o755) 22 | 23 | def test_do_except_example(self): 24 | number = main.do_except_example() 25 | self.assertIsNone(number) 26 | 27 | def test_do_raise_example(self): 28 | with self.assertRaises(Exception) as cm: 29 | main.do_raise_example() 30 | ex = cm.exception 31 | self.assertEqual(ex.args[0], 'I am Exception') 32 | 33 | @httpretty.activate 34 | def test_do_urllib_example(self): 35 | httpretty.register_uri( 36 | httpretty.GET, 'http://www.google.com', body='Fake Google', 37 | status=200) 38 | 39 | response = main.do_urllib_example() 40 | self.assertEqual(response.code, 200) 41 | -------------------------------------------------------------------------------- /pm/onepiece/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py3 3 | 4 | [testenv] 5 | deps = 6 | coverage 7 | httpretty 8 | nose 9 | commands = nosetests -c nose.cfg 10 | -------------------------------------------------------------------------------- /pm/slide/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/slide.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/slide.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/slide" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/slide" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | 179 | slides: 180 | $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides 181 | @echo 182 | @echo "Build finished. The slides are in $(BUILDDIR)/slides." 183 | -------------------------------------------------------------------------------- /pm/slide/_static/tracsphinx.css: -------------------------------------------------------------------------------- 1 | /* Trac specific styling */ 2 | 3 | @import url("sphinxdoc.css"); 4 | 5 | /* Structure */ 6 | 7 | div.footer { 8 | background-color: #4b4d4d; 9 | text-align: center; 10 | } 11 | 12 | div.bodywrapper { 13 | border-right: none; 14 | } 15 | 16 | /* Sidebar */ 17 | 18 | div.sphinxsidebarwrapper { 19 | -moz-box-shadow: 2px 2px 7px 0 grey; 20 | -webkit-box-shadow: 2px 2px 7px 0 grey; 21 | box-shadow: 2px 2px 7px 0 grey; 22 | padding: 0 0 1px .4em; 23 | } 24 | 25 | div.sphinxsidebar h3 a, 26 | div.sphinxsidebar h4 a { 27 | color: #b00; 28 | } 29 | 30 | div.sphinxsidebar h3, 31 | div.sphinxsidebar h4 { 32 | padding: 0; 33 | color: black; 34 | } 35 | 36 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 37 | background: none; 38 | border: none; 39 | border-bottom: 1px solid #ddd; 40 | } 41 | 42 | div.sphinxsidebar input { 43 | border: 1px solid #d7d7d7; 44 | } 45 | 46 | p.searchtip { 47 | font-size: 90%; 48 | color: #999; 49 | } 50 | 51 | /* Navigation */ 52 | 53 | div.related ul li a { color: #b00 } 54 | div.related ul li a:hover { 55 | color: #b00; 56 | } 57 | 58 | /* Content */ 59 | 60 | body { 61 | font: normal 13px Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; 62 | background-color: #4b4d4d; 63 | border: none; 64 | border-top: 1px solid #aaa; 65 | } 66 | h1, h2, h3, h4 { 67 | font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; 68 | font-weight: bold; 69 | letter-spacing: -0.018em; 70 | page-break-after: avoid; 71 | } 72 | 73 | h1 { color: #555 } 74 | h2 { border-bottom: 1px solid #ddd } 75 | 76 | div.body a { text-decoration: none } 77 | a, a tt { color: #b00 } 78 | a:visited, a:visited tt { color: #800 } 79 | 80 | :link:hover, :visited:hover, 81 | a:link:hover tt, a:visited:hover tt { 82 | background-color: #eee; 83 | color: #555; 84 | } 85 | 86 | a.headerlink, a.headerlink:hover { 87 | color: #d7d7d7 !important; 88 | font-size: .8em; 89 | font-weight: normal; 90 | vertical-align: text-top; 91 | margin: 0; 92 | padding: .5em; 93 | } 94 | a.headerlink:hover { 95 | background: none; 96 | } 97 | 98 | div.body h1 a, div.body h2 a, div.body h3 a, 99 | div.body h4 a, div.body h5 a, div.body h6 a { 100 | color: #d7d7d7 !important; 101 | } 102 | 103 | dl.class { 104 | -moz-box-shadow: 1px 1px 6px 0 #888; 105 | -webkit-box-shadow: 1px 1px 6px 0 #888; 106 | box-shadow: 1px 1px 6px 0 #888; 107 | padding: .5em; 108 | } 109 | dl.function { 110 | margin-bottom: 24px; 111 | } 112 | 113 | dl.class > dt, dl.function > dt { 114 | border-bottom: 1px solid #ddd; 115 | } 116 | 117 | th.field-name { 118 | white-space: nowrap; 119 | font-size: 90%; 120 | color: #555; 121 | } 122 | 123 | td.field-body > ul { 124 | list-style-type: square; 125 | } 126 | 127 | td.field-body > ul > li > strong { 128 | font-weight: normal; 129 | font-style: italic; 130 | } 131 | 132 | /* Admonitions */ 133 | 134 | div.admonition p.admonition-title, div.warning p.admonition-title { 135 | background: none; 136 | color: #555; 137 | border: none; 138 | } 139 | 140 | div.admonition { 141 | background: none; 142 | border: none; 143 | border-left: 2px solid #acc; 144 | } 145 | 146 | div.warning { 147 | background: none; 148 | border: none; 149 | border-left: 3px solid #c33; 150 | } 151 | 152 | /* Search */ 153 | 154 | dl:target, dt:target, .highlighted { background-color: #ffa } 155 | 156 | -------------------------------------------------------------------------------- /pm/slide/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # slide documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jan 24 11:05:57 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = ['hieroglyph'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'slide' 47 | copyright = u'2014, akun' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '1.0.0' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '1.0.0' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = ['_build'] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all 73 | # documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # A list of ignored prefixes for module index sorting. 91 | #modindex_common_prefix = [] 92 | 93 | # If true, keep warnings as "system message" paragraphs in the built documents. 94 | #keep_warnings = False 95 | 96 | 97 | # -- Options for HTML output ---------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'default' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | #html_title = None 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # Add any extra paths that contain custom files (such as robots.txt or 133 | # .htaccess) here, relative to this directory. These files are copied 134 | # directly to the root of the documentation. 135 | #html_extra_path = [] 136 | 137 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 138 | # using the given strftime format. 139 | #html_last_updated_fmt = '%b %d, %Y' 140 | 141 | # If true, SmartyPants will be used to convert quotes and dashes to 142 | # typographically correct entities. 143 | #html_use_smartypants = True 144 | 145 | # Custom sidebar templates, maps document names to template names. 146 | #html_sidebars = {} 147 | 148 | # Additional templates that should be rendered to pages, maps page names to 149 | # template names. 150 | #html_additional_pages = {} 151 | 152 | # If false, no module index is generated. 153 | #html_domain_indices = True 154 | 155 | # If false, no index is generated. 156 | #html_use_index = True 157 | 158 | # If true, the index is split into individual pages for each letter. 159 | #html_split_index = False 160 | 161 | # If true, links to the reST sources are added to the pages. 162 | #html_show_sourcelink = True 163 | 164 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 165 | #html_show_sphinx = True 166 | 167 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 168 | #html_show_copyright = True 169 | 170 | # If true, an OpenSearch description file will be output, and all pages will 171 | # contain a tag referring to it. The value of this option must be the 172 | # base URL from which the finished HTML is served. 173 | #html_use_opensearch = '' 174 | 175 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 176 | #html_file_suffix = None 177 | 178 | # Output file base name for HTML help builder. 179 | htmlhelp_basename = 'slidedoc' 180 | 181 | 182 | # -- Options for LaTeX output --------------------------------------------- 183 | 184 | latex_elements = { 185 | # The paper size ('letterpaper' or 'a4paper'). 186 | #'papersize': 'letterpaper', 187 | 188 | # The font size ('10pt', '11pt' or '12pt'). 189 | #'pointsize': '10pt', 190 | 191 | # Additional stuff for the LaTeX preamble. 192 | #'preamble': '', 193 | } 194 | 195 | # Grouping the document tree into LaTeX files. List of tuples 196 | # (source start file, target name, title, 197 | # author, documentclass [howto, manual, or own class]). 198 | latex_documents = [ 199 | ('index', 'slide.tex', u'slide Documentation', 200 | u'akun', 'manual'), 201 | ] 202 | 203 | # The name of an image file (relative to this directory) to place at the top of 204 | # the title page. 205 | #latex_logo = None 206 | 207 | # For "manual" documents, if this is true, then toplevel headings are parts, 208 | # not chapters. 209 | #latex_use_parts = False 210 | 211 | # If true, show page references after internal links. 212 | #latex_show_pagerefs = False 213 | 214 | # If true, show URL addresses after external links. 215 | #latex_show_urls = False 216 | 217 | # Documents to append as an appendix to all manuals. 218 | #latex_appendices = [] 219 | 220 | # If false, no module index is generated. 221 | #latex_domain_indices = True 222 | 223 | 224 | # -- Options for manual page output --------------------------------------- 225 | 226 | # One entry per manual page. List of tuples 227 | # (source start file, name, description, authors, manual section). 228 | man_pages = [ 229 | ('index', 'slide', u'slide Documentation', 230 | [u'akun'], 1) 231 | ] 232 | 233 | # If true, show URL addresses after external links. 234 | #man_show_urls = False 235 | 236 | 237 | # -- Options for Texinfo output ------------------------------------------- 238 | 239 | # Grouping the document tree into Texinfo files. List of tuples 240 | # (source start file, target name, title, author, 241 | # dir menu entry, description, category) 242 | texinfo_documents = [ 243 | ('index', 'slide', u'slide Documentation', 244 | u'akun', 'slide', 'One line description of project.', 245 | 'Miscellaneous'), 246 | ] 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #texinfo_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #texinfo_domain_indices = True 253 | 254 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 255 | #texinfo_show_urls = 'footnote' 256 | 257 | # If true, do not generate a @detailmenu in the "Top" node's menu. 258 | #texinfo_no_detailmenu = False 259 | -------------------------------------------------------------------------------- /pm/slide/index.rst: -------------------------------------------------------------------------------- 1 | Sphinx写的Slides 2 | ================ 3 | 4 | akun 5 | 6 | Why 7 | === 8 | 9 | 10 | * yes 11 | * no 12 | * maybe 13 | 14 | How 15 | === 16 | 17 | * create 18 | 19 | * step1 20 | * step2 21 | 22 | * read 23 | * update 24 | * delte 25 | 26 | What 27 | ==== 28 | 29 | * apple 30 | * dog 31 | * cat 32 | 33 | Ref 34 | === 35 | 36 | * http://pm.readthedocs.org/ 37 | 38 | End 39 | === 40 | 41 | Thanks;-) 42 | -------------------------------------------------------------------------------- /pm/slide/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\slide.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\slide.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /pm/starwars.skywalker/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='starwars.skywalker', 7 | version='0.0.1', 8 | packages=find_packages(), 9 | namespace_packages=['starwars'] 10 | ) 11 | -------------------------------------------------------------------------------- /pm/starwars.skywalker/starwars/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /pm/starwars.skywalker/starwars/skywalker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.skywalker/starwars/skywalker/__init__.py -------------------------------------------------------------------------------- /pm/starwars.skywalker/starwars/skywalker/anakin.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.skywalker/starwars/skywalker/anakin.py -------------------------------------------------------------------------------- /pm/starwars.skywalker/starwars/skywalker/luke.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.skywalker/starwars/skywalker/luke.py -------------------------------------------------------------------------------- /pm/starwars.skywalker/starwars/skywalker/shmi.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.skywalker/starwars/skywalker/shmi.py -------------------------------------------------------------------------------- /pm/starwars.yoda/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name='starwars.yoda', 7 | version='0.0.1', 8 | packages=find_packages(), 9 | namespace_packages=['starwars'] 10 | ) 11 | -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/__init__.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/be.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/be.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/force.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/force.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/may.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/may.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/the.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/the.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/with.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/with.py -------------------------------------------------------------------------------- /pm/starwars.yoda/starwars/yoda/you.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/pm/starwars.yoda/starwars/yoda/you.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # docs 2 | Sphinx>=1.6.7 3 | hieroglyph>=1.0.0 4 | 5 | # pm example code 6 | future>=0.16.0 7 | 8 | # test 9 | coverage>=4.5 10 | httpretty>=0.8.14 11 | nose>=1.3.7 12 | -------------------------------------------------------------------------------- /slides/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Git.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Git.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Git" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Git" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | 179 | 180 | slides: 181 | $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides 182 | @echo "Build finished. The HTML slides are in $(BUILDDIR)/slides." 183 | 184 | -------------------------------------------------------------------------------- /slides/_static/auto_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/auto_test.png -------------------------------------------------------------------------------- /slides/_static/custom.css: -------------------------------------------------------------------------------- 1 | h1 {font-size: 48px;} 2 | article {font-size: 24px;} 3 | h2 {position: static;} 4 | strong {color: #F00;} 5 | table.docutils {font-size: 14px;font-family: 'Courier New';} 6 | pre {overflow: auto;} 7 | -------------------------------------------------------------------------------- /slides/_static/dict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/dict.png -------------------------------------------------------------------------------- /slides/_static/git_3_kingdom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/git_3_kingdom.png -------------------------------------------------------------------------------- /slides/_static/git_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/git_log.png -------------------------------------------------------------------------------- /slides/_static/git_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/git_simple.png -------------------------------------------------------------------------------- /slides/_static/github_blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/github_blog.png -------------------------------------------------------------------------------- /slides/_static/google_developers_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/google_developers_icon_128.png -------------------------------------------------------------------------------- /slides/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/logo.png -------------------------------------------------------------------------------- /slides/_static/rst_editor_qt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/rst_editor_qt1.png -------------------------------------------------------------------------------- /slides/_static/space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/space.png -------------------------------------------------------------------------------- /slides/_static/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/start.png -------------------------------------------------------------------------------- /slides/_static/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akun/pm/45616076014a284ed69d7ce9ee08797fc2fdcdb6/slides/_static/triangle.png -------------------------------------------------------------------------------- /slides/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import sys 5 | import os 6 | 7 | # If extensions (or modules to document with autodoc) are in another directory, 8 | # add these directories to sys.path here. If the directory is relative to the 9 | # documentation root, use os.path.abspath to make it absolute, like shown here. 10 | #sys.path.insert(0, os.path.abspath('.')) 11 | 12 | # -- General configuration ------------------------------------------------ 13 | 14 | # If your documentation needs a minimal Sphinx version, state it here. 15 | #needs_sphinx = '1.0' 16 | 17 | # Add any Sphinx extension module names here, as strings. They can be 18 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 19 | # ones. 20 | extensions = [ 21 | 'sphinx.ext.doctest', 22 | 'sphinx.ext.intersphinx', 23 | 'sphinx.ext.todo', 24 | 'sphinx.ext.coverage', 25 | 'sphinx.ext.pngmath', 26 | 'sphinx.ext.ifconfig', 27 | ] 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # The suffix of source filenames. 33 | source_suffix = '.rst' 34 | 35 | # The encoding of source files. 36 | #source_encoding = 'utf-8-sig' 37 | 38 | # The master toctree document. 39 | master_doc = 'index' 40 | 41 | # General information about the project. 42 | project = u'如何成功运作软件项目' 43 | copyright = u'2014, kun' 44 | 45 | # The version info for the project you're documenting, acts as replacement for 46 | # |version| and |release|, also used in various other places throughout the 47 | # built documents. 48 | # 49 | # The short X.Y version. 50 | version = '' 51 | # The full version, including alpha/beta/rc tags. 52 | release = '' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | #language = None 57 | 58 | # There are two options for replacing |today|: either, you set today to some 59 | # non-false value, then it is used: 60 | #today = '' 61 | # Else, today_fmt is used as the format for a strftime call. 62 | #today_fmt = '%B %d, %Y' 63 | 64 | # List of patterns, relative to source directory, that match files and 65 | # directories to ignore when looking for source files. 66 | exclude_patterns = ['_build'] 67 | 68 | # The reST default role (used for this markup: `text`) to use for all 69 | # documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | #keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output ---------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | html_title = u'如何成功运作软件项目' 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # Add any extra paths that contain custom files (such as robots.txt or 129 | # .htaccess) here, relative to this directory. These files are copied 130 | # directly to the root of the documentation. 131 | #html_extra_path = [] 132 | 133 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 134 | # using the given strftime format. 135 | #html_last_updated_fmt = '%b %d, %Y' 136 | 137 | # If true, SmartyPants will be used to convert quotes and dashes to 138 | # typographically correct entities. 139 | #html_use_smartypants = True 140 | 141 | # Custom sidebar templates, maps document names to template names. 142 | #html_sidebars = {} 143 | 144 | # Additional templates that should be rendered to pages, maps page names to 145 | # template names. 146 | #html_additional_pages = {} 147 | 148 | # If false, no module index is generated. 149 | #html_domain_indices = True 150 | 151 | # If false, no index is generated. 152 | #html_use_index = True 153 | 154 | # If true, the index is split into individual pages for each letter. 155 | #html_split_index = False 156 | 157 | # If true, links to the reST sources are added to the pages. 158 | #html_show_sourcelink = True 159 | 160 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 161 | #html_show_sphinx = True 162 | 163 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 164 | #html_show_copyright = True 165 | 166 | # If true, an OpenSearch description file will be output, and all pages will 167 | # contain a tag referring to it. The value of this option must be the 168 | # base URL from which the finished HTML is served. 169 | #html_use_opensearch = '' 170 | 171 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 172 | #html_file_suffix = None 173 | 174 | # Output file base name for HTML help builder. 175 | htmlhelp_basename = 'Gitdoc' 176 | 177 | 178 | # -- Options for LaTeX output --------------------------------------------- 179 | 180 | latex_elements = { 181 | # The paper size ('letterpaper' or 'a4paper'). 182 | #'papersize': 'letterpaper', 183 | 184 | # The font size ('10pt', '11pt' or '12pt'). 185 | #'pointsize': '10pt', 186 | 187 | # Additional stuff for the LaTeX preamble. 188 | #'preamble': '', 189 | } 190 | 191 | # Grouping the document tree into LaTeX files. List of tuples 192 | # (source start file, target name, title, 193 | # author, documentclass [howto, manual, or own class]). 194 | latex_documents = [ 195 | ('index', 'Git.tex', u'Git使用分享 Documentation', 196 | u'kun', 'manual'), 197 | ] 198 | 199 | # The name of an image file (relative to this directory) to place at the top of 200 | # the title page. 201 | #latex_logo = None 202 | 203 | # For "manual" documents, if this is true, then toplevel headings are parts, 204 | # not chapters. 205 | #latex_use_parts = False 206 | 207 | # If true, show page references after internal links. 208 | #latex_show_pagerefs = False 209 | 210 | # If true, show URL addresses after external links. 211 | #latex_show_urls = False 212 | 213 | # Documents to append as an appendix to all manuals. 214 | #latex_appendices = [] 215 | 216 | # If false, no module index is generated. 217 | #latex_domain_indices = True 218 | 219 | 220 | # -- Options for manual page output --------------------------------------- 221 | 222 | # One entry per manual page. List of tuples 223 | # (source start file, name, description, authors, manual section). 224 | man_pages = [ 225 | ('index', 'git', u'Git使用分享 Documentation', 226 | [u'kun'], 1) 227 | ] 228 | 229 | # If true, show URL addresses after external links. 230 | #man_show_urls = False 231 | 232 | 233 | # -- Options for Texinfo output ------------------------------------------- 234 | 235 | # Grouping the document tree into Texinfo files. List of tuples 236 | # (source start file, target name, title, author, 237 | # dir menu entry, description, category) 238 | texinfo_documents = [ 239 | ('index', 'Git', u'Git使用分享 Documentation', 240 | u'kun', 'Git', 'One line description of project.', 241 | 'Miscellaneous'), 242 | ] 243 | 244 | # Documents to append as an appendix to all manuals. 245 | #texinfo_appendices = [] 246 | 247 | # If false, no module index is generated. 248 | #texinfo_domain_indices = True 249 | 250 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 251 | #texinfo_show_urls = 'footnote' 252 | 253 | # If true, do not generate a @detailmenu in the "Top" node's menu. 254 | #texinfo_no_detailmenu = False 255 | 256 | 257 | # -- Hieroglyph Slide Configuration ------------ 258 | 259 | extensions += [ 260 | 'hieroglyph', 261 | ] 262 | 263 | slide_theme = 'slides2' 264 | slide_levels = 3 265 | slide_theme_options = { 266 | 'presenters': [ 267 | { 268 | 'name': 'kun', 269 | 'twitter': '@zhegkun', 270 | 'www': 'http://zhengkun.info/', 271 | 'github': 'http://github.com/akun' 272 | }, 273 | ], 274 | } 275 | 276 | # Place custom static assets in the _static directory and uncomment 277 | # the following lines to include them 278 | 279 | # slide_theme_options = { 280 | # 'custom_css': 'custom.css', 281 | # 'custom_js': 'custom.js', 282 | # } 283 | slide_theme_options = { 284 | 'custom_css': 'custom.css', 285 | } 286 | 287 | # ---------------------------------------------- 288 | 289 | 290 | 291 | # Example configuration for intersphinx: refer to the Python standard library. 292 | #intersphinx_mapping = {'http://docs.python.org/': None} 293 | -------------------------------------------------------------------------------- /slides/how_to_run_a_successful_software_project.rst: -------------------------------------------------------------------------------- 1 | 回顾 2 | ---- 3 | 4 | .. rst-class:: build 5 | 6 | * 软件开发过程杂谈:http://example.zhengkun.info/loop/ 7 | * .. image:: /_static/start.png 8 | 9 | 前提 10 | ---- 11 | 12 | .. rst-class:: build 13 | 14 | * 这里特指Python项目 15 | * 需求分析不在讲解范围内 16 | 17 | + 这里前提是认为需求明确了,进行实现阶段的事情 18 | + 核心引擎、类库等这种类型项目,其实最大的需求来源往往是自身! 19 | 20 | * 按项目的高标准、通用要求,实际操作还是具体问题具体分析 21 | 22 | 部分要素 23 | -------- 24 | 25 | .. rst-class:: build 26 | 27 | * 代码/设计 28 | * 测试 29 | * 文档 30 | * 安装/部署 31 | * 必备三件套:VCS + SCMS + CI 32 | * 具体问题具体分析(铁三角) 33 | 34 | 易被忽略的要素之一:测试 35 | ------------------------ 36 | 37 | .. rst-class:: build 38 | 39 | * 一行代码的改动要不要测试? 40 | * 看到一个统计数据:“...在引入代码评审之前,55%的\ **单行**\ 维护代码改动都是错误的...” 41 | * 几个案例 42 | 43 | .. nextslide:: 44 | :increment: 45 | .. rst-class:: build 46 | 47 | * .. image:: http://coolshell.cn//wp-content/uploads/2014/02/gotofail.jpg 48 | * .. image:: /_static/space.png 49 | * .. image:: /_static/dict.png 50 | * 引入缺陷排行耪:拼写错误、空格、大中小括号、分号等标点。。 51 | 52 | .. nextslide:: 53 | :increment: 54 | .. rst-class:: build 55 | 56 | * 编码过程中测试理应花费的时间以及自动化的重要性 57 | * .. image:: /_static/auto_test.png 58 | 59 | .. nextslide:: 60 | :increment: 61 | .. rst-class:: build 62 | 63 | * 单元测试 64 | 65 | + 没有写过单元测试的程序员,你的程序员生涯是不完整的。。 66 | + 这个说法可能略微夸张。。 67 | + 保证一定的测试覆盖率 68 | + 100%? 69 | + 核心模块、复杂模块尽量保证 70 | 71 | * 功能测试 72 | 73 | + 看情况,比如:AppScan、爬虫这种最好能有对应的功能测试 74 | + UI更是,尽量自动化,比如:UI功能逐渐用Selenium跑测试 75 | 76 | 易被忽略的要素之二:文档 77 | ------------------------ 78 | 79 | .. rst-class:: build 80 | 81 | * .. image:: http://www.iconpng.com/png/softdimension/ms-word-2.png 82 | * Word 83 | * **No** 84 | 85 | .. nextslide:: 86 | :increment: 87 | .. rst-class:: build 88 | 89 | * .. image:: /_static/rst_editor_qt1.png 90 | * Python Doc | reStructuredText | MarkDown 91 | * **YES** 92 | 93 | .. nextslide:: 94 | :increment: 95 | .. rst-class:: build 96 | 97 | * 最起码需要的技术方面的文档 98 | * 如何开发项目指导文档 99 | * 安装/部署文档 100 | * API/接口文档,特别是类库方面的程序 101 | * ChangeLog 102 | * FAQ 103 | * TODO or 路线图 104 | * 研发文档,主要:概要设计/构架设计、核心模块设计 105 | 106 | .. nextslide:: 107 | :increment: 108 | .. rst-class:: build 109 | 110 | * 程序员A接手一个项目问:有文档吗? 111 | * 程序员A开发一个项目被要求写文档:文档?笑话! 112 | * 代码就是文档,文档就是代码,不要分家 113 | * 推荐 114 | 115 | + Sphinx编写 116 | + Python项目算是标配 117 | + 可以代码中的Python Doc直接生成文档 118 | + 特别是对类库的项目,写API文档很方便 119 | + 可以版本控制起来 120 | 121 | 易被忽略的要素之三:安装/部署 122 | ----------------------------- 123 | 124 | .. rst-class:: build 125 | 126 | * Makefile 127 | * setup.py 128 | * install.sh 129 | * pip install kssched 130 | * 软件包 or ISO安装镜像 131 | 132 | 必备三件套简述 133 | -------------- 134 | 135 | .. rst-class:: build 136 | 137 | * 三件套(公司的闭源项目为例) 138 | 139 | + VCS(版本控制系统) 140 | + SCMS(软件配置管理系统) 141 | + CI(持续集成) 142 | 143 | * Subversion系三件套 144 | 145 | + Subversion 146 | + Trac 147 | + Bitten 148 | 149 | * Git系三件套 150 | 151 | + Git 152 | + GitLab 153 | + GitLab CI 154 | 155 | .. nextslide:: 156 | :increment: 157 | .. rst-class:: build 158 | 159 | * 核心是版本控制系统 160 | * 提交日志详细、规范,方便ChangeLog编写 161 | * 代码稳定/没特殊情况,保持: 162 | 163 | + 提交粒度细,提交代码量少,降低别人code review单独 164 | + 一次提交只处理一个事情 165 | + 区分代码风格修改和代码逻辑修改,分别提交 166 | + 除非:代码乱,需要重大重构 167 | 168 | * 能版本控制的都加入版本控制 169 | 170 | + 一切开发行为,统一在版本仓库中进行自动记录 171 | + 不要仅仅将项目的源代码纳入到版本控制下,也应该包括文档、FAQ、设计注释和任何人们希望编辑的内容 172 | + 二进制、大文件等加入版本控制要慎重 173 | + 不要将生成的文件置入版本控制 174 | + 高标准是:功能代码、测试代码、对应文档在一次提交中得到体现 175 | 176 | “代码”为王 177 | ---------- 178 | 179 | .. rst-class:: build 180 | 181 | * 设计!设计!设计! 182 | 183 | + 简洁 184 | + 以最少的代码实现最多的功能 185 | 186 | * API/接口的设计 187 | 188 | + 输入/输出 189 | + 契约式编程 190 | 191 | * 代码风格,通用约定:PEP 8 192 | 193 | .. nextslide:: 194 | :increment: 195 | .. rst-class:: build 196 | 197 | * 代码风格,项目内部约定,比如: 198 | 199 | + UI代码都预留国际化处理等 200 | + import顺序:Python官方库、第三方库、公司公共库、项目公共库、模块库等 201 | + 绝对import等 202 | + 避免魔术数字等 203 | + 等等有利于此项目的各种约定 204 | 205 | * 必要的注释 206 | 207 | + 注释更多是为了解释为什么这么做 208 | + 好的命名本身就解释了代码是做什么 209 | 210 | * **Code Review** 211 | 212 | + 大多数的Bug是消灭在这个阶段的 213 | 214 | 具体问题具体分析 215 | ---------------- 216 | 217 | .. rst-class:: build 218 | 219 | * 没错,这是一句废话。。 220 | * .. image:: /_static/triangle.png 221 | 222 | .. nextslide:: 223 | :increment: 224 | .. rst-class:: build 225 | 226 | * 各种所谓XDD开发哲学 227 | * TDD / BDD / DDD 228 | * Test / Behavior / Domain Drive Development 229 | * Talent / Bug / Deadline Drive Development 230 | * TDD,测试驱动开发,其实更好的解释应该是:更重视设计的开发流程 231 | 232 | 总结 233 | ---- 234 | 235 | .. rst-class:: build 236 | 237 | * 要重视的:安装/部署、文档、测试 238 | * 善用三件套:Git + GitLab + GitLab CI 239 | * 代码/设计为王 240 | * 目的:让用户(客户/用户/其他程序员),能更放心、更安心、更开心的使用 241 | 242 | 题外:Joel衡量法则 243 | ------------------ 244 | 245 | .. rst-class:: build 246 | 247 | * 你们用不用源文件管理系统? 248 | * 你们可以把整个系统从源码到CD映像文件一步建成吗? 249 | * 你们每天白天都把从系统源码到CD映像做一遍吗? 250 | * 你们有软件虫管理系统吗? 251 | * 你们在写新程序之前总是把现有程序里已知的虫解决吗? 252 | * 你们的产品开发日程安排是否反映最新的开发进展情况? 253 | * 你们有没有软件开发的详细说明书? 254 | * 你们的程序员是否工作在安静的环境里? 255 | * 你们是否使用现有市场上能买到的最好的工具? 256 | * 你们有没有专职的软件测试人员? 257 | * 你们招人面试时是否让写一段程序? 258 | * 你们是否随便抓一些人来试用你们的软件? 259 | 260 | 参考 261 | ---- 262 | 263 | * http://producingoss.com/zh/ 264 | * http://coolshell.cn/articles/11112.html 265 | * http://coolshell.cn/articles/4875.html 266 | * https://code.google.com/p/kcpycamp/wiki/HowtoScm 267 | * http://producingoss.com/zh/vc.html 268 | * http://chinese.joelonsoftware.com/Articles/TheJoelTest.html 269 | * 《软件系统构架:使用视点和视角与利益相关者合作》 270 | 271 | Q & A 272 | ----- 273 | 274 | * ? & [.|!] 275 | 276 | -------------------------------------------------------------------------------- /slides/index.rst: -------------------------------------------------------------------------------- 1 | Slides目录 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | git_part1 8 | subversion2git 9 | how_to_run_a_successful_software_project 10 | -------------------------------------------------------------------------------- /slides/subversion2git.rst: -------------------------------------------------------------------------------- 1 | Subversion项目迁移Git 2 | ===================== 3 | 4 | * kun 5 | * 2014-06-05 6 | 7 | Step1 - 准备环境 + 规范Subversion 8 | --------------------------------- 9 | 10 | .. rst-class:: build 11 | 12 | * 安装用到的工具的软件包(以Ubuntu为例) 13 | 14 | :: 15 | 16 | $ sudo apt-get install subversion git 17 | $ sudo apt-get install git-core libsvn-perl perl libterm-readkey-perl 18 | 19 | * 确认Subversion地址 20 | 21 | :: 22 | 23 | # 后面统一用$PROJECT表示项目的Subversion地址 24 | # 这里的示例项目名称是west 25 | https://scms.example.com/svn/projects/west/ 26 | 27 | .. nextslide:: 28 | :increment: 29 | .. rst-class:: build 30 | 31 | * 规范项目在Subversion的目录结构 32 | 33 | + 标准的trunk、branches、tags目录布局 34 | + branches和tags目录下的分支和标签保持平级,例如:tags/v1.0.0可以;tags/1.x/v1.0.0多了层目录 35 | 36 | + 如果不是平级,以tags为例,先执行svn mv操作 37 | 38 | :: 39 | 40 | # 方式1 远程svn mv 41 | $ svn mv $PROJECT/tags/1.x/v1.0.0 $PROJECT/tags/v1.0.0 42 | 43 | # 方式2 本地svn mv 44 | $ svn co $PROJECT west_subversion 45 | $ cd west_subversion 46 | $ svn mv tags/1.x/v1.0.0 tags/v1.0.0 47 | 48 | .. nextslide:: 49 | :increment: 50 | .. rst-class:: build 51 | 52 | * 最后规范后的目录示例如下 53 | 54 | :: 55 | 56 | west 57 | ├── trunk 58 | │  ├── docs 59 | │ ├── west 60 | │ ├── setup.py 61 | │ └── README.rst 62 | ├── branches 63 | │ ├── hotfix_add_user_error 64 | │ ├── hotfix_issuse_9527 65 | │ ├── feature_unittest4app 66 | │ └── feature_multi_add_user 67 | └── tags 68 | ├── v1.0.0 69 | ├── v1.0.1 70 | ├── v2.0.0 71 | └── v2.1.0 72 | 73 | Step3 - 生成提交者ID和邮箱 74 | -------------------------- 75 | 76 | .. rst-class:: build 77 | 78 | :: 79 | 80 | # example.com代表组织的邮箱,比如:knownsec.com 81 | # 但如果个人邮箱不是统一的组织的话,就需要手工编辑users.txt了 82 | svn log $PROJECT --xml | grep -P "^(.*?)<\/author>/$1 = $1 \<$1\@example.com\>/' > users.txt 84 | 85 | Step4 - 迁出项目代码(git svn) 86 | ------------------------------- 87 | 88 | .. rst-class:: build 89 | 90 | :: 91 | 92 | # --authors-file是得到的git log提交记录映射好提交者的信息 93 | # --no-metadata是得到的git log不带上对应的Subversion信息了,更干净 94 | # --localtime是得到的git log以本地时间为准,建议用上 95 | # --stdlayout是先前准备的按规范目录风格来迁出代码 96 | git svn clone $PROJECT --authors-file=users.txt --no-metadata \ 97 | --localtime --stdlayout 98 | 99 | Step5 - 转化成Git的仓库格式(tags和branches) 100 | --------------------------------------------- 101 | 102 | .. rst-class:: build 103 | 104 | :: 105 | 106 | # 处理tag 107 | git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | \ 108 | while read tagname; do git tag "$tagname" "tags/$tagname"; \ 109 | git branch -r -d "tags/$tagname"; done 110 | 111 | # 处理branch 112 | git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | \ 113 | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; \ 114 | git branch -r -d "$branchname"; done 115 | 116 | Step6 - 添加到远程仓库(以GitLab为例) 117 | -------------------------------------- 118 | 119 | .. rst-class:: build 120 | 121 | * GitLab中创建项目west 122 | * 添加本地Git项目到刚创建的远程Git仓库 123 | 124 | :: 125 | 126 | git remote add origin git@scms.example.com:group/west.git 127 | git push origin --all 128 | git push origin --tags 129 | 130 | Step7 - 一些清理工作 131 | -------------------- 132 | 133 | .. rst-class:: build 134 | 135 | * Subversion历史上错误的tags 136 | * Subversion历史上临时的branches 137 | * 都会完整转化到Git仓库中的tag和branch 138 | * 可以在GitLab上直接删除这些不要的 139 | 140 | Step8 - 完成迁移 141 | ---------------- 142 | 143 | .. rst-class:: build 144 | 145 | * TODO: 迁移前用Trac做SCMS的Subversion的项目截图 146 | * TODO: 迁移后用GitLab做SCMS的Git的项目截图 147 | 148 | 遗留问题 149 | -------- 150 | 151 | .. rst-class:: build 152 | 153 | * Subversion允许空目录,转化Git用git svn,处理空目录带上--preserve-empty-dirs可能会报错 154 | * 类似svn:externals,svn:ignore,svn:merge等属性丢失 155 | * 不过问题不大,可以接受 156 | * Subversion迁移Git算是基本平滑迁移 157 | 158 | 参考 159 | ---- 160 | 161 | * http://git-scm.com/book/zh/ 162 | * https://www.semitwist.com/articles/article/view/the-better-svn-git-guide 163 | * http://git.661346.n2.nabble.com/PATCH-1-2-git-svn-fix-occasional-quot-Failed-to-strip-path-quot-error-on-fetch-next-commit-td7584266.html 164 | * http://git.661346.n2.nabble.com/git-svn-error-quot-Not-a-valid-object-name-quot-td7579457.html 165 | * http://git.661346.n2.nabble.com/SVN-gt-Git-but-with-special-changes-td6840904.html 166 | 167 | Q & A 168 | ----- 169 | 170 | * ? & [.|!] 171 | 172 | END 173 | --- 174 | 175 | * Thanks! 176 | --------------------------------------------------------------------------------