标签归档:读书总结

无分支开发

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

假设我们现在使用的是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. 如果不再需要抽象层,就移除它。

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

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

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

《持续交付》总结一

关于耦合

在做程序设计时我们经常会听到“高内聚,低耦合”,这是我们追求的目标。 其中内聚是指一个模块内各个元素彼此结合的紧密程度, 高内聚是指一个模块或一个类内部各个元素之间关系紧密,争取用最少的元素和方法实现相应的功能; 耦合是指模块之间互相关联程度的度量, 而低耦合是指一个程序中各个模块之间的联系少和相互依赖程度低,一个模块只需要具体实现一个功能。 总的来说,我们所追求的设计是这样的,每个模块职责单一,一个模块只完成一个相对独立的特定子功能,并且和其他模块之间的关系简单(嗯,保持简单), 在不同的应用场景下复用这些模块,从而组成新的系统或大的模块。

在编写代码的过程中,我们应该尽量实现程序的内聚性。内聚性差的程序通常会让人产生这里什么都有,一片杂乱的景象, 特别是那种将类作为若干不同功能函数集的设计。和内聚性差一样,也会让人产生不好情绪还有高耦合。 耦合就是一些代码或一些模块对另一些代码或模块的依赖。而依赖破坏了当初的分块,严重的模块间耦合会让当初的模块化的设想付之一炬。 虽然这种耦合产生的依赖不是很好,但是我们并不能消除耦合。因为正是这种耦合,各种程序模块才能产生互操作。 所以我们只能在允许的范围内尽量减少不必要的耦合。

我们可以将耦合分为以下三类:

  1. 可以接受的耦合
  2. 不建议使用的耦合
  3. 真的不建议使用的耦合

直接看这三个分类真没啥意义,关键是这些分类中所包含的耦合实例。

可以接受的耦合

可用的耦合是指这种耦合会经常存在,而且有时也必须存在,其害处不大。如数据耦合和标记耦合。 何为 数据耦合,看这样一个例子:

function main() {
    output_name('name');
}

function output_name($name) {
    echo $name;
}
main();

这种以函数间传递参数的方式产生的函数是不可避免的,这对程序没有太大的影响。

标记耦合 是指将一个大的数据结构或类传递给另一个模块或类,而该模块只需要这个数据结构的一小部分,或只需要这个类的一个属性值。 以传递为类为例:

class Foo {
    private $_name;

    public function __construct() {
        $this->_name = __CLASS__;
    }

    public function getName() {
        return $this->_name;
    }
}

function output_name(Foo $foo) {
    echo $foo->getName();
}

output_name(new Foo());

其实我们应该是将getName()的输出直接传递给输出函数。 标记函数在一定程序上阻碍了我们对于程序的理解,因为我们需要去了解这个程序到底调用了类中的哪一个方法或使用了数据结构中的哪一个属性。

不建议使用的耦合

不建议使用的耦合是指那种我们会经常见到的不会影响整体的架构,但是有一些坏味道的代码,如控制耦合,时间耦合

控制耦合 是指一个函数传递的某个参数影响另一个函数中所作的控制决定。有时候这种实现只是为了复用一些代码。 如下示例:

function edit($is_add = 0) {
    if ($is_add) {
        //  一些需要添加的字段
    }else{
        //  一些编辑的字段
    }

    //  公共的字段
    //  保存数据
}

以上的示例只是想复用编辑和添加的操作中一些公共的东西,从而引入是否是添加操作的变量。 如果要重构以上代码,可以将一个函数分为三个函数,添加和修改各是一个,公共代码放一个函数。

时间耦合 是指代码中各个元素之间依赖于时间的关系, 以类的public方法调用为例,如果一个方法A在调用时需要先调用另一个方法B,则这里就是时间耦合。 看一个示例:

class File {

    private $_path;

    public function __construct() {

    }

    public function read() {
        echo 'read file from ', $this->_path, '<br />';
    }

    public function setPath($path) {
        $this->_path = $path;
    }
}

$file = new File();
$file->setPath(__FILE__);
$file->read();

以上是一个示例性质的文件类,在读取一个文件的文件内容之前需要先设置这个文件所在的地址。 这种形式的时间耦合应该尽量避免,如果要重构这样的代码,可以将路径作为一个变量传递给构造函数。

真的不建议使用的耦合

这类耦合一旦出现,虽然不会赤地千里,但会严重影响整体的架构和模块间的关联。如公用耦合

