邵珠庆の日记 生命只有一次,你可以用它来做很多伟大的事情–Make the world a little better and easier


274月/13

PHP中设置Session过期方法

发布在 邵珠庆

Session的有效期是随浏览器进程的,浏览器关掉后就没了,那么怎么像Cookies那样设置过期时间呢?下面这段代码就可以实现。

 
1
2
3
4
5
<?php
$cookies_life_time = 24 * 3600;   //过期时间,单位为秒,这里的设置即为一天
session_start();
setcookie(session_name() ,session_id(), time() + $cookies_life_time, "/");
?>
244月/13

PHP读取文件的常见方法

发布在 邵珠庆

整理了一下PHP中读取文件的几个方法,方便以后查阅。

1.fread

string fread ( int $handle , int $length )

fread() 从 handle 指向的文件中读取最多 length 个字节。该函数在读取完最多 length 个字节数,或到达 EOF 的时候,或(对于网络流)当一个包可用时,或(在打开用户空间流之后)已读取了 8192 个字节时就会停止读取文件,视乎先碰到哪种情况。

fread() 返回所读取的字符串,如果出错返回 FALSE。

复制代码
<?php
    $filename = "/usr/local/something.txt";
    $handle = fopen($filename, "r");//读取二进制文件时,需要将第二个参数设置成'rb'
    
    //通过filesize获得文件大小,将整个文件一下子读到一个字符串中
    $contents = fread($handle, filesize ($filename));
    fclose($handle);
?>
复制代码

如果所要读取的文件不是本地普通文件,而是远程文件或者流文件,就不能用这种方法,因为,filesize不能获得这些文件的大小。此时,你需要通过feof()或者fread()的返回值判断是否已经读取到了文件的末尾。

例如:

复制代码
<?php
    $handle = fopen('http://www.baidu.com', 'r');
    $content = '';
    while(!feof($handle)){
        $content .= fread($handle, 8080);
    }
    echo $content;
    fclose($handle);
?>
复制代码

或者:

复制代码
<?php
    $handle = fopen('http://www.baidu.com', 'r');
    $content = '';
    while(false != ($a = fread($handle, 8080))){//返回false表示已经读取到文件末尾
        $content .= $a;
    }
    echo $content;
    fclose($handle);
?>
复制代码

 

2.fgets

 string fgets ( int $handle [, int $length ] )

  fgets()从 handle 指向的文件中读取一行并返回长度最多为 length - 1 字节的字符串。碰到换行符(包括在返回值中)、EOF 或者已经读取了 length - 1 字节后停止(看先碰到那一种情况)。如果没有指定 length,则默认为 1K,或者说 1024 字节。

复制代码
<?php
    $handle = fopen('./file.txt', 'r');
    while(!feof($handle)){
        echo fgets($handle, 1024);
    }
    fclose($handle);
?>
复制代码

Note: length 参数从 PHP 4.2.0 起成为可选项,如果忽略,则行的长度被假定为 1024。从 PHP 4.3 开始,忽略掉 length 将继续从流中读取数据直到行结束。如果文件中的大多数行都大于 8KB,则在脚本中指定最大行的长度在利用资源上更为有效。从 PHP 4.3 开始本函数可以安全用于二进制文件。早期的版本则不行。

3.fgetss

string fgetss ( resource $handle [, int $length [, string $allowable_tags ]] )

跟fgets功能一样,但是fgetss会尝试从读取的文本中去掉任何 HTML 和 PHP 标记,可以用可选的第三个参数指定哪些标记不被去掉。

复制代码
<?php
    $handle = fopen('./file.txt', 'r');
    while(!feof($handle)){
        echo fgetss($handle, 1024, '<br>');
    }
    fclose($handle);
?>
复制代码

 

4.file

array file ( string $filename [, int $use_include_path [, resource $context ]] )
将文件内容读入一个数组中,数组的每一项对应文件中的一行,包括换行符在内。不需要行结束符时可以使用 rtrim() 函数过滤换行符。

复制代码
<?php
    $a = file('./file.txt');
    foreach($a as $line => $content){
        echo 'line '.($line + 1).':'.$content;
    }
?>
复制代码

 5.readfile

int readfile ( string $filename [, bool $use_include_path [, resource $context ]] )

读入一个文件并写入到输出缓冲。返回从文件中读入的字节数。如果出错返回 FALSE 并且除非是以 @readfile() 形式调用,否则会显示错误信息。

<?php
    $size = readfile('./file.txt');
    echo $size;
?>

 

6.file_get_contents

string file_get_contents ( string $filename [, bool $use_include_path [, resource $context [, int $offset [, int $maxlen ]]]] )

将文件读入一个字符串。第三个参数$context可以用来设置一些参数,比如访问远程文件时,设置超时等等。

另外,file_get_contents相对于以上几个函数,性能要好得多,所以应该优先考虑使用file_get_contents。但是readfile貌似比file_get_contents性能好一点(?),因为它不需要调用fopen。

复制代码
<?php 
    $ctx = stream_context_create(array( 
        'http' => array( 
            'timeout' => 1    //设置超时
            ) 
        ) 
    ); 
    echo file_get_contents("http://www.baidu.com/", 0, $ctx); 
?>
复制代码

7.fpassthru

   int fpassthru ( resource $handle )

将给定的文件指针从当前的位置读取到 EOF 并把结果写到输出缓冲区。

复制代码
<?php 
    header("Content-Type:text/html;charset=utf-8"); 
    $handle = fopen('./test2.php', 'r');
    fseek($handle, 1024);//将指针定位到1024字节处
    fpassthru($handle);
?>
复制代码

 

8.parse_ini_file

array parse_ini_file ( string $filename [, bool $process_sections ] )

parse_ini_file() 载入一个由 filename 指定的 ini 文件,并将其中的设置作为一个联合数组返回。如果将最后的 process_sections 参数设为 TRUE,将得到一个多维数组,包括了配置文件中每一节的名称和设置。process_sections 的默认值是 FALSE。

注意:

1. 如果 ini 文件中的值包含任何非字母数字的字符,需要将其括在双引号中(")。
2. 有些保留字不能作为 ini 文件中的键名,包括:null,yes,no,true 和 false。值为 null,no 和 false 等效于 "",值为 yes 和 true 等效于 "1"。字符 {}|&~![()" 也不能用在键名的任何地方,而且这些字符在选项值中有着特殊的意义。

test.ini文件内容:

复制代码
; This is a sample configuration file
; Comments start with ';', as in php.ini

[first_section]
one = 1
five = 5
animal = BIRD

[second_section]
path = "/usr/local/bin"
URL = "http://www.example.com/~username
复制代码

test.php内容:

<?php 
    $config = parse_ini_file('./test.ini', ture);
    print_r($config);
?>

输出内容:

复制代码
Array
(
    [first_section] => Array
        (
            [one] => 1
            [five] => 5
            [animal] => BIRD
        )

    [second_section] => Array
        (
            [path] => /usr/local/bin
            [URL] => http://www.example.com/~username
        )

)
复制代码

 

 

 

几个注意事项:

1. 鼓励在处理二进制文件时使用 b 标志,即使系统并不需要,这样可以使脚本的移植性更好。 

2. allow_url_fopen选项激活了 URL 形式的 fopen 封装协议使得可以访问 URL 对象例如文件。默认的封装协议提供用 ftp 和 http 协议来访问远程文件,一些扩展库例如 zlib 可能会注册更多的封装协议。出于安全性考虑,此选项只能在 php.ini 中设置。

3. 如果要打开有特殊字符的 URL (比如说有空格),就需要使用 urlencode() 进行 URL 编码。

184月/13

JavaScript跨域总结与解决办法

发布在 邵珠庆

本文来自网络(http://f2e.me/200904/cross-scripting/,该网址已不能访问),仅作个人读书笔记之用,并稍作修改和补充。

什么是跨域

JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦。这里把涉及到跨域的一些问题简单地整理一下:

首先什么是跨域,简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。更详细的说明可以看下表:

URL 说明 是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允许
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名 不允许
特别注意两点:
第一,如果是协议和端口造成的跨域问题“前台”是无能为力的,
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。

接下来简单地总结一下在“前台”一般处理跨域的办法,后台proxy这种方案牵涉到后台配置,这里就不阐述了,有兴趣的可以看看yahoo的这篇文章:《JavaScript: Use a Web Proxy for Cross-Domain XMLHttpRequest Calls

1、document.domain+iframe的设置

对于主域相同而子域不同的例子,可以通过设置document.domain的办法来解决。具体的做法是可以在http://www.a.com/a.html和http://script.a.com/b.html两个文件中分别加上document.domain = ‘a.com’;然后通过a.html文件中创建一个iframe,去控制iframe的contentDocument,这样两个js文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况,如果你异想天开的把script.a.com的domian设为alibaba.com那显然是会报错地!代码如下:

www.a.com上的a.html

document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    // 在这里操纵b.html
    alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};

script.a.com上的b.html

document.domain = 'a.com';

这种方式适用于{www.kuqin.com, kuqin.com, script.kuqin.com, css.kuqin.com}中的任何页面相互通信。

备注:某一页面的domain默认等于window.location.hostname。主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。 domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。

问题:
1、安全性,当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2、如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。

2、动态创建script

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。具体的做法可以参考YUI的Get Utility

这里判断script节点加载完毕还是蛮有意思的:ie只能通过script的readystatechange属性,其它浏览器是script的load事件。以下是部分判断script加载完毕的方法。

js.onload = js.onreadystatechange = function() {
    if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        // callback在此处执行
        js.onload = js.onreadystatechange = null;
    }
};

3、利用iframe和location.hash

这个办法比较绕,但是可以解决完全跨域情况下的脚步置换问题。原理是利用location.hash来进行传值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息,cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面,这时的hash值可以做参数传递用。cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe;Firefox可以修改)。同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一点有变化则获取获取hash值。代码如下:

先是a.com下的文件cs1.html文件:

function startRequest(){
    var ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
    document.body.appendChild(ifr);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1) : '';
        if (console.log) {
            console.log('Now the data is '+data);
        }
    } catch(e) {};
}
setInterval(checkHash, 2000);

cnblogs.com域名下的cs2.html:

//模拟一个简单的参数处理操作
switch(location.hash){
    case '#paramdo':
        callBack();
        break;
    case '#paramset':
        //do something……
        break;
}

