REST风格的软件架构

REST风格的软件架构

REST(REpresentational State Transfer),表征状态转移,是一种针对分布式架构的软件架构风格,这种软件架构风格已经被广泛的应用在了Web API的设计上面,具有以下几个特点:

Resources 一切都是资源

分布在整个网络中的内容都是一种资源,并由URI来确定,资源可以是文本、图片、服务等实体的存在,并用URI来提供这种资源,例如, /book/2 可以用来提供id为2的一本书。

Representation 表现层

资源是一种实体,需要通过一种形式表现出来,这种形式可以是HTML、XML、JSON等文本格式,也可以是图片、音频或视频等多媒体格式,例如,通过访问 /book/2 返回了一下JSON数据:

1
2
3
4
5
{
"title": "Gone with the Wind",
"author": "Margaret Mitchell"
}

JSON数据就这本书的表现层。

State Transfer 显示的使用HTTP方法来转化资源的状态

使用HTTP规范中的GET、POST、PUT、DELETE方法来操作资源,例如

GET /book/2

可以获得id为2的一本书,HTTP方法与操作资源的对应关系如下

POST 创建或更新资源

GET 获取资源

PUT 更新资源

DELETE 删除资源

举例如下:
同样是访问 /book/2,

POST 创建一本书

GET 获取一本的信息

PUT 更新书的信息

DELETE 删除id为2的书

Stateless 无状态

服务器端不会保存请求之间的上下文状态。

Client-Server 基于C/S结构

Client端不关心数据如何存储,Server端也不关心怎样展现数据,因此,Client和Server可以分开部署,适用于iOS与Android等移动客户端与Server端的通信。

java泛型的理解

这是自己java泛型的理解,心得体会!

几年前当Java5还未正式发布的时候,看到过一些人写的介绍Tiger中的新特性,当时对我第一感觉冲击最大的就是泛型(generics)和注释(annotation),因为它们直接影响了我们编码的语法习惯。在后来的使用过程中,对于泛型一直没有特别深入的使用过,没有遇到那样的需求和场景。只需要了解Java中的泛型是编译期的,运行期被“擦拭”掉了;然后还有几种通配符的表示就足够了。

直到一天我在查看Java5中Enum的源代码时,发现它是这么定义的:

1
2
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {

这个类似递归结构的 Enum> 究竟表达什么意思?

随后我又看到了在Collections工具类中的max 方法:

1
2
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll) {

怎么TMD还会有这么复杂的泛型表达式!?(幸好的是这种情况在我们实际开发过程中不多见,甚至不应该见到,大概只有像JDK这种为了保留对老版本的兼容才会设计出这么复杂的泛型表达式出来。)

上面的问题,你可以通过:http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf 来获取答案,中文版的在:http://blog.csdn.net/explorers/archive/2005/08/15/454837.aspx
这篇泛型指南的文章,非常详细。

或许你看了上面的文章,依然心存疑虑。我下面的内容则是对这篇文档的一些补充(会和这篇文档有点重合)
回到我之前抛出的问题上:

1
2
3
4
5
6
7
8
public static void foo(List<? extends Number> l){
l.add(new Integer(2)); // 编译通过么? Why ?
}
public static void bar(List<? super Number> l){
l.add(new Integer(2)); // 编译通过么? Why ?
l.add(new Float(2)); // ok?
}

这里主要说说 <? extends T> 和 <? super T> 这两种通配符对于方法参数的使用原则。
即 PECS 原则 (producser-extends, consumer-super) 或者也叫 Get and Put 原则

当没有使用通配符的情况下,我们定义一个方法:

1
2
3
4
public static <E> void test(List<E> l){
E e = l.get(0);
l.set(0, e);
}

我们从List中 get和set都没有问题,因为这个E 它的类型是某种明确的类型。

而当使用通配符时来描述参数时,就有些不同了。

我们先定义一下两种通配符:

<? extends E> 是 Upper Bound(上限) 的通配符

<? super E> 是 Lower Bound(下限) 的通配符

1) 当使用 Upper Bound 通配符时:

1
2
3
4
public static void test(List<?> list){
Object e = list.get(0); // get OK
list.set(0, e); // set 编译报错
}

上面代码中通配符<?> 是 <? extends Object> 的简写。(关于<?>是否和<? extends Object>完全等价,我遇到个小插曲,在结束的时候来描述)

在eclipse里错误提示为:
The method set(int, capture#2-of ?) in the type List is not applicable for the arguments (int, Object)

注: 是一个占位符,表示编译器对通配符的捕获,更多见:

http://www.ibm.com/developerworks/cn/java/j-jtp04298.html

set报错的原因是因为此时方法中的类型是不可具体化的(reified),你可以传递一个String,Number,Book,等任何继承自Object的类作为List的参数类型给test方法,而list要求集合中的类型必须是一致的,set的时候没有办法保证set进去的数据类型是否和list中原本的类型一致,比如你传给test方法的是 List, 那么在方法中set进去一个Object显然类型就不一致了。这也是通配符带来灵活性的同时所要付出的代价。

结论:使用了 <? extends E> 这样的通配符,test方法的参数list变成了只能get不能set(除了null) 或者不严谨的说它变成了只读参数了, 有些类似一个生产者,提供数据。

2) 当使用 Lower Bound 的通配符时:

1
2
3
4
5
6
7
public static void test(List<? super Number> list){
Number n = list.get(0); // 编译错误
Object o = list.get(0); // OK
list.set(0, new Object()); // 编译错误
list.set(0, new Long(0)); // OK
list.set(0, new Integer(0)); // OK
}

这时get只能get出最宽泛的父类型,即Object。

这时set的时候,必须是Number或Number的子类。

原因和上面的get类似。

结论: 使用了<? super E> 这种通配符,test方法的参数list的get受到了很大的制约,只能最宽泛的方式来获取list中的数据,相当于get只提供了数据最小级别的访问权限(想想,你可能原本是放进去了一个Book,却只能当作Object来访问)。它更多适合于set的使用场景,像是一个消费者,主要用来消费数据。

上面便是对通配符的使用原则的说明,简单的说 PECS原则是指导我们在泛型方法中使用通配符的直接原则。参数作为生产者使用<? extends E>,作为消费者时使用<? super E> 。

那么说完了PECS原则,我们再回过头来分析那两个复杂的泛型表达式是怎么含义

1
class Enum<E extends Enum<E>>

它确实是一个 “递归类型限制”(recursive type bound)
要说明白这个问题,我们要先明白2点:

a) Enum可以理解为一组同一类型的有限的数据集合;

b) Java对Enum中的数据类型要求必须也是枚举类型(即必须是继承Enum类的)

对于 a) 我们先定义一个 Enum 表明定义了T这种类型作为它内部的数据类型,这么看它就像个普通的集合。

再来根据 b) 定义类型E,要求E必须是继承自 Enum,便成了 >
实际上 E和T是一回事,它们是同一类型, 所以它就是 >

暂停,可能我上面的表达不太合理可能会误人子弟,递归类型限制是有些抽象,它应该有严谨的数学描述,我想不清楚怎么表达,我先用另一个简单些例子来说明吧

1
public static <T extends Comparable<T>> T max(List<T> list)

这个方法用来获取list 中的最大值,它定义了一个 > 的类型表达式

这个递归类型限制的表达式容易理解一些,

> 表示的是:针对可以与自身进行比较的每个类型T。
或者说,每个实现了Comparable接口的类型T,比如 String 实现了 Comparable , Long实现了Comparable等等

而Enum因为使用enum关键字的原因,让我们忽略了它底层的实现其实也是

1
class EnumSample extends Enum<EnumSample>

这一事实,比如我们定义了 BoundKind 这样的一个枚举:

1
public enum BoundKind{ }

编译器会转换为:

1
public final class BoundKind extends java.lang.Enum<BoundKind>

看到了,这和 String implements Comparable 类似

这样我们套回到> 就是 >

这下好理解了, > 直接按字面理解:每个继承自Enum的类型E,比如

BoundKind 继承了 Enum

通过与>的对比,我们可以理解 >了。

那现在我们再回到Collections工具类中的max 方法:

1
2
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll) {

我们先简化一下这个表达式,看看> 怎么理解?

既然 > 我们都理解了,把Comparable改为

Comparable<? super T> 也没什么费解的

在《Java1.5 Generics Tutorial》一文中的解释是:
T 精确的(exactly)和自己能比较是不需要的。所需要的 是 T能 够和它的父类中的一个进行比较。

而《Effictive Java》第二版中对此是用 PECS原则来解释的:

下面是修改过的使用通配符类型的声明:

1
2
public static <T extends Comparable<? super T>>
T max(List<? extends T> list)

为了从初始声明中得到修改后的版本,要应用PECS转换两次,最直接的是运用到参数List。它产生T实例,因此将类型从List改为List<? extends T>。(ok好理解)
。。。。。。
更灵活的是运用到类型参数T。最初T 被指定用来扩展Comparable,但是T的comparable消费T实例(并产生表示顺序关系的整值)。因此,参数化类型Comparable被有限制通配符类型Comparable<? super T>取代。comparable始终是消费者,因此使用时始终应该是Comparable<? super T> 优先于 Comparable。

蓝色粗体的那句翻译的不好。还是看一下代码来理解吧:

1
2
3
4
5
6
7
8
9
10
11
public static <T extends Comparable<? super T>>
T max(Collection<? extends T> coll) {
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (next.compareTo(candidate) > 0) // here comparaTo
candidate = next;
}
return candidate;
}

