作者归档:admin

无分支开发

在一个代码库上创建分支的能力是版本控制系统最重要的特性。它的作用是创建一个副本,并将在这个副本上进行操作,从此它就有了它自己的发展方向,也许有一天会回来,回来时就会合并。分支的作用就是为了更好的帮助并行开发,即在同一时刻能够在两个或更多的工作流上面开发,而不会相互影响。分支开会可以用于哪些应用场景呢?

假设我们现在使用的是SVN,并且都是在主干上开发,此时A项目组下在开发,而B项目组准备发布,并且两个项目组中间有一些公共的代码,这个时候就产生了B项目组发布对于A项目组的时间依赖。此时分支开发是一种解决方案。

常见的分支方法有两种:

  1. 在开发时分支。针对不同的项目或开发团队做分支操作,每个项目或开发团队有一个副本,其所有的操作都在副本上进行,在发布时将分支合并到主干,做整体的合并集成,并做整体发布。这是先分后合的招式。
  2. 在发布时分支。主干上开发完成发布后,创建分支,在分支上修改BUG,当修改的BUG发布时将修改的内容合并到主干。这是先合后分再合的招式。

分支是基于并行开发的一种理想状态,即各个分支是完全独立存在的,并且互不干扰,各成系统。但是现实是各个分支基本上都是有一些关联或依赖的。在开发时可能没有感觉,但是当需要将分支的内容合并时,就会产生较多的冲突和问题,虽然现在的版本控制系统已经在这个方面做得相当好了,但是实际操作中还是会存在一些问题,还是会花费较多的时间。因此在做分支开发时,通常会建议以尽可能高的频率将分支的代码合并到主干净,并且确保主干的正常运行,这样会在一定程序上减缓最终实施合并时的冲突。

什么是无分支开发呢?所谓无分支开发就是指所有的开发工作都在主干上进行,当然这是一种理想情况,但是我们可以尽可能向它靠拢。如果实施了无分支开发,此时就不会存在分支、合并、解决合并冲突等问题了,解决合并冲突的问题最好的方法就是永远不要合并,这和赌博不输的最好的办法就是永远不赌一样。为什么说这是理想情况呢?因为在我们的项目中存在大量的依赖,一个文件或一个类,甚至一个函数可能会被多个人或多个团队修改,如果是多个团队的多个项目并行的话,这咱依赖关系将会影响项目的发布。那么怎么减少这些依赖,嗯,是减少,因为我们基本不可能完全去掉这种依赖,这和我们写代码一样,一些有必要的耦合是完全存在的,并且有其存在的必要性。

  1. 迪米特法则。迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),”不要和陌生人说话“,也就是说一个对象应当对其他对象有尽可能少的了解。这是到类级,对象级别的讲究。
  2. 减小需求的粒度。以一种更加敏捷的方式实现需求,将大的需求分割成小的需求,以增量的方式实现并发布。
  3. 使用通过抽象来模拟分支的方式实现代码库中大范围的变更。
  4. 对于公共部分做到文件级的版本发布。即一次发布多个文件的多个版本。
  5. 使用模块和组件,从功能、团队或变更频率等角度对代码进行切割,以解耦代码间的依赖.
  6. 将新功能隐藏起来,直到它完成。

以上的几点更适合于小规模团队。

关于第一点,这是面向对象设计的重要原则之一,除此之外,我们经常还会念叨开闭原则,KISS等等。这些都是实现无分支开发时代码级的一些优化措施。

关于第二点,将大的需求变为小的需求本身就是一个比较困难的工作,拆分的原则是什么?粒度以多大 为优,这些都需要依据实现的情况进行判断,以周为单位?以页面为单位?这种方式会比较轻,可以随时停下,即使需求方向有问题,我们也是可以在较少损失的情况下调头。如果我们坚持这样做,那么就意味着我们在解决一个问题:保持应用的持续可工作。

关于第三点,通过抽象来模拟分支的实现步骤如下:

  1. 当需求分解后还是无法增量开发或增量开发无法解决问题时,考虑引入中间层或抽象层。使要修改的部分与调用者分离,使其直接低耦合。
  2. 在需要修改的那部分系统代码上创建一个抽象层。
  3. 重构系统的其他部分,让他使用这个抽象层。
  4. 创建一种新的实现代码,在它完成之前不要将它作为产品代码的一部分。
  5. 更新抽象层,让他使用这个新的实现代码。(如果没有完善的回滚机制,建议在此处做切换开关,即以一个变量控制抽象层的实现,当然,这并不是一个优雅的解决方案)。
  6. 移除原来的实现代码
  7. 如果不再需要抽象层,就移除它。

关于第四点,对于公共部分的代码,其修改频率较之其它功能模块更频繁,并且可能存在需要发布旧版本的需求。

关于第五点,模块化,组件化在架构设计中现在已经是非常普遍的行为,但是要想完全模块化或组件化,其难度较大,尽可能的向这方面靠拢,以一定的规则划分模块,针对模块进行设计实现,并在最后将所有模块的结果有机的结合起来。这是在设计层面的分支和合并操作的替代品。