function callBack(){
    try {
        parent.location.hash = 'somedata';
    } catch (e) {
        // ie、chrome的安全机制无法修改parent.location.hash,
        // 所以要利用一个中间的cnblogs域下的代理iframe
        var ifrproxy = document.createElement('iframe');
        ifrproxy.style.display = 'none';
        ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata';    // 注意该文件在"a.com"域下
        document.body.appendChild(ifrproxy);
    }
}

a.com下的域名cs3.html

//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);

当然这样做也存在很多缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等……

4、window.name实现的跨域数据传输

文章较长列在此处不便于阅读,详细请看 window.name实现的跨域数据传输

5、使用HTML5 postMessage

HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,“*”表示不作限制

a.com/index.html中的代码:

b.com/index.html中的代码:

参考文章:《精通HTML5编程》第五章——跨文档消息机制https://developer.mozilla.org/en/dom/window.postmessage

6、利用flash

这是从YUI3的IO组件中看到的办法,具体可见http://wiht.link/YUI-intro
可以看在Adobe Developer Connection看到更多的跨域代理文件规范:ross-Domain Policy File SpecificationsHTTP Headers Blacklist

94月/13

HTML5 开发者需要了解的技巧和工具汇总

发布在 邵珠庆

HTML5现在已经成为了Web开发中的热门话题,大多数现代浏览器(Safari、Chrome,Firefox,IE10和移动设备)都支持HTML5。即使HTML5的规范还没有制定完成,但许多开发者已经将其作为Web开发项目中的主要技术。一些网站巨头,如Google、Facebook、Twitter和YouTube等,都建立在HTML5基础上。 



HTML5中最令人兴奋的功能莫过于画布(canvas)和强大的表单功能,画布功能已经可以在大部分浏览器中完美体验(除了IE),但对于新表单元素的支持还不是太好。对Web开发者来说,是时候开始HTML5开发了。 



要进行HTML5开发,本文中的一些技巧、工具可以让你缩短学习的时间,提高开发的效率。 



一、HTML5支持测试列表 



在开始之前,你需要了解现代的浏览器以及移动平台对于HTML5的支持情况。 

 

二、让HTML5元素可用 



老版本的IE浏览器不能识别新的HTML元素。但是,可以使用一些JavaScript或CSS解决方案来弥补这个缺陷。 

 

  • HTML5Shiv:此脚本可以使IE浏览器识别HTML5元素。
  • HTML5 Enabler:功能与HTML5Shiv类似。
  • Modernizr:它使得开发者可以在支持HTML5和CSS3的浏览器中充分利用HTML5和CSS3的特性进行开发,同时又不会牺牲其他不支持这些新技术的浏览器的控制。
  • HTML5 Reset:它提供了一组HTML、CSS文件,让你能够以最少的时间来启动一个新的项目。它使用modernizr来支持HTML5 和 CSS3。

三、浏览器插件 



下面是一些JavaScript插件,可以弥补一些浏览器对HTML5的支持问题。 



1. VideoJS 



VideoJS是一个HTML5的视频播放器,可以在所有浏览器中使用,包括IE6和移动设备。对于不支持HTML5的浏览器则自动使用Flash播放器来播放。 

 





2. AudioJS 



HTML音频播放器。用来让HTML5 的 <audio> 标签可以在各种浏览器上使用,包括移动设备。 

 





3. HTML5Widget 



HTML5的表单模块,包括日历,调色板,滑动部件,客户端验证等。 

 





4. Webforms2 



HTML5 表单属性的支持,例如pattern、required和autofocus。 

 





5. LimeJS 



LimeJS是HTML5的游戏框架,用于为现代触摸设备和桌面浏览器创建快速、本地化的游戏。 

 





6. FlexieJS 



支持CSS3弹性盒子模型(Flexible Box Model)。 

 





四、在线工具 



此外,还有一些在线工具,可以帮助开发者加快HTML5项目的开发。 



1. HTML5 Boilerplate 



HTML5Boilerplate 是一个HTML / CSS /JS模板,是实现跨浏览器正常化、性能优化、稳定的可选功能如跨域Ajax和Flash的最佳实践。开发者称之为技巧集合,目的是满足你开发一个跨浏览器,并且面向未来的网站的需求。 

 





2. Switch to HTML5 



非常有用的在线工具,可以根据你的喜好生成HTML5文档结构。 

 





3. Initializr 



Initializr是一个HTML5模板生成器,以帮助你开始HTML5项目的开发 。它建立在HTML5 Boilerplate之上。 

 





4. HTML5 Visual 速查表 

 





5. HTML5 Canvas 速查表 

 





6. HTML5 笔记 

 





五、其他 



你可以通过下面的链接来跟踪HTML5的更新。 



HTML5追踪 



你可以通过下面的链接获得HTML5网站的设计灵感。这个网站库中包含了大量的使用HTML5技术的网站。 



HTML5Gallery 



VIA http://www.queness.com/post/9375/tips-tricks-and-tools-you-will-need-to-start-using-html5-today 

273月/13

编写可读代码的艺术

发布在 邵珠庆

代码为什么要易于理解

“Code should be written to minimize the time it would take for someone else to understand it.”

日常工作的事实是:

  • 写代码前的思考和看代码的时间远大于真正写的时间
  • 读代码是很平常的事情,不论是别人的,还是自己的,半年前写的可认为是别人的代码
  • 代码可读性高,很快就可以理解程序的逻辑,进入工作状态
  • 行数少的代码不一定就容易理解
  • 代码的可读性与程序的效率、架构、易于测试一点也不冲突

整本书都围绕“如何让代码的可读性更高”这个目标来写。这也是好代码的重要标准之一。

如何命名

变量名中应包含更多信息

使用含义明确的词,比如用download而不是get,参考以下替换方案:

1
2
3
4
send -> deliver, dispatch, announce, distribute, route
 find -> search, extract, locate, recover
start -> lanuch, create, begin, open
 make -> create,set up, build, generate, compose, add, new

避免通用的词

tmpretval这样词,除了说明是临时变量和返回值之外,没有任何意义。但是给他加一些有意义的词,就会很明确:

1
2
3
tmp_file = tempfile.NamedTemporaryFile()
...
SaveData(tmp_file, ...)

不使用retval而使用变量真正代表的意义:

1
sum_squares += v[i]; // Where's the "square" that we're summing? Bug!

嵌套的for循环中,ij也有同样让人困惑的时候:

1
2
3
4
for (int i = 0; i < clubs.size(); i++)
 for (int j = 0; j < clubs[i].members.size(); j++)
 for (int k = 0; k < users.size(); k++) if (clubs[i].members[k] == users[j])
 cout << "user[" << j << "] is in club[" << i << "]" << endl;

换一种写法就会清晰很多:

1
if (clubs[ci].members[mi] == users[ui]) # OK. First letters match.

所以,当使用一些通用的词,要有充分的理由才可以。

使用具体的名字

CanListenOnPort就比ServerCanStart好,can start比较含糊,而listen on port确切的说明了这个方法将要做什么。

--run_locally就不如--extra_logging来的明确。

增加重要的细节,比如变量的单位_ms,对原始字符串加_raw

如果一个变量很重要,那么在名字上多加一些额外的字就会更加易读,比如将string id; // Example: "af84ef845cd8"换成string hex_id;

1
2
3
4
Start(int delay) --> delay → delay_secs
 CreateCache(int size) --> size → size_mb
ThrottleDownload(float limit) --> limit → max_kbps
 Rotate(float angle) --> angle → degrees_cw

更多例子:

1
2
3
4
password -> plaintext_password
 comment -> unescaped_comment
 html -> html_utf8
 data -> data_urlenc

对于作用域大的变量使用较长的名字

在比较小的作用域内,可以使用较短的变量名,在较大的作用域内使用的变量,最好用长一点的名字,编辑器的自动补全都可以很好的减少键盘输入。对于一些缩写前缀,尽量选择众所周知的(如str),一个判断标准是,当新成员加入时,是否可以无需他人帮助而明白前缀代表什么。

合理使用_-等符号,比如对私有变量加_前缀。

1
2
3
4
5
6
7
8
var x = new DatePicker(); // DatePicker() 是类的"构造"函数,大写开始
var y = pageHeight(); // pageHeight() 是一个普通函数
 
var $all_images = $("img"); // $all_images 是jQuery对象
var height = 250; // height不是
 
//id和class的写法分开
<div id="middle_column" class="main-content"> ...

命名不能有歧义

命名的时候可以先想一下,我要用的这个词是否有别的含义。举个例子:

1
results = Database.all_objects.filter("year <= 2011")

现在的结果到底是包含2011年之前的呢还是不包含呢?

使用minmax代替limit

1
2
3
4
5
6
7
CART_TOO_BIG_LIMIT = 10
 if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT:
 Error("Too many items in cart.")
 
MAX_ITEMS_IN_CART = 10
 if shopping_cart.num_items() > MAX_ITEMS_IN_CART:
 Error("Too many items in cart.")

对比上例中CART_TOO_BIG_LIMITMAX_ITEMS_IN_CART,想想哪个更好呢?

使用firstlast来表示闭区间

1
2
3
4
print integer_range(start=2, stop=4)
# Does this print [2,3] or [2,3,4] (or something else)?
 
set.PrintKeys(first="Bart", last="Maggie")

firstlast含义明确,适宜表示闭区间。

使用beiginend表示前闭后开(2,9))区间

1
2
3
PrintEventsInRange("OCT 16 12:00am", "OCT 17 12:00am")
 
PrintEventsInRange("OCT 16 12:00am", "OCT 16 11:59:59.9999pm")

上面一种写法就比下面的舒服多了。

Boolean型变量命名

1
bool read_password = true;

这是一个很危险的命名,到底是需要读取密码呢,还是密码已经被读取呢,不知道,所以这个变量可以使用user_is_authenticated代替。通常,给Boolean型变量添加ishascanshould可以让含义更清晰,比如:

1
2
SpaceLeft() --> hasSpaceLeft()
bool disable_ssl = false --> bool use_ssl = true

符合预期

1
2
3
4
5
6
7
public class StatisticsCollector {
 public void addSample(double x) { ... }
 public double getMean() {
 // Iterate through all samples and return total / num_samples
 }
 ...
}

在这个例子中,getMean方法遍历了所有的样本,返回总额,所以并不是普通意义上轻量的get方法,所以应该取名computeMean比较合适。

漂亮的格式

写出来漂亮的格式,充满美感,读起来自然也会舒服很多,对比下面两个例子:

1
2
3
4
5
6
7
8
9
10
11
12
class StatsKeeper {
 public:
 // A class for keeping track of a series of doubles
 void Add(double d); // and methods for quick statistics about them
 private: int count; /* how many so far
 */ public:
 double Average();
 private: double minimum;
 list<double>
 past_items
 ;double maximum;
};

什么是充满美感的呢:

1
2
3
4
5
6
7
8
9
10
11
12
// A class for keeping track of a series of doubles
// and methods for quick statistics about them.
class StatsKeeper {
 public:
 void Add(double d);
 double Average();
 private:
 list<double> past_items;
 int count; // how many so far
 double minimum;
 double maximum;
};

考虑断行的连续性和简洁

这段代码需要断行,来满足不超过一行80个字符的要求,参数也需要注释说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PerformanceTester {
 public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(
 500, /* Kbps */
 80, /* millisecs latency */
 200, /* jitter */
 1 /* packet loss % */);
 
 public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(
 45000, /* Kbps */
 10, /* millisecs latency */
 0, /* jitter */
 0 /* packet loss % */);
 
 public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(
 100, /* Kbps */
 400, /* millisecs latency */
 250, /* jitter */
 5 /* packet loss % */);
}

考虑到代码的连贯性,先优化成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PerformanceTester {
 public static final TcpConnectionSimulator wifi =
 new TcpConnectionSimulator(
 500, /* Kbps */
 80, /* millisecs latency */ 200, /* jitter */
 1 /* packet loss % */);
 
 public static final TcpConnectionSimulator t3_fiber =
 new TcpConnectionSimulator(
 45000, /* Kbps */
 10, /* millisecs latency */
 0, /* jitter */
 0 /* packet loss % */);
 
 public static final TcpConnectionSimulator cell =
 new TcpConnectionSimulator(
 100, /* Kbps */
 400, /* millisecs latency */
 250, /* jitter */
 5 /* packet loss % */);
}

连贯性好一点,但还是太罗嗦,额外占用很多空间:

1
2
3
4
5
6
7
8
9
10
11
12
public class PerformanceTester {
 // TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
 // [Kbps] [ms] [ms] [percent]
 public static final TcpConnectionSimulator wifi =
 new TcpConnectionSimulator(500, 80, 200, 1);
 
 public static final TcpConnectionSimulator t3_fiber =
 new TcpConnectionSimulator(45000, 10, 0, 0);
 
 public static final TcpConnectionSimulator cell =
 new TcpConnectionSimulator(100, 400, 250, 5);
}

用函数封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Turn a partial_name like "Doug Adams" into "Mr. Douglas Adams".
// If not possible, 'error' is filled with an explanation.
string ExpandFullName(DatabaseConnection dc, string partial_name, string* error);
 
DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection, "Doug Adams", &error)
 == "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(database_connection, " Jake Brown ", &error)
 == "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(ExpandFullName(database_connection, "John", &error) == "");
assert(error == "more than one result");

上面这段代码看起来很脏乱,很多重复性的东西,可以用函数封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CheckFullName("Doug Adams", "Mr. Douglas Adams", "");
CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");
CheckFullName("No Such Guy", "", "no match found");
CheckFullName("John", "", "more than one result");
 
void CheckFullName(string partial_name,
 string expected_full_name,
 string expected_error) {
 // database_connection is now a class member
 string error;
 string full_name = ExpandFullName(database_connection, partial_name, &error);
 assert(error == expected_error);
 assert(full_name == expected_full_name);
}

列对齐

列对齐可以让代码段看起来更舒适:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CheckFullName("Doug Adams" , "Mr. Douglas Adams" , "");
CheckFullName(" Jake Brown ", "Mr. Jake Brown III", "");
CheckFullName("No Such Guy" , "" , "no match found");
CheckFullName("John" , "" , "more than one result");
 
commands[] = {
 ...
 { "timeout" , NULL , cmd_spec_timeout},
 { "timestamping" , &opt.timestamping , cmd_boolean},
 { "tries" , &opt.ntry , cmd_number_inf},
 { "useproxy" , &opt.use_proxy , cmd_boolean},
 { "useragent" , NULL , cmd_spec_useragent},
 ...
};

代码用块区分

1
2
3
4
5
6
7
8
9
10
11
12
13
class FrontendServer {
 public:
 FrontendServer();
 void ViewProfile(HttpRequest* request);
 void OpenDatabase(string location, string user);
 void SaveProfile(HttpRequest* request);
 string ExtractQueryParam(HttpRequest* request, string param);
 void ReplyOK(HttpRequest* request, string html);
 void FindFriends(HttpRequest* request);
 void ReplyNotFound(HttpRequest* request, string error);
 void CloseDatabase(string location);
 ~FrontendServer();
};

上面这一段虽然能看,不过还有优化空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FrontendServer {
 public:
 FrontendServer();
 ~FrontendServer();
 // Handlers
 void ViewProfile(HttpRequest* request);
 void SaveProfile(HttpRequest* request);
 void FindFriends(HttpRequest* request);
 
 // Request/Reply Utilities
 string ExtractQueryParam(HttpRequest* request, string param);
 void ReplyOK(HttpRequest* request, string html);
 void ReplyNotFound(HttpRequest* request, string error);
 
 // Database Helpers
 void OpenDatabase(string location, string user);
 void CloseDatabase(string location);
};

再来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Import the user's email contacts, and match them to users in our system.
# Then display a list of those users that he/she isn't already friends with.
def suggest_new_friends(user, email_password):
 friends = user.friends()
 friend_emails = set(f.email for f in friends)
 contacts = import_contacts(user.email, email_password)
 contact_emails = set(c.email for c in contacts)
 non_friend_emails = contact_emails - friend_emails
 suggested_friends = User.objects.select(email__in=non_friend_emails)
 display['user'] = user
 display['friends'] = friends
 display['suggested_friends'] = suggested_friends
 return render("suggested_friends.html", display)

全都混在一起,视觉压力相当大,按功能化块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def suggest_new_friends(user, email_password):
 # Get the user's friends' email addresses.
 friends = user.friends()
 friend_emails = set(f.email for f in friends)
 
 # Import all email addresses from this user's email account.
 contacts = import_contacts(user.email, email_password)
 contact_emails = set(c.email for c in contacts)
 
 # Find matching users that they aren't already friends with.
 non_friend_emails = contact_emails - friend_emails
 suggested_friends = User.objects.select(email__in=non_friend_emails)
 
 # Display these lists on the page. display['user'] = user
 display['friends'] = friends
 display['suggested_friends'] = suggested_friends
 
 return render("suggested_friends.html", display)

让代码看起来更舒服,需要在写的过程中多注意,培养一些好的习惯,尤其当团队合作的时候,代码风格比如大括号的位置并没有对错,但是不遵循团队规范那就是错的。

如何写注释

当你写代码的时候,你会思考很多,但是最终呈现给读者的就只剩代码本身了,额外的信息丢失了,所以注释的目的就是让读者了解更多的信息。

应该注释什么

不应该注释什么

这样的注释毫无价值:

1
2
3
4
5
6
7
8
9
10
// The class definition for Account
class Account {
 public:
 // Constructor
 Account();
 // Set the profit member to a new value
 void SetProfit(double profit);
 // Return the profit from this Account
 double GetProfit();
};

不要像下面这样为了注释而注释:

1
2
3
4
// Find a Node with the given 'name' or return NULL.
// If depth <= 0, only 'subtree' is inspected.
// If depth == N, only 'subtree' and N levels below are inspected.
Node* FindNodeInSubtree(Node* subtree, string name, int depth);

不要给烂取名注释

1
2
3
// Enforce limits on the Reply as stated in the Request,
// such as the number of items returned, or total byte size, etc.
void CleanReply(Request request, Reply reply);

注释的大部分都在解释clean是什么意思,那不如换个正确的名字:

1
2
// Make sure 'reply' meets the count/byte/etc. limits from the 'request'
void EnforceLimitsFromRequest(Request request, Reply reply);

记录你的想法

我们讨论了不该注释什么,那么应该注释什么呢?注释应该记录你思考代码怎么写的结果,比如像下面这些:

1
2
3
4
5
6
7
// Surprisingly, a binary tree was 40% faster than a hash table for this data.
// The cost of computing a hash was more than the left/right comparisons.
 
// This heuristic might miss a few words. That's OK; solving this 100% is hard.
 
// This class is getting messy. Maybe we should create a 'ResourceNode' subclass to
// help organize things.

也可以用来记录流程和常量:

1
2
3
4
5
6
7
// TODO: use a faster algorithm
// TODO(dustin): handle other image formats besides JPEG
 
NUM_THREADS = 8 # as long as it's >= 2 * num_processors, that's good enough.
 
// Impose a reasonable limit - no human can read that much anyway.
const int MAX_RSS_SUBSCRIPTIONS = 1000;

可用的词有:

  • TODO : Stuff I haven’t gotten around to yet
  • FIXME : Known-broken code here
  • HACK : Adimittedly inelegant solution to a problem
  • XXX : Danger! Major problem here

站在读者的角度去思考

当别人读你的代码时,让他们产生疑问的部分,就是你应该注释的地方。

1
2
3
4
5
6
7
struct Recorder {
 vector<float> data;
 ...
 void Clear() {
 vector<float>().swap(data); // Huh? Why not just data.clear()?
 }
};

很多C++的程序员啊看到这里,可能会想为什么不用data.clear()来代替vector.swap,所以那个地方应该加上注释:

1
2
// Force vector to relinquish its memory (look up "STL swap trick")
vector<float>().swap(data);

说明可能陷阱

你在写代码的过程中,可能用到一些hack,或者有其他需要读代码的人知道的陷阱,这时候就应该注释:

1
void SendEmail(string to, string subject, string body);

而实际上这个发送邮件的函数是调用别的服务,有超时设置,所以需要注释:

1
2
// Calls an external service to deliver email. (Times out after 1 minute.)
void SendEmail(string to, string subject, string body);

全景的注释

有时候为了更清楚说明,需要给整个文件加注释,让读者有个总体的概念:

1
2
// This file contains helper functions that provide a more convenient interface to our
// file system. It handles file permissions and other nitty-gritty details.

总结性的注释

即使是在函数内部,也可以有类似文件注释那样的说明注释:

1
2
3
4
5
# Find all the items that customers purchased for themselves.
for customer_id in all_customers:
 for sale in all_sales[customer_id].sales:
 if sale.recipient == customer_id:
 ...