公用耦合 通俗的讲,公共耦合就是使用了全局变量。当两个函数或两个模块访问同一个全局变量时,它们就被公用耦合了。 在PHP中一些全局变量$_GET,$_POST等经常会在一些代码中用到,如果有两段代码都使用了这些变量并且修改了同一个值,但是双方互不相知,此时就会出BUG。

领域耦合 指的是在应用的代码中嵌入了领域或商业知识与规则,简单的讲就是将业务规则硬编码到应用中,为一些特定的业务定制应用。 这在我们的开发中会经常出现,一般会通过配置或特定的领域语言减轻系统某些部分因为耦合带来的问题。

以上的各种耦合的定义都来自《高质量程序设计艺术》一书。

PHP设计模式笔记:使用PHP实现备忘录模式

PHP设计模式笔记:使用PHP实现备忘录模式

【意图】
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样可以在以后把该对象的状态恢复到之前保存的状态。【GOF95】

【备忘录模式结构图】

备忘录模式

备忘录模式

【备忘录模式中主要角色】
1、备忘录(Memento)角色:
存储发起人(Originator)对象的内部状态,而发起人根据需要决定备忘录存储发起人的哪些内部状态。
备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。

2、发起人(Originator)角色:
创建一个含有当前的内部状态的备忘录对象
使用备忘录对象存储其内部状态

3、负责人(Caretaker)角色:
负责保存备忘录对象,不检查备忘录对象的内容

【备忘录模式的优点和缺点】
备忘录模式的优点:
1、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取。
2、简化了发起人(Originator)类。发起人(Originator)不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理它们所需要的这些状态的版本
3、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

备忘录模式的缺点:
1、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
2、当负责人角色将一个备忘录存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否会很昂贵。
3、当发起人角色的状态改变的时候,有可能这个状态无效。

【备忘录模式适用场景】
1、必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
2、如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

【备忘录模式与其它模式】
1、命令模式(command模式):Command模式也可以用来恢复对象的状态,一般Command模式可以支持多级状态的回滚,Memento只是简单的恢复(快照)。在Command模式的每一个undo中,可以使用Memento来保存对象的状态。
2、迭代器模式(Iterator模式):备忘录可以用于迭代

【备忘录模式PHP示例】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 
<?php
 
/**
 * 备忘录模式 2010-10-09 sz
 * @author phppan.p#gmail.com  http://www.phppan.com                                                       
 * 哥学社成员(http://www.blog-brother.com/)
 * @package design pattern
 */
 
/**
 * 发起人(Originator)角色
 */
class Originator {
 
    private $_state;
 
    public function __construct() {
        $this->_state = '';
    }
 
    /**
     * 创建备忘录
     * @return Memento 包含当前状态的备忘录对象
     */
    public function createMemento() {
        return new Memento($this->_state);
    }
 
    /**
     * 将发起人恢复到备忘录对象记录的状态上
     * @param Memento $memento
     */
    public function restoreMemento(Memento $memento) {
        $this->_state = $memento->getState();
    }
 
    public function setState($state) {
        $this->_state = $state;
    }
 
    public function getState() {
        return $this->_state;
    }
 
    /**
     * 测试用方法,显示状态
     */
    public function showState() {
        echo "Original Status:", $this->getState(), "<br />";
    }
 
}
 
/**
 * 备忘录(Memento)角色
 */
class Memento {
 
    private $_state;
 
    public function __construct($state) {
        $this->setState($state);
    }
 
    public function getState() {
        return $this->_state;
    }
 
    public function setState($state) {
        $this->_state = $state;
    }
 
}
 
/**
 * 负责人(Caretaker)角色
 */
class Caretaker {
 
    private $_memento;
 
    public function getMemento() {
        return $this->_memento;
    }
 
    public function setMemento(Memento $memento) {
        $this->_memento = $memento;
    }
 
}
 
/**
 * 客户端
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
 
        /* 创建目标对象 */
        $org = new Originator();
        $org->setState('open');
        $org->showState();
 
        /* 创建备忘 */
        $memento = $org->createMemento();
 
        /* 通过Caretaker保存此备忘 */
        $caretaker = new Caretaker();
        $caretaker->setMemento($memento);
 
        /* 改变目标对象的状态 */
        $org->setState('close');
        $org->showState();
 
        /* 还原操作 */
        $org->restoreMemento($caretaker->getMemento());
        $org->showState();
    }
 
}
 
Client::main();
?>