第5行,Bloch认为 next.compareTo(cand) 是一句消费操作,在消费一个candidate对象时,根据PECS原则,candidate的类型应该使用 <? super T> 来提高它的灵活性。

我觉得Bloch将第5行当作消费操作挺别扭的,我个人偏向《Java1.5 Generics Tutorial》中的解释。
但归根到底,都是降低限制,提高比较时的灵活性。

最后,我们再来完整的理解:>
就只是比 > 多了一个限制(bounds)。

Object & Comparable<? super T> 是一个多限定(multiple bounds)的用法,

语法为: T1 & T2 … & Tn

一个有多个界限的类型的参数是所有界限中列出来的类型的子类。当多个界限被使用的时候,界限中的第一个类型被用作这个类型参数的erasure。

最终这个方法的返回值,按照第一个限定,擦拭为Object类型了。这是因为在以前版本中此方法就是返回的Object类型,需要兼容。

因为多限定(multiple bounds)的存在,泛型方法中又对应的增加了一个很不优雅的调用方式。下面用一段代码来说明:

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
public class GenericsTest {
static class Book {};
static class StoryBook extends Book implements Comparable<StoryBook> {
@Override
public int compareTo(StoryBook o) {
return 0; //FIXME
}};
static class TechBook extends Book implements Comparable<TechBook> {
@Override
public int compareTo(TechBook o) {
return 0; //FIXME
}};
public static <E> Set<E> merge(Set<? extends E> s1, Set<? extends E> s2) {
HashSet<E> newSet = new HashSet<E>(s1);
newSet.addAll(s2);
return newSet;
}
public static void main(String[] args) {
HashSet<StoryBook> s1 = new HashSet<StoryBook>();
HashSet<TechBook> s2 = new HashSet<TechBook>();
Set<Book> sb = merge(s1, s2); // 错误
// 需通过显式的类型参数(explicit type parameter)来告诉它要使用哪种类型
Set<Book> bs = GenericsTest.<Book>merge(s1,s2); //OK
// 或者
Set<Comparable<?>> s = GenericsTest.<Comparable<?>>merge(s1,s2);
}
}

上面直接调用merge(s1,s2) 那行代码错误的提示信息:
Type mismatch: cannot convert from Set> to Set
这归于泛型的类型推导(type inference),当无法推导出明确的类型时,就需要显式的描述,如上面代码中红色粗体字。

后注:
有关于 <?> 与 <? extends Object>是否是一回事
今天中午发现同事桌上有本《Java编程思想》第四版,随手翻了一下,发现泛型一章的介绍中,有句描述:“UnboundedWildcards.java 展示了编译器处理List<?>和List<? Extends Object>时是不同的。”
这让我奇怪,查看了一下它的代码,主要因为是对于Raw类型的造型为泛型时的警告信息不同。
将一个Raw的ArrayList造型给 List<?> 没有问题,而给List<? Extends Object>却会有警告。

在网上查了一下,发现对于<?>与<? extends Object>是否等同,是有些不同意见的。

http://mail.openjdk.java.net/pipermail/compiler-dev/2008-April/000316.html

http://bugs.sun.com/view_bug.do?bug_id=6559175

这个报告里,有两段代码反映了两者的不同:
(1)

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Object customer = null;
foo((List<? extends String>) customer ); //[1]
foo((List<? extends Object>) customer ); //[2] 编译有警告
foo((List<?>) customer ); //[3] 编译没有警告
}
public static void foo(List<?> list) {
}

(2)

1
2
Object o2 = new List<?>[3]; // 编译居然OK,估计直接当作Raw处理了
Object o3 = new List<? extends Object>[3]; // 报错

上面两段代码,表明了当与Raw类型造型时,<?>在编译器的处理方式的确与<? Extends Object>有所不同,根据场景它可能被编译器忽略掉泛型信息而直接当作Raw类型,而<? Extends Object>则不会。

但这种差异,有些吹毛求疵,除了跟Raw类型转换方面存在差异,在语义上两者可以认为是完全等同的,见:http://bugs.sun.com/view_bug.do?bug_id=6480391

The introduction of the capture conversion simplified a lot of things. One of the things it did is make “?” equivalent to “? extends Object”. Unfortunately, JLS3 doesn’t say they are equivalent.

SUN的开发人员回复说:

? should be considered equivalent to ? extends Object. I will note this at the end of the text about bounds for wildcards in 4.5.1.
……
Hence, Foo<?> is semantically equivalent to Foo<? extends Object>

但查了一下发现目前 JLS3中还依然没有增加他说要加的那句注释,见:

http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.5.1

我们暂从语义上认为两者相等。

java中使HttpDelete可以发送body信息

RESTful api中用到了DELETE方法,android开发的同事遇到了问题,使用HttpDelete执行DELETE操作的时候,不能携带body信息,研究了很久之后找到了解决方法。

我们查看httpclient-4.2.3的源码可以发现,methods包下面包含HttpGet,HttpPost,HttpPut,HttpDelete等类来实现http的常用操作。

其中,HttpPost继承自HttpEntityEnclosingRequestBase,HttpEntityEnclosingRequestBase类又实现了HttpEntityEnclosingRequest接口,实现了setEntity的方法。 而HttpDelete继承自HttpRequestBase,没有实现setEntity的方法,因此无法设置HttpEntity对象。

这样解决方法就明显了,我们可以自己实现一个MyHttpDelete类,继承自HttpEntityEnclosingRequestBase,覆盖其中的getMethod方法,使其返回“DELETE”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyHttpDelete extends HttpEntityEnclosingRequestBase {
//
public static final String METHOD_NAME = "DELETE";
//
public String getMethod() {
return METHOD_NAME;
}
//
public MyHttpDelete(final String uri) {
super();
setURI(URI.create(uri));
}
//
public MyHttpDelete(final URI uri) {
super();
setURI(uri);
}
//
public MyHttpDelete() {
super();
}
}

使用delete方法时,直接可以按下面方式操作

1
2
3
4
5
6
7
8
9
10
//
DefaultHttpClient httpClient = new DefaultHttpClient();
//
MyHttpDelete delete = new MyHttpDelete("http://url.com");
//
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
//
delete.setEntity(new UrlEncodedFormEntity(nameValuePairs));
//
HttpResponse response = httpClient.execute(delete);

按进程消耗内存多少排序

1
ps -eo rss,pmem,pcpu,vsize,args | sort -k 1 -r -n | less

解析一下:
ps 都知道,是linux,unix显示进程信息的,
-e 是显示所有进程,
-o是定制显示信息的格式

rss: resident set size, 表示进程占用RAM(内存)的大小,单位是KB
pmem: %M, 占用内存的百分比

pcpu:%C,占用cpu的百分比
vsize: 表示进程占用的虚拟内存的大小,KB

args:进程名(command)

sort命令对ps结果进行排序
-k 1:按第一个参数 rss进行排序

-r:逆序
-n:numeric,按数字来排序

javascript中实现简单的模板替换

最近搞了一下js,最不习惯的就是字符串生成都要用字符串拼装或者join的方式,所以尝试一下看能否实现简单的模板替换,效果还不错。

1
2
3
4
5
function str_format(str, obj) {
return str.replace(/\{\s*(\w+)\s*\}/g, function(_i, _1) {
return obj[_1] != null ? obj[_1] : '';
});
};

很多朋友会说性能差一些,可能确实如此,不过对我来说,相比编写的舒适来说,这点性能差别实在无足轻重了。

Python常见问题

is 和 == 的区别 与 列表的赋值问题

is 与 == 所代表的含义不同,is 表示“是”,说明里那个对象所引用的区域是相同的,而 == 表示“值相等”。两者区别可以通过下面的代码看出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> names= ["Daniel","Jackie"]
>>> n = names
>>> n is names
True
>>> n == names
True
>>> m = names[:]
>>> m is names
False
>>> m == names
True
>>> n[0] = 'Json'
>>> n
['Json', 'Jackie']
>>> names
['Json', 'Jackie']
>>>

当用 = 赋值时,names 与 n 引用同一块内存区域,所以names 与 n 是完全相同的,== 和 is 均返回True。所以改变 n[0] 会同时改变 names[0]的值,如代码最后一部分显示的一样。如果既想对列表进行改变值的操作,同时又希望保存旧版本供其他操作,这时候怎么办呢?这就需要复制整个列表的切片。如 m = names[ : ] 这样会产生一个names的副本,但是不是同一块内存区域,用 is 和 == 检查发现, is 返回 False , == 返回 True 也可以说明这一点。

字符串的格式化

简单的字符串格式化

1
2
3
4
5
>>> format = "Hello, %s. %s enough for ya?"
>>> values = ('world', 'Hot')
>>> print format % values
Hello, world. Hot enough for ya?
>>>

%s 为转换说明符,标记需要在此位置插入字符。

1
2
3
4
5
6
7
>>> '%10.8f' % pi
'3.14159265'
>>> '%.5s' % 'Daniel Hou'
'Danie'
>>> '%.*s' % (5, 'Daniel Hou')
'Danie'
>>>

%10.8f 10为宽度,不足时前面为空格,8为精度。 %.5s 则为从后面取出前5位,%.s 中 代表精度从后面的元组中读取。