或者按照函数的步进,写一些注释:

1
2
3
4
5
6
7
8
def GenerateUserReport():
 # Acquire a lock for this user
 ...
 # Read user's info from the database
 ...
 # Write info to a file
 ...
 # Release the lock for this user

很多人不愿意写注释,确实,要写好注释也不是一件简单的事情,也可以在文件专门的地方,留个写注释的区域,可以写下你任何想说的东西。

注释应简明准确

前一个小节讨论了注释应该写什么,这一节来讨论应该怎么写,因为注释很重要,所以要写的精确,注释也占据屏幕空间,所以要简洁。

精简注释

1
2
3
4
// The int is the CategoryType.
// The first float in the inner pair is the 'score',
// the second is the 'weight'.
typedef hash_map<int, pair<float, float> > ScoreMap;

这样写太罗嗦了,尽量精简压缩成这样:

1
2
// CategoryType -> (score, weight)
typedef hash_map<int, pair<float, float> > ScoreMap;

避免有歧义的代词

1
// Insert the data into the cache, but check if it's too big first.

这里的it's有歧义,不知道所指的是data还是cache,改成如下:

1
// Insert the data into the cache, but check if the data is too big first.

还有更好的解决办法,这里的it就有明确所指:

1
// If the data is small enough, insert it into the cache.

语句要精简准确

1
# Depending on whether we've already crawled this URL before, give it a different priority.

这句话理解起来太费劲,改成如下就好理解很多:

1
# Give higher priority to URLs we've never crawled before.

精确描述函数的目的

1
2
// Return the number of lines in this file.
int CountLines(string filename) { ... }

这样的一个函数,用起来可能会一头雾水,因为他可以有很多歧义:

  • ”” 一个空文件,是0行还是1行?
  • “hello” 只有一行,那么返回值是0还是1?
  • “hellon” 这种情况返回1还是2?
  • “hellon world” 返回1还是2?
  • “hellonr crueln worldr” 返回2、3、4哪一个呢?

所以注释应该这样写:

1
2
// Count how many newline bytes ('n') are in the file.
int CountLines(string filename) { ... }

用实例说明边界情况

1
2
3
// Rearrange 'v' so that elements < <span class="wp_keywordlink"><a href="http://blog.jobbole.com/24057/" title="Pivot — 创业者最重要的本领" rel="nofollow" target="_blank">Pivot</a></span> come before those >= <span class="wp_keywordlink"><a href="http://blog.jobbole.com/24057/" title="Pivot — 创业者最重要的本领" rel="nofollow" target="_blank">Pivot</a></span>;
// Then return the largest 'i' for which v[i] < pivot (or -1 if none are < pivot)
int Partition(vector<int>* v, int pivot);

这个描述很精确,但是如果再加入一个例子,就更好了:

1
2
3
// ...
// Example: Partition([8 5 9 8 2], 8) might result in [5 2 | 8 9 8] and return 1
int Partition(vector<int>* v, int pivot);

说明你的代码的真正目的

1
2
3
4
5
6
7
8
void DisplayProducts(list<Product> products) {
 products.sort(CompareProductByPrice);
 // Iterate through the list in reverse order
 for (list<Product>::reverse_iterator it = products.rbegin(); it != products.rend();
 ++it)
 DisplayPrice(it->price);
 ...
}

这里的注释说明了倒序排列,单还不够准确,应该改成这样:

1
2
// Display each price, from highest to lowest
for (list<Product>::reverse_iterator it = products.rbegin(); ... )

函数调用时的注释

看见这样的一个函数调用,肯定会一头雾水:

1
Connect(10, false);

如果加上这样的注释,读起来就清楚多了:

1
2
3
4
def Connect(timeout, use_encryption): ...
 
# Call the function using named parameters
Connect(timeout = 10, use_encryption = False)

使用信息含量丰富的词

1
2
3
4
// This class contains a number of members that store the same information as in the
// database, but are stored here for speed. When this class is read from later, those
// members are checked first to see if they exist, and if so are returned; otherwise the
// database is read from and that data stored in those fields for next time.

上面这一大段注释,解释的很清楚,如果换一个词来代替,也不会有什么疑惑:

1
// This class acts as a caching layer to the database.

简化循环和逻辑

流程控制要简单

让条件语句、循环以及其他控制流程的代码尽可能自然,让读者在阅读过程中不需要停顿思考或者在回头查找,是这一节的目的。

条件语句中参数的位置

对比下面两种条件的写法:

1
2
3
4
5
if (length >= 10)
while (bytes_received < bytes_expected)
 
if (10 <= length)
while (bytes_expected > bytes_received)

到底是应该按照大于小于的顺序来呢,还是有其他的准则?是的,应该按照参数的意义来

  • 运算符左边:通常是需要被检查的变量,也就是会经常变化的
  • 运算符右边:通常是被比对的样本,一定程度上的常量

这就解释了为什么bytes_received < bytes_expected比反过来更好理解。

if/else的顺序

通常,if/else的顺序你可以自由选择,下面这两种都可以:

1
2
3
4
5
6
7
8
9
10
11
if (a == b) {
 // Case One ...
} else {
 // Case Two ...
}
 
if (a != b) {
 // Case Two ...
} else {
 // Case One ...
}

或许对此你也没有仔细斟酌过,但在有些时候,一种顺序确实好过另一种:

  • 正向的逻辑在前,比如if(debug)就比if(!debug)
  • 简单逻辑的在前,这样ifelse就可以在一个屏幕显示 – 有趣、清晰的逻辑在前

举个例子来看:

1
2
3
4
5
6
7
8
9
if (!url.HasQueryParameter("expand_all")) {
 response.Render(items);
 ...
} else {
 for (int i = 0; i < items.size(); i++) {
 items[i].Expand();
 }
 ...
}

看到if你首先想到的是expand_all,就好像告诉你“不要想大象”,你会忍不住去想它,所以产生了一点点迷惑,最好写成:

1
2
3
4
5
6
7
8
9
if (url.HasQueryParameter("expand_all")) {
 for (int i = 0; i < items.size(); i++) {
 items[i].Expand();
 }
 ...
} else {
 response.Render(items);
 ...
}

三目运算符(?:)

1
2
3
4
5
6
7
8
time_str += (hour >= 12) ? "pm" : "am";
 
Avoiding the ternary operator, you might write:
 if (hour >= 12) {
 time_str += "pm";
 } else {
 time_str += "am";
}

使用三目运算符可以减少代码行数,上例就是一个很好的例证,但是我们的真正目的是减少读代码的时间,所以下面的情况并不适合用三目运算符:

1
2
3
4
5
6
7
return exponent >= 0 ? mantissa * (1 << exponent) : mantissa / (1 << -exponent);
 
if (exponent >= 0) {
 return mantissa * (1 << exponent);
} else {
 return mantissa / (1 << -exponent);
}

所以只在简单表达式的地方用。

避免使用do/while表达式

1
2
3
do {
 continue;
} while (false);

这段代码会执行几遍呢,需要时间思考一下,do/while完全可以用别的方法代替,所以应避免使用。

尽早return

1
2
3
4
5
public boolean Contains(String str, String substr) {
 if (str == null || substr == null) return false;
 if (substr.equals("")) return true;
 ...
}

函数里面尽早的return,可以让逻辑更加清晰。

减少嵌套

1
2
3
4
5
6
7
8
9
10
11
if (user_result == SUCCESS) {
 if (permission_result != SUCCESS) {
 reply.WriteErrors("error reading permissions");
 reply.Done();
 return;
 }
 reply.WriteErrors("");
} else {
 reply.WriteErrors(user_result);
}
reply.Done();

这样一段代码,有一层的嵌套,但是看起来也会稍有迷惑,想想自己的代码,有没有类似的情况呢?可以换个思路去考虑这段代码,并且用尽早return的原则修改,看起来就舒服很多:

1
2
3
4
5
6
7
8
9
10
11
12
if (user_result != SUCCESS) {
 reply.WriteErrors(user_result);
 reply.Done();
 return;
}
if (permission_result != SUCCESS) {
 reply.WriteErrors(permission_result);
 reply.Done();
 return;
}
reply.WriteErrors("");
reply.Done();

同样的,对于有嵌套的循环,可以采用同样的办法:

1
2
3
4
5
6
7
8
9
for (int i = 0; i < results.size(); i++) {
 if (results[i] != NULL) {
 non_null_count++;
 if (results[i]->name != "") {
 cout << "Considering candidate..." << endl;
 ...
 }
 }
}

换一种写法,尽早return,在循环中就用continue:

1
2
3
4
5
6
7
8
for (int i = 0; i < results.size(); i++) {
 if (results[i] == NULL) continue;
 non_null_count++;
 
 if (results[i]->name == "") continue;
 cout << "Considering candidate..." << endl;
 ...
}

拆分复杂表达式

很显然的,越复杂的表达式,读起来越费劲,所以应该把那些复杂而庞大的表达式,拆分成一个个易于理解的小式子。

用变量

将复杂表达式拆分最简单的办法,就是增加一个变量:

1
2
3
4
5
6
if line.split(':')[0].strip() == "root":
 
//用变量替换
username = line.split(':')[0].strip()
if username == "root":
 ...

或者这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (request.user.id == document.owner_id) {
 // user can edit this document...
}
...
if (request.user.id != document.owner_id) {
// document is read-only...
}
 
//用变量替换
final boolean user_owns_document = (request.user.id == document.owner_id);
if (user_owns_document) {
 // user can edit this document...
}
...
if (!user_owns_document) {
 // document is read-only...
}

逻辑替换

  • 1) not (a or b or c) <–> (not a) and (not b) and (not c)
  • 2) not (a and b and c) <–> (not a) or (not b) or (not c)

所以,就可以这样写:

1
2
3
4
if (!(file_exists && !is_protected)) Error("Sorry, could not read file.");
 
//替换
if (!file_exists || is_protected) Error("Sorry, could not read file.");

不要滥用逻辑表达式

1
assert((!(bucket = FindBucket(key))) || !bucket->IsOccupied());

这样的代码完全可以用下面这个替换,虽然有两行,但是更易懂:

1
2
bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket->IsOccupied());

像下面这样的表达式,最好也不要写,因为在有些语言中,x会被赋予第一个为true的变量的值:

1
x = a || b || c

拆解大表达式

