window下安装sass

1.如果没有安装ruby先到:http://rubyinstaller.org/ 下载安装包安装

2.下载cacert.pem 地址:http://pan.baidu.com/s/1slGS84t

3.将cacert.pem 放置在ruby安装的根目录 如:C:Ruby23-x64

4.新增环境变量 SSL_CERT_FILE=C:Ruby23-x64cacert.pem

5.打开cmd 输入 gem sources -a https://ruby.taobao.org/

6.源添加成功后 输入 gem install sass

这样就安装了 sass 了

Centos系统下修改环境变量PATH路径的方法

比如要把/etc/apache/bin目录添加到PATH中,方法有三:

1.#PATH=$PATH:/etc/apache/bin
使用这种方法,只对当前会话有效,也就是说每当登出或注销系统以后,PATH 设置就会失效

2.#vi /etc/profile
在适当位置添加 PATH=$PATH:/etc/apache/bin (注意:= 即等号两边不能有任何空格)
这种方法最好,除非你手动强制修改PATH的值,否则将不会被改变

3.#vi ~/.bash_profile
修改PATH行,把/etc/apache/bin添加进去
这种方法是针对用户起作用的
注意:想改变PATH,必须重新登陆才能生效,以下方法可以简化工作:

如果修改了/etc/profile,那么编辑结束后执行source profile 或 执行点命令 ./profile,PATH的值就会立即生效了。
这个方法的原理就是再执行一次/etc/profile shell脚本,注意如果用sh /etc/profile是不行的,因为sh是在子shell进程中执行的,即使PATH改变了也不会反应到当前环境中,但是source是在当前 shell进程中执行的,所以我们能看到PATH的改变。

Zeal: 离线 API 文档浏览 + 搜索工具

OSX 上有 Dash, 是个离线的 API 文档搜索 + 浏览工具.

Zeal 是基于 QT 做的一个开源实现. 功能上还很残… 不过非 Mac 用户总归是有得用了.

下载安装

项目主页: http://zealdocs.org/ 官方提供 Windows 和 Ubuntu 的安装包.

Archlinux 用户可以用我的 pkgbuild: zeal-git. 使用中出了问题请告知

其他系统用户只能下载源码并编译: https://github.com/jkozera/zeal#how-to-compile

下载具体文档

刚安装好之后是没有文档可以看的, 需要自己下载. Zeal 兼容 Dash 的 docset 格式, 因此虽然内置下载的界面中选项不多, 但扩充的可能性是有的.

内置的下载界面在这里有截图示意. 不过试了一下 Ruby2 的无法下载. 作者在这里解释说是 Google Drive 的原因. 大家可以自行下载并解压到指定的目录就好了 – – 具体操作方法还是看作者的解释好了.

python 元编程

元编程

黑魔法防御

元编程是一种黑魔法,正派人士都很畏惧。——张教主

何谓元编程

  • 编写一个程序,能够操纵,改变其他程序,这就是元编程
  • 最简单的来说,C的宏就是元编程的一种
  • 元编程的另一大代表则是lisp的宏
  • 虽然不常见,但是汇编级别也是可以元编程的,例如可执行文件压缩
  • 如果源语言和目标语言一样,就是我们最常见的反射

元编程的几种形式

  • 文本宏语言,C宏,M4,Flex,Bison,Gperf
  • S表达式宏,lisp/scheme S表达式的特殊之处在于,他既是数据又是代码,因此S表达式宏可以很轻易的改变代码结构
  • 反射,动态数据结构变更

Python下元编程的几个手段

  • 预定义方法
  • 函数赋值
  • descriptor
  • 元类
  • eval

预定义方法

没啥好多说的,看下面这个例子:

class A(object):

    def __init__(self, o):
        self.__obj__ = o

    def __getattr__(self, name):
        if hasattr(self.__obj__, name):
            return getattr(self.__obj__, name)
        return self.__dict__[name]

    def __iter__(self):
        return self.__obj__.__iter__()

l = []
a = A(l)

for i in xrange(101): a.append(i)

print sum(a)

这是一个再简单不过的agent类,不过不怎么完美。因为__iter__属于预定义函数,不会调用__getattr__来获得。因此还需要额外定义。下面章节中,我们将看到一种简单的多的方法来实现agent类。

另外,提一点细节的差异。__getattr__,__setattr__相对还是比较上层的,至少在这两个函数中,可以访问__dict__。而__getattribute__这个函数中,使用self.__dict__会引发递归,需要用object.__getattribute__(self, name)。相对的,__getattribute__只能用于new style class。

同样,__getattr__,__setattr__,__getattribute__的用法不止于此。通过定义这三个函数,可以对类的成员做出非常多的变化。但是,和下面提到的手段比起来,这无疑是比较初级的。

函数赋值

我们看这个从socket.py中摘出来的例子:

_delegate_methods = ("recv", "recvfrom", "recv_into", "recvfrom_into",
                     "send", "sendto")

def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, _sock=None):
    if _sock is None:
        _sock = _realsocket(family, type, proto)
    self._sock = _sock
    for method in _delegate_methods:
        setattr(self, method, getattr(_sock, method))

当你调用s.recv(4)的时候,你以为自己在调用_socketobject的方法?错了,那方法其实是对应的_realsocket的。这是替换实例函数的例子。


这可以做什么用?我们来看我写的一个http代理装饰器。