1
2
3
4
>>> phonebook = { 'Hou' : 1234, 'Daniel':5678}
>>> "Hou's phone is %(Hou)s." % phonebook
"Hou's phone is 1234."
>>>

可以通过以上的方式来访问字典中的对应字段。括号中为字典的key

Python中的惰性求值

在逻辑运算存在。例如, a and b ,当 a 为 False 时,直接返回 False ,不再运算 b; a or b,当为 a 为 True 时,直接返回 a 的值, 否则返回 b 的值。避免了代码的无用计算。运用以上的特性可以实现 java 中的三元运算符的功能。例如:a if b or c当 b 为 真时,返回 a,否则返回 c

range的边界问题与xrange

1
2
3
>>> range(0,10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

可以看出,range不包含上界,相当于集合中的 [ a, b )xrang 函数作用类似于 range ,不同的地方在不 range 函数一次创建整个序列,而 xrange 一次只创建一个数,因此,需要迭代一个巨大的序列的时候, xrange 更加高效。

递归

实现 n 的阶乘,一般实现:

1
2
3
4
5
6
7
8
9
>>> def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
>>> factorial(10)
3628800
>>>

递归实现:

1
2
3
4
5
6
7
8
9
10
>>> def factorial(n):
if n == 1:
return 1
else:
return n*factorial(n-1)
>>> factorial(10)
3628800
>>>

使用递归实现二分法查找(binary search)

1
2
3
4
5
6
7
8
9
10
11
12
13
def search(seq, num, lower=0, upper=None):
upper = len(seq) - 1
if lower == upper :
return lower
else:
middle = (lower + upper) // 2
if num > seq[middle]:
return search(seq, num, middle + 1, upper)
else:
return search(seq, num, lower, middle)
seq = [1, 2, 3, 4, 5, 6, 7]
print search(seq, 7)

Java Script 编码规范

以下文档大多来自:

参考规范

前言

下面的章节描述的是一个 合理 的现代 JavaScript 开发风格指南,并非硬性规定。其想送出的核心理念是高度统一的代码风格(the law of code style consistency)。你为项目所择风格都应为最高准则。作为一个描述放置于你的项目中,并链接到这个文档作为代码风格一致性、可读性和可维护性的保证。

规范概要

1. JS/CSS 文件编码统一采用 UTF8编码

2. 代码缩进使用4个空格缩进替代 tab 缩进

3. 一行代码长度尽量保持80列左右

4. 如果编辑器支持在文件保存的时候自动删除行末和空行中的空格(注意:要么全部采用,要么全不采用,否则会产生过多的diff信息)

5. JS/CSS 最终发布到产品中的时候需要被压缩,以减小静态资源文件大小,提升页面加载速度

6. JS里声明变量必须加上 var 关键字,推荐一个 var 同时声明多个变量,或者一组有逻辑关系的变量,避免一个变量一个 var.

7. 使用 Array 和 Object 语法直接声明并将其初始化,更易读且性能更好, 而不使用 Array 和 Object 构造器.

8. JS里使用单引号 (‘) 优于双引号 (“).

9. JS代码结尾统一约定加’;’

10. 没有特殊原因避免使用 with/eval

11. 对于if/else等后面的语句即使只有一行代码也需要在该行代码的首尾加上’{}’.

12. 字符串拼接在少量(次数为个位数)的情况下可以使用’+’, 大量的时候使用数组 join(), 或者尽可能采用模板引擎渲染:比如jsRender等, 如果是Extjs可以采用XTemplate.

13. 对于数组赋值操作快于 push()操作, 所以尽量使用赋值操作.

14. for循环遍历:for(var i = 0, l = arr.length; i < l; i++){// doSomething here } 采用这种方式而不是 i < arr.length, 前一种方式只会计算一次 arr 的长度,而后一种方式会计算数组长度 arr.length + 1 次,效率比较低

15. 字符串转换为整数,推荐使用 parseInt(num, 10) 这种方式,+num 写法简单,在操作次数极少的情况下也可以酌情使用。

16. 变量比较的时候总是判断最好、最精确的值,推荐使用’===’少用’==’(可以参考jQuery代码里面, 可以看到只有在’== null’的时候才可能使用’==’,其他情况一律使用的是’===’).

17. JS里变量命名规范使用 functionNamesLikeThis, variableNamesLikeThis, ClassNamesLikeThis, EnumNamesLikeThis, methodNamesLikeThis, 和 SYMBOLIC_CONSTANTS_LIKE_THIS, 尤其不要跟python里面的变量命名方式混淆了.

18. JS文件名应该使用小写字符, 以避免在有些系统平台上不识别大小写的命名方式. 文件名以.js结尾, 不要包含除 ‘-‘ 和 ‘‘ 外的标点符号(使用 ‘-‘ 优于 ‘‘), 我们约定统一使用js-file-name.js这种类型,对于template文件命名方式为 template_name.html形式.

19. 所有的html DOM里面的id, Extjs配置项里面的id 以及所有样式里面的 class命名使用中划线,如’id-name’/‘class-name’.

20.公共的js第三方类库放在static/js/common/lib下,jQuery相关类库放在static/js/common/lib/jQplugin下,我们自己开发的公共类库放在static/js/common下

21. 鉴于有很多代码是复制粘贴过来的,所以大家要保证自己的代码风格良好且易于阅读,不然别人拷过去后不好的风格就蔓延开了,而且会导致其他人效仿。

22. 对于复制粘贴然后做相应修改以实现功能的代码,请务必清理干净,不要有’忘了删除的不影响逻辑的代码’,同时记得将变量名改成适合当前业务场景的有意义的变量名, 不要因为不影响逻辑就保留原来的不适合当前场景的名字

23. 对于系统中出现的大段注释的、过时的、废弃的代码务必及时清理干净,谁制造谁清理,否则其他人也不敢清理,越积越多

24. 不要使用魔法数字,尽量定义一个常量来表示该数字,并加上相应的注释,否则后期可能出现因为数字变化而导致牵一发而动全身,需要到处修改,增加维护成本

25. 注释尽量采用jsdoc的代码注释风格,普通业务代码不做要求,不过通用js类库要求尽量详尽以方便其他人阅读使用

26. 在开发相应功能的时候尽量抽象化、组件化、通用化:考虑这个东西其他地方会不会用到,能不能做成一个组件?而不是类似的代码到处复制、修改或者让大家都去写一遍

27. 类似地,在解决问题的时候要考虑下其他地方会不会存在同样的问题?能不能统一解决掉?尤其对于类似ExtJs的Bug这种,能不能做最少的改动解决所有同样的问题,类似于全局补丁.

28. 代码风格跟其他JS文件的代码风格保持一致

29. 新增、修改、查看等表单在 popup的时候需要重新初始化清除原来数据

30. 代码提交前用JSHint检查一下

前端资源Build

项目必须总是提供一些通用的方法来检验(can be linted)、测试和压缩源码以为产品阶段使用做准备。对于此类工作 Ben Alman 所写的 grunt 可谓首屈一指。通过简单的配置即可完成自动对CSS进行检查/压缩/合并,对JS进行检查/压缩/合并,对html文件进行压缩,删除创建目录,拷贝文件,压缩打包等,十分方便。

参考的grunt配置文件:

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
/**
* Grunt file for front end resource check/minify/compress.
*/
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
asi : true,
curly : false,
eqeqeq : true,
immed : false,
latedef : true,
newcap : true,
noarg : true,
sub : true,
undef : true,
boss : true,
eqnull : true,
smarttabs : true,
browser : true,
jquery : true,
white : false,
laxbreak : false,
laxcomma : true,
expr : true,
devel : false,
globals : {
module : true,
Mousetrap : true,
jQuery : true
}
},
all: ['Gruntfile.js', 'js/**/*.js', '!js/**/*.min.js', '!js/json2.js', '!js/jquery.simplemodal.js']
},
clean: {
release: {
src: ['release/']
}
},
uglify: {
options: {
banner: '/*! <%= pkg.description %> Ver: <%= pkg.version %> Date: <%= grunt.template.today("yyyy/mm/dd HH:MM:ss") %> */\n'
},
dist: {
// Grunt will search for "**/?.js" under "js/" when the "uglify" task
// runs and build the appropriate src-dest file mappings then, so you
// don't need to update the Gruntfile when files are added or removed.
files: [
{
expand : true, // Enable dynamic expansion.
cwd : 'js/', // Src matches are relative to this path.
src : ['jquery.simplemodal.js','index.js'], // Actual pattern(s) to match.
dest : 'release/js/' // Destination path prefix.
// ext : '.js' // Dest filepaths will have this extension. '.min.js' is recommeded.
}
]
}
},
cssmin: {
minify: {
expand : true,
cwd : 'style/',
src : ['*.css'],
dest : 'release/style/',
ext : '.css'
}
},
htmlmin: { // Task
dist: { // Target
options: { // Target options
removeComments: true,
collapseWhitespace: true
},
files: { // Dictionary of files
'release/index.html': 'index.html' // 'destination': 'source'
}
}
},
copy: {
main: {
files: [
{expand: true, cwd: '.', src: ['js/*.min.js', 'js/json2.js', 'images/*'], dest: 'release/'}
]
}
},
compress: {
main: {
options: {
archive: 'release/executable.zip' // make a zipfile
},
files: [
// {src: ['release/**'], dest: 'release/'}, // includes files in path and its subdirs
{expand: true, cwd: 'release/', src: ['**'], dest: 'executable/'} // makes all src relative to cwd
]
}
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.registerTask('min' , ['clean', 'uglify', 'cssmin', 'htmlmin', 'copy', 'compress']);
grunt.registerTask('check' , ['jshint']);
grunt.registerTask('default', ['jshint', 'clean', 'uglify', 'cssmin', 'htmlmin', 'copy', 'compress']);
};