关于第六点,将新功能隐藏起来,直到它完成。这是在没有增量式发布,小步快跑的前提下,或需求确实无法拆分,一组特性一定要一起实现,此时通常会开一个分支做这些新功能的发布,而无分支开发中,可以将这部分代码放在主干,但这些功能对用户不可见,以某种配置项(文件或数据库都行)的方式来操作其对用户的可见性。

《持续交付》总结一

2011年度总结

时间在不知不觉中从我们的指间悄悄的溜走,有些忙乱的2011已经离我们而去,是我们度过了2011,还是2011度过了我们?

回首一年的历程,有过出错,有过迷茫,有过灰心,也有过激情迸发,随着时间的流逝,一切都过去了。现在的我们不需要为那些过去而惋惜什么,珍惜当下,错过的不要再错,做得好的我们继续……

2011就这样过去了,以前没有写总结的习惯,那么从2011开始吧。

今年工作重心发生了一些变化,从纯粹的简单的写程序变成了业余写程序。从开始的各种忙乱,各种不适应,各种出错,各种请求指点,到能应付常规工作,能够满足项目进程推进,能够关注项目技术选型和决策,能够挤出一些时间陪女朋友。虽然过程之中还有错误出现,但是已经有了一些初步的想法和解决方案,这些待到下一次写总结前解决吧。当然,对于写程序,还是非常有热情,也非常希望能够参与项目的开发工作,只是没能实现,也希望能挤出时间回到那个纯粹的编码时间。这些在来年会有一些改变。

今年一个读书比较杂的一年,从douban读书列表中看到总记是60+本书,包括技术、管理、心理学、时间管理、经济学、小说等等。还是和之前一样,不求甚解。不过也有重读之前读过的一些书。对于经常翻书的我来说,重读经典书籍是非常有必要的,这个问题需要在2012有更深刻的体现。

TIPI项目在2011年发布了6章,第7章也写得差不多了,但是由于团队内部认为本章及前面章节需要优化,则没有在元旦节前发布,这也算一个遗憾。这一年中对于TIPI项目的时间投入较多,当然这不仅仅是我一个人的投入,更多的是reeze,er在这个项目上的付出,感谢上苍让我遇到TIPI的兄弟,从兄弟们的身上我也学到了许多,包括对于项目的参与,关于文档的书写,对技术的热情等等。谢谢!这个项目还将继续,在来年会有更多,质量更好的章节发布。

今年的blog没有之前写得多了,也差不多在一周一篇的样子,这个速度在2012也是要保持的。在写blog的过程中能够梳理自己的知识体现,记录自己学到看到的东西,记录自己曾经出现问题的地方,沉淀自己的过去。在evernote中积累了N多的未完成的文章列表,可能是当时有一个点,想写一下,记录下来后却没有时间去清理,这在2012是需要不断清理的。坚持、沉淀、厚积。

需要感谢人:Damon、reeze、er、晓民、江江、家里的领导、国恒、念洋、Poleon、Hilda、Justin、Ben、fffox、blank yao ….. 

以上没有先后,只是想表示自己的谢意,当然肯定还有遗漏的,感谢你们在2011年对我好,对我不好,陪我扯淡、让我陪我高兴,让我伤心…… 我这条线在2011是与你们的直线想交,因为有了你们,所以才精彩,谢谢!

2012,给个量化的值吧。

读书:10+技术书,30+非技术且不含小说

工作:把没有实现的那2个想法实现,参与开发,每周至少一个工作日在编码。

生活:把1件大事给办了。

TIPI;发布3章+

wordpress升级到3.3引起的血案

某位哥哥因为看着wordpress3.3的升级提示激动了,一不小心就将它升级了,升级后一切正常,只是之前可用的附件上传功能现在不能用了,显示报错如下:

Warning: touch() [function.touch]: SAFE MODE Restriction in effect.
 The script whose uid is 10041 is not allowed to access /tmp owned by uid 0 in ......

各种纠结……

依据提示我们知道是由于PHP开启了安全模式的原因,最简单的办法:修改php.ini文件,将safe_mode设置为off,将安全模式关闭。

只是这是生产环境,关闭安全模式不太靠谱。只能换方案。
依据刚才的错误提示,我们知道是安全模式在检查执行程序的文件的UID与需要写入的文件夹的UID不一样,这是因为在PHP的安全模式中,对于部分函数是需要检查被操作的文件或目录是否与被执行的脚本有相同的 UID(所有者),很不幸,touch就是这样的函数之一。当然,有这个规则就应该有跳过这个规则的办法,在php.ini文件中,我们可以设置safe_mode_include_dir参数,多个路径以冒号隔开(Windows是分号),在此参数内设置的目录及其子目录都将会越过 UID/GID 检查。

除此之外,安全模式相关的还有一个open_basedir设置项,PHP 所能打开的文件限制在open_basedir指定的目录树,包括文件本身。本指令不受安全模式打开或者关闭的影响。