1
2
3
4
5
6
7
8
9
10
11
12
var update_highlight = function (message_num) {
 if ($("#vote_value" + message_num).html() === "Up") {
 $("#thumbs_up" + message_num).addClass("highlighted");
 $("#thumbs_down" + message_num).removeClass("highlighted");
 } else if ($("#vote_value" + message_num).html() === "Down") {
 $("#thumbs_up" + message_num).removeClass("highlighted");
 $("#thumbs_down" + message_num).addClass("highlighted");
 } else {
 $("#thumbs_up" + message_num).removeClass("highighted");
 $("#thumbs_down" + message_num).removeClass("highlighted");
 }
};

这里面有很多重复的语句,我们可以用变量还替换简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var update_highlight = function (message_num) {
 var thumbs_up = $("#thumbs_up" + message_num);
 var thumbs_down = $("#thumbs_down" + message_num);
 var vote_value = $("#vote_value" + message_num).html();
 var hi = "highlighted";
 
 if (vote_value === "Up") {
 thumbs_up.addClass(hi);
 thumbs_down.removeClass(hi);
 } else if (vote_value === "Down") {
 thumbs_up.removeClass(hi);
 thumbs_down.addClass(hi);
 } else {
 thumbs_up.removeClass(hi);
 thumbs_down.removeClass(hi);
 }
}

消除变量

前一节,讲到利用变量来拆解大表达式,这一节来讨论如何消除多余的变量。

没用的临时变量

1
2
now = datetime.datetime.now()
root_message.last_view_time = now

这里的now可以去掉,因为:

  • 并非用来拆分复杂的表达式
  • 也没有增加可读性,因为`datetime.datetime.now()`本就清晰
  • 只用了一次

所以完全可以写作:

1
root_message.last_view_time = datetime.datetime.now()

消除条件控制变量

1
2
3
4
5
6
7
8
boolean done = false;
while (/* condition */ && !done) {
 ...
 if (...) {
 done = true;
 continue;
 }
}

这里的done可以用别的方式更好的完成:

1
2
3
4
5
6
while (/* condition */) {
 ...
 if (...) {
 break;
 }
}

这个例子非常容易修改,如果是比较复杂的嵌套,break可能并不够用,这时候就可以把代码封装到函数中。

减少变量的作用域

我们都听过要避免使用全局变量这样的忠告,是的,当变量的作用域越大,就越难追踪,所以要保持变量小的作用域。

1
2
3
4
5
6
7
8
9
10
11
12
class LargeClass {
 string str_;
 void Method1() {
 str_ = ...;
 Method2();
 }
 void Method2() {
 // Uses str_
 }
 // Lots of other methods that don't use str_
 ... ;
}

这里的str_的作用域有些大,完全可以换一种方式:

1
2
3
4
5
6
7
8
9
10
class LargeClass {
 void Method1() {
 string str = ...;
 Method2(str);
 }
 void Method2(string str) {
 // Uses str
 }
 // Now other methods can't see str.
};

str通过变量函数参数传递,减小了作用域,也更易读。同样的道理也可以用在定义类的时候,将大类拆分成一个个小类。

不要使用嵌套的作用域

1
2
3
4
5
6
7
8
9
# No use of example_value up to this point.
if request:
 for value in request.values:
 if value > 0:
 example_value = value
 break
 
for logger in debug.loggers:
 logger.log("Example:", example_value)

这个例子在运行时候会报example_value is undefined的错,修改起来不算难:

1
2
3
4
5
6
7
8
9
example_value = None
if request:
 for value in request.values:
 if value > 0: example_value = value
 break
 
if example_value:
 for logger in debug.loggers:
 logger.log("Example:", example_value)

但是参考前面的消除中间变量准则,还有更好的办法:

1
2
3
4
5
6
7
8
9
def LogExample(value):
 for logger in debug.loggers:
 logger.log("Example:", value)
 
 if request:
 for value in request.values:
 if value > 0:
 LogExample(value) # deal with 'value' immediately
 break

用到了再声明

在C语言中,要求将所有的变量事先声明,这样当用到变量较多时候,读者处理这些信息就会有难度,所以一开始没用到的变量,就暂缓声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
def ViewFilteredReplies(original_id):
 filtered_replies = []
 root_message = Messages.objects.get(original_id)
 all_replies = Messages.objects.select(root_id=original_id)
 root_message.view_count += 1
 root_message.last_view_time = datetime.datetime.now()
 root_message.save()
 
 for reply in all_replies:
 if reply.spam_votes <= MAX_SPAM_VOTES:
 filtered_replies.append(reply)
 
 return filtered_replies

读者一次处理变量太多,可以暂缓声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
def ViewFilteredReplies(original_id):
 root_message = Messages.objects.get(original_id)
 root_message.view_count += 1
 root_message.last_view_time = datetime.datetime.now()
 root_message.save()
 
 all_replies = Messages.objects.select(root_id=original_id)
 filtered_replies = []
 for reply in all_replies:
 if reply.spam_votes <= MAX_SPAM_VOTES:
 filtered_replies.append(reply)
 
 return filtered_replies

变量最好只写一次

前面讨论了过多的变量会让读者迷惑,同一个变量,不停的被赋值也会让读者头晕,如果变量变化的次数少一些,代码可读性就更强。

一个例子

假设有一个页面,如下,需要给第一个空的input赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<input type="text" id="input1" value="Dustin">
<input type="text" id="input2" value="Trevor">
<input type="text" id="input3" value="">
<input type="text" id="input4" value="Melissa">
...
var setFirstEmptyInput = function (new_value) {
 var found = false;
 var i = 1;
 var elem = document.getElementById('input' + i);
 while (elem !== null) {
 if (elem.value === '') {
 found = true;
 break;
 }
 i++;
 elem = document.getElementById('input' + i);
 }
 if (found) elem.value = new_value;
 return elem;
};

这段代码能工作,有三个变量,我们逐一去看如何优化,found作为中间变量,完全可以消除:

1
2
3
4
5
6
7
8
9
10
11
12
13
var setFirstEmptyInput = function (new_value) {
 var i = 1;
 var elem = document.getElementById('input' + i);
 while (elem !== null) {
 if (elem.value === '') {
 elem.value = new_value;
 return elem;
 }
 i++;
 elem = document.getElementById('input' + i);
 }
 return null;
};

再来看elem变量,只用来做循环,调用了很多次,所以很难跟踪他的值,i也可以用for来修改:

1
2
3
4
5
6
7
8
9
10
11
var setFirstEmptyInput = function (new_value) {
 for (var i = 1; true; i++) {
 var elem = document.getElementById('input' + i);
 if (elem === null)
 return null; // Search Failed. No empty input found.
 if (elem.value === '') {
 elem.value = new_value;
 return elem;
 }
 }
};

工程师就是将大问题分解为一个个小问题,然后逐个解决,这样也易于保证程序的健壮性、可读性。如何分解子问题,下面给出一些准则:

  • 看看这个方法或代码,问问你自己“这段代码的最终目标是什么?”
  • 对于每一行代码,要问“它与目标直接相关,或者是不相关的子问题?”
  • 如果有足够多行的代码是处理与目标不直接相关的问题,那么抽离成子函数

来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
ajax_post({
 url: 'http://example.com/submit',
 data: data,
 on_success: function (response_data) {
 var str = "{n";
 for (var key in response_data) {
 str += " " + key + " = " + response_data[key] + "n";
 }
 alert(str + "}");
 // Continue handling 'response_data' ...
 }
});

这段代码的目标是发送一个ajax请求,所以其中字符串处理的部分就可以抽离出来:

1
2
3
4
5
6
7
var format_pretty = function (obj) {
 var str = "{n";
 for (var key in obj) {
 str += " " + key + " = " + obj[key] + "n";
 }
 return str + "}";
};

意外收获

有很多理由将format_pretty抽离出来,这些独立的函数可以很容易的添加feature,增强可靠性,处理边界情况,等等。所以这里,可以将format_pretty增强,就会得到一个更强大的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var format_pretty = function (obj, indent) {
 // Handle null, undefined, strings, and non-objects.
 if (obj === null) return "null";
 if (obj === undefined) return "undefined";
 if (typeof obj === "string") return '"' + obj + '"';
 if (typeof obj !== "object") return String(obj);
 if (indent === undefined) indent = "";
 
 // Handle (non-null) objects.
 
 var str = "{n";
 for (var key in obj) {
 str += indent + " " + key + " = ";
 str += format_pretty(obj[key], indent + " ") + "n"; }
 return str + indent + "}";
};

这个函数输出:

1
2
3
4
5
6
7
8
9
10
11
{
 key1 = 1
 key2 = true
 key3 = undefined
 key4 = null
 key5 = {
 key5a = {
 key5a1 = "hello world"
 }
 }
}

多做这样的事情,就是积累代码的过程,这样的代码可以复用,也可以形成自己的代码库,或者分享给别人。

业务相关的函数

那些与目标不相关函数,抽离出来可以复用,与业务相关的也可以抽出来,保持代码的易读性,例如:

1
2
3
4
5
6
7
8
9
10
11
business = Business()
business.name = request.POST["name"]
 
url_path_name = business.name.lower()
url_path_name = re.sub(r"['.]", "", url_path_name)
url_path_name = re.sub(r"[^a-z0-9]+", "-", url_path_name)
url_path_name = url_path_name.strip("-")
business.url = "/biz/" + url_path_name
 
business.date_created = datetime.datetime.utcnow()
business.save_to_database()

抽离出来,就好看很多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CHARS_TO_REMOVE = re.compile(r"['.']+")
CHARS_TO_DASH = re.compile(r"[^a-z0-9]+")
 
def make_url_friendly(text):
 text = text.lower()
 text = CHARS_TO_REMOVE.sub('', text)
 text = CHARS_TO_DASH.sub('-', text)
 return text.strip("-")
 
business = Business()
business.name = request.POST["name"]
business.url = "/biz/" + make_url_friendly(business.name)
business.date_created = datetime.datetime.utcnow()
business.save_to_database()

简化现有接口

我们来看一个读写cookie的函数:

1
2
3
4
5
6
7
8
var max_results;
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
 var c = cookies[i];
 c = c.replace(/^[ ]+/, ''); // remove leading spaces
 if (c.indexOf("max_results=") === 0)
 max_results = Number(c.substring(12, c.length));
}

这段代码实在太丑了,理想的接口应该是这样的:

1
2
set_cookie(name, value, days_to_expire);
delete_cookie(name);