规范详情

JavaScript 编码风格

空白
永远都不要混用空格和Tab。 开始一个项目,在写代码之前,选择软缩进(空格)或者 Tab(作为缩进方式),并将其作为最高准则。
为了可读, 我总是推荐在你的编辑中设计4个字母宽度的缩进 — 这等同于四个空格或者四个空格替代一个 Tab。 如果你的编辑器支持,请总是打开 “显示不可见字符” 这个设置。好处是:
保证一致性 去掉行末的空格
去掉空行的空格 提交和对比更具可读性

行末和空行
留白会破坏 diff 并使diff 结果变得更不可读。考虑包括一个预提交的 hook 自动删除行末和空行中的空格。
花括号, 换行

1
2
3
4
5
// if/else/for/while/try 通常都有小括号、花括号和多行
// 这有助于可读, 以下是很糟糕的写法
if(condition) doSomething();
while(condition) iterating++;
for(var i=0;i<100;i++) someIterativeFn();

命名

通常, 使用 functionNamesLikeThis, variableNamesLikeThis, ClassNamesLikeThis, EnumNamesLikeThis, methodNamesLikeThis, 和 SYMBOLIC_CONSTANTS_LIKE_THIS.

属性和方法

文件或类中的 私有 属性, 变量和方法名应该以下划线 “_” 开头.
保护 属性, 变量和方法名不需要下划线开头, 和公共变量名一样.

方法和函数参数

可选参数以 opt_ 开头.
函数的参数个数不固定时, 应该添加最后一个参数 var_args 为参数的个数. 你也可以不设置 var_args而取代使用 arguments.
可选和可变参数应该在 @param 标记中说明清楚. 虽然这两个规定对编译器没有任何影响, 但还是请尽量遵守
Getters 和 Setters
Getters 和 setters 并不是必要的. 但只要使用它们了, 就请将 getters 命名成 getFoo() 形式, 将 setters 命名成 setFoo(value) 形式. (对于布尔类型的 getters, 使用 isFoo() 也可.)

命名空间

JavaScript 不支持包和命名空间.
不容易发现和调试全局命名的冲突, 多个系统集成时还可能因为命名冲突导致很严重的问题. 为了提高 JavaScript 代码复用率, 我们遵循下面的约定以避免冲突.
为全局代码使用命名空间
在全局作用域上, 使用一个唯一的, 与工程/库相关的名字作为前缀标识. 比如, 你的工程是 “Project Sloth”, 那么命名空间前缀可取为 sloth.*.

1
2
3
4
var sloth = {};
sloth.sleep = function() {
...
};

许多 JavaScript 库, 包括 the Closure Library and Dojo toolkit 为你提供了声明你自己的命名空间的函数. 比如:

1
2
3
4
goog.provide('sloth');
sloth.sleep = function() {
...
};

明确命名空间所有权
当选择了一个子命名空间, 请确保父命名空间的负责人知道你在用哪个子命名空间, 比如说, 你为工程 ‘sloths’ 创建一个 ‘hats’ 子命名空间, 那确保 Sloth 团队人员知道你在使用 sloth.hats.
外部代码和内部代码使用不同的命名空间
“外部代码” 是指来自于你代码体系的外部, 可以独立编译. 内外部命名应该严格保持独立. 如果你使用了外部库, 他的所有对象都在 foo.hats. 下, 那么你自己的代码不能在 foo.hats.下命名, 因为很有可能其他团队也在其中命名.

1
2
3
4
5
6
7
8
foo.require('foo.hats');
/**
* WRONG -- Do NOT do this.
* @constructor
* @extend {foo.hats.RoundHat}
*/
foo.hats.BowlerHat = function() {
};

重命名那些名字很长的变量, 提高可读性
主要是为了提高可读性. 局部空间中的变量别名只需要取原名字的最后部分.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @constructor
*/
some.long.namespace.MyClass = function() {
};
/**
* @param {some.long.namespace.MyClass} a
*/
some.long.namespace.MyClass.staticHelper = function(a) {
...
};
myapp.main = function() {
var MyClass = some.long.namespace.MyClass;
var staticHelper = some.long.namespace.MyClass.staticHelper;
staticHelper(new MyClass());
};

除非是枚举类型, 不然不要访问别名变量的属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @enum {string} */
some.long.namespace.Fruit = {
APPLE: 'a',
BANANA: 'b'
};
myapp.main = function() {
var Fruit = some.long.namespace.Fruit;
switch (fruit) {
case Fruit.APPLE:
...
case Fruit.BANANA:
...
}
};
myapp.main = function() {
var MyClass = some.long.namespace.MyClass;
MyClass.staticHelper(null);
};

不要在全局范围内创建别名, 而仅在函数块作用域中使用.
文件名
文件名应该使用小写字符, 以避免在有些系统平台上不识别大小写的命名方式. 文件名以.js结尾, 不要包含除 - 和 外的标点符号(使用 - 优于 ).
自定义 toString() 方法
应该总是成功调用且不要抛异常.
可自定义 toString() 方法, 但确保你的实现方法满足: (1) 总是成功 (2) 没有其他负面影响. 如果不满足这两个条件, 那么可能会导致严重的问题, 比如, 如果 toString() 调用了包含 assert 的函数, assert 输出导致失败的对象, 这在 toString() 也会被调用.
延迟初始化
可以
没必要在每次声明变量时就将其初始化.
明确作用域
任何时候都需要
任何时候都要明确作用域 - 提高可移植性和清晰度. 例如, 不要依赖于作用域链中的 window 对象. 可能在其他应用中, 你函数中的 window 不是指之前的那个窗口对象.
代码格式化
大括号
分号会被隐式插入到代码中, 所以你务必在同一行上插入大括号. 例如:

1
2
3
4
5
if (something) {
// ...
} else {
// ...
}

数组和对象的初始化
如果初始值不是很长, 就保持写在单行上:

1
2
var arr = [1, 2, 3]; // No space after [ or before ].
var obj = {a: 1, b: 2, c: 3}; // No space after { or before }.

初始值占用多行时, 缩进2个空格.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Object initializer.
var inset = {
top: 10,
right: 20,
bottom: 15,
left: 12
};
// Array initializer.
this.rows_ = [
'"Slartibartfast" <fjordmaster@magrathea.com>',
'"Zaphod Beeblebrox" <theprez@universe.gov>',
'"Ford Prefect" <ford@theguide.com>',
'"Arthur Dent" <has.no.tea@gmail.com>',
'"Marvin the Paranoid Android" <marv@googlemail.com>',
'the.mice@magrathea.com'
];
// Used in a method call.
goog.dom.createDom(goog.dom.TagName.DIV, {
id: 'foo',
className: 'some-css-class',
style: 'display:none'
}, 'Hello, world!');

函数参数
尽量让函数参数在同一行上. 如果一行超过 80 字符, 每个参数独占一行, 并以4个空格缩进, 或者与括号对齐, 以提高可读性. 尽可能不要让每行超过80个字符. 比如下面这样:

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
// Four-space, wrap at 80. Works with very long function names, survives
// renaming without reindenting, low on space.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
};
// Four-space, one argument per line. Works with long function names,
// survives renaming, and emphasizes each argument.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
};
// Parenthesis-aligned indentation, wrap at 80. Visually groups arguments,
// low on space.
function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
// ...
}
// Parenthesis-aligned, one argument per line. Visually groups and
// emphasizes each individual argument.
function bar(veryDescriptiveArgumentNumberOne,
veryDescriptiveArgumentTwo,
tableModelEventHandlerProxy,
artichokeDescriptorAdapterIterator) {
// ...
}

传递匿名函数
如果参数中有匿名函数, 函数体从调用该函数的左边开始缩进2个空格, 而不是从 function 这个关键字开始. 这让匿名函数更加易读 (不要增加很多没必要的缩进让函数体显示在屏幕的右侧).

1
2
3
4
5
6
7
8
9
10
var names = items.map(function(item) {
return item.name;
});
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
if (a1.equals(a2)) {
someOtherLongFunctionName(a1);
} else {
andNowForSomethingCompletelyDifferent(a2.parrot);
}
});

更多的缩进
事实上, 除了 初始化数组和对象 , 和传递匿名函数外, 所有被拆开的多行文本要么选择与之前的表达式左对齐, 要么以4个(而不是2个)空格作为一缩进层次.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
someWonderfulHtml = '' +
getEvenMoreHtml(someReallyInterestingValues, moreValues,
evenMoreParams, 'a duck', true, 72,
slightlyMoreMonkeys(0xfff)) +
'';
thisIsAVeryLongVariableName =
hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();
thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();
someValue = this.foo(
shortArg,
'Some really long string arg - this is a pretty common case, actually.',
shorty2,
this.bar());
if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
!ambientNotification.isActive() && (client.isAmbientSupported() ||
client.alwaysTryAmbientAnyways()) {
ambientNotification.activate();
}

空行
使用空行来划分一组逻辑上相关联的代码片段.