safe_mode_include_dir和open_basedir指定的限制实际上是一个前缀,而非一个目录名。这也就是说“safe_mode_include_dir = /tmp”将允许访问“/tmp和“/template”(如果它们存在的话),。如果希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线,例如:“safe_mode_include_dir = /tmp”。

这两个与本次安全问题相关的内容都添加了/tmp和当前操作的目录,发现依旧报错。

追根溯源,从源码中开始吧,如果对于PHP源码不太熟悉,那么我们可以从本次事件的关键点safe_mode_include_dir开始。

在PHP源码中搜索safe_mode_include_dir,除了与此配置项相关的初始化操作,我们可以找到验证此项的函数php_check_safe_mode_include_dir。现在,我们可以用GDB debug的方法查看,给php_check_safe_mode_include_dir函数增加断点,运行一个你认为一定会经过此参数检查的PHP函数调用,如fopen等。运行,发现会在断点处停下,如果我们运行一个只包含了touch函数的代码,会发现程序会执行完,即touch函数根本就没有调用php_check_safe_mode_include_dir函数,即在安全模式下,根本无法跳过UID的检测。

或者我们直接查看touch函数的PHP源码实现,其代码在/ext/standard/filestat.c文件649行。在其代码中我们可以清楚的看到其实现过程在处理了相关参数后,就直接判断完全模式和检测UID了,如下代码:

PHP_FUNCTION(touch)
{
 
...
 
/* Safe-mode */
if (PG(safe_mode) && (!php_checkuid(filename, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
RETURN_FALSE;
}
 
/* Check the basedir */
if (php_check_open_basedir(filename TSRMLS_CC)) {
RETURN_FALSE;
}
 
...
 
}

找到了问题的根源,也许是PHP的BUG,也许是其它考量,待确认。

那么这个问题怎么解决呢?

比较wordpress3.3和wordpress3.2的代码,我们发现wordpress在处理附件上传时最终都会调用wp_handle_upload函数(/wp-admin/include/file.c)。在此函数中wordpress3.3版本多了一句:

$tmp_file = wp_tempnam($filename);

在wp_tempnam函数中,最终会通过touch生成临时文件,从前面我们可以知道我们暂时无法解决在此环境下的touch函数的问题,那么我们只能适应这个函数,也就是说我们需要将新创建的临时文件的目录UID和执行的PHP文件的UID设置为一样,那么这个临时文件的目录是否可以改变?我们查看wp_tempnam函数,发现其最先取的临时目录的地址为常量WP_TEMP_DIR的值,此值默认是没有设置的,如果这个值没有才会取其它值。此时,解决方案就浮出水面了:在wp-config.php文件中设置常量WP_TEMP_DIR的值为网站目录下某个UID和PHP脚本一样UID的目录即可。

附gdb bt显示的内容

#0 php_check_safe_mode_include_dir (path=0x86f4fcc "/tmp/aaa43243.tmp")
at /home/martin/project/c/phpsrc/main/fopen_wrappers.c:319
#1 0x0829d97b in php_plain_files_stream_opener (wrapper=0x85eb624, 
path=0x86f4fcc "/tmp/aaa43243.tmp", mode=0x86f6b38 "a", options=4, 
opened_path=0x0, context=0x86f5560)
at /home/martin/project/c/phpsrc/main/streams/plain_wrapper.c:991
#2 0x0829842d in _php_stream_open_wrapper_ex (
path=0x86f4fcc "/tmp/aaa43243.tmp", mode=0x86f6b38 "a", options=12, 
opened_path=0x0, context=0x86f5560)
at /home/martin/project/c/phpsrc/main/streams/streams.c:1867
#3 0x08229b73 in php_if_fopen (ht=2, return_value=0x86f5544, 
return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
at /home/martin/project/c/phpsrc/ext/standard/file.c:909
#4 0x0817b088 in phar_fopen (ht=2, return_value=0x86f5544, 
return_value_ptr=0x0, this_ptr=0x0, return_value_used=1)
at /home/martin/project/c/phpsrc/ext/phar/func_interceptors.c:418
#5 0x0831b9a1 in zend_do_fcall_common_helper_SPEC (execute_data=0x8727eb8)
at /home/martin/project/c/phpsrc/Zend/zend_vm_execute.h:313
#6 0x082f4bc6 in execute (op_array=0x86f545c)
at /home/martin/project/c/phpsrc/Zend/zend_vm_execute.h:104
#7 0x082d2256 in zend_execute_scripts (type=8, retval=0x0, file_count=3)
---Type <return> to continue, or q <return> to quit---
at /home/martin/project/c/phpsrc/Zend/zend.c:1194
#8 0x08281b70 in php_execute_script (primary_file=0xbffff2a0)
at /home/martin/project/c/phpsrc/main/main.c:2225
#9 0x08351b97 in main (argc=4, argv=0xbffff414)
at /home/martin/project/c/phpsrc/sapi/cli/php_cli.c:1190