对于并不理想的接口,你永远可以用自己的函数做封装,让接口更好用。

按自己需要写接口

1
2
3
4
5
6
7
ser_info = { "username": "...", "password": "..." }
user_str = json.dumps(user_info)
cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE)
encrypted_bytes = cipher.update(user_str)
encrypted_bytes += cipher.final() # flush out the current 128 bit block
url = "http://example.com/?user_info=" + base64.urlsafe_b64encode(encrypted_bytes)
...

虽然终极目的是拼接用户信息的字符,但是代码大部分做的事情是解析python的object,所以:

1
2
3
4
5
def url_safe_encrypt(obj):
 obj_str = json.dumps(obj)
 cipher = Cipher("aes_128_cbc", key=PRIVATE_KEY, init_vector=INIT_VECTOR, op=ENCODE) encrypted_bytes = cipher.update(obj_str)
 encrypted_bytes += cipher.final() # flush out the current 128 bit block
 return base64.urlsafe_b64encode(encrypted_bytes)

这样在其他地方也可以调用:

1
2
user_info = { "username": "...", "password": "..." }
url = "http://example.com/?user_info=" + url_safe_encrypt(user_info)

分离子函数是好习惯,但是也要适度,过度的分离成多个小函数,也会让查找变得困难。

代码应该是一次只完成一个任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var place = location_info["LocalityName"]; // e.g. "Santa Monica"
if (!place) {
 place = location_info["SubAdministrativeAreaName"]; // e.g. "Los Angeles"
}
if (!place) {
 place = location_info["AdministrativeAreaName"]; // e.g. "California"
}
if (!place) {
 place = "Middle-of-Nowhere";
}
if (location_info["CountryName"]) {
 place += ", " + location_info["CountryName"]; // e.g. "USA"
} else {
 place += ", Planet Earth";
}
 
return place;

这是一个用来拼地名的函数,有很多的条件判断,读起来非常吃力,有没有办法拆解任务呢?

1
2
3
4
var town = location_info["LocalityName"]; // e.g. "Santa Monica"
var city = location_info["SubAdministrativeAreaName"]; // e.g. "Los Angeles"
var state = location_info["AdministrativeAreaName"]; // e.g. "CA"
var country = location_info["CountryName"]; // e.g. "USA"

先拆解第一个任务,将各变量分别保存,这样在后面使用中不需要去记忆那些繁长的key值了,第二个任务,解决地址拼接的后半部分:

1
2
3
4
5
6
7
// Start with the default, and keep overwriting with the most specific value. var second_half = "Planet Earth";
if (country) {
 second_half = country;
}
if (state && country === "USA") {
 second_half = state;
}

再来解决前半部分:

1
2
3
4
5
6
7
8
9
10
var first_half = "Middle-of-Nowhere";
if (state && country !== "USA") {
 first_half = state;
}
if (city) {
 first_half = city;
}
if (town) {
 first_half = town;
}

大功告成:

1
return first_half + ", " + second_half;

如果注意到有USA这个变量的判断的话,也可以这样写:

1
2
3
4
5
6
7
8
9
var first_half, second_half;
if (country === "USA") {
 first_half = town || city || "Middle-of-Nowhere";
 second_half = state || "USA";
} else {
 first_half = town || city || state || "Middle-of-Nowhere";
 second_half = country || "Planet Earth";
}
return first_half + ", " + second_half;

要把一个复杂的东西解释给别人,一些细节很容易就让人产生迷惑,所以想象把你的代码用平实的语言解释给别人听,别人是否能懂,有一些准则可以帮助你让代码更清晰:

  • 用最平实的语言描述代码的目的,就像给读者讲述一样
  • 注意描述中关键的字词
  • 让你的代码符合你的描述

下面这段代码用来校验用户的权限:

1
2
3
4
5
6
7
8
9
10
11
$is_admin = is_admin_request();
if ($document) {
 if (!$is_admin && ($document['username'] != $_SESSION['username'])) {
 return not_authorized();
 }
} else {
 if (!$is_admin) {
 return not_authorized();
 }
}
// continue rendering the page ...

这一段代码不长,里面的逻辑嵌套倒是复杂,参考前面章节所述,嵌套太多非常影响阅读理解,将这个逻辑用语言描述就是:

1
2
3
4
有两种情况有权限:
1、你是管理员(admin)
2、你拥有这个文档
否则就没有权限

根据描述来写代码:

1
2
3
4
5
6
7
8
if (is_admin_request()) {
 // authorized
} elseif ($document && ($document['username'] == $_SESSION['username'])) {
 // authorized
} else {
 return not_authorized();
}
// continue rendering the page ...

最易懂的代码就是没有代码!

  • 去掉那些没意义的feature,也不要过度设计
  • 重新考虑需求,解决最简单的问题,也能完成整体的目标
  • 熟悉你常用的库,周期性研究他的API

还有一些与测试相关的章节,留给你自己去研读吧,再次推荐此书:

143月/13

用于展现图表的50种JavaScript库

发布在 邵珠庆

 

在很多项目中都会有在前端展现数据图表的需求,而在开发过程中,开发者往往会使用一些JavaScript库,从而更有效地达到想要的目标。最近,TechSlide上的一篇文章总结了50种用于展现图表的JavaScript库,并对每种库做了简要的说明。这对于想要选择合适JavaScript库的开发者很有参考意义。

  文章作者首推的库是D3,他说到:

  它非常让人惊叹,我很喜欢它的简洁性。它的文档非常完备,源代码托管在GitHub上,而且不断会添加新的示例。有一种叫做Tributary的创建D3原型的工具,其中有很多非常棒的示例。这个库非常好,以至于xcharts、nvd3、Rickshaw、Cubism.js、dc.js、xkcd都是基于它构建的。如果你想要做出优秀的自定义数据可视化效果,那么D3可能是你最佳选择,或者对于更简单的图,你可以选择上面所提到的基于D3的库。最后,我强烈推荐阅读Scott Murray关于D3的免费书《Interactive Data Visualization for the Web》和《Dashing D3 tutorials》。

  接下来,他列举并简要说明了其它用于展现数据、制作表格和图表的JavaScript库,列在前20位的如下:

  • HighCharts——它非常强大,你可以在JSFiddle中查看和编辑大量示例。它不免费,但拥有很多客户(IBM、NASA、MasterCard等)。它还向下兼容IE 8。
  • jqPlot——如果你已经在使用jQuery,不想为HighCharts付费,而且情况很简单,不需要D3那样复杂的库,那么jqPlot是很好的选择。
  • dygraphs——一种开源的JavaScript库,可以做出可交互、可缩放的时间线图表。对于大数据集合非常适用。
  • Protovis——和D3出自同一支团队之手,是一种免费的开源库。你可以查看这个stackoveflow 页面来了解D3与其的区别。
  • Flot Charts——与jqPlot一样,Flot是一种针对jQuery的纯JavaScript库,专注于简单的用法、引人注目的外观和交互特性。
  • Google Chart Tools——强大、免费、易于使用。内容丰富,从最简单的线状图到负责的层级树状图都有,在展示页面中提供了大量设计良好的图表类型。
  • dc.js——基于D3的JavaScript图表库,拥有本地跨过滤器(crossfilter)的支持,并让你可以高效率地浏览大型多维数据集。
  • xcharts——基于D3用于构建自定义图表的库。
  • nvd3——让你可以构建可重用的图表和图表组件,同时具有d3.js的强大功能。
  • rickshaw——用于创建可交互时间线图表的JavaScript工具。
  • Cubism.js——用于可视化时间线的D3插件。使用了Cubism构建更好的实时仪表盘,可以从Graphite、Cube和其他源拉取数据。
  • xkcd——让你可以使用D3在JavaScript中做出XKCD样式的图表。
  • jQuery Sparklines——一种jQuery插件,可以直接在浏览器中创建小型的内嵌图表。
  • peity——一种简单的jQuery插件,可以把元素的内容转换成简单的饼图、线图和柱状图。
  • BonsaiJS——一种轻量级的图形库,拥有直观的图形API和SVG渲染器。
  • Flotr——为Prototype.js所用的JavaScript图表库。它拥有很多特性,像对负数值的支持、鼠标跟踪、选定支持、缩放支持、事件挂钩、CSS样式支持、在画布(canvas)中包含文字、旋转的标签、渐变颜色、图形标题和子标题、电子表格、CSV数据下载等等。
  • ProtoChart——物如其名,ProtoChart让你可以使用JavaScript和Prototype创建很漂亮的图表。它是一种开源库。
  • Flotr2——HumbleSoftware当前正在做的项目,让你可以使用Canvas和JavaScript创建图表。
  • jQuery-Visualize——HTML的table元素驱动的HTML5 canvas图表。也是针对jQuery的图表插件。
  • JS Charts——基于JavaScript的图表生成器,只需要很少甚至不需要编码。免费版会有水印,可以通过付费去掉。
  • ……

  文章中还列举的JavaScript库有:PlotKitMilkChartmoochartmoowheeltable2chartCanvas 3D GraphTufteGraphArborJSTimePlotgRaphaelICOElychartsZingChartRGraphDojo ChartingBluffcanvasXpressccchartJITJSXGraphSmoothie ChartsYUI ChartsamchartsEmprise JavaScript ChartsFusionChartsJavaScript Diagram BuilderjGraphSencha Touch ChartsStyle ChartAwesomeChartJS等,都各有千秋,如果你对这些库感兴趣的话,可以访问相应的链接或者阅读原文

  这个列表对于想要利用JavaScript技术创建图表展现数据的开发者来说,非常具有参考意义,你可以从中选择最适合的库,从而高效、高质量地完成任务。

272月/13

CI框架源码完全分析之核心文件Codeigniter.php

发布在 邵珠庆

$assign_to_config['subclass_prefix']));
}

/*
*php 程序运行默认是30s,这里用set_time_limt延长了,关于set_time_Limit() http://www.phpddt.com/php/set_time_limit.html
* 扩展阅读,关于safe_mode:http://www.phpddt.com/php/643.html ,你会完全明白的
*/
if (function_exists("set_time_limit") == TRUE AND @ini_get("safe_mode") == 0)
{
@set_time_limit(300);
}

/*
* 加载Benchmark,它很简单,就是计算任意两点之间程序的运行时间
*/
$BM =& load_class('Benchmark', 'core');
$BM->mark('total_execution_time_start');
$BM->mark('loading_time:_base_classes_start');