1
2
3
4
5
doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);
nowDoSomethingWith(y);
andNowWith(z);

二元和三元操作符

操作符始终跟随着前行, 这样就不用顾虑分号的隐式插入问题. 如果一行实在放不下, 还是按照上述的缩进风格来换行.

1
2
3
4
5
6
7
8
var x = a ? b : c; // All on one line if it will fit.
// Indentation +4 is OK.
var y = a ?
longButSimpleOperandB : longButSimpleOperandC;
// Indenting to the line position of the first operand is also OK.
var z = a ?
moreComplicatedB :
moreComplicatedC;

括号
只在需要的时候使用
不要滥用括号, 只在必要的时候使用它.
对于一元操作符(如delete, typeof 和 void ), 或是在某些关键词(如 return, throw, case, new )之后, 不要使用括号.
字符串
使用 ‘ 优于 “
无论你选择单引号还是双引号都无所谓,在 JavaScript 中它们在解析上没有区别。而绝对需要强制的是一致性。 永远不要在同一个项目中混用两种引号,选择一种,并保持一致。
单引号 (‘) 优于双引号 (“). 当你创建一个包含 HTML 代码的字符串时就知道它的好处了.

1
var msg = 'This is some HTML';

前置逗号(Comma First)
请勿使用。所有使用这个文档作为基本风格指南的项目都不允许前置逗号的代码格式,除非明确指定或者作者要求。
注释
单行注释放于代码上方为首选
多行也可以
行末注释应被避免!
JSDoc 的方式也不错,但需要比较多的时间
使用 JSDoc
我们使用 JSDoc 中的注释风格. 行内注释使用 // 变量 的形式. 另外, 我们也遵循 C++ 代码注释风格 . 这也就是说你需要:
版权和著作权的信息,
文件注释中应该写明该文件的基本信息(如, 这段代码的功能摘要, 如何使用, 与哪些东西相关), 来告诉那些不熟悉代码的读者.
类, 函数, 变量和必要的注释,
期望在哪些浏览器中执行,
正确的大小写, 标点和拼写.
为了避免出现句子片段, 请以合适的大/小写单词开头, 并以合适的标点符号结束这个句子.
现在假设维护这段代码的是一位初学者. 这可能正好是这样的!
目前很多编译器可从 JSDoc 中提取类型信息, 来对代码进行验证, 删除和压缩. 因此, 你很有必要去熟悉正确完整的 JSDoc .
顶层/文件注释
顶层注释用于告诉不熟悉这段代码的读者这个文件中包含哪些东西. 应该提供文件的大体内容, 它的作者, 依赖关系和兼容性信息. 如下:

1
2
3
4
5
6
// Copyright 2009 Google Inc. All Rights Reserved.
/**
* @fileoverview Description of file, its uses and information
* about its dependencies.
* @author user@google.com (Firstname Lastname)
*/

类注释
每个类的定义都要附带一份注释, 描述类的功能和用法. 也需要说明构造器参数. 如果该类继承自其它类, 应该使用 @extends 标记. 如果该类是对接口的实现, 应该使用 @implements 标记.

1
2
3
4
5
6
7
8
9
10
11
/**
* Class making something fun and easy.
* @param {string} arg1 An argument that makes this more interesting.
* @param {Array.<number>} arg2 List of numbers to be processed.
* @constructor
* @extends {goog.Disposable}
*/
project.MyClass = function(arg1, arg2) {
// ...
};
goog.inherits(project.MyClass, goog.Disposable);

方法与函数的注释
提供参数的说明. 使用完整的句子, 并用第三人称来书写方法说明.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Converts text to some completely different text.
* @param {string} arg1 An argument that makes this more interesting.
* @return {string} Some return value.
*/
project.MyClass.prototype.someMethod = function(arg1) {
// ...
};
/**
* Operates on an instance of MyClass and returns something.
* @param {project.MyClass} obj Instance of MyClass which leads to a long
* comment that needs to be wrapped to two lines.
* @return {boolean} Whether something occured.
*/
function PR_someMethod(obj) {
// ...
}

对于一些简单的, 不带参数的 getters, 说明可以忽略.

1
2
3
4
5
6
/**
* @return {Element} The element for the component.
*/
goog.ui.Component.prototype.getElement = function() {
return this.element_;
};

属性注释
也需要对属性进行注释.

1
2
3
4
5
/**
* Maximum number of things per pane.
* @type {number}
*/
project.MyClass.prototype.someProperty = 4;

JSDoc 缩进
如果你在 @param, @return, @supported, @this 或 @deprecated 中断行, 需要像在代码中一样, 使用4个空格作为一个缩进层次.

1
2
3
4
5
6
7
8
9
10
/**
* Illustrates line wrapping for long param/return descriptions.
* @param {string} foo This is a param with a description too long to fit in
* one line.
* @return {number} This returns something that has a description too long to
* fit in one line.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};

不要在 @fileoverview 标记中进行缩进.
虽然不建议, 但也可对说明文字进行适当的排版对齐. 不过, 这样带来一些负面影响, 就是当你每次修改变量名时, 都得重新排版说明文字以保持和变量名对齐.

1
2
3
4
5
6
7
8
9
10
/**
* This is NOT the preferred indentation method.
* @param {string} foo This is a param with a description too long to fit in
* one line.
* @return {number} This returns something that has a description too long to
* fit in one line.
*/
project.MyClass.prototype.method = function(foo) {
return 5;
};

枚举

1
2
3
4
5
6
7
8
9
/**
* Enum for tri-state values.
* @enum {number}
*/
project.TriState = {
TRUE: 1,
FALSE: -1,
MAYBE: 0
};

注意一下, 枚举也具有有效类型, 所以可以当成参数类型来用.

1
2
3
4
5
6
7
/**
* Sets project state.
* @param {project.TriState} state New project state.
*/
project.setState = function(state) {
// ...
};

JavaScript 语言规范
变量
声明变量必须加上 var 关键字.
当你没有写 var, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突. 另外, 如果没有加上, 很难明确该变量的作用域是什么, 变量也很可能像在局部作用域中, 很轻易地泄漏到 Document 或者 Window 中, 所以务必用 var 去声明变量.

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
// 变量
var foo = "bar",
num = 1,
undef;
// 字面量标识:
var array = [],
object = {};
// 在一个作用域(函数)内只使用一个 `var` 有助于提升可读性
// 并且让你的声明列表变得有条不紊 (还帮你省了几次键盘敲击)
// 不好
var foo = "";
var bar = "";
var qux;
// 好的做法
var foo = "",
bar = "",
quux;
// 或者..
var // 对这些变量的注释
foo = "",
bar = "",
quux;
// `var` 语句必须总是在各自作用域(函数)顶部
// 同样适应于来自 ECMAScript 6 的常量
// 不好
function foo() {
// 在变量前有语句
var bar = "",
qux;
}
// 好
function foo() {
var bar = "",
qux;
// 所有语句都在变量之后
}

常量
常量的形式如: NAMES_LIKE_THIS, 即使用大写字符, 并用下划线分隔. 你也可用 @const 标记来指明它是一个常量. 但请永远不要使用 const 关键词.
Decision:
对于基本类型的常量, 只需转换命名.

1
2
3
4
5
/**
* The number of seconds in a minute.
* @type {number}
*/
goog.example.SECONDS_IN_A_MINUTE = 60;

对于非基本类型, 使用 @const 标记.

1
2
3
4
5
6
7
8
9
10
/**
* The number of seconds in each of the given units.
* @type {Object.<number>}
* @const
*/
goog.example.SECONDS_TABLE = {
minute: 60,
hour: 60 * 60,
day: 60 * 60 * 24
}

这标记告诉编译器它是常量.
至于关键词 const, 因为 IE 不能识别, 所以不要使用.
分号
总是使用分号.
如果仅依靠语句间的隐式分隔, 有时会很麻烦. 你自己更能清楚哪里是语句的起止. 而且有些情况下, 漏掉分号会很危险,可能会导致代码合并错误等,又比如:

1
2
3
4
5
6
7
8
9
10
// 1.
MyClass.prototype.myMethod = function() {
return 42;
} // No semicolon here.
(function() {
// Some initialization code wrapped in a function to create a scope for locals.
})();
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese] // No semicolon here.
// 2. conditional execution a la bash
-1 == resultOfOperation() || die();

这段代码会发生些什么诡异事呢?
报 JavaScript 错误 - 例子1上的语句会解释成, 一个函数带一匿名函数作为参数而被调用, 返回42后, 又一次被”调用”, 这就导致了错误.
当 resultOfOperation() 返回非 NaN 时, 就会调用die, 其结果也会赋给 THINGS_TO_EAT.
为什么?
JavaScript 的语句以分号作为结束符, 除非可以非常准确推断某结束位置才会省略分号. 上面的例子产出错误, 均是在语句中声明了函数/对象/数组直接量, 但 闭括号(‘}’或’]’)并不足以表示该语句的结束. 在 JavaScript 中, 只有当语句后的下一个符号是后缀或括号运算符时, 才会认为该语句的结束. 参考:JS分号自动插入机制
遗漏分号有时会出现很奇怪的结果, 所以确保语句以分号结束.
块内函数声明
不要在块内声明一个函数,不要写成:

1
2
3
if (x) {
function foo() {}
}