def http_proxy(proxyaddr, username=None, password=None):
    def reciver(func):
        def creator(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
            sock = func(family, type, proto)
            sock.connect(proxyaddr)
            def newconn(addr): http_connect(sock, addr, username, password)
            sock.connect, sock.connect_ex = newconn, newconn
            return sock
        return creator
    return reciver

我们再看descriptor里面的这个例子:


class A(object):

    def b(self):
        print 'ok'

a = A()
print A.b, a.b
<unbound method A.b> <bound method A.b of <__main__.A object at 0x7f81620d9990>>
print a.b.im_self == a, a.b.im_func == A.b.im_func
True True
print A.__dict__['b'], A.b.im_func
<function b at 0x7f81620db500> <function b at 0x7f81620db500>

def c(self): print 'not ok'
A.b = c

print A.b, a.b
<unbound method A.c> <bound method A.c of <__main__.A object at 0x7f81620d9990>>
print a.b.im_self == a, a.b.im_func == A.b.im_func
True True
print A.__dict__['b'], A.b.im_func
<function c at 0x7f81620db488> <function c at 0x7f81620db488>

a.b()
not ok

这同样是函数替换,不过替换的是类函数方法。

Descriptor

所谓descriptor,就是带有__get__和__set__函数的对象。当访问某个对象的某个属性,这个属性又是一个descriptor时。返回值是descriptor的__get__调用的返回,set同理类推。带有__set__的称为data descriptor,只有__get__的称为non data descriptor。

python访问某个对象的某个属性时,是按照以下次序的:

  1. class的data descriptor。
  2. instance属性,无论其是否是descriptor,不调用__get__。
  3. class属性,包括non data descriptor。

使用descriptor,可以很容易的定义a.name之类获得值和设定的操作中需要执行什么。

实际上,我们使用的类函数就是基于descriptor做的。


class A(object):

    def b(self):
        print 'ok'

a = A()
print A.b, a.b
print a.b.im_self == a, a.b.im_func == A.b.im_func
print A.__dict__['b'], A.b
<function b at 0x7f81620db500> <unbound method A.b>

最后一个A.__dict__[‘b’], A.b,揭示了一个问题,两者不一致。至于为什么?那是因为descriptor在起作用,在A.b的时候,调用了某个__get__,将函数和类组合成和method对象丢了出来。这个__get__在哪里呢?我们来看这么个例子。


def f(self): print self['a'], 'ok'

print dir(f)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']  
f({'a': 1})
1 ok

o = {'a': 1}
m = f.__get__(o, dict)
print m
<bound method dict.f of {'a': 1}>
m()
1 ok

这可说的不能再明白了,function对象本身就具备__get__,是non data descriptor。按照上述的规则,排在instance之后。所以,我们给instance加载属性,可以重载掉类的函数。

我们看下面这个例子,这同样是从本公司的业务系统中摘出来简化的。


class Meta(type):
    def __new__(cls, name, bases, attrs):
        for k, v in attrs.items():
            if hasattr(v, '__meta_init__'): v.__meta_init__(k)
        return type.__new__(cls, name, bases, attrs)

class AttrBase(object):

    def __meta_init__(self, k): self.name = k
    def __get__(self, obj, cls): return obj[self.name]
    def __set__(self, obj, value): obj[self.name] = value

class Base(dict):
    __metaclass__ = Meta

class User(Base):

    name = AttrBase()

b = User()
b.name = 'shell'
print b
print b.name

注意到,当你访问b.name的时候,实际上是去访问了b[‘name’]。这个过程不是通过User类重载__getattr__实现的,而是通过descriptor。另外,我们处理这个例子的时候,用到了元类。下面一节介绍一下元类。

元类

我们先看这么一个例子:

class Base(dict):
    __metaclass__ = Meta
    def output(self, o): print 'hello, %s' % o
b = Base()
b.output('world')

你认为输出是什么?

再加上下面的代码呢?

class Meta(type):
    def __new__(cls, name, bases, attrs):
        output = attrs['output']
        attrs['output'] = lambda self, x: output(self, 'python')
        return type.__new__(cls, name, bases, attrs)

实际上,输出是hello, python。


为什么?我们要从type说起。在python中,出乎我们的意料,type不是一个函数,而是一个类。type的作用不仅仅可以显示某个对象属于哪个类,更重要的是,type可以动态的创建类。就像下面这样。

A = type('A', (object,), {'b': 1})
a = A()
print A, a.b

我们稍加变化,可以变成这样的代码。没什么区别。

def f(name, bases, attrs):
    attrs['c'] = 2
    return type(name, bases, attrs)

A = f('A', (object,), {'b': 1})
a = A()
print A, a.b, a.c

最后,我们把代码变成这个样子。

def f(name, bases, attrs):
    attrs['c'] = 2
    return type(name, bases, attrs)

class A(object):
    __metaclass__ = f
    b = 1

a = A()
print A, a.b, a.c

__metaclass__实际上,就是指创建类A的时候,要用什么函数进行生成。


可是且慢,type并不是一个函数,而是一个类阿。其实我们不妨这么看,类本身,可以视作是一个构造函数。

class A(object): pass
def B(): return A()

a = A()
b = B()
print a, b

由两者创建出来的对象并没有什么本质区别。所以,以下两个东西,其实在使用上是等价的。

class M(type):
    def __new__(cls, name, bases, attrs):
        attrs['c'] = 2
        return type.__new__(cls, name, bases, attrs)

def f(name, bases, attrs):
    attrs['c'] = 2
    return type(name, bases, attrs)

A = M('A', (object,), {'b': 1})
a = A()
print A, a.b, a.c

既然如此,我们当然可以在__metaclass__中,将f替换为M。

class M(type):
    def __new__(cls, name, bases, attrs):
        attrs['c'] = 2
        return type.__new__(cls, name, bases, attrs)

class A(object):
    __metaclass__ = M
    b = 1

a = A()
print A, a.b, a.c

这就是本文最上面的元类的来历。

我们甚至可以创建元类的元类。

class M1(type):
    def __new__(cls, name, bases, attrs):
        def f(cls, name, bases, attrs):
            attrs['c'] = 2
            return type.__new__(cls, name, bases, attrs)
        attrs['__new__'] = f
        return type.__new__(cls, name, (type,), attrs)

class M2(object):
    __metaclass__ = M1

class A(object):
    __metaclass__ = M2
    b = 1

a = A()
print A, a.b, a.c

当然,大家可能疑惑,为什么舍弃function,而使用元类。function固然简单,但是function是无法继承的。这里不仅仅指我们无法创建一个Meta的子类,扩充meta的行为。而且,使用function的类,一旦继承,其子类是不会管父类的__metaclass__定义的。

Eval

大家看看下面这个程序,谁能看出是干什么的?

exec(compile(__import__('zlib').decompress(
__import__('base64').b64decode('eJylU9Fq4zAQfPdX7FGKpOIqDZQ+BPIVfTlognDs
dSLOlowkN02/vru20jR3HIU7gbG0u5qZ1Ug3PxZjDIuddYvhlA7eFbYffEgQsIR4iiW8d3ZX
wq6K+PRYwh6TH1LRBt+Dj5CLhyodiqLBlmb1L9naDjmkVgXQONp0AD+g+0yUIIJQUEVo7VzD
I8A68+jd0yO62jcomV7Xvh8CxkgAOmDVSKXU36GPGdpfoFuvj8EmlEKImz9axjesJXMQhjRm
bsoYKZhcKN3gp4Cv2Vkr5Uktl5BacRuFUqRB0MewtCJKuIWgiiKg4Ri1GVCf+SjNwc1ZwOb5
jmnpd6GlxUzGkzPZRgqp75TYqM0VI68JVE1+jO5/HGl9gM46BOuu4jxsO6V0TFVIkRFl7ng1
OZmb1X2V6oPkUqX3wY9DlEv18rD9N/+m6/DFj8t9yQ4EvhpT6wfsBpkbHoJ1CcgdeF3qB2Cs
hA52J3imsk7/HNkj5teM6KoeJd1+XYX9K2lVv4G83I9boL7pNXyzr6BjMobjxsB6DcKYvrLO
GDELo8fU2ZhK4B10avP70vPvArVcbelgRjELaalwNpZdEPckngxqbJ1kxlOAXcTpNRbZLMa5
dpYivG9KQCunLvBtqFwzRgyS4vmVMdYqn2fxAdHyTCM=')),'', 'exec'))

看不出来是吧?那先看看这个例子:

def remove_list(li, obj):
    lic = li[:]
    lic.remove(obj)
    return lic

ops = ["+", "-", "*", "/"]
def gen_make(nums, *exes):
    if len(nums) == 0:
        try:
            if eval("".join(exes)) == 24: print "".join(exes).replace(".0", "")
        except: pass
    elif len(exes) == 0:
        for n in nums: gen_make(remove_list(nums, n), str(n) + ".0")
    else:
        if len(exes) > 1:
            exes = list(exes)
            exes.insert(0, '(')
            exes.append(')')
        for n in nums:
            for op in ops:
                gen_make(remove_list(nums, n), str(n) + ".0", op, *exes)

gen_make([3, 4, 6, 8])

这是我写的一个24点计算程序,相对有点取巧。核心是利用字符串拼装表达式,然后用eval看看是不是等于24。相对来说,不使用eval的代码就要复杂很多。当然,下面这个版本要完整很多。

from itertools import combinations

class opt(object):
    def __init__(self, name, func, ex=True):
        self.name, self.func, self.exchangable = name, func, ex
    def __str__(self): return self.name
    def __call__(self, l, r): return self.func(l, r)
    def fmt(self, l, r):
        return '(%s %s %s)' % (fmt_exp(l), str(self), fmt_exp(r))

def eval_exp(e):
    if not isinstance(e, tuple): return e
    try: return e[0](eval_exp(e[1]), eval_exp(e[2]))
    except: return None

def fmt_exp(e): return e[0].fmt(e[1], e[2]) if isinstance(e, tuple) else str(e)
def print_exp(e): print fmt_exp(e), eval_exp(e)

def chkexp(target):
    def do_exp(e):
        if abs(eval_exp(e) - target) < 0.001: print fmt_exp(e), '=', target
    return do_exp

def iter_all_exp(f, ops, ns, e=None):
    if not ns: return f(e)
    for r in set(ns):
        ns.remove(r)
        if e is None: iter_all_exp(f, ops, ns, r)
        else:
            for op in ops:
                iter_all_exp(f, ops, ns, (op, e, r))
                if not op.exchangable:
                    iter_all_exp(f, ops, ns, (op, r, e))
        ns.append(r)

opts = [
    opt('+', lambda x, y: x+y),
    opt('-', lambda x, y: x-y, False),
    opt('*', lambda x, y: x*y),
    opt('/', lambda x, y: float(x)/y, False),]

if __name__ == '__main__':
    iter_all_exp(chkexp(24), opts, [3, 4, 6, 8])

回到最上面的那个表达式,那是一个程序被zip后base64的结果。当然,这个结果字符串被写入一个程序中,程序会自动解开内容进行eval。这种方法能够将任何代码变为一行流。而这个被变换的程序,就是实现这个功能的。

语法合成转换

语法转换的最著名例子是orm,为什么?orm实际上,将python语法转换成了sql语法。

慎用元类

正派人士为什么畏惧黑魔法?因为元编程会破坏直觉。

作为一个受到多年训练的程序员,你应当对你熟练使用的语言有一种直觉。看到dict(zip(title, data))就应当想到,这是一个拼接数据生成字典的代码。看到[(k, v) for k, v in d.iteritems() if k…]就应当知道,这是一个过滤算法。这是在长期使用程序后形成的一种条件反射。

而元编程会很大程度的破坏这种直觉。这也是为什么我很讨厌C++的算符重载的原因。你能够想像么?o = a + b;这个表达式,其实想表达的是两颗特定条件的树的拼和(concat)过程,而非合并(merge)过程。每次使用重载过的系统,我都需要重新训练我的直觉系统。

python的元编程具有同样可怕的效果。还记得eval中那个自压缩的例子么?那是一个极端,将人类可理解的程序编码为了人类无法理解的。而meta的那个例子说明,元编程可以在不知不觉中修改原始的定义。

python中的元编程手段远远不止上述这些,很多时候,我们自己都毫无感觉。甚至,要修改一个行为,不一定需要元编程,重载同样也可以让人摸不着头脑。但是由于元编程的复杂性,用户更难在其中进行源码阅读,跟踪,调试。

在设计,规划这类代码的时候,必须注意。首先,你的设计需要尽量符合直觉,尽量让使用者感到舒服。其次,你需要比常规程序更多的文档,尽量减少用户在阅读源码上的时间——除非你万分的有信心,用户能够毫无障碍的阅读你的源码。最后,你需要比较精细的测试,和更多的,更友好错误处理。因为一旦发生异常,用户可能无法处理不友好的抛出。

ORM的意义和目标

为什么要用ORM

ORM的根本目的,是将关系型数据库模型转换为面对对象模型。此外,他还兼具了一些其他功能。例如:

  • 跨数据库
  • 对象缓存
  • 延迟执行

对象缓存

对象缓存的目的在于减少SQL的执行,增加程序执行速度,减少数据库开销。从某种意义来说,写的好的程序是不需要对象缓存的。但是这个“写的好”对程序设计提出了及其变态的要求。他要求无论程序由多少个组件组成,他们都必须能彼此传递数据,甚至知道对方的存在和细节,这样才能消除无效的查询和提交。但是这一要求使得代码之间产生了紧耦合,不利于系统的扩展。

Lazy Evaluation

lazy evaluation,中文翻译为惰性求值。指的是表达式的执行被延缓到真正需要值的时候。在ORM中,lazy evaluation一般是指查询过程不发生在查询语句生成的时候,而发生在实际发生数据请求的时候。

两者的区别在于,非lazy evaluation需要一次性完成表达式拼装,因此其逻辑是集中式的,不利于模块化。而lazy evaluation则可以将表达式逻辑的拼装分散在各个系统中。这同样是从系统耦合性和扩展性上来的需求。

另一种的lazy evaluation则是,在请求数据的时候只返回数据的一部分,当枚举到后续部分时再继续请求数据。如果情况合适,这个技巧可以有效减少计算开销和网络负载,或者减小响应时间。但是返回片段过小,请求过于频繁,应用场景不正确,反而会降低效率。

REDIS和RDBMS的区别

ACID

ACID是RDBMS的四个基本特性,即:

  • 原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
  • 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性限制没有被破坏。
  • 隔离性(Isolation,又称独立性):当两个或者多个事务并发访问(此处访问指查询和修改的操作)数据库的同一数据时所表现出的相互关系。
  • 持久性(Durability):在事务完成以后,该事务对数据库所作的更改便持久地保存在数据库之中,并且是完全的。

redis的ACID:

  • 原子性:redis本身是原子的,但是redis集群做不到原子性。redis只有一个线程(后台线程不处理实际业务),因此redis线性化处理每个指令。每个指令的处理都是不可打断的,原子化的。对于一系列指令,redis有pipeline。然而,如果使用kv将数据存储分布在多个节点上,那么实际上是无法保证多个节点同时成功或失败的。
  • 一致性:一般而言,在单个节点上,只要写的不是太差,满足原子性的多数都满足一致性。但是当冗余数据或者数据约束跨越多节点时,很容易发生不一致。
  • 隔离性:我们无法让redis满足隔离性,单节点也不行。
  • 持久性:根据配置,redis可能满足持久性。如果打开aof模式,redis的性能会大幅下降,但是此时满足持久性。如果使用dump模式或者干脆用Replication替代,那么显然不满足持久性。

ACID不完整造成的问题

  • redis不支持隔离性,实际上普通数据库中涉及隔离性往往也很晕很绕。因此如果你打算设计一个ORM来完成隔离性,我建议你更换数据库。

    在redis中解决这个问题的唯一方法是引入对象锁,包括全局锁,表锁,或者行级锁。但是这太重了。

  • 如果你的所有查询和写入都不冗余,也没有跨越多个对象的约束,那么多节点不会破坏一致性。

    例如,你在user中保存了用户的权限信息。在session中,为了加速访问,你复制了这一信息。那么,user和session中的数据构成冗余。当某个session的user和他不在同一个节点中的时候,我们无法通过pipeline保证对两者的操作同时完成或者同时失败。所以,可能会破坏一致性。 又例如,你需要限制只能有10个user具备管理员权限。这些user可能分布在多个不同的节点上,同样,我们也无法保证操作时一定不会破坏这个约束。 WAL(预写式日志Write-Ahead Logging)可以有效的解决这个问题,但是在redis这种轻量级业务系统上使用WAL怎么看都太重了。 当然,你可能觉得这没什么大不了。那是因为在这个例子中,两个数据有主次关系,即使一致性被破坏也无所谓。

  • redis中没有关系,因此你需要重新设计关系系统。然而双边关系系统会造成冗余数据,引发一致性问题。

    例如:我们的user对象有parent和children两个属性,是一个自身的一对多关系。有两个对象,user1和user2,刚好分别分布在node1和node2两个节点上。那么,user1和user2的关系建立过程需要同时修改node1和node2。如果不加以特殊的控制,很难保证node1和node2同步完成或失败。

  • redis中没有索引系统,因此无法使用where子句,也做不到unique。不过可以通过一个额外的键追踪数据来做到这点。然而如果你自制了索引系统,那么形成了冗余数据。因此,使用索引会引发一致性问题。

    一个比较好的解决方法是(我没实验过),在服务器端,利用lua来写一些过程,负责数据的查询。

REDISOBJ

Redisobj的对象框架

大家应该猜到了,在基于redis的ORM中,我们主要需要使用元类和descriptor两种元编程方法。下面是使用时的样例代码:

class User(redisobj.Base):
    username = redisobj.String()
    password = redisobj.String()
    priv = redisobj.Integer()
    domain = redisobj.ForeignKey('Domain')

class Domain(redisobj.Base):
    name = redisobj.String()

class UserGroup(redisobj.Base):
    name = redisobj.String()

def run(c):
    d1 = Domain(name='domain0')
    c.save(d1)

    u1 = User(username='user1', priv=1, domain=d1)
    u2 = User(username='user2', priv=1, domain=d1)
    ug1 = UserGroup(name='usergroup1')
    ug2 = UserGroup(name='usergroup2')
    c.save(u1, u2, ug1, ug2)

    c.flush()
    u3 = c.load_by_id(User, 1)
    print u3.copy()

    u3.priv = 2
    u3.password = 'abc'
    del u3.username
    c.save(u3)
    c.flush()
    u4 = c.load_by_id(User, 1)
    print u4.copy()

    c.delete(u2)
    try: c.load_by_id(User, 2)
    except LookupError: print 'yes, User1 disappeared.'
    print c.list(User)

我们分析一下上述代码。User,Domain,UserGroup三者,都是继承自redisobj.Base,而这个类,则是由元类创建的。因此,元类Meta可以轻易的替换其中的属性。我将所有继承自AttrBase的全部归并到一起。具体的DataType,例如Integer,String,都是派生自这个类。于是,Base的所有继承者,都可以用Class.__attrs__访问属性列表。而使用instance.prarmeter_name的时候,descriptor发生作用,读取Base中的具体数据。这大概构成了redisobj的对象框架。

Redisobj的Manager

manager是整个redisobj的核心,所有的保存,加载,都直接和manager打交道。当然,一种更加好看的方法是将manager全局化,然后在Base中添加方法(相应的,子类中需要添加类方法)来进行save/delete等行为。然而这将manager限定为全局只有一个(包括集群)。实际中碰到的很多例子,一个程序需要处理超过一个的redis(或者redis集群)。因此,我们在设计的时候保持了manager和object分离的设计思路。

不完整的ForeignKey

ForeignKey是所有数据类型中最特殊的一个,因为它重载了预定的descriptor。在我们的数据字典中,他和Integer没有区别(在关系上,ForeignKey也是Integer的一个子类)。然而,由于重载__get__和__set__。因此你可以认为obj.fk是一个对象。

注意,这里为什么重载descriptor,而不是直接将对象load入数字字典。因此被load入的对象也可能具备引用。反复引用之下,我们直接load对象的行为可能引发整个数据库被load入缓存的风险。而通过descriptor,我们可以在需要的时候载入对象,从而实现lazy evaluation。

但是这是不完整的行为!注意到redisobj里面只有FK,从来没有说反向引用,关系之类的说法。也就是说,当你在一个对象中保存另一个对象,可没有反向引用自动生成,当然也没有办法找到到底有多少个对象引用了当前对象。诚然,你可以自己做反向引用,然后自行添加。然而其中的不一致性问题需要自行解决。

 

作者:zihua | 转自:http://www.pythontip.com/blog/post/4159/

关于Javascript的元编程

相信大家对元编程多少有些了解,元编程简单说就是“编写代码的代码”,换个高雅解释即是“元编程是编写在运行时操纵语言构件的代码”。

反射用元编程解释就是,一门语言拥有对自身的元编程能力就表现在反射。

Demo短小,所以没多加过多的业务注释,毕竟代码很短,不是想告诉解决某一特殊的模式问题。只是想传达一种编程范式,不必深究其中的业务逻辑。

下面的demo问题模型如下:

    “一个部门有很多例如电脑之类的硬件资源,这些硬件需要IT部门管理,

     每台电脑又由很多零件组成(显卡,声卡,CPU,鼠标,键盘……),

     这个电脑财产的管理软件维护工作在你的手上,不过这个第一版不是你开发的,

     IT部门对你维护的此款管理硬件管理系统提出了一个新的需求,

      要求电脑组件价格超过100刀乐的要在输出前加上‘*’  ”

下面这个DS类是核心的底层接口类。你也可以叫它model层。封装了底层关于电脑的数据库。总之这个是别人给你提供的。你要做的就是在这个封装了数据信息的接口基础上做上层开发。

function DS( computorId ){
    
    this.computorId = computorId;
    //不同的电脑有不同的配置。
    this.data = {
        
        mouseInfo : "鼠标",
        mousePrice : "999",
        keyboardinfo : "键盘",
        keyboardprice : "888",

        lcdInfo : "驱动",

        lcdPrice : "888"

        /*
         *略去其他音响,声卡,先卡等属性
         */
    }
}

//DS 您可以当做数据库系统,或者前端开发过程中你也可以当做后台返回的json数据。 
DS.prototype = {
    constructor : DS,
    get_mouse_info : function() { return this.data.mouseInfo; }, //取回鼠标信息
    get_mouse_price : function() { return this.data.mousePrice; }, //取回鼠标价格
    get_keyboard_info : function() { return this.data.keyboardinfo; },//取回键盘信息

    get_keyboard_price : function() { return this.data.keyboardprice; }//取回键盘信息

    /*
     *还有其他一些关于显示器,音响,声卡等等信息和价格
     */
}

下面让我们看看最直观的方式

function Computer( id, data_source ){
    this.id = id;
    this.data_source = data_source;
}

//没有重构之前的源代码,这种写法中规中矩,初级程序员都会直观想到这种方式。作为大牛的你一定不会这么平庸的写代码
Computer.prototype = {
    mouse : function() {
        var info = this.data_source.get_mouse_info( this.id ),
            price = this.data_source.get_mouse_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }
    keyboard : function() {
        var info = this.data_source.get_keyboard_info( this.id ),
            price = this.data_source.get_keyboard_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }

    /*

   此处略去其他显示器,音响之类的信息。

   */
}
var zs = new Computer('2834750234', new DS(2342341244));

从上面的代码我们能直观分析出来,这种写法重复性很强,很多重复工作。典型硬编码。电脑有多少设备就需要手动的定义多少种取回设备信息价格的方法。可扩展性和维护性极差。

 

 

//第一次改进-动态派发。

function Computer( id, data_source ) {
    this.id = id; //电脑的Id信息
    this.data_source = data_source; //电脑的组件信息
}
Computer.prototype = {
    mouse : function() {
        return this.component( 'mouse' );
    },
    keyboard : function() {
        return this.component( 'keyboard' ); //动态的调用方法,只需传递方法的字符串参数。
    },

    //电脑其他显示器,音响之类的省略,如果问题模型的组件越多,此处的重复也很多,不过较之第一种,已经优化了许多。
    component : function( name ) {
        var methodName = 'get_' + name,
            info = this.data_source[ methodName + '_info' ]( this.id ), //我们首先要取出关于电脑的一些组件info
            price = this.data_source[ methodName + '_price' ]( this.id ),//其次我们要取出关于电脑的一些price
            result = "mouse:" + info + price;
            reuslt = price >= 100? "*" + result : result; //判断价格,高于100块的加上'*'
        return reuslt;
    }
}

var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log(comObj.keyboard());

 

我们看到较之第一种已经优化了很多,重复性工作也变少了,动态派发的小技巧全在javascript对象方法的动态调用。虽说抽象出通用层,但是还是避免不了硬编码,可维护性也较差。

 

 

 

进一步改进-动态创建方法,对于第二种方法,我们还是无法满足,毕竟重复工作还是占了很大一部分。作为一个大牛的你一定不会写出这种中级程序员的代码,于是你像高级程序员做法发起挑战

var Computer = function() {
    var AimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
    }

//第三种写法在于动态的创建方法,动态创建方法?你没听错,就是代码执行中创建方法,这要谢谢我们伟大的new Function(),以前对new Function着实无法理解,谁会这么定义一个function。
    AimClass.define_component = function( name ) {
        var name = name,
            fnBody = 'var methodName = "get_' + name + '",' +
                     'info = this.data_source[ methodName + "_info" ]( this.id ),' +
                     'price = this.data_source[ methodName + "_price" ]( this.id ),' +
                     'result = "mouse:" + info + price;' + 
                     'reuslt = price >= 100? "*" + result : result;' +
                     'return reuslt;'
        this.prototype[ name ] = new Function( 'name', fnBody );//此处是重点,动态创建方法(包括get_*_info,get_*_price);你再也不用手动的去定义那些讨厌的方法了。
        return this;
    }
    AimClass.define_component( 'mouse' ) //不过在此你还是要调用下你的类方法
            .define_component( 'keyboard' );//只需你把方法参数写进去就可以

    //这里还有很多关于显示器,音响之类的。从这里可以看出虽然第三种照第一,第二种优化了很多重复工作。可还是觉得还是需要调用很多这个创建实例方法的类方法
    return AimClass;
}()


var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log(comObj.keyboard());

动态定义方法 相比 动态派发 有了进一步的优化,但是这种优化不是颠覆性的。这里你还需要手动的过程即“AimClass.define_component()”。

 

 

改进之最后一步(内省方式进一步优化代码),终于我们来到终极改造,也是你作为大牛应该一展身手之处

var Computer = function( allMethod ){
    var aimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
    }
    AimClass.define_component = function( name ) {
        var name = name,
            fnBody = 'var methodName = "get_' + name + '",' +
                     'info = this.data_source[ methodName + "_info" ]( this.id ),' +
                     'price = this.data_source[ methodName + "_price" ]( this.id ),' +
                     'result = "mouse:" + info + price;' + 
                     'reuslt = price >= 100? "*" + result : result;' +
                     'return reuslt;'
        this.prototype[ name ] = new Function( 'name', fnBody );
    }
    for( var i in allMethod ) { //javascript对象内省机制。这种机制,解放了你的双手。不需要重复工作。不需要重复调用动态创建实例方法的类方法
        var reg = /^get_(.+)_info$/, str = '';
        if ( allMethod.hasOwnProperty( i ) && ( ( typeof allMethod[ i ] ) == 'function' ) && ( i != 'constructor' ) ) {
            str = i.replace( reg, '$1' );
            AimClass.define_component( str );
        }
    }
    return AimClass;
}( DS.prototype ) //把DS的所有方法穿入当参数。
var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log( comObj );
console.log( comObj.keyboard() );

最后的代码我们看到运用点正则的技巧还有对js对象(DS.prototype)内省机制。

 

我们看之间耍的小把戏已经让一个冗余的代码变得可维护性很强。

下面我们来看另外一种奇淫巧计。

由于javascript没有methodmissing这样迷人的内核方法。我自己模拟一个,当然这种模拟是有缺陷的。缺陷就是方法的调用是间接调用。而且模拟的方法不是内核方法。

var AimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
}
AimClass.prototype = {
    constructor: AimClass,

    methodmissing: function( name, args ) {
        var methodName = 'get_' + name;
        if ( !this.data_source[ methodName + '_info' ] ) {
            return '找不到此设备信息'
        }
        var info = this.data_source[ methodName + '_info' ]( this.id ),
            price = this.data_source[ methodName + '_price' ]( this.id ),
            result = "mouse:" + info + price;
        reuslt = price >= 100? "*" + result : result;
        console.log(result);
        return this;
    },
    
    methods: function() {
        var args = Array.prototype.slice.call( arguments ),
            methodName = args.shift() || undefined,
            methodArgs = args.length > 1? args : [];

        if ( typeof methodName == 'undefined' ) {
            return;
        } 
        if( this[ methodName ] ) {
            return this[ methodName ].apply( this, methodArgs );
        } else {
            return this[ 'methodmissing' ]( methodName, methodArgs );
        }
    }
}

var b = new AimClass( 12, new DS( '15' ) );

b.methods('keyboard').methods('www');

对象调用方法,例如obj.fn1();说白了过程不过就是向obj对象发送一条‘fn1’的消息,这里我们用b.methods来模拟发消息的过程。

以上代码大部分要做的事情是操作语言构件,而并非直接要处理业务逻辑。让代码去管理代码,好比你直接去管理各代码‘士兵’,不如设立一个代码‘将军’。

本文转自:http://www.cnblogs.com/liuyanlong/archive/2013/05/27/3102161.html

PHP chdir()

chdir

(PHP 4, PHP 5, PHP 7)

chdir改变目录

说明

bool chdir ( string $directory )

将 PHP 的当前目录改为 directory

参数

directory

新的当前目录

返回值

成功时返回 TRUE, 或者在失败时返回 FALSE

错误/异常

Throws an error of level E_WARNING on failure.

范例

Example #1 chdir() 例子

<?php

// current directory
echo getcwd() . "n";

chdir('public_html');

// current directory
echo getcwd() . "n";

?>

以上例程的输出类似于:

/home/vincent
/home/vincent/public_html

注释

Note: 当启用 安全模式时, PHP 会在执行脚本时检查被脚本操作的目录是否与被执行的脚本有相同的 UID(所有者)。

参见

新版百度分享如何自定义描述,图片,链接?

一、概述

百度分享代码已升级到2.0,本页将介绍新版百度分享的安装配置方法,请点击左侧列表查看相关章节。

二、代码结构

分享代码可以分为三个部分:HTML、设置和js加载,示例如下:

代码结构如下:

<div class="bdsharebuttonbox" data-tag="share_1">
	<!-- 此处添加展示按钮 -->
</div>
<script>
	window._bd_share_config = {
	//此处添加分享具体设置
	}

	//以下为js加载部分
	with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?cdnversion='+~(-new Date()/36e5)];
</script>

三、按钮标签

按钮标签代码

<div class="bdsharebuttonbox" data-tag="share_1">
	<a class="bds_mshare" data-cmd="mshare"></a>
	<a class="bds_qzone" data-cmd="qzone" href="#"></a>
	<a class="bds_tsina" data-cmd="tsina"></a>
	<a class="bds_baidu" data-cmd="baidu"></a>
	<a class="bds_renren" data-cmd="renren"></a>
	<a class="bds_tqq" data-cmd="tqq"></a>
	<a class="bds_more" data-cmd="more">更多</a>
	<a class="bds_count" data-cmd="count"></a>
</div>

说明:

只有普通页面分享需要按钮标签。划词分享、图片分享无需添加HTML结构。

HTML结构可以放在body的任意位置,可复制多份。

class=”bdsharebuttonbox” 部分为dom选择器,请勿改动。

data-tag属性为分享按钮标识,用于实现同一页面中多分享按钮不同配置,详见设置部分

data-cmd属性为分享目标标识,取值请参见:分享媒体id对应表。此外值为more时点击展现更多弹窗,值为count时展现分享数。

HTML代码中其他部分均可自定义。

四、自定义设置

设置部分结构如下,如不需要某项功能,删除相应的配置项即可。

设置:

<script>
	window._bd_share_config = {
		common : {
			//此处放置通用设置
		},
		share : [
			//此处放置分享按钮设置
		],
		slide : [
			//此处放置浮窗分享设置
		],
		image : [
			//此处放置图片分享设置
		],
		selectShare : [
			//此处放置划词分享设置
		]
	}
</script>

4.1 通用设置

通用设置将作用于所有分享类型,可将通用设置放于此处,如分享内容、分享url等。

通用设置

<script>
	window._bd_share_config = {
		common : {		
			bdText : '',	
			bdDesc : '',	
			bdUrl : '', 	
			bdPic : '',		
			...
		}
	}
</script>

通用设置项解析:

配置项名称 值类型 格式和取值 描述
bdText string 自定义 分享的内容
bdDesc string 自定义 分享的摘要
bdUrl string 自定义 分享的Url地址
bdPic string 自定义 分享的图片
bdSign string on|off|normal 是否进行回流统计。
‘on’: 默认值,使用正常方式挂载回流签名(#[数字签名])
‘off’: 关闭数字签名,不统计回流量
‘normal’: 使用&符号连接数字签名,不破坏原始url中的#锚点
bdMini int 1|2|3 下拉浮层中分享按钮的列数
bdMiniList array [‘qzone’,’tsina’,…] 自定义下拉浮层中的分享按钮类型和排列顺序。详见分享媒体id对应表
onBeforeClick function function(cmd,config){} 在用户点击分享按钮时执行代码,更改配置。
cmd为分享目标id,config为当前设置,返回值为更新后的设置。
onAfterClick function function(cmd){} 在用户点击分享按钮后执行代码,cmd为分享目标id。可用于统计等。
bdPopupOffsetLeft int 正|负数 下拉浮层的y偏移量
bdPopupOffsetTop int 正|负数 下拉浮层的x偏移量

4.2 分享按钮设置

分享按钮设置的值为数组或对象,值为数组时可对多个分享按钮应用不同的设置。

分享按钮设置

<script>
	window._bd_share_config = {
		share : [{
			"tag" : "share_1",
			"bdSize" : 32,
			...
		},{
			"tag" : "share_2",
			"bdSize" : 16,
			...
		}]
	}
</script>

分享按钮配置项解析:

配置项名称 值类型 格式和取值 描述
tag string 与data-tag一致 表示该配置只会应用于data-tag值一致的分享按钮。
如果不设置tag,该配置将应用于所有分享按钮。
bdSize int 16|24|32 分享按钮的尺寸
bdCustomStyle string 样式文件地址 自定义样式,引入样式文件

4.3 浮窗分享设置

浮窗分享设置的值为数组或对象,值为数组时可在页面显示多个分享浮窗。

浮窗分享设置

<script>
	window._bd_share_config = {
		slide : [{	   
			bdImg : 0,
			bdPos : "right",
			bdTop : 100
		},{
			bdImg : 0,
			bdPos : "left",
			bdTop : 100
		}]
	}
</script>

浮窗分享设置项解析:

配置项名称 值类型 格式和取值 描述
bdImg string 0|1|2|3|4|5|6|7|8 分享浮窗图标的颜色。
bdPos string left|right 分享浮窗的位置
bdTop int 分享浮窗与可是区域顶部的距离(px)

4.4 图片分享设置

图片分享设置的值为数组或对象,值为数组时可对图片应用不同的设置。

图片分享设置

<script>
	window._bd_share_config = {
		image : [{
			"tag" : "img_1",
			viewType : 'list',
			viewPos : 'top',
			viewColor : 'black',
			viewSize : '16',
			viewList : ['qzone','tsina','huaban','tqq','renren']
		},{
			"tag" : "img_2",
			viewType : 'list',
			viewPos : 'top',
			viewColor : 'black',
			viewSize : '16',
			viewList : ['qzone','tsina','huaban','tqq','renren']
		}]
	}
</script>

图片分享设置项解析:

配置项名称 值类型 格式和取值 描述
tag string 与data-tag一致 表示该配置只会应用于data-tag值一致的图片。如果不设置tag,该配置将应用于所有图片。
viewType string list|collection 图片分享按钮样式。
viewPos string top|bottom 图片分享展示层的位置。
viewColor string black|white 图片分享展示层的背景颜色。
viewSize int 16|24|32 图片分享展示层的图标大小。
viewList array [‘qzone’,’tsina’,…] 自定义展示层中的分享按钮类型和排列顺序。详见分享媒体id对应表

4.5 划词分享设置

划词分享设置

<script>
	window._bd_share_config = {
		selectShare : [{
			"bdSelectMiniList" : ['qzone','tqq','kaixin001','bdxc','tqf'],
			"bdContainerClass" : "容器class名"
		}]
	}
</script>

图片分享设置项解析:

配置项名称 值类型 格式和取值 描述
bdSelectMiniList array [‘qzone’,’tsina’,…] 自定义弹出浮层中的分享按钮类型和排列顺序。详见分享媒体id对应表
bdContainerClass string myclassname 自定义划词分享的激活区域

五、引入javascript

加载js

<script>
	with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?cdnversion='+~(-new Date()/36e5)];
</script>

说明:

请将代码放于</body>之前。

六、完整示例代码

完整代码如下,请根据自身情况修改。

完整示例代码

<div class="bdsharebuttonbox" data-tag="share_1">
	<a class="bds_mshare" data-cmd="mshare"></a>
	<a class="bds_qzone" data-cmd="qzone" href="#"></a>
	<a class="bds_tsina" data-cmd="tsina"></a>
	<a class="bds_baidu" data-cmd="baidu"></a>
	<a class="bds_renren" data-cmd="renren"></a>
	<a class="bds_tqq" data-cmd="tqq"></a>
	<a class="bds_more" data-cmd="more">更多</a>
	<a class="bds_count" data-cmd="count"></a>
</div>
<script>
	window._bd_share_config = {
		common : {
			bdText : '自定义分享内容',	
			bdDesc : '自定义分享摘要',	
			bdUrl : '自定义分享url地址', 	
			bdPic : '自定义分享图片'
		},
		share : [{
			"bdSize" : 16
		}],
		slide : [{	   
			bdImg : 0,
			bdPos : "right",
			bdTop : 100
		}],
		image : [{
			viewType : 'list',
			viewPos : 'top',
			viewColor : 'black',
			viewSize : '16',
			viewList : ['qzone','tsina','huaban','tqq','renren']
		}],
		selectShare : [{
			"bdselectMiniList" : ['qzone','tqq','kaixin001','bdxc','tqf']
		}]
	}
	with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?cdnversion='+~(-new Date()/36e5)];
</script>

七、分享媒体id对应表

分享媒体id对应表

名称 ID
印象笔记 evernotecn
网易热 h163
一键分享 mshare
QQ空间 qzone
新浪微博 tsina
人人网 renren
腾讯微博 tqq
百度相册 bdxc
开心网 kaixin001
腾讯朋友 tqf
百度贴吧 tieba
豆瓣网 douban
百度新首页 bdhome
QQ好友 sqq
和讯微博 thx
百度云收藏 bdysc
美丽说 meilishuo
蘑菇街 mogujie
点点网 diandian
花瓣 huaban
堆糖 duitang
和讯 hx
飞信 fx
有道云笔记 youdao
麦库记事 sdo
轻笔记 qingbiji
人民微博 people
新华微博 xinhua
邮件分享 mail
我的搜狐 isohu
摇篮空间 yaolan
若邻网 wealink
天涯社区 ty
Facebook fbook
Twitter twi
linkedin linkedin
复制网址 copy
打印 print
百度中心 ibaidu
微信 weixin
股吧 iguba

八、工具

检测分享代码版本

//打开已安装分享代码的页面,在控制台中执行:
javascript:b=(window.bdShare||window._bd_share_main);alert(b?'u5F53u524Du9875u9762u7684u5206u4EABu4EE3u7801u7248u672Cu4E3AuFF1A'+(b.version||'1.0'):'u5F53u524Du9875u9762u6CA1u6709u5B89u88C5u5206u4EABu4EE3u7801u3002')

55款最棒的DevOps开源工具

在介绍Devops工具之前,先跟随码花来了解下:Devops是个啥?

DevOps免费开源工具

Devops=【Development】+【Operations】。

简言之,Devops主要用于开发、测试、运维之间的沟通、协作与整合,减少开发和运营之间的摩擦,从而快速部署软件或应用程序,并且可以快速检测。

DevOps开源工具

作为小白,你可能就要问了:那,Devops到底是个什么样的存在形式,是个软件还是啥?

错!!!Devops既不是软件、也不是网站、更不是代码,而是一组方法、过程与系统的统称。

Devops包含了很多优秀想法和原则,它鼓励开发部门和运维部门通力合作。在DevOps环境中,开发人员和系统管理员会构建一些关系、流程和工具,从而更好的与客户互动,最终提供更好的服务。


简单了解了Devops是个啥之后,一起来看看下面这55款最棒的开源工具,可以帮助你很好的实行 DevOps。

DevOps开源工具

一、开发工具

版本控制&协作开发

1、版本控制系统 Git

Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。

2、代码托管平台 GitLab

GitLab是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。

3、代码评审工具 Gerrit

Gerrit是一个免费、开放源代码的代码审查软件,使用网页界面。利用网页浏览器,同一个团队的软件程序员,可以相互审阅彼此修改后的程序代码,决定是否能够提交,退回或者继续修改。它使用Git作为底层版本控制系统。

4、版本控制系统 Mercurial

Mercurial是一种轻量级分布式版本控制系统,采用 Python 语言实现,易于学习和使用,扩展性强。

5、版本控制系统 Subversion

Subversion 是一个版本控制系统,相对于的RCS、CVS,采用了分支管理系统,它的设计目标就是取代CVS。互联网上免费的版本控制服务多基于Subversion。

6、版本控制系统 Bazaar

Bazaar 是一个分布式的版本控制系统,它发布在 GPL 许可协议之下,并可用于 Windows、GNU/Linux、Unix 以及 Mac OS 系统。

二、自动化构建和测试

1、Apache Ant

Apache Ant是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。

2、Maven

Maven 除了以程序构建能力为特色之外,还提供 Ant 所缺少的高级项目管理工具。由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目,而使用 Ant 则需要十几行。事实上,由于 Maven 的面向项目的方法,许多 Apache Jakarta 项目现在使用 Maven,而且公司项目采用 Maven 的比例在持续增长。

3、Selenium (SeleniumHQ)

ThoughtWorks公司的一个集成测试的强大工具。

4、PyUnit

Python单元测试框架(The Python unit testing framework),简称为PyUnit, 是Kent Beck和Erich Gamma这两位聪明的家伙所设计的 JUnit 的Python版本。

5、QUnit

QUnit 是 jQuery 的单元测试框架。

6、JMeter

JMeter 是 Apache 组织的开放源代码项目,它是功能和性能测试的工具,100% 的用 java 实现。

7、Gradle

Gradle 就是可以使用 Groovy 来书写构建脚本的构建系统,支持依赖管理和多项目,类似 Maven,但比之简单轻便。

8、PHPUnit

PHPUnit 是一个轻量级的PHP测试框架。它是在PHP5下面对JUnit3系列版本的完整移植,是xUnit测试框架家族的一员(它们都基于模式先锋Kent Beck的设计)。

三、持续集成&交付

1、Jenkins

Jenkins 的前身是 Hudson,它是一个可扩展的持续集成引擎。

2、Capistrano

Capistrano 是一个用来并行的在多台机器上执行相同命令的工具,使用用来安装一整批机器。它最初是被开发用来发布 Rails 应用的。

3、BuildBot

BuildBot 是一个系统的自动化编译/测试周期最需要的软件,以验证代码的变化。通过自动重建和测试每次发生了变化的东西,在建设迅速查明之前,减少不必要的失败。

4、Fabric

fabric8 是开源 Java Containers(JVMs) 深度管理集成平台。有了 fabric8 可以非常方便的从 UI 和 UX 一致的中央位置进行自动操作,配置和管理。fabric8 同时提供一些非功能性需求,比如配置管理,服务发现故障转移,集中化监控,自动化等等。

5、Travis CI

Travis CI 是一个基于云的持续集成项目, 目前已经支持大部分主流语言了,比如:C,PHP,Ruby,Python,Nodejs等等。

6、Continuum

Apache Continuum 是最新的 CI 服务器之一,也是值得关注的一个新进入者。基于 Web 的界面使得配置项目很容易。而且,还不需要安装 Web 服务器,因为 Continuum 内置了 Jetty Web 服务器。

并且,Continuum 可以作为 Windows 服务运行,还在应用程序的某些部分嵌入了上下文敏感的文档,从而提供了很多帮助。

7、LuntBuild

LuntBuild 是一个强大自动构建的工具。通过一个简洁的web接口就可以很容易地进行系统的持续构建。

8、CruiseControl

CruiseControl 是一个针对持续构建程序(项目持续集成)的框架,它包括一个email通知的插件,Ant和各种各样的CVS工具。CruiseControl提供了一个Web接口,可随时查看当前的编译状况和历史状况。

9、Integrity

Integrity 是 Ruby 开发的持续集成服务器。

10、Gump

Gump 是 Apache 的整合工具。它以 Python 写成、完全支持 Apache Ant、Apache Maven 等等软件组建工具。

11、Go

Go 是 Google 开发的一种编译型,并发型,并具有垃圾回收功能的编程语言。

四、部署工具

容器平台

1、Docker

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

2、Rocket

Rocket (也叫 rkt)是 CoreOS 推出的一款容器引擎,和 Docker 类似,帮助开发者打包应用和依赖包到可移植容器中,简化搭环境等部署工作。

3、Ubuntu(LXC)

LXD 是 ubuntu 基于 LXC 技术的重构,容器天然支持非特权和分布式。LXD 与 Docker 的思路不同,Docker 是 PAAS,LXD 是 IAAS。LXC 项目由一个 Linux 内核补丁和一些 userspace 工具组成。

这些 userspace 工具使用由补丁增加的内核新特性,提供一套简化的工具来维护容器。

配置管理

1、Chef

Chef 是一个系统集成框架,为整个架构提供配置管理功能。

2、Puppet

Puppet,您可以集中管理每一个重要方面,您的系统使用的是跨平台的规范语言,管理所有的单独的元素通常聚集在不同的文件,如用户, CRON作业,和主机一起显然离散元素,如包装,服务和文件。

3、CFengine

Cfengine(配置引擎)是一种 Unix 管理工具,其目的是使简单的管理的任务自动化,使困难的任务变得较容易。Cfengine 适用于管理各种环境,从一台主机到上万台主机的机群均可使用。

4、Bash

Bash 是大多数Linux系统以及Mac OS X v10.4默认的shell,它能运行于大多数Unix风格的操作系统之上,甚至被移植到了Microsoft Windows上的Cygwin系统中,以实现windows的POSIX虚拟接口。此外,它也被DJGPP项目移植到了MS-DOS上。

5、Rudder

Rudder 已改名为Flannel,为每个使用 Kubernetes 的机器提供一个子网。也就是说 Kubernetes 集群中的每个主机都有自己一个完整的子网,例如机器 A 和 B 可以有 10.0.1.0/24 和 10.0.2.0/24 子网。

6、RunDeck

RunDeck 是用 Java/Grails 写的开源工具,帮助用户在数据中心或者云环境中自动化各种操作和流程。通过命令行或者web界面,用户可以对任意数量的服务器进行操作,大大降低了对服务器自动化的门槛。

7、Saltstack

Saltstack 可以看做是func的增强版+Puppet的弱化版。使用Python编写。非常好用,快速可以基于EPEL部署。Salt 是一个开源的工具用来管理你的基础架构,可轻松管理成千上万台服务器。

8、Ansible

Ansible 提供一种最简单的方式用于发布、管理和编排计算机系统的工具,你可在数分钟内搞定。Ansible 是一个模型驱动的配置管理器,支持多节点发布、远程任务执行。默认使用 SSH 进行远程连接。无需在被管理节点上安装附加软件,可使用各种编程语言进行扩展。

微服务平台

1、OpenShift

OpenShift 是由红帽推出的一款面向开源开发人员开放的平台即服务(PaaS)。 OpenShift通过为开发人员提供在语言、框架和云上的更多的选择,使开发人员可以构建、测试、运行和管理他们的应用。

2、Cloud Foundry

Cloud Foundry 是VMware于2011年4月12日推出的业界第一个开源PaaS云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

3、Kubernetes

Kubernetes 是来自 Google 云平台的开源容器集群管理系统。基于 Docker 构建一个容器的调度服务。该系统可以自动在一个容器集群中选择一个工作容器供使用。其核心概念是 Container Pod。

4、Mesosphere

Apache Mesos 是一个集群管理器,提供了有效的、跨分布式应用或框架的资源隔离和共享,可以运行Hadoop、MPI、Hypertable、Spark。

服务开通

1、Puppet

Puppet,您可以集中管理每一个重要方面,您的系统使用的是跨平台的规范语言,管理所有的单独的元素通常聚集在不同的文件,如用户, CRON作业,和主机一起显然离散元素,如包装,服务和文件。

2、Docker Swarm

Docker Swarm 是一个Dockerized化的分布式应用程序的本地集群,它是在Machine所提供的功能的基础上优化主机资源的利用率和容错服务。具体来说,Docker Swarm支持用户创建可运行Docker Daemon的主机资源池,然后在资源池中运行Docker容器。Docker Swarm可以管理工作负载并维护集群状态。

3、Vagrant

Vagrant 是一个基于 Ruby 的工具,用于创建和部署虚拟化开发环境。它使用 Oracle 的开源 VirtualBox 虚拟化系统,使用 Chef 创建自动化虚拟环境。

4、Powershell

5、OpenStack Heat

五、维护

日志记录

1、Logstash

Logstash 是一个应用程序日志、事件的传输、处理、管理和搜索的平台。你可以用它来统一对应用程序日志进行收集管理,提供 Web 接口用于查询和统计。

2、CollectD

collectd 是一个守护(daemon)进程,用来收集系统性能和提供各种存储方式来存储不同值的机制。比如以RRD 文件形式。

3、StatsD

StatsD 是一个简单的网络守护进程,基于 Node.js 平台,通过 UDP 或者 TCP 方式侦听各种统计信息,包括计数器和定时器,并发送聚合信息到后端服务,例如 Graphite。

六、监控,警告&分析

1、Nagios

Nagios 是一个监视系统运行状态和网络信息的监视系统。Nagios能监视所指定的本地或远程主机以及服务,同时提供异常通知功能等。

2、Ganglia

Ganglia 是一个跨平台可扩展的,高性能计算系统下的分布式监控系统,如集群和网格。它是基于分层设计,它使用广泛的技术,如XML数据代表,便携数据传输,RRDtool用于数据存储和可视化。

3、Sensu

Sensu 是开源的监控框架。主要特性:高度可组合;提供一个监控代理,一个事件处理器和文档 APIs;为云而设计;Sensu 的现代化架构允许监控大规模的动态基础设施,能够通过复杂的公共网络监控几千个全球分布式的机器和服务;热情的社区。

4、zabbix

zabbix 是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。

5、ICINGA

ICINGA 项目是 由Michael Luebben、HendrikB?cker和JoergLinge等人发起的,他们都是现有的Nagios项目社区委员会的成员,他们承诺,新的开源项目将完全兼容以前的Nagios应用程序及扩展功能。

6、Graphite

Graphite 是一个用于采集网站实时信息并进行统计的开源项目,可用于采集多种网站服务运行状态信息。Graphite服务平均每分钟有4800次更新操作。

7、Kibana

Kibana 是一个为 Logstash 和 ElasticSearch 提供的日志分析的 Web 接口。可使用它对日志进行高效的搜索、可视化、分析等各种操作。