//加载钩子,后期会分析到,这玩意特好,扩展它能改变CI的运行流程
$EXT =& load_class('Hooks', 'core');

//这里就是一个钩子啦,其实就是该钩子程序在这里执行
$EXT->_call_hook('pre_system');

//加载配置文件,这里面都是一些加载或获取配置信息的函数
$CFG =& load_class('Config', 'core');

// 如果在index.php中也有配置$assign_to_config,则也把它加入到$CFG
if (isset($assign_to_config))
{
$CFG->_assign_to_config($assign_to_config);
}

//加载utf8组件、URI组件、Router组件
$UNI =& load_class('Utf8', 'core');
$URI =& load_class('URI', 'core');
$RTR =& load_class('Router', 'core');
$RTR->_set_routing();

//如果在index.php中定义了$routing,那么就会覆盖上面路由
if (isset($routing))
{
$RTR->_set_overrides($routing);
}

//加载output输出组件,不然你怎么用$this->Load->view()啊
$OUT =& load_class('Output', 'core');

//又见钩子,这里你可以自己写钩子程序替代Output类的缓存输出
if ($EXT->_call_hook('cache_override') === FALSE)
{
if ($OUT->_display_cache($CFG, $URI) == TRUE)
{
exit;
}
}

//安全组件啦,防xss攻击啊,csrf攻击啊
//关于xss攻击:http://www.phpddt.com/php/php-prevent-xss.html
//关于csrf:攻击:http://www.phpddt.com/reprint/csrf.html
$SEC =& load_class('Security', 'core');

//加载输入组件,就是你常用的$this->input->post();等
$IN =& load_class('Input', 'core');

//加载语言组件啦
$LANG =& load_class('Lang', 'core');

//引入CI的控制器父类
require BASEPATH.'core/Controller.php';

function &get_instance()
{
return CI_Controller::get_instance();
}

//当然你扩展了CI_Controller控制器的话,也要引入啦
if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php'))
{
require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php';
}

//加载你自己应用中的控制器Controller,如果没有当然error啦
if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'))
{
show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
}
include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php');

// 好的基础的类都加载完毕了,咱可以mark一下
$BM->mark('loading_time:_base_classes_end');

//路由获取了控制器名和方法名,比如说默认welcome/index
$class = $RTR->fetch_class();
$method = $RTR->fetch_method();
//这里CI规定一般非公共的方法以_开头,下面是判断,如果URI不可访问就show_404()
if ( ! class_exists($class)
OR strncmp($method, '_', 1) == 0
OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller')))
)
{
if ( ! empty($RTR->routes['404_override']))
{
$x = explode('/', $RTR->routes['404_override']);
$class = $x[0];
$method = (isset($x[1]) ? $x[1] : 'index');
if ( ! class_exists($class))
{
if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
{
show_404("{$class}/{$method}");
}

include_once(APPPATH.'controllers/'.$class.'.php');
}
}
else
{
show_404("{$class}/{$method}");
}
}

//又是钩子,该钩子发生在控制器实例化之前的
$EXT->_call_hook('pre_controller');

//又mark一个点
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_start');
//终于实例化控制器了
$CI = new $class();

//钩子,不想多说了
$EXT->_call_hook('post_controller_constructor');

/*
* ------------------------------------------------------
* Call the requested method
* ------------------------------------------------------
*/
// Is there a "remap" function? If so, we call it instead
if (method_exists($CI, '_remap'))
{
$CI->_remap($method, array_slice($URI->rsegments, 2));
}
else
{
// is_callable() returns TRUE on some versions of PHP 5 for private and protected
// methods, so we'll use this workaround for consistent behavior
if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
{
// Check and see if we are using a 404 override and use it.
if ( ! empty($RTR->routes['404_override']))
{
$x = explode('/', $RTR->routes['404_override']);
$class = $x[0];
$method = (isset($x[1]) ? $x[1] : 'index');
if ( ! class_exists($class))
{
if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
{
show_404("{$class}/{$method}");
}

include_once(APPPATH.'controllers/'.$class.'.php');
unset($CI);
$CI = new $class();
}
}
else
{
show_404("{$class}/{$method}");
}
}

// 终于调用方法了,$this->load->view()把内容放到缓存区
call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
}
$BM->mark('controller_execution_time_( '.$class.' / '.$method.' )_end');
$EXT->_call_hook('post_controller');

//这里就是把缓存区的内容输出了
if ($EXT->_call_hook('display_override') === FALSE)
{

$OUT->_display();
}

$EXT->_call_hook('post_system');
//关闭数据库的链接
if (class_exists('CI_DB') AND isset($CI->db))
{
$CI->db->close();
}

 
192月/13

10 个不错的 jQuery 代码片段

发布在 邵珠庆

 

[代码] 图片预加载