虽然很多 JS 引擎都支持块内声明函数, 但它不属于 ECMAScript 规范 (见 ECMA-262, 第13和14条). 各个浏览器糟糕的实现相互不兼容, 有些也与未来 ECMAScript 草案相违背. ECMAScript 只允许在脚本的根语句或函数中声明函数. 如果确实需要在块中定义函数, 建议使用函数表达式来初始化变量:

1
2
3
if (x) {
var foo = function() {}
}

标准特性
标准特性总是优于非标准特性.
最大化可移植性和兼容性, 尽量使用标准方法而不是用非标准方法, (比如, 优先用string.charAt(3) 而不用 string[3] , 通过 DOM 原生函数访问元素, 而不是使用应用封装好的快速接口.
不要封装基本类型
没有任何理由去封装基本类型, 另外还存在一些风险:

1
2
3
4
var x = new Boolean(false);
if (x) {
alert('hi'); // Shows 'hi'.
}

除非明确用于类型转换, 其他情况请千万不要这样做!

1
2
3
4
var x = Boolean(0);
if (x) {
alert('hi'); // This will never be alerted.
}
1
2
typeof Boolean(0) == 'boolean';
typeof new Boolean(0) == 'object';

有时用作 number, string 或 boolean时, 类型的转换会非常实用.
闭包
可以, 但小心使用.
闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档.
有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:

1
2
3
function foo(element, a, b) {
element.onclick = function() { /* uses a and b */ };
}

这里, 即使没有使用 element, 闭包也保留了 element, a 和 b 的引用, 由于 element 也保留了对闭包的引用, 这就产生了循环引用, 这就不能被 GC 回收. 这种情况下, 可将代码重构为:

1
2
3
4
5
6
7
function foo(element, a, b) {
element.onclick = bar(a, b);
}
function bar(a, b) {
return function() { /* uses a and b */ }
}
eval()

只用于解析序列化串 (如: 解析 RPC 响应), 而且解析序列号字符串用JSON.parse()会更好,所以最好放弃使用 eval().
eval() 会让程序执行的比较混乱, 当 eval() 里面包含用户输入的话就更加危险. 可以用其他更佳的, 更清晰, 更安全的方式写你的代码, 所以一般情况下请不要使用 eval(). 当碰到一些需要解析序列化串的情况下(如, 计算 RPC 响应), 使用 eval 很容易实现.
解析序列化串是指将字节流转换成内存中的数据结构. 比如, 你可能会将一个对象输出成文件形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
users = [
{
name: 'Eric',
id: 37824,
email: 'jellyvore@myway.com'
},
{
name: 'xtof',
id: 31337,
email: 'b4d455h4x0r@google.com'
},
...
];

很简单地调用 eval 后, 把表示成文件的数据读取回内存中.
类似的, eval() 对 RPC 响应值进行解码. 例如, 你在使用 XMLHttpRequest 发出一个 RPC 请求后, 通过 eval () 将服务端的响应文本转成 JavaScript 对象:

1
2
3
4
5
6
7
8
9
10
11
12
var userOnline = false;
var user = 'nusrat';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
xmlhttp.send('');
// Server returns:
// userOnline = true;
if (xmlhttp.status == 200) {
eval(xmlhttp.responseText);
}
// userOnline is now true.
with() {}

不要使用
使用 with 让你的代码在语义上变得不清晰. 因为 with 的对象, 可能会与局部变量产生冲突, 从而改变你程序原本的用义. 下面的代码是干嘛的?

1
2
3
4
with (foo) {
var x = 3;
return x;
}

答案: 任何事. 局部变量 x 可能被 foo 的属性覆盖, 当它定义一个 setter 时, 在赋值 3 后会执行很多其他代码. 所以不要使用 with 语句.
this
仅在对象构造器, 方法, 闭包中使用.
this 的语义很特别. 有时它引用一个全局对象(大多数情况下), 调用者的作用域(使用 eval时), DOM 树中的节点(添加事件处理函数时), 新创建的对象(使用一个构造器), 或者其他对象(如果函数被 call() 或 apply()).
使用时很容易出错, 所以只有在下面两个情况时才能使用:
在构造器中
对象的方法(包括创建的闭包)中
for-in 循环
只用于 object/map/hash 的遍历
对 Array 用 for-in 循环有时会出错. 因为它并不是从 0 到 length - 1 进行遍历, 而是所有出现在对象及其原型链的键值. 下面就是一些失败的使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function printArray(arr) {
for (var key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // This works.
var a = new Array(10);
printArray(a); // This is wrong.
a = document.getElementsByTagName('*');
printArray(a); // This is wrong.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // This is wrong again.
a = new Array;
a[3] = 3;
printArray(a); // This is wrong again.
// 而遍历数组通常用最普通的 for 循环.
function printArray(arr) {
var l = arr.length;
for (var i = 0; i < l; i++) {
print(arr[i]);
}
}

多行字符串
不要使用. 不要这样写长字符串:

1
2
3
4
5
6
var myString = 'A rather long string of English text, an error message \
actually that just keeps going and going -- an error \
message to make the Energizer bunny blush (right through \
those Schwarzenegger shades)! Where was I? Oh yes, \
you\'ve got an error and all the extraneous whitespace is \
just gravy. Have a nice day.';

在编译时, 不能忽略行起始位置的空白字符; “\” 后的空白字符会产生奇怪的错误; 虽然大多数脚本引擎支持这种写法, 但它不是 ECMAScript 的标准规范.
Array 和 Object 直接量
使用 Array 和 Object 语法, 而不使用 Array 和 Object 构造器.
使用 Array 构造器很容易因为传参不恰当导致错误.

1
2
3
4
5
6
7
8
9
10
// Length is 3.
var a1 = new Array(x1, x2, x3);
// Length is 2.
var a2 = new Array(x1, x2);
// If x1 is a number and it is a natural number the length will be x1.
// If x1 is a number but not a natural number this will throw an exception.
// Otherwise the array will have one element with x1 as its value.
var a3 = new Array(x1);
// Length is 0.
var a4 = new Array();

如果传入一个参数而不是2个参数, 数组的长度很有可能就不是你期望的数值了.
为了避免这些歧义, 我们应该使用更易读的直接量来声明.

1
2
3
4
var a = [x1, x2, x3];
var a2 = [x1, x2];
var a3 = [x1];
var a4 = [];

虽然 Object 构造器没有上述类似的问题, 但鉴于可读性和一致性考虑, 最好还是在字面上更清晰地指明. 例如:

1
2
3
4
5
6
var o = new Object();
var o2 = new Object();
o2.a = 0;
o2.b = 1;
o2.c = 2;
o2['strange key'] = 3;

应该写成:

1
2
3
4
5
6
7
var o = {};
var o2 = {
a: 0,
b: 1,
c: 2,
'strange key': 3
};

不要修改内置对象的原型
千万不要修改内置对象, 如 Object.prototype 和 Array.prototype 的原型. 而修改内置对象, 如 Function.prototype 的原型, 虽然少危险些, 但仍会导致调试时的诡异现象. 所以也要避免修改其原型.
JavaScript 小技巧(Tips and Tricks)
True 和 False 布尔表达式
下面的布尔表达式都返回 false:

1
2
3
4
null
undefined
'' // 空字符串
0 //数字0

但小心下面的, 可都返回 true:

1
2
3
'0' // 字符串0
[] // 空数组
{} // 空对象

下面段比较糟糕的代码:

1
while (x != null) {

你可以直接写成下面的形式(只要你希望 x 不是 0 和空字符串, 和 false):

1
while (x) {

如果你想检查字符串是否为 null 或空:

1
if (y != null && y != '') {

但这样会更好:

1
if (y) {

注意: 还有很多需要注意的地方, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Boolean('0') == true
'0' != true
0 != null
0 == []
0 == false
Boolean(null) == false
null != true
null != false
Boolean(undefined) == false
undefined != true
undefined != false
Boolean([]) == true
[] != true
[] == false
Boolean({}) == true
{} != true
{} != false
条件(三元)操作符 (?:)

三元操作符用于替代下面的代码:

1
2
3
4
5
if (val != 0) {
return foo();
} else {
return bar();
}

你可以写成:

1
return val ? foo() : bar();

在生成 HTML 代码时也是很有用的:

1
2
3
4
var html = '<input type="checkbox"' +
(isChecked ? ' checked' : '') +
(isEnabled ? '' : ' disabled') +
' name="foo">';

&& 和 ||
二元布尔操作符是可短路的, 只有在必要时才会计算到最后一项.
“||” 被称作为 ‘default’ 操作符, 因为可以这样:

1
2
3
4
5
6
7
8
9
10
/** @param {*=} opt_win */
function foo(opt_win) {
var win;
if (opt_win) {
win = opt_win;
} else {
win = window;
}
// ...
}

你可以使用它来简化上面的代码:

1
2
3
4
5
/** @param {*=} opt_win */
function foo(opt_win) {
var win = opt_win || window;
// ...
}

“&&” 也可简短代码.比如:

1
2
3
4
5
6
7
if (node) {
if (node.kids) {
if (node.kids[index]) {
foo(node.kids[index]);
}
}
}

你可以像这样来使用:

1
2
3
if (node && node.kids && node.kids[index]) {
foo(node.kids[index]);
}

或者:

1
2
3
4
var kid = node && node.kids && node.kids[index];
if (kid) {
foo(kid);
}

不过这样就有点儿过头了:

1
node && node.kids && node.kids[index] && foo(node.kids[index]);

使用 join() 来创建字符串
通常是这样使用的:

1
2
3
4
5
6
7
8
9
10
11
function listHtml(items) {
var html = '<div class="foo">';
for (var i = 0; i < items.length; ++i) {
if (i > 0) {
html += ', ';
}
html += itemHtml(items[i]);
}
html += '</div>';
return html;
}

但这样在 IE 下非常慢, 可以用下面的方式:

1
2
3
4
5
6
7
function listHtml(items) {
var html = [];
for (var i = 0; i < items.length; ++i) {
html[i] = itemHtml(items[i]);
}
return '<div class="foo">' + html.join(', ') + '</div>';
}

你也可以是用数组作为字符串构造器, 然后通过 myArray.join(‘’) 转换成字符串. 不过由于赋值操作快于数组的 push(), 所以尽量使用赋值操作.
类型检测 (来源于 jQuery Core Style Guidelines)
直接类型(实际类型,Actual Types)

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
String:
typeof variable === "string"
Number:
typeof variable === "number"
Boolean:
typeof variable === "boolean"
Object:
typeof variable === "object"
Array:
Array.isArray(arrayLikeObject)
(如果可能的话)
Node:
elem.nodeType === 1
null:
variable === null
null or undefined:
variable == null
undefined:
全局变量:
typeof variable === "undefined"
局部变量:
variable === undefined
属性:
object.prop === undefined
object.hasOwnProperty(prop)
"prop" in object

转换类型(强制类型,Coerced Types)
考虑下面这个的含义…
给定的 HTML:

1
<input type="text" id="foo-input" value="1">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// `foo` 已经被赋予值 `0`,类型为 `number`
var foo = 0;
// typeof foo;
// "number"
...
// 在后续的代码中,你需要更新 `foo`,赋予在 input 元素中得到的新值
foo = document.getElementById("foo-input").value;
// 如果你现在测试 `typeof foo`, 结果将是 `string`
// 这意味着你在 if 语句检测 `foo` 有类似于此的逻辑:
if (foo === 1) {
importantTask();
}
// `importantTask()` 将永远不会被执行,即使 `foo` 有一个值 "1"
// 你可以巧妙地使用 + / - 一元运算符强制转换类型以解决问题:
foo = +document.getElementById("foo-input").value;
// ^ + 一元运算符将它右边的运算对象转换为 `number`
// typeof foo;
// "number"
if (foo === 1) {
importantTask();
}
// `importantTask()` 将被调用

对于强制类型转换这里有几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var number = 1,
string = "1",
bool = false;
number;
// 1
number + "";
// "1"
string;
// "1"
+string;
// 1
+string++;
// 1
string;
// 2
bool;
// false
+bool;
// 0
bool + "";
// "false"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var number = 1,
string = "1",
bool = true;
string === number;
// false
string === number + "";
// true
+string === number;
// true
bool === number;
// false
+bool === number;
// true
bool === string;
// false
bool === !!string;
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var num = 2.5;
parseInt(num, 10);
// 等价于...
~~num;
num >> 0;
num >>> 0;
// 结果都是 2
// 时刻牢记心底, 负值将被区别对待...
var neg = -2.5;
parseInt(neg, 10);
// 等价于...
~~neg;
neg >> 0;
// 结果都是 -2
// 但是...
neg >>> 0;
// 结果即是 4294967294

字符串转换为整数
将字符串转换为整数有以下几种方式,可以在这里进行测试对比:

1
2
3
4
5
6
7
8
9
10
11
var number1 = "45";
// ParseInt() Test on chrome for mac:31% slower
var i = parseInt(number1);
// Using unary Test on chrome for mac:67% slower
var j = +number1;
// Number constructor Test on chrome for mac:59% slower
var k = Number(number1);
// By multplication Test on chrome for mac:69% slower
var l = number1 * 1;
// parseInt with Test on chrome for mac:radix ±3.85% fastest
var m = parseInt(number1, 10);

所以推荐使用 parseInt(number1, 10) 这种方式,不过 +number1 更为简单,在操作次数极少的情况下也可以酌情使用。
对比运算

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
// 当只是判断一个 array 是否有长度,相对于使用这个:
if (array.length > 0) ...
// ...判断真伪, 请使用这种:
if (array.length) ...
// 当只是判断一个 array 是否为空,相对于使用这个:
if (array.length === 0) ...
// ...判断真伪, 请使用这种:
if (!array.length) ...
// 当只是判断一个 string 是否为空,相对于使用这个:
if (string !== "") ...
// ...判断真伪, 请使用这种:
if (string) ...
// 当只是判断一个 string 是为空,相对于使用这个:
if (string === "") ...
// ...判断真伪, 请使用这种:
if (!string) ...
// 当只是判断一个引用是为真,相对于使用这个:
if (foo === true) ...
// ...判断只需像你所想,享受内置功能的好处:
if (foo) ...
// 当只是判断一个引用是为假,相对于使用这个:
if (foo === false) ...
// ...使用叹号将其转换为真
if (!foo) ...
// ...需要注意的是:这个将会匹配 0, "", null, undefined, NaN
// 如果你 _必须_ 是布尔类型的 false,请这样用:
if (foo === false) ...
// 如果想计算一个引用可能是 null 或者 undefined,但并不是 false, "" 或者 0,
// 相对于使用这个:
if (foo === null || foo === undefined) ...
// ...享受 == 类型强制转换的好处,像这样:
if (foo == null) ...
// 谨记,使用 == 将会令 `null` 匹配 `null` 和 `undefined`
// 但不是 `false`,"" 或者 0
null == undefined

总是判断最好、最精确的值,上述是指南而非教条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类型转换和对比运算说明
// 首次 `===`,`==` 次之 (除非需要松散类型的对比)
// `===` 总不做类型转换,这意味着:
"1" === 1;
// false
// `==` 会转换类型,这意味着:
"1" == 1;
// true
// 布尔, 真 & 伪
// 布尔:
true, false
// 真:
"foo", 1
// 伪:
"", 0, null, undefined, NaN, void 0
Misc

这个部分将要说明的想法和理念都并非教条。相反更鼓励对现存实践保持好奇,以尝试提供完成一般 JavaScript 编程任务的更好方案。
提前返回值提升代码的可读性并且没有太多性能上的差别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不好:
function returnLate(foo) {
var ret;
if (foo) {
ret = "foo";
} else {
ret = "quux";
}
return ret;
}
// 好:
function returnEarly(foo) {
if (foo) {
return "foo";
}
return "quux";
}

for循环遍历

1
2
3
4
5
6
7
8
9
// for循环遍历:
for(var i = 0, l = arr.length; i < l; i++){
// doSomething here
}
// 上面的方式优于:
for(var i = 0; i < arr.length; i++){
// doSomething here
}
// 前一种方式只会计算一次 arr 的长度,而后一种方式会计算 arr.length + 1 次,效率比较低

保持一致性.
当你在编辑代码之前, 先花一些时间查看一下现有代码的风格. 如果他们给算术运算符添加了空格, 你也应该添加. 如果他们的注释使用一个个星号盒子, 那么也请你使用这种方式.
代码风格中一个关键点是整理一份常用词汇表, 开发者认同它并且遵循, 这样在代码中就能统一表述. 我们在这提出了一些全局上的风格规则, 但也要考虑自身情况形成自己的代码风格. 但如果你添加的代码和现有的代码有很大的区别, 这就让阅读者感到很不和谐. 所以, 避免这种情况的发生.

python为mysql实现restful接口

最近在做游戏服务分层的时候,一直想把mysql的访问独立成一个单独的服务DBGate,原因如下:

  • 请求收拢到DBGate,可以使DBGate变为无状态的,方便横向扩展
  • 当请求量或者存储量变大时,mysql需要做分库分表,DBGate可以内部直接处理,外界无感知
  • 通过restful限制对数据请求的形式,仅支持简单的get/post/patch/put 进行增删改查,并不支持复杂查询。这个也是和游戏业务的特性有关,如果网站等需要复杂查询的业务,对此并不适合
  • DBGate使用多进程模式,方便控制与mysql之间的链接数,进行mysql访问量阀值保护
  • 方便在DBGate上进行访问量统计,慢查询统计、权限控制等等一系列逻辑
  • 目前是使用python,以后要使用其他语言进行mysql操作时,只要进行标准的http请求即可,不会出现不兼容的情况
    当然坏处也是有的:
  • 首当其冲就是单次请求的响应时间变长,毕竟中间加了一层服务,并且还是http格式
  • 部署上比原来复杂了一些,很多对mysql直接操作的思维需要进行转变,一开始可能会有些不适

不过总的来说,还是利大于弊,所以最终还是决定搭建DBGate

当然,我们不可能去手工挨个写每个库表对应的restful服务,值得庆幸的是django和flask都提供了对应的解决方案,我们一个个介绍.

Flask
参考链接: flask-restless

flask-restless使用方法比较简单,我直接贴一下代码即可:

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
# -*- coding: utf-8 -*-
import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restless import APIManager
app = Flask(__name__)
db = SQLAlchemy(app)
restless = APIManager(app, flask_sqlalchemy_db=db)
class User(db.Model):
"""
user
"""
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
create_time = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
login_time = db.Column(db.DateTime)
restless.create_api(User, methods=['GET', 'POST', 'DELETE', 'PATCH', 'PUT'], results_per_page=100)
db.create_all()
if __name__ == '__main__':
app.run(port=25000)

其对应的restful操作如下:

1
2
3
4
5
获取用户列表: GET /user
添加用户: POST /user
获取单个用户: GET /user/1
覆盖单个用户: PUT /user/1
修改单个用户: PATCH /user/1

注意:

  • 在http请求中,记得加入header: Content-Type: application/json
  • flask-restless中,PUT和PATCH一样,都是传入什么字段,只修改什么字段,不会完全覆盖

Django
参考链接: Django REST framework

Django用起来要更复杂一些,也因为django版自带了一个可视化的操作页面,如下:

  1. 在settings中添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
#'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
'rest_framework.permissions.IsAdminUser',
]
}
  1. 通过startapp建立一个app: demo

  2. 修改demo的models:

1
2
3
4
5
class User(models.Model):
# key是保留字
password = models.IntegerField()
nick = models.CharField(max_length=255)
create_time = models.DateTimeField(default=datetime.datetime.now)
  1. 在demo下新建serializers.py
1
2
3
4
5
6
7
from rest_framework import serializers
from models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
  1. 在demo下修改views.py
1
2
3
4
5
6
7
8
9
10
from django.shortcuts import render
from rest_framework import viewsets
from serializers import UserSerializer
from models import User
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
  1. 在demo下新建urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os.path
from django.conf.urls import patterns, include, url
from django.conf.urls.static import static
from django.conf import settings
import views
from rest_framework import routers
appname = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
router = routers.DefaultRouter()
router.register('users', views.UserViewSet, appname)
urlpatterns = patterns('',
url(r'^', include(router.urls)),
)
  1. 在mysite.urls下include demo.urls和rest_framework.urls
1
2
3
4
5
urlpatterns = patterns('',
url(r'^demo/', include('demo.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
)
  1. 执行初始化数据操作:
1
python manage.py syncdb

之后访问: http://127.0.0.1:8000/demo 即可看到如下界面了:

对应的测试代码如下:

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
import json
import requests
from urlparse import urljoin
BASE_URL = 'http://127.0.0.1:16500/'
AUTH = ('admin', 'admin')
def test_get_user_list():
rsp = requests.get(urljoin(BASE_URL, '/demo/users/'), auth=AUTH, headers={
'Accept': 'application/json'
})
assert rsp.ok
def test_post_user_list():
json_data = dict(
password=0,
nick='oo',
create_time='2014-03-3T03:3:3'
)
rsp = requests.post(urljoin(BASE_URL, '/demo/users/'), auth=AUTH, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
}, data=json.dumps(json_data))
assert rsp.ok
def test_get_user():
rsp = requests.get(urljoin(BASE_URL, '/demo/users/1'), auth=AUTH, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
})
assert rsp.ok
def test_put_user():
json_data = dict(
password=100,
nick='xx',
create_time='2014-03-3T03:3:3'
)
# 注意最后的 /
rsp = requests.put(urljoin(BASE_URL, '/demo/users/1/'), auth=AUTH, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
}, data=json.dumps(json_data),
)
assert rsp.ok, rsp.status_code
def test_patch_user():
json_data = dict(
password=300,
)
rsp = requests.patch(urljoin(BASE_URL, '/demo/users/1/'), auth=AUTH, headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
}, data=json.dumps(json_data),
)
assert rsp.ok, rsp.status_code

Django REST framework 是严格区分PUT和PATCH的,这一点和flask-restless 不一样,需要注意。

java在运行时动态增加枚举类型

最近在使用一个内部框架的时候,希望能够在运行时指定枚举类型,却发现这是一件挺麻烦的事情(不找别的替代方式,就是要动态增加enum的类型),方法也不正统,不过作为有趣的尝试,研究研究也无妨,下面的内容主要来自于《Java–create enum instances dynamically》这样一篇文章。在一切开始前,如果你想问,为什么非要增加/改变enum类型?其实这是一个非常好的问题,多数情况下这是应对被避免的,但是这不在今天我的讨论范围内。

一、方法 void addEnum(Class enumType, String enumName),用于增加一个枚举类型:

/**
 * Add an enum instance to the enum class given as argument
 * 
 * @param the type of the enum (implicit)
 * @param enumType the class of the enum to be modified
 * @param enumName the name of the new enum instance to be added to the class.
 */
@SuppressWarnings("unchecked")
public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName) {

    // 0. Sanity checks
    if (!Enum.class.isAssignableFrom(enumType)) {
        throw new RuntimeException("class " + enumType + " is not an instance of Enum");
    }
    // 1. Lookup "$VALUES" holder in enum class and get previous enum instances
    Field valuesField = null;
    Field[] fields = TestEnum.class.getDeclaredFields();
    for (Field field : fields) {
        if (field.getName().contains("$VALUES")) {
            valuesField = field;
            break;
        }
    }
    AccessibleObject.setAccessible(new Field[] { valuesField }, true);

    try {

        // 2. Copy it 
        T[] previousValues = (T[]) valuesField.get(enumType);
        List values = new ArrayList(Arrays.asList(previousValues));

        // 3. build new enum
        T newValue = (T) makeEnum(enumType, // The target enum class 
                enumName, // THE NEW ENUM INSTANCE TO BE DYNAMICALLY ADDED 
                values.size(),
                new Class<><[] {}, // can be used to pass values to the enum constuctor
                new Object[] {}); // can be used to pass values to the enum constuctor

        // 4. add new value
        values.add(newValue);

        // 5. Set new values field
        setFailsafeFieldValue(valuesField, null, 
                values.toArray((T[]) Array.newInstance(enumType, 0)));

        // 6. Clean enum cache
        cleanEnumCache(enumType);

    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e.getMessage(), e);
    }
}

基于反射,把枚举类的属性列表全部取出来,增加一个新的枚举类型以后再放回去。上面的第3、5、6步下面分别说明。

二、在其中使用到了 makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) 这样一个方法:

private static Object makeEnum(Class<?> enumClass, String value, int ordinal, 
        Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
    Object[] parms = new Object[additionalValues.length + 2];
    parms[0] = value;
    parms[1] = Integer.valueOf(ordinal);
    System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
    return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
}

private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, 
        Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
    Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
    parameterTypes[0] = String.class;
    parameterTypes[1] = int.class;
    System.arraycopy(additionalParameterTypes, 0, 
            parameterTypes, 2, additionalParameterTypes.length);
    return reflectionFactory.newConstructorAccessor(
            enumClass.getDeclaredConstructor(parameterTypes));
}

这是真正使用newInstance方法来创建新的枚举对象的方法,注意其中的构造器参数类型,第一个是String,第二个是int,这是需要符合enum内部实现的。

三、setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException, IllegalAccessException,这个修改方法modifier并设值的方法:

private static void setFailsafeFieldValue(Field field, Object target, Object value)
         throws NoSuchFieldException, IllegalAccessException {

    // let's make the field accessible
    field.setAccessible(true);

    // next we change the modifier in the Field instance to
    // not be final anymore, thus tricking reflection into
    // letting us modify the static final field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    int modifiers = modifiersField.getInt(field);

    // blank out the final bit in the modifiers int
    modifiers &= ~Modifier.FINAL;
    modifiersField.setInt(field, modifiers);

    FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
    fa.set(target, value);
}