01 (function($) {
02   var cache = [];
03   // Arguments are image paths relative to the current page.
04   $.preLoadImages = function() {
05     var args_len = arguments.length;
06     for (var i = args_len; i--;) {
07       var cacheImage = document.createElement('img');
08       cacheImage.src = arguments[i];
09       cache.push(cacheImage);
10     }
11   }
12  
13 jQuery.preLoadImages("image1.gif""/path/to/image2.png");

[代码] 在新窗口打开链接 (target=”blank”)

1 $('a[@rel$='external']').click(function(){
2      this.target = "_blank";
3 });
4  
5 /*
6    Usage:
7    <a href="http://www.catswhocode.com" rel="external">catswhocode.com</a>
8 */

[代码] 当支持 JavaScript 时为 body 增加 class

1 /* 该代码只有1行,但是最简单的用来检测浏览器是否支持 JavaScript 的方法,如果支持 JavaScript 就在 body 元素增加一个 hasJS 的 class */
2 $('body').addClass('hasJS');

[代码] 平滑滚动页面到某个锚点

01 $(document).ready(function() {
02     $("a.topLink").click(function() {
03         $("html, body").animate({
04             scrollTop: $($(this).attr("href")).offset().top + "px"
05         }, {
06             duration: 500,
07             easing: "swing"
08         });
09         return false;
10     });
11 });

[代码] 鼠标滑动时的渐入和渐出

1 $(document).ready(function(){
2     $(".thumbs img").fadeTo("slow", 0.6); // This sets the opacity of the thumbs to fade down to 60% when the page loads
3  
4     $(".thumbs img").hover(function(){
5         $(this).fadeTo("slow", 1.0); // This should set the opacity to 100% on hover
6     },function(){
7         $(this).fadeTo("slow", 0.6); // This should set the opacity back to 60% on mouseout
8     });
9 });

[代码] 制作等高的列

1 var max_height = 0;
2 $("div.col").each(function(){
3     if ($(this).height() > max_height) { max_height = $(this).height(); }
4 });
5 $("div.col").height(max_height);

[代码] 在一些老的浏览器上启用 HTML5 的支持

01 (function(){
02     if(!/*@cc_on!@*/0)
03         return;
04     var e ="abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,header,hgroup,mark,menu,meter,nav,output,progress,section,time,video".split(','),i=e.length;while(i--){document.createElement(e[i])}
05 })()
06  
07 //然后在head中引入该js
08 <!--[if lt IE 9]>
09 <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
10 <![endif]-->

[代码] 测试浏览器是否支持某些 CSS3 属性

01 var supports = (function() {
02    var div = document.createElement('div'),
03       vendors = 'Khtml Ms O Moz Webkit'.split(' '),
04       len = vendors.length;
05  
06    return function(prop) {
07       if ( prop in div.style ) return true;
08  
09       prop = prop.replace(/^[a-z]/, function(val) {
10          return val.toUpperCase();
11       });
12  
13       while(len--) {
14          if ( vendors[len] + prop in div.style ) {
15             // browser supports box-shadow. Do what you need.
16             // Or use a bang (!) to test if the browser doesn't.
17             return true;
18          }
19       }
20       return false;
21    };
22 })();
23  
24 if ( supports('textShadow') ) {
25    document.documentElement.className += ' textShadow';

[代码] 获取 URL 中传递的参数

1 $.urlParam = function(name){
2     var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
3     if (!results) { return 0; }
4     return results[1] || 0;
5 }

[代码] 禁用表单的回车键提交

1 $("#form").keypress(function(e) {
2   if (e.which == 13) {
3     return false;
4   }
5 });

112月/13

Python 入門語法和類型

发布在 邵珠庆

Python的设计目标之一是让源代码具备高度的可读性。它设计时尽量使用其它语言经常使用的标点符号和英语单词,让源代码整体看起来很整洁美观。它不像静态语言如C、Pascal那样需要重复书写声明语句,也不像它们的语法那样经常有特殊情况和惊喜。

缩进

Python开发者有意让违反了缩进规则的程序不能通过编译,以此来强制程序员养成良好的编程习惯。并且在Python语言里,缩进而非花括号或者某种关键字,被用于表示语句块的开始和退出。增加缩进表示语句块的开始,而减少缩进则表示语句块的退出。缩进成为了语法的一部分。例如

if语句:

 

if age < 21:
    print("你不能買酒。")
    print("不過你能買口香糖。")
print("這句話處於if語句塊的外面。")

根据PEP的规定,必须使用4个空格来表示每级缩进。使用Tab字符和其它数目的空格虽然都可以编译通过,但不符合编码规范。支持Tab字符和其它数目的空格仅仅是为了兼容很旧的Python程序和某些有问题的编辑器。

语句和控制流

  • if语句,当条件成立时执行语句块。经常与else,elif(相当于else if)配合使用。
  • for语句,遍历列表、字符串、字典、集合等迭代器,依次处理迭代器中的每个元素。
  • while语句,当条件为真时,循环执行语句块。
  • try语句。与except,finally配合使用处理在程序运行中出现的异常情况。
  • class语句。用于定义类型。
  • def语句。用于定义函数和类型的方法。
  • pass语句。表示此行为空,不执行任何操作。
  • assert语句。用于程序调试阶段时测试运行条件是否满足。
  • with语句。Python2.6以后定义的语法,在一个场景中运行语句块。比如,运行语句块前加锁,然后在语句块运行结束后释放它。
  • yield语句。在迭代器函数内使用,用于返回一个元素。自从Python 2.5版本以后。这个语句变成一个运算符。

表达式

Python的表达式写法与C/C++类似。只是在某些写法有所差别。

  • 主要的算术运算符与C/C++类似。+, -, *, /, //, **, ~, %分别表示加法或者取正、减法或者取负、乘法、除法、整除、乘方、取补、取模。>>, <<表示右移和左移。&, |, ^表示二进制的AND, OR, XOR运算。>, <, ==, !=, <=, >=用于比较两个表达式的值,分别表示大于、小于、等于、不等于、小于等于、大于等于。在这些运算符里面,~, |, ^, &, <<, >>必须应用于整数。
  • Python使用andornot表示逻辑运算。
  • is, is not用于比较两个变量是否是同一个对象。in, not in用于判断一个对象是否属于另外一个对象。
  • Python支持"列表推导式"(list comprehension),比如计算0-9的平方和:
>>> sum(x * x for x in range(10))285

 

  • Python使用lambda表示匿名函数。匿名函数体只能是表达式。比如:
>>> add=lambda x, y : x + y>>> add(3,2)5

 

  • Python使用y if cond else x表示条件表达式。意思是当cond为真时,表达式的值为y,否则表达式的值为x。相当于C++和Java里的cond?y:x
  • Python区分列表(list)和元组(tuple)两种类型。list的写法是[1,2,3],而tuple的写法是(1,2,3)。可以改变list中的元素,而不能改变tuple。在某些情况下,tuple的括号可以省略。tuple对于赋值语句有特殊的处理。因此,可以同时赋值给多个变量,比如:
>>> x, y=1, 2 #同时给x,y赋值,最终结果:x=1, y=2

特别地,可以使用以下这种形式来交换两个变量的值:

>>> x, y=y, x #最终结果:y=1, x=2
  • Python使用'(单引号)和"(双引号)来表示字符串。与Perl、Unix Shell语言或者Ruby、Groovy等语言不一样,两种符号作用相同。一般地,如果字符串中出现了双引号,就使用单引号来表示字符串;反之则使用双引号。如果都没有出现,就依个人喜好选择。出现在字符串中的\(反斜杠)被解释为特殊字符,比如\n表示换行符。表达式前加r指示Python不解释字符串中出现的\。这种写法通常用于编写正则表达式或者Windows文件路径。
  • Python支持列表切割(list slices),可以取得完整列表的一部分。支持切割操作的类型有str, bytes, list, tuple等。它的语法是...[left:right]或者...[left:right:stride]。假定nums变量的值是[1, 3, 5, 7, 8, 13, 20],那么下面几个语句为真:
  • nums[2:5] == [5, 7, 8] 从下标为2的元素切割到下标为5的元素,但不包含下标为2的元素。
  • nums[1:] == [3, 5, 7, 8, 13, 20] 切割到最后一个元素。
  • nums[:-3] == [1, 3, 5, 7] 从最开始的元素一直切割到倒数第3个元素。
  • nums[:] == [1, 3, 5, 7, 8, 13, 20] 返回所有元素。改变新的列表不会影响到nums。
  • nums[1:5:2] == [3, 7]

函数

Python的函数支持递归、默认参数值、可变参数,但不支持函数重载。为了增强代码的可读性,可以在函数后书写”文档字符串“(Documentation Strings,或者简称docstrings),用于解释函数的作用、参数的类型与意义、返回值类型与取值范围等。可以使用内置函数help()打印出函数的使用帮助。比如:

复制代码
1 >>> def randint(a, b):
2 ...     "Return random integer in range [a, b], including both end points."
3 ...
4 >>> help(randint)
5 Help on function randint in module __main__:
6  
7 randint(a, b)
8     Return random integer in range [a, b], including both end points.
复制代码

对象的方法

对象的方法是指绑定到对象的函数。调用对象方法的语法是instance.method(arguments)。它等价于调用Class.method(instance, arguments)。当定义对象方法时,必须显式地定义第一个参数为self,用于访问对象的内部数据。self相当于C++, Java里面的this变量。比如:

复制代码
class Fish:
    def eat(self, food):
        if food is not None:
            self.hungry=False
 
#构造Fish的实例:
f=Fish()
#以下两种调用形式是等价的:
Fish.eat(f, "earthworm")
f.eat("earthworm")
复制代码

Python认识一些以”__“开始并以"__"结束的特殊方法名,它们用于实现运算符重载和实现多种特殊功能。

类型

Python采用动态类型系统。在编译的时候,Python不会检查对象是否拥有被调用的方法或者属性,而是直至运行时,才做出检查。所以操作对象时可能会抛出异常。不过,虽然Python采用动态类型系统,它同时也是强类型的。Python禁止没有明确定义的操作,比如数字加字符串。

与其它面向对象语言一样,Python允许程序员定义类型。构造一个对象只需要像函数一样调用类型即可,比如,对于前面定义的Fish类型,使用Fish()。类型本身也是特殊类型type的对象(type类型本身也是type对象),这种特殊的设计允许对类型进行反射编程。

Python内置丰富的数据类型。与Java、C++相比,这些数据类型有效地减少代码的长度。下面这个列表简要地描述了Python内置数据类型(适用于Python 3.x):

类型 描述 例子
str 一个由字符组成的不可更改的有串行。在Python 3.x里,字符串由Unicode字符组成。 'Wikipedia'
"Wikipedia"
"""Spanning
multiple
lines"""
bytes 一个由字节组成的不可更改的有串行。 b'Some ASCII'
b"Some ASCII"
list 可以包含多种类型的可改变的有串行 [4.0, 'string', True]
tuple 可以包含多种类型的不可改变的有串行 (4.0, 'string', True)
set, frozenset 与数学中集合的概念类似。无序的、每个元素唯一。 {4.0, 'string', True}
frozenset([4.0, 'string', True])
dict 一个可改变的由键值对组成的无串行。 {'key1': 1.0, 3: False}
int 精度不限的整数 42
float 浮点数。精度与系统相关。 3.1415927
complex 复数 3+2.7j
bool 逻辑值。只有两个值:真、假 True
False

除了各种数据类型,Python语言还用类型来表示函数、模块、类型本身、对象的方法、编译后的Python代码、运行时信息等等。因此,Python具备很强的动态性。

数学运算

Python使用与C、Java类似的运算符,支持整数与浮点数的数学运算。同时还支持复数运算与无穷位数(实际受限于计算机的能力)的整数运算。除了求绝对值函数abs()外,大多数数学函数处于mathcmath模块内。前者用于实数运算,而后者用于复数运算。使用时需要先导入它们,比如:

>>> import math
>>> print(math.sin(math.pi/2))
1.0

fractions模块用于支持分数运算;decimal模块用于支持高精度的浮点数运算。

Python定义求余运行a % b的值处于开区间[0, b)内,如果b是负数,开区间变为(b, 0]。这是一个很常见的定义方式。不过其实它依赖于整除的定义。为了让方程式:b * (a // b) + a % b = a恒真,整除运行需要向负无穷小方向取值。比如7 // 3的结果是2,而(-7) // 3的结果却是-3。这个算法与其它很多编程语言不一样,需要注意,它们的整除运算会向0的方向取值。

Python允许像数学的常用写法那样连着写两个比较运行符。比如a < b < ca < b and b < c等价。C++的结果与Python不一样,首先它会先计算a < b,根据两者的大小获得0或者1两个值之一,然后再与c进行比较。

52月/13

PHP的简单正则判断字符串类型

发布在 邵珠庆

前两天写PHP代码的时候遇到要判断一个变量是否为数字,所以就把相关的文章简单的摘抄了过来:

php 正则验证字符串是否为数字
方法一:
php中利用正则表达式验证字符串是否为数字一件非常容易的事情,最主要的是如何写好正则表达式以及掌握正则表达式的写法,在此利用正则表达式的方式来列举一下判断数字的方法。

<?

if($str){
  if(eregi("^[0-9]+$",$str)){
    $str=(int)$str;
  }else{
    echo "获取到的数据不是有效的数字类型,操作将停止!";
    exit();
  }
}else{
  echo "需要验证的数据为空,操作停止!";
  exit();
}

?>

方法二:
建议大家对关键的参数必须做过滤。

如数字正则过滤
if(preg_match("/^\d*$/",$fgid)) {echo('是数字');}else{echo('不是数字');}

或者用函数
if(is_numeric($fgid)) {echo('是数字');}else{ echo('不是数字');}

这两种方法的区别是  is_numeric小数也会认为是数字,而前面正则会把小数点当作字符。

附一些常用的正则运算:

验证数字:^[0-9]*$
验证n位的数字:^\d{n}$
验证至少n位数字:^\d{n,}$
验证m-n位的数字:^\d{m,n}$
验证零和非零开头的数字:^(0|[1-9][0-9]*)$
验证有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
验证有1-3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
验证非零的正整数:^\+?[1-9][0-9]*$
验证非零的负整数:^\-[1-9][0-9]*$
验证非负整数(正整数 + 0)  ^\d+$
验证非正整数(负整数 + 0)  ^((-\d+)|(0+))$
验证长度为3的字符:^.{3}$
验证由26个英文字母组成的字符串:^[A-Za-z]+$
验证由26个大写英文字母组成的字符串:^[A-Z]+$
验证由26个小写英文字母组成的字符串:^[a-z]+$
验证由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
验证由数字、26个英文字母或者下划线组成的字符串:^\w+$
验证用户密码:^[a-zA-Z]\w{5,17}$ 正确格式为:以字母开头,长度在6-18之间,只能包含字符、数字和下划线。
验证是否含有 ^%&‘,;=?$\” 等字符:[^%&‘,;=?$\x22]+
验证汉字:^[\u4e00-\u9fa5],{0,}$
验证Email地址:^\w+[-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
验证InternetURL:^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ ;^[a-zA-z]+://(w+(-w+)*)(.(w+(-w+)*))*(?S*)?$
验证电话号码:^(\(\d{3,4}\)|\d{3,4}-)?\d{7,8}$:–正确格式为:XXXX-XXXXXXX,XXXX-XXXXXXXX,XXX-XXXXXXX,XXX-XXXXXXXX,XXXXXXX,XXXXXXXX。
验证身份证号(15位或18位数字):^\d{15}|\d{}18$
验证一年的12个月:^(0?[1-9]|1[0-2])$ 正确格式为:“01”-“09”和“1”“12”
验证一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$    正确格式为:01、09和1、31。
整数:^-?\d+$
非负浮点数(正浮点数 + 0):^\d+(\.\d+)?$
正浮点数   ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
非正浮点数(负浮点数 + 0) ^((-\d+(\.\d+)?)|(0+(\.0+)?))$
负浮点数  ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数  ^(-?\d+)(\.\d+)?