四、清除枚举缓存:cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException:

private static void cleanEnumCache(Class<?> enumClass) 
        throws NoSuchFieldException, IllegalAccessException {
    blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
    blankField(enumClass, "enumConstants"); // IBM JDK
}

private static void blankField(Class<?> enumClass, String fieldName) 
        throws NoSuchFieldException, IllegalAccessException {
    for (Field field : Class.class.getDeclaredFields()) {
        if (field.getName().contains(fieldName)) {
            AccessibleObject.setAccessible(new Field[] { field }, true);
            setFailsafeFieldValue(field, enumClass, null);
            break;
        }
    }
}

最终的运行时测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static enum TestEnum {
a,
b,
c;
};
public static void main(String[] args) {
// Dynamically add 3 new enum instances d, e, f to TestEnum
addEnum(TestEnum.class , "d");
addEnum(TestEnum.class , "e");
addEnum(TestEnum.class , "f");
// Run a few tests just to show it works OK.
System.out.println(Arrays.deepToString(TestEnum.values()));
// Shows : [a, b, c, d, e, f]
}

oracle 临时表空间扩容

临时表空间主要用途是在数据库进行排序运算、管理索引、访问视图等操作时提供临时的运算空间,当运算完成之后系统会自动清理。临时表空间不足时会影响系统性能。

查询临时表空间

1
2
3
4
sql>select file_name,tablespace_name,bytes/1024/1024 as "total temp space(M)" from dba_temp_files;
FILE_NAME TABLESPACE_NAME total temp space(M)
-------------------------------------- --------------------- -------------------
/home/database/oracle/oradata/zuobian/temp01.dbf TEMP 512

查看临时表空间剩余容量

1
2
3
4
SQL> select tablespace_name,bytes_free/1024/1024 "free temp space(M)" from v$temp_space_header;
TABLESPACE_NAME free temp space(M)
-------------------------- ------------------
TEMP 256

重新设定临时表空间容量

1
2
SQL> alter database tempfile '/home/database/oracle/oradata/zuobian/temp01.dbf' resize 1024M;
Database altered