<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
   <title>BeiYuu.com</title>
   <link href="http://beiyuu.com/atom.xml" rel="self" type="application/atom+xml"/>
   <link href="http://beiyuu.com" rel="alternate" type="text/html" />
   <updated>2014-07-19T11:45:53-07:00</updated>
   <id>http://beiyuu.com</id>
   <author>
     <name></name>
     <email></email>
   </author>

   
   <entry>
     <title>VPS环境搭建详解 (Virtualenv+Gunicorn+Supervisor+Nginx)</title>
     <link href="http://beiyuu.com/vps-config-python-vitrualenv-flask-gunicorn-supervisor-nginx"/>
     <updated>2014-01-25T00:00:00-08:00</updated>
     <id>http://beiyuu.com/vps-config-python-vitrualenv-flask-gunicorn-supervisor-nginx</id>
     <content type="html">&lt;p&gt;新用户注册购买&lt;a href=&quot;https://www.digitalocean.com/?refcode=f95f7297ed94&quot; title=&quot;DigitalOcean&quot;&gt;DigitalOcean&lt;/a&gt;的VPS，使用优惠码&lt;code&gt;2014SSD&lt;/code&gt;（或请尝试&lt;code&gt;10TOSHIP&lt;/code&gt;）有$10赠送，可用两个月。DO采取丧心病狂的低价竞争策略，每月$5即可享用全功能的SSD硬盘VPS，具体去看看&lt;a href=&quot;https://www.digitalocean.com/?refcode=f95f7297ed94&quot; title=&quot;DigitalOcean&quot;&gt;这里&lt;/a&gt;吧。&lt;/p&gt;

&lt;p&gt;注册，选择套餐、机房、系统(我选默认Ubuntu 12)，付款成功，可以开始配置了。&lt;/p&gt;

&lt;p&gt;我们目标实现一个支持多个独立域名网站的线上Python环境，这会用到&lt;a href=&quot;http://www.virtualenv.org/en/latest/&quot; title=&quot;Virtualenv&quot;&gt;Virtualenv&lt;/a&gt;， &lt;a href=&quot;http://flask.pocoo.org/docs/&quot; title=&quot;Flask&quot;&gt;Flask&lt;/a&gt;， &lt;a href=&quot;http://gunicorn.org/&quot; title=&quot;Gunicorn&quot;&gt;Gunicorn&lt;/a&gt;， &lt;a href=&quot;http://supervisord.org/&quot; title=&quot;Supervisor&quot;&gt;Supervisor&lt;/a&gt;， &lt;a href=&quot;http://nginx.com/&quot; title=&quot;Nginx&quot;&gt;Nginx&lt;/a&gt;。&lt;/p&gt;

&lt;h2&gt;配置用户环境&lt;/h2&gt;

&lt;p&gt;因为要跑多个站，所以最好将他们完全隔离，每个站对应一个用户，于是我们有了：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; User        Site

 bob         dylan     ##bob用户有一个dylan的站
michael     jackson    ##michael用户有一个jackson的站
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注册成功后，会收到DO发来的&lt;code&gt;root&lt;/code&gt;账户的密码邮件，&lt;code&gt;ssh root@你的IP地址&lt;/code&gt;登录上去开始添加用户。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##推荐安装zsh作为默认shell
sudo apt-get update
sudo apt-get install zsh

##安装oh-my-zsh插件
cd ~/.
##自动安装脚本
wget --no-check-certificate https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

##添加用户bob
##参数-d：指定用户目录
##参数-m：如果目录不存在则创建
##参数-s：只用用户使用的 shell
useradd bob -d /home/bob -m -s /bin/zsh

#添加用户michael
useradd michael -d /home/michael -m -s /bin/zsh

##以上参数也可以修改passwd文件来调整
sudo vim /etc/passwd

##sudo和用户组管理在
visudo
sudo vim /etc/sudoers
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;新增用户之后，需要解锁：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##为新增用户设置一个初始密码即可解锁
passwd bob
passwd michael
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用ssh-keygen建立信任关系可以方便登录管理：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##本地机器
##会在~/.ssh目录下生成秘钥文件id_rsa、id_rsa.pub
ssh-keygen -t [rsa|dsa]

##复制公钥文件id_rsa.pub
scp ~/.ssh/id_rsa.pub bob@digitalocean:~/.ssh

##VPS上，添加本地机器的信任关系
cd ~/.ssh
cat id_rsa.pub &amp;gt;&amp;gt; ~/.ssh/authorized_keys

##OK，从本地机器登录到VPS的bob用户就不需要密码了
##同理，也可以添加到michael用户的.ssh目录下
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;更多资料可以阅读：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.chinaunix.net/old_jh/4/438660.html&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;Linux的用户和用户组管理&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://sofish.de/1685&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;把 Mac 上的 bash 换成 zsh&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://leeiio.me/bash-to-zsh-for-mac/&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;zsh – 给你的Mac不同体验的Terminal&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/kongqz/article/details/6338690&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;ssh-keygen的使用方法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.ruanyifeng.com/blog/2014/03/server_setup.html&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;Linux服务器的初步配置流程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.ruanyifeng.com/blog/2014/03/server_setup.html&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;Linux服务器的初步配置流程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;为每个APP创建Virtualenv&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.virtualenv.org/en/latest/&quot; title=&quot;Virtualenv&quot;&gt;Virtualenv&lt;/a&gt;可以为每个Python应用创建独立的开发环境，使他们互不影响，Virtualenv能够做到：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在没有权限的情况下安装新套件&lt;/li&gt;
  &lt;li&gt;不同应用可以使用不同的套件版本&lt;/li&gt;
  &lt;li&gt;套件升级不影响其他应用&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;安装Virtualenv&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##先安装Python的包管理pip
sudo apt-get install pip

##用pip安装virtualenv
sudo pip install virtualenv

##建议用bob用户登录操作
##bob用户创建dylan的virtualenv
cd /home/bob
virtualenv dylan

##激活virtualenv
cd /home/bob/dylan
source ./bin/activate

##取消激活只需
deactivate

##michael用户如法炮制即可
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;安装Flask&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://flask.pocoo.org/docs/&quot; title=&quot;Flask&quot;&gt;Flask&lt;/a&gt;是Python流行的一个web框架，但是Flask比Django轻量了许多，使用更加直观，这里并不展开讲Flask的细节，当做一个Hello Wordld来用就好了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##安装Flask
##依然在virtualenv activate的环境下
pip install Flask

##根目录下
vim runserver.py

##写入Flask的Hello World
from flask import Flask
app = Flask(__name__)

@app.route(&#39;/&#39;)
def hello_world():
    return &#39;Hello World!&#39;

    if __name__ == &#39;__main__&#39;:
        app.run()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;写入之后，如果在本地机器上可以运行&lt;code&gt;python runserver.py&lt;/code&gt;，然后打开&lt;code&gt;127.0.0.1:5000&lt;/code&gt;看到Hello World!了，但在VPS，这样不行，等待后面配置吧。&lt;/p&gt;

&lt;h2&gt;安装Gunicorn&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://gunicorn.org/&quot; title=&quot;Gunicorn&quot;&gt;Gunicorn&lt;/a&gt;是用于部署WSGI应用的，任何支持WSGI的都可以，虽说直接&lt;code&gt;python runserver.py&lt;/code&gt;这样网站也能跑起来，但那是方便开发而已，在线上环境，还是需要更高效的组件来做。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##安装Gunicorn
##依然在Virtualenv环境下
pip install gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Gunicorn的配置是必须的，因为我们要上两个独立的站，所以不能都用默认的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##在bob的dylan项目下
cd /home/bob/dylan
vim gunicorn.conf

##文件内写入以下内容
##指定workers的数目，使用多少个进程来处理请求
##绑定本地端口
workers = 3
bind = &#39;127.0.0.1:8000&#39;

##在michael的jackson项目下
cd /home/michael/jackson
vim gunicorn.conf

##写入文件内容
##与dylan的端口要不一样
workers = 3
bind = &#39;127.0.0.1:8100&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终的目录结构应该是这样的&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/home/
└── bob  //用户目录
│   ├── logs
│   └── dylan  //项目目录
│       ├── bin
│       │   ├── activate
│       │   ├── easy_install
│       │   ├── gunicorn
│       │   ├── pip
│       │   └── python
│       ├── include
│       │   └── python2.7 -&amp;gt; /usr/include/python2.7
│       ├── lib
│       │   └── python2.7
        ├── local
│       │   ├── bin -&amp;gt; /home/shenye/shenyefuli/bin
│       │   ├── include -&amp;gt; /home/shenye/shenyefuli/include
│       │   └── lib -&amp;gt; /home/shenye/shenyefuli/lib
│       │
│       │ //以上目录是Virtualenv生成的
│       ├── gunicorn_conf.py  //Gunicorn的配置文件
│       └── runserver.py  //hello_world程序
│
│
└── michael  //用户目录
    ├── logs
    └── jackson //项目目录
        ├── bin
        │   ├── activate
        │   ├── easy_install
        │   ├── gunicorn
        │   ├── pip
        │   └── python
        ├── include
        │   └── python2.7 -&amp;gt; /usr/include/python2.7
        ├── lib
        │   └── python2.7
        ├── local  //以上这些文件都是Virtualenv需要的
        │   ├── bin -&amp;gt; /home/shenye/shenyefuli/bin
        │   ├── include -&amp;gt; /home/shenye/shenyefuli/include
        │   └── lib -&amp;gt; /home/shenye/shenyefuli/lib
        │
        │ //以上目录是Virtualenv生成的
        ├── gunicorn_conf.py  //Gunicorn的配置文件
        └── runserver.py  //hello_world程序
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;安装Supervisor&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://supervisord.org/&quot; title=&quot;Supervisor&quot;&gt;Supervisor&lt;/a&gt;可以同时启动多个应用，最重要的是，当某个应用Crash的时候，他可以自动重启该应用，保证可用性。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##安装Supervisor
##sudo安装
sudo apt-get install supervisor

##启动服务
sudo service supervisor start
##终止服务
sudo service supervisor stop
##也可以直接kill pid
ps -A | grep supervisor
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;修改了程序代码，或者修改了配置，需要手动重启supervisor服务，尤其是摸不着头脑的错误的时候，重启最能解决问题！&lt;/p&gt;

&lt;p&gt;安装好之后，开始配置各应用的supervisor服务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##supervisor的配置文件位置在：
/etc/supervisor/supervisor.conf

##为了代码好看一些，我们分别放置各项目的配置文件
##新建bob的dylan项目配置文件
touch /etc/supervisor/conf.d/dylan.conf

##文件内容
[program:dylan]
##注意项目目录和gunicorn的配置文件地址
command=/home/bob/dylan/bin/gunicorn runserver:app -c /home/bob/dylan/gunicorn.conf
directory=/home/bob/dylan
user=bob
autostart=true
autorestart=true
##log文件的位置
stdout_logfile=/home/bob/logs/gunicorn_supervisor.log


##新建michael的jackson项目配置文件
touch /etc/supervisor/conf.d/jackson.conf

##文件内容
[program:jackson]
command=/home/michael/jackson/bin/gunicorn runserver:app -c /home/michael/jackson/gunicorn.conf
directory=/home/michael/jackson
user=michael
autostart=true
autorestart=true
stdout_logfile=/home/michael/logs/gunicorn_supervisor.log
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;写好配置之后：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##重新读取配置
sudo supervisorctl reread

##启动服务
sudo supervisorctl start dylan
sudo supervisorctl start jackson

##停止服务
sudo supervisorctl stop dylan
sudo supervisorctl stop jackson

##有问题就重启supervisor的总服务
sudo service supervisor stop
sudo service supervisor start
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;安装Nginx&lt;/h2&gt;

&lt;p&gt;有了&lt;a href=&quot;http://gunicorn.org/&quot; title=&quot;Gunicorn&quot;&gt;Gunicorn&lt;/a&gt;、&lt;a href=&quot;http://supervisord.org/&quot; title=&quot;Supervisor&quot;&gt;Supervisor&lt;/a&gt;，本地的环境的算是搭好了，但是我们需要让VPS上的网站从外网可以访问，这时候需要Nginx。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://nginx.com/&quot; title=&quot;Nginx&quot;&gt;Nginx&lt;/a&gt;是轻量级、性能强、占用资源少，能很好的处理高并发的反向代理软件，是我们的不二选择：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##安装Nginxx
sudo apt-get install nginx

##启动服务
sudo service nginx start

##查看VPS的IP地址
ifconfig eth0 | grep inet | awk &#39;{ print $2  }&#39;

##重启和暂停服务
sudo service nginx restart
sudo service nginx stop
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx的配置文件和Supervisor类似，不同的程序可以分别配置，然后被总配置文件include：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;##Nginx的配置文件地址
/etc/nginx/nginx.conf

##新建bob的dylan项目配置文件
##在sites-available目录下
touch /etc/nginx/sites-available/dylan.com

##文件内容
server {
        listen   80;             //端口
        server_name dylan.com;   //访问域名

        root /home/bob/dylan/;
        access_log /home/bob/logs/access.log;
        error_log /home/bob/logs/access.log;

        location / {
                proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                if (!-f $request_filename) {
                        proxy_pass http://127.0.0.1:8000;  //这里是dylan的gunicorn端口
                        break;
                }
        }
}


##michael的jackson项目
touch /etc/nginx/sites-available/jackson.com

##文件内容
server {
        listen   80;               //端口
        server_name jackson.com;   //访问域名

        root /home/michael/jackson/;
        access_log /home/michael/logs/access.log;
        error_log /home/michael/logs/access.log;

        location / {
                proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                if (!-f $request_filename) {
                        proxy_pass http://127.0.0.1:8100;  //这里是jackson的gunicorn端口
                        break;
                }
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;配置完成之后，&#39;sudo service nginx restart&#39;重启一下服务，再配置一下本地的Hosts，打开浏览器应该就能看到了。&lt;/p&gt;

&lt;h2&gt;完成&lt;/h2&gt;

&lt;p&gt;至此，一个完整的环境搭建就完成了，推荐试用&lt;a href=&quot;https://www.digitalocean.com/?refcode=f95f7297ed94&quot; title=&quot;DigitalOcean&quot;&gt;DigitalOcean&lt;/a&gt;的VPS看看，&lt;code&gt;2014SSD&lt;/code&gt;（或请尝试&lt;code&gt;10TOSHIP&lt;/code&gt;）的优惠码也可以试试看看有没有过期哦~&lt;/p&gt;
</content>
   </entry>
   
   <entry>
     <title>CSS3动画详解</title>
     <link href="http://beiyuu.com/css3-animation"/>
     <updated>2013-08-24T00:00:00-07:00</updated>
     <id>http://beiyuu.com/css3-animation</id>
     <content type="html">&lt;h2&gt;CSS3动画&lt;/h2&gt;

&lt;p&gt;有人认为CSS动画是做了js的事情，较真起来也算，只是已经抢占许多年了，早些年要实现鼠标滑过链接变色的基本效果，需要动用Java Applet，后来只需给HTML元素加事件&lt;code&gt;onclick=changecolor()&lt;/code&gt;，再之后正如你所知，只要写&lt;code&gt;:hover&lt;/code&gt;、&lt;code&gt;:focus&lt;/code&gt;这样的伪类即可，同样的，现在有了CSS3动画。&lt;/p&gt;

&lt;h4&gt;CSS3动画的优势：&lt;/h4&gt;

&lt;ul&gt;
    &lt;li&gt;写起来非常方便，不会js也没问题&lt;/li&gt;
    &lt;li&gt;有些动画js也不能很好的胜任，比如让一个元素在二维、三维空间旋转&lt;/li&gt;
    &lt;li&gt;运行效果流畅，让浏览器去优化性能&lt;/li&gt;
    &lt;li&gt;浏览器从底层优化动画序列，例如当tab不可见的时候，降低更新的频率提高整体性能&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;劣势：&lt;/h4&gt;

&lt;ul&gt;
    &lt;li&gt;CSS3动画应用的范围还是有限&lt;/li&gt;
    &lt;li&gt;兼容性：对于增强体验的Feature来说，可以无视&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;可以做动画效果的属性&lt;/h3&gt;

&lt;p&gt;理论上来说，任何单独的CSS属性都可以做动画的效果，比如：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;width&lt;/code&gt;：10px 到 100px&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;padding&lt;/code&gt;：0px 到 20px&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;color&lt;/code&gt;：#F00 到 #00F&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;top&lt;/code&gt;：0px 到 10px&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;border-radius&lt;/code&gt;：3px 到 8px&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;transform&lt;/code&gt;：rotate(0deg) 到 ratate(45deg)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;你也可以给&lt;code&gt;red&lt;/code&gt;、&lt;code&gt;blue&lt;/code&gt;这样的赋值的颜色属性加transition或animation，它会被自动转化为对应的RGB值。&lt;/p&gt;

&lt;h3&gt;不可以做动画效果的属性&lt;/h3&gt;

&lt;p&gt;看下面这些例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#container p {
    display: none;
    transition: all 3s ease;
}

#container:hover p {
    display: block;
}

/**********************/

#container p {
    height: 0px;
    transition: all 3s ease;
}

#container:hover p {
    height: auto;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;属性从无到有或到不确定值，动画效果不会生效，因为浏览器不知道如何去做，对于元素从无到有，你可以选择&lt;code&gt;opacity&lt;/code&gt;属性来处理。&lt;/p&gt;

&lt;h2&gt;CSS3 Transition&lt;/h2&gt;

&lt;p&gt;Transition是被用到最多的也是最简单的CSS3动画类型。如果要做一个10px宽的蓝色元素在3s后变成一个100px宽的红色元素的效果，Transition可以平滑实现，你只需要声明起始和终止这两个状态。&lt;/p&gt;

&lt;p&gt;Transition的触发也很简单，可以用&lt;code&gt;:hover&lt;/code&gt;、&lt;code&gt;:focus&lt;/code&gt;这样的伪类来触发，也可以通过改变元素的样式来触发。&lt;/p&gt;

&lt;h3&gt;transition的属性&lt;/h3&gt;

&lt;h4&gt;transition-property&lt;/h4&gt;

&lt;p&gt;transition-property用来声明transition会被应用到的属性。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#container p.one {
    transition-property: color;
}

#container p.two {
    transition-property: width;
}

#container p.three {
    transition-property: color, width;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你想应用到所有属性，那可以简单写作&lt;code&gt;all&lt;/code&gt;，也可以通过&lt;code&gt;none&lt;/code&gt;来关闭transition。&lt;/p&gt;

&lt;h4&gt;transition-duration&lt;/h4&gt;

&lt;p&gt;transition-duration用来声明动画持续的时长，可以是s也可以是ms&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#container p.one {
    transition-duration: 3s;
}

#container p.two {
    transition-duration: 3000ms;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;transition-timing-function&lt;/h4&gt;

&lt;p&gt;transition-timing-function声明了动画的缓动类型，有下面几个选项：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;ease&lt;/code&gt;：默认项，动画效果慢慢开始然后加速，到中点后再减速最后缓慢到达终点&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;ease-in-out&lt;/code&gt;：与ease类似，加减速更柔和一些&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;ease-in&lt;/code&gt;：开始比较慢，但是加速和停止曲线比较陡峭&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;ease-out&lt;/code&gt;：开始较快，然后缓慢停止&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;linear&lt;/code&gt;：线性平均速率，通常在color和opacity属性的变化上&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;最后，还有&lt;code&gt;cubic-bezier&lt;/code&gt;函数，可以自己创造更多更优美的缓动类型。&lt;/p&gt;

&lt;h4&gt;transition-delay&lt;/h4&gt;

&lt;p&gt;transition-delay声明了动画延迟开始的时间，很容易理解&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#container p.one {
    transition-delay: 0.5s;
}

#container p.two {
    transition-delay: 500ms;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;transition简写&lt;/h3&gt;

&lt;p&gt;上面介绍了transition的属性，他们也可以合并成一项，省去了许多拼写，当然也别忘记浏览器前缀：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#container p {
    transition-property: all;
    transition-duration: 3s;
    transition-timing-function: ease-in-out;
    transition-delay: 0.5s;
}


#element {
    /* starting state styles */
    color: #F00;
    -webkit-transition: all 3s ease-in-out 0.5s;
    transition: all 3s ease-in-out 0.5s;
}

#element:hover {
    /* ending state styles */
    color: #00F;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;transition的高级用法&lt;/h3&gt;

&lt;h4&gt;不同的transition效果&lt;/h4&gt;

&lt;p&gt;看这样的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;p#animate {
    color: #ff6;
    transition: all 3s ease-in-out 0.5s;
}

p#animate:hover {
    color: #0f0;
    transform: scale(4);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在这个例子中，当鼠标hover，元素在0.5s之后在3s内放大四倍，鼠标移开，需要同样的时间回到原来的状态。如果想要不同的效果，可以这样写：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;p#animate {
    color: #ff6;
    transition: all 0.5s ease-in-out;
}

p#animate:hover {
    color: #0f0;
    transform: scale(4);
    transition: all 3s ease-in-out 0.5s;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;多个transition&lt;/h4&gt;

&lt;p&gt;需要给多个transition指定不同的效果时，&lt;code&gt;all&lt;/code&gt;属性解决不了，可以这样写：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;p#animate {
    width: 10em;
    background-color: #F00;
    border-radius: 5px;
    transition-property: width, border-radius, background-color;
    transition-duration: 1s, 2s;
    transition-timing-function:  ease, ease-out, linear;
}

p#animate:hover {
    width: 20em;
    background-color: #00F;
    border-radius: 50%;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意其中的&lt;code&gt;transition-duration&lt;/code&gt;只写了两个，那么第三个&lt;code&gt;transition-property&lt;/code&gt;属性&lt;code&gt;background-color&lt;/code&gt;就用循环到第一个，也就是说他的&lt;code&gt;transition-duration&lt;/code&gt;值是&lt;code&gt;1s&lt;/code&gt;。&lt;/p&gt;

&lt;h3&gt;transition示例&lt;/h3&gt;

&lt;div id=&quot;transition1&quot;&gt;
#transition1 {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:350px;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:#1abc9c;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transition-propety:width,background-color;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transition-duration:.5s, 1s;&lt;br&gt;
}&lt;br&gt;
#transition1:hover {&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;width:450px;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:#8e44ad;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transition-duration:.5s, 3s;&lt;br&gt;
}&lt;br&gt;
&lt;/div&gt;


&lt;h2&gt;CSS3 Animation&lt;/h2&gt;

&lt;h3&gt;Animation和Transition的不同&lt;/h3&gt;

&lt;ul&gt;
    &lt;li&gt;和transition一样都可以定义开始和结束状态，但是animation还可以指定更确定的中间状态&lt;/li&gt;
    &lt;li&gt;animation可以像transition一样被触发，也可以自动运行&lt;/li&gt;
    &lt;li&gt;animation可以无限循环的运行下去，也可以指定运行的次数&lt;/li&gt;
    &lt;li&gt;animation可以在顺序运行也可以反向运行&lt;/li&gt;
    &lt;li&gt;animatino写起来稍麻烦些，但是依然比js简单许多&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;定义keyframes&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;@keyframes colorchange {
    0%   { background-color: #00F; /* from: blue */ }
    25%  { background-color: #F00; /* red        */ }
    50%  { background-color: #0F0; /* green      */ }
    75%  { background-color: #F0F; /* purple     */ }
    100% { background-color: #00F; /* to: blue   */ }
}

@-webkit-keyframes colorchange {
    0%   { background-color: #00F; /* from: blue */ }
    25%  { background-color: #F00; /* red        */ }
    50%  { background-color: #0F0; /* green      */ }
    75%  { background-color: #F0F; /* purple     */ }
    100% { background-color: #00F; /* to: blue   */ }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在这个例子中，只是定义了&lt;code&gt;background-color&lt;/code&gt;这一个属性，如有需要，可以换做其他。对于&lt;code&gt;0%&lt;/code&gt;这个也可以用&lt;code&gt;from&lt;/code&gt;关键字来替代，同样的可以用&lt;code&gt;to&lt;/code&gt;来代替&lt;code&gt;100%&lt;/code&gt;，过渡状态，你可以定义任何百分比，类似&lt;code&gt;12.5%&lt;/code&gt;这样的也可以，不过就不用给自己找麻烦了吧。浏览器的&lt;code&gt;prefix&lt;/code&gt;也不能少。&lt;/p&gt;

&lt;h3&gt;应用到元素&lt;/h3&gt;

&lt;p&gt;将&lt;code&gt;animation&lt;/code&gt;应用到元素的属性写法，和&lt;code&gt;transition&lt;/code&gt;差不太多，顺序都一致，就不在一个个参数重复说明，直接看代码吧：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#myelement {
    animation-name: colorchange; /**这里引用了前面定义的动画**/
    animation-duration: 5s;
    animation-timing-function: linear;
    animation-delay: 1s;
    animation-iteration-count: infinite;
    animation-direction: alternate;
}

/****简写****/
#myelement {
    -webkit-animation: colorchange 5s linear 1s infinite alternate;
    animation: colorchange 5s linear 1s infinite alternate;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;animation-iteration-count&lt;/code&gt;用来指定动画循环的次数，无限循环用&lt;code&gt;infinite&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;animation-direction有四个值：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;normal&lt;/code&gt;：默认，从0%执行到100%&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;reverse&lt;/code&gt;：动画从100%执行到0%&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;alternate&lt;/code&gt;：动画在0%到100%之间往复执行&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;alternate-reverse&lt;/code&gt;与&lt;code&gt;alternate&lt;/code&gt;一致，不过是从100%开始&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Animation示例&lt;/h3&gt;

&lt;div id=&quot;ani1&quot;&gt;Animate color&lt;/div&gt;


&lt;h2&gt;CSS3 Transform&lt;/h2&gt;

&lt;p&gt;有了&lt;code&gt;transition&lt;/code&gt;和&lt;code&gt;animation&lt;/code&gt;之后，就可以做出些漂亮的动画效果，如果再搭配&lt;code&gt;transform&lt;/code&gt;这一CSS3动画利器，就更出彩了。&lt;/p&gt;

&lt;h3&gt;CSS3 2D Transform&lt;/h3&gt;

&lt;p&gt;运用CSS3 2D Transform的技术，可以更自由轻松的来修饰HTML元素。CSS3 2D Transform的基本方法有下面这些：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;translate()&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;rotate()&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;scale()&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;skew()&lt;/code&gt;&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;matrix()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Translate&lt;/h4&gt;

&lt;p&gt;使用&lt;code&gt;translate()&lt;/code&gt;方法，可以将HTML元素在x-y轴平面上做位移，且不会影响到其他元素。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;div{
    -webkit-transform: translate(20px,20px);
    -moz-transform: translate(20px,20px);
    -o-transform: translate(20px,20px);
    transform: translate(20px,20px);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;效果如下：&lt;/p&gt;

&lt;div id=&quot;translate1&quot;&gt;
&lt;div id=&quot;trans-inner1&quot;&gt;Normal Div&lt;/div&gt;
&lt;div id=&quot;trans-inner2&quot;&gt;transform:tranlated(40px, 40px)&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;Rotate&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;rotate()&lt;/code&gt;方法可以将元素按照时钟方向旋转，参数可以是&lt;code&gt;0deg&lt;/code&gt;到&lt;code&gt;360deg&lt;/code&gt;，也是在x-y轴平面，示例如下：&lt;/p&gt;

&lt;div id=&quot;rotate1&quot;&gt;
&lt;div id=&quot;rota-inner1&quot;&gt;Normal Div&lt;/div&gt;
&lt;div id=&quot;rota-inner2&quot;&gt;transform:rotate(-30deg)&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;Scale&lt;/h4&gt;

&lt;p&gt;和名字的一样，&lt;code&gt;scale()&lt;/code&gt;方法用来放大一个元素，依然是在x-y轴平面，看示例：&lt;/p&gt;

&lt;div id=&quot;scale1&quot;&gt;
    &lt;div id=&quot;sca-inner1&quot;&gt;Normal Div&lt;/div&gt;
    &lt;div id=&quot;sca-inner2&quot;&gt;transform:scale(1.5,1.3)&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;Skew&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;skew()&lt;/code&gt;方法可以将元素按照指定参数进行扭曲，你需要指定x、y轴的扭曲角度，看示例：&lt;/p&gt;

&lt;div id=&quot;skew1&quot;&gt;
&lt;div id=&quot;sk-inner1&quot;&gt;Normal Div&lt;/div&gt;
&lt;div id=&quot;sk-inner2&quot;&gt;transform:skew(30deg,0)&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;Matrix&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;matrix()&lt;/code&gt;方法是以上所有2D效果的方法的总和，写法如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;div{
    transform: matrix(a,b,c,d,tx,ty);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本质上&lt;code&gt;scale&lt;/code&gt;、&lt;code&gt;skew&lt;/code&gt;、&lt;code&gt;rotate&lt;/code&gt;、&lt;code&gt;translate&lt;/code&gt;的效果都是通过&lt;code&gt;matrix&lt;/code&gt;实现的，&lt;code&gt;tx&lt;/code&gt;、&lt;code&gt;ty&lt;/code&gt;表示位移量，关于&lt;code&gt;matrix&lt;/code&gt;方法更详细的介绍可以参考这里：&lt;a href=&quot;http://www.zhangxinxu.com/wordpress/2012/06/css3-transform-matrix-%E7%9F%A9%E9%98%B5/&quot;&gt;理解CSS3 transform中的Matrix(矩阵)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;CSS3 3D Transform&lt;/h3&gt;

&lt;p&gt;了解了2D Transform之后，3D Transform的概念也不会太难，他给HTML元素在x-y平面加上了z轴，我们一个个来看看：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;translate3d(tx,ty,tz)&lt;/code&gt;：他定义了一个3D的位移方法，增加了z轴的偏移量&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;translateZ(tz)&lt;/code&gt;：这个方法只在Z轴偏移，与&lt;code&gt;translateX()&lt;/code&gt;和&lt;code&gt;translateY()&lt;/code&gt;相似&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;scale3d(sx,sy,sz)&lt;/code&gt;：在原有的&lt;code&gt;scale&lt;/code&gt;方法上增加了z轴的参数&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;scaleZ(sz)&lt;/code&gt;：同理，只放大z轴，与&lt;code&gt;scaleX()&lt;/code&gt;和&lt;code&gt;scaleY()&lt;/code&gt;类似&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;rotate3d(rx,ry,rz)&lt;/code&gt;：将元素以给定参数的某一个轴方向旋转&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;rotateX(angle)，rotateY(angle)&lt;/code&gt;和&lt;code&gt;rotateZ(angle)&lt;/code&gt;：只按照某一个轴旋转，&lt;code&gt;rotate3d(1,0,0,30deg)&lt;/code&gt;相当于&lt;code&gt;rotateX(30deg)&lt;/code&gt;，其他类推。&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;来看看例子：&lt;/p&gt;

&lt;div class=&quot;transform-con&quot;&gt;
&lt;div id=&quot;trans-3&quot; class=&quot;inner&quot;&gt;
width:100%;&lt;br&gt;
height:100%;&lt;br&gt;
transform: translateZ(-200px);
&lt;/div&gt;
&lt;/div&gt;




&lt;div class=&quot;transform-con&quot;&gt;
&lt;div id=&quot;trans-31&quot; class=&quot;inner&quot;&gt;
width:100%;&lt;br&gt;
height:100%;&lt;br&gt;
transform: translateZ(100px);
&lt;/div&gt;
&lt;/div&gt;




&lt;div class=&quot;transform-con&quot;&gt;
&lt;div id=&quot;rotate-31&quot; class=&quot;inner&quot;&gt;
width:100%;&lt;br&gt;
height:100%;&lt;br&gt;
transform: rotateX(45deg);
&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&quot;transform-con&quot;&gt;
&lt;div id=&quot;rotate-32&quot; class=&quot;inner&quot;&gt;
width:100%;&lt;br&gt;
height:100%;&lt;br&gt;
transform: rotateY(45deg);
&lt;/div&gt;
&lt;/div&gt;


&lt;div class=&quot;transform-con&quot;&gt;
&lt;div id=&quot;rotate-33&quot; class=&quot;inner&quot;&gt;
width:100%;&lt;br&gt;
height:100%;&lt;br&gt;
transform: rotateZ(45deg);
&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;任何有3D变换的元素，不论最后只是做了2D的变换，或者什么都没做&lt;code&gt;translate3d(0,0,0)&lt;/code&gt;，都会触发浏览器去计算。不过，以后会更新优化也不一定。&lt;/p&gt;

&lt;h3&gt;Perspective&lt;/h3&gt;

&lt;p&gt;激活元素的3D空间，需要&lt;code&gt;perspective&lt;/code&gt;属性，写法有两种：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;transform: perspective( 600px );
/**或者**/
perspective: 600px;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这两种不同写法，当应用元素只有一个时候，并没有区别，当有多个元素的时候，我们看看效果：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#pers-red .item{
  background: red;
  transform: perspective( 400px ) rotateY(45deg);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;pers-con&quot; id=&quot;pers-red&quot;&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;


&lt;pre&gt;&lt;code&gt;#pers-blue {
  perspective: 400px;
}

#pers-blue .item{
  background: blue;
  transform: rotateY( 45deg );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;pers-con&quot; id=&quot;pers-blue&quot;&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;item&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;上面这两种写法，都触发了元素的3D行为，函数型的写法&lt;code&gt;transform:perspective(400px)&lt;/code&gt;适用于单个元素，会对每一个元素做3D视图的变换，而&lt;code&gt;perspective:400px&lt;/code&gt;的写法，需写在父元素上，然后以父元素的视角，对多个子元素进行3D变换，多个子元素共享同一个3D空间，可以自己打开console修改感受一下。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;perspective&lt;/code&gt;的参数值，决定了3D效果的强烈程度，可以想象为距离多远去观察元素。值越大，观察距离就越远，同样的旋转值，看起来效果就弱一些；值越小，距离越近，3D效果就更强烈。&lt;/p&gt;

&lt;h4&gt;perspective-orgin&lt;/h4&gt;

&lt;p&gt;通常，对一个元素进行3D变换的时候，变换点都是元素的中心点，如果你想以其他的位置为变换点，那就可以用这个属性来做调整：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;perspective-orgin: 20% 70%;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个是默认值的&lt;code&gt;perspective-orign:50% 50%&lt;/code&gt;：&lt;/p&gt;

&lt;div id=&quot;transform1&quot;&gt;
&lt;div class=&quot;inner&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/city&quot; alt=&quot;Nature&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/food&quot; alt=&quot;Nature&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/people&quot; alt=&quot;Nature&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;这个是&lt;code&gt;perspective-orgin: 0% 50%;&lt;/code&gt;&lt;/p&gt;

&lt;div id=&quot;transform1&quot; sytle=&quot;-webkit-perspective-origin:0% 50%;perspective-origin:0% 50%&quot;&gt;
&lt;div class=&quot;inner&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/nature&quot; alt=&quot;Nature&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/animals&quot; alt=&quot;Nature&quot;&gt;
&lt;img src=&quot;http://lorempixel.com/150/150/abstract&quot; alt=&quot;Nature&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;h4&gt;transform-style&lt;/h4&gt;

&lt;p&gt;这个参数用来共享父元素的3D空间，这样说起来有些抽象，下面第一个翻卡片的例子中会讲到。&lt;/p&gt;

&lt;h4&gt;backface-visibility&lt;/h4&gt;

&lt;p&gt;backface-visibility 属性可用于隐藏内容的背面。默认情况下，背面可见，这意味着即使在翻转后，变换的内容仍然可见。但当 backface-visibility 设置为 hidden 时，旋转后内容将隐藏，因为旋转后正面将不再可见。该功能可帮助你模拟多面的对象，例如下例中使用的卡片。通过将 backface-visibility 设置为 hidden，可以确保只有正面可见。&lt;/p&gt;

&lt;h2&gt;CSS3 动画实例&lt;/h2&gt;

&lt;p&gt;下面例子中的代码，为了方便查看都没有写浏览器前缀，也没有加入其他的修饰属性，所以实际应用时，不要忘记哦，当然也可以直接console查看。&lt;/p&gt;

&lt;h3&gt;CSS3 翻纸牌&lt;/h3&gt;

&lt;p&gt;做一个翻纸牌的效果，结构很简单：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;div id=&quot;cardflip&quot;&amp;gt;
  &amp;lt;div id=&quot;card1&quot;&amp;gt;
    &amp;lt;div class=&quot;front&quot;&amp;gt;1&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;back&quot;&amp;gt;2&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;.cardflip&lt;/code&gt;是整个3D效果的容器，&lt;code&gt;#card1&lt;/code&gt;是翻转效果的元素，&lt;code&gt;.front&lt;/code&gt;和&lt;code&gt;.back&lt;/code&gt;是翻转的两面。添加样式：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#cardflip {
    width: 200px;
    height: 260px;
    position: relative;
    perspective: 800px;
}

#card1 {
    width: 100%;
    height: 100%;
    position: absolute;
    transform-style: preserve-3d;
    transition: transform 1s;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先给&lt;code&gt;#cardflip&lt;/code&gt;添加&lt;code&gt;perspective&lt;/code&gt;属性，这样才能触发3D变换，之后&lt;code&gt;#card1&lt;/code&gt;就在父元素的3D空间中了，用了&lt;code&gt;absolute&lt;/code&gt;来定位子元素，设置宽高都是&lt;code&gt;100%&lt;/code&gt;，这样就可以让&lt;code&gt;transform-origin&lt;/code&gt;在元素的中心点，这个后面再讨论。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;transform-style&lt;/code&gt;有两个值，一个是默认的&lt;code&gt;flat&lt;/code&gt;一个是&lt;code&gt;preserve-3d&lt;/code&gt;，由于&lt;code&gt;perspective&lt;/code&gt;的3D空间，只能作用于直接的子元素，那么&lt;code&gt;.front&lt;/code&gt;和&lt;code&gt;.back&lt;/code&gt;也需要&lt;code&gt;#cardflip&lt;/code&gt;的3D空间的话，就需要给&lt;code&gt;#card1&lt;/code&gt;添加这个属性，&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#card1 div{
    position: absolute;
    width: 100%;
    height: 100%;
    backface-visibility: hidden;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;只有&lt;code&gt;#card1 div&lt;/code&gt;元素共享了外层元素的3D空间之后，3D变换的属性才能生效，这时候的&lt;code&gt;backface-visibility&lt;/code&gt;才有效，设置为&lt;code&gt;hidden&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#card1 .front {
    background: red;
}

#card1 .back {
    background: blue;
    transform: rotateY( 180deg );
}

#card1.flipped {
    transform: rotateY( 180deg );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为设置了&lt;code&gt;backface-visibility&lt;/code&gt;，而&lt;code&gt;.back&lt;/code&gt;默认就是以Y轴旋转了180度，空间想象一下，&lt;code&gt;.back&lt;/code&gt;就转到背面去了，所以&lt;code&gt;hidden&lt;/code&gt;属性生效，就看不到&lt;code&gt;.back&lt;/code&gt;了。&lt;/p&gt;

&lt;p&gt;当&lt;code&gt;#card1&lt;/code&gt;添加了&lt;code&gt;.flipped&lt;/code&gt;的样式，&lt;code&gt;#card1&lt;/code&gt;以Y轴旋转了180度，这时候&lt;code&gt;.front&lt;/code&gt;转到了背面，而&lt;code&gt;.back&lt;/code&gt;从背面转到了前面，所以就完成了切换。这一段需要仔细的想一想。好了，看看下面的实例，点击即可翻转：&lt;/p&gt;

&lt;div id=&quot;cardflip&quot;&gt;
&lt;div id=&quot;card1&quot;&gt;
&lt;div class=&quot;front&quot;&gt;1&lt;/div&gt;
&lt;div class=&quot;back&quot;&gt;2&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;我们再给这个翻转加一些偏移的效果，看起来会不那么生硬。这就用到了&lt;code&gt;transform-origin&lt;/code&gt;，这个参数：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#card1 { transform-origin: left center; }

#card1.flipped {
  transform: translateX( 100% ) rotateY( 180deg );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;默认的&lt;code&gt;transform-origin&lt;/code&gt;是&lt;code&gt;center center&lt;/code&gt;，我们改成&lt;code&gt;left center&lt;/code&gt;之后，就不再以元素的x方向的中心为轴旋转，而是以元素的左边为Y轴旋转，所以还需要给整个&lt;code&gt;#card1&lt;/code&gt;加一个位移量&lt;code&gt;translate&lt;/code&gt;，值是&lt;code&gt;100%&lt;/code&gt;，就是元素本身的宽度。&lt;/p&gt;

&lt;p&gt;可以在console里面去掉&lt;code&gt;#card1.flipped&lt;/code&gt;的&lt;code&gt;translate&lt;/code&gt;帮助理解。&lt;/p&gt;

&lt;div id=&quot;cardflip1&quot;&gt;
&lt;div id=&quot;card2&quot;&gt;
&lt;div class=&quot;front&quot;&gt;1&lt;/div&gt;
&lt;div class=&quot;back&quot;&gt;2&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;这里有一点需要注意，当元素在z轴上有了位移，或者朝向负角度旋转，会导致元素在页面上无法被鼠标点击到，想像一下3D空间，这个元素已经位于整个页面平面的&lt;strong&gt;里面&lt;/strong&gt;，所以无法触及了。&lt;/p&gt;

&lt;h3&gt;CSS3 立方体&lt;/h3&gt;

&lt;p&gt;做完了反转卡片的效果，肯定还想做更炫的，来试试做一个立方体吧：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;section id=&quot;cube-con&quot;&amp;gt;
  &amp;lt;div id=&quot;cube&quot;&amp;gt;
    &amp;lt;figure class=&quot;front&quot;&amp;gt;1&amp;lt;/figure&amp;gt;
    &amp;lt;figure class=&quot;back&quot;&amp;gt;2&amp;lt;/figure&amp;gt;
    &amp;lt;figure class=&quot;right&quot;&amp;gt;3&amp;lt;/figure&amp;gt;
    &amp;lt;figure class=&quot;left&quot;&amp;gt;4&amp;lt;/figure&amp;gt;
    &amp;lt;figure class=&quot;top&quot;&amp;gt;5&amp;lt;/figure&amp;gt;
    &amp;lt;figure class=&quot;bottom&quot;&amp;gt;6&amp;lt;/figure&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;

#cube-con {
    width: 200px;
    height: 200px;
    position: relative;
    perspective: 1000px;
}

#cube {
    width: 100%;
    height: 100%;
    position: absolute;
    transform-style: preserve-3d;
}

#cube figure {
    width: 196px;
    height: 196px;
    display: block;
    position: absolute;
    border: 2px solid black;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这一部分和上一个例子没有太大的差别，应该都能理解每一个属性的含义了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#cube .front  { transform: rotateY(   0deg ) translateZ( 100px ); }
#cube .back   { transform: rotateX( 180deg ) translateZ( 100px ); }
#cube .right  { transform: rotateY(  90deg ) translateZ( 100px ); }
#cube .left   { transform: rotateY( -90deg ) translateZ( 100px ); }
#cube .top    { transform: rotateX(  90deg ) translateZ( 100px ); }
#cube .bottom { transform: rotateX( -90deg ) translateZ( 100px ); }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;立方体的每一个面，经过&lt;code&gt;rotate&lt;/code&gt;旋转之后，就放置在了他该被放置的地方，但是这时候会发现，这些层叠加在一起，还没有成为一个立方体，这时候需要给Z轴一个位移，想象一下我们的视角点在&lt;code&gt;#cube&lt;/code&gt;正中间，拉伸z轴之后，&lt;code&gt;.right&lt;/code&gt;、&lt;code&gt;left&lt;/code&gt;等面就会有一定的角度，参考画画时候的透视，因为刚好在中心点，所以位移量就是宽度的一半。分步过程可以看&lt;a href=&quot;http://desandro.github.io/3dtransforms/examples/cube-01-steps.html&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;因为z轴拉伸之后，原来的对象会被放大一些，这样就会模糊掉，为了去掉这个影响，我们需要把立方体再推回原来的视角平面，于是：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#cube { transform: translateZ( -100px ); }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成了立方体，想让某个面旋转到前方，只需转动整个立方体，不用去调整每个面：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#cube.show-front  { transform: translateZ( -100px ) rotateY(    0deg ); }
#cube.show-back   { transform: translateZ( -100px ) rotateX( -180deg ); }
#cube.show-right  { transform: translateZ( -100px ) rotateY(  -90deg ); }
#cube.show-left   { transform: translateZ( -100px ) rotateY(   90deg ); }
#cube.show-top    { transform: translateZ( -100px ) rotateX(  -90deg ); }
#cube.show-bottom { transform: translateZ( -100px ) rotateX(   90deg ); }

/**还有过渡效果**/
#cube { transition: transform 1s; }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;section id=&quot;cube-con&quot;&gt;&lt;/p&gt;

&lt;div id=&quot;cube&quot;&gt;
&lt;figure class=&quot;front&quot;&gt;1&lt;/figure&gt;
&lt;figure class=&quot;back&quot;&gt;2&lt;/figure&gt;
&lt;figure class=&quot;right&quot;&gt;3&lt;/figure&gt;
&lt;figure class=&quot;left&quot;&gt;4&lt;/figure&gt;
&lt;figure class=&quot;top&quot;&gt;5&lt;/figure&gt;
&lt;figure class=&quot;bottom&quot;&gt;6&lt;/figure&gt;
&lt;/div&gt;


&lt;p&gt;&lt;/section&gt;&lt;/p&gt;

&lt;div id=&quot;cube-btn&quot;&gt;
&lt;button data-class=&quot;show-front&quot;&gt;Show Front&lt;/button&gt;
&lt;button data-class=&quot;show-back&quot;&gt;Show Back&lt;/button&gt;
&lt;button data-class=&quot;show-right&quot;&gt;Show Right&lt;/button&gt;
&lt;button data-class=&quot;show-left&quot;&gt;Show Left&lt;/button&gt;
&lt;button data-class=&quot;show-top&quot;&gt;Show Top&lt;/button&gt;
&lt;button data-class=&quot;show-bottom&quot;&gt;Show Bottom&lt;/button&gt;
&lt;/div&gt;


&lt;h3&gt;3D 旋转跑马灯&lt;/h3&gt;

&lt;p&gt;做幻灯片展示的方法有很多，我们用CSS3的3D技术来试试看：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;section class=&quot;container&quot;&amp;gt;
  &amp;lt;div id=&quot;carousel&quot;&amp;gt;
    &amp;lt;figure&amp;gt;1&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;2&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;3&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;4&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;5&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;6&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;7&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;8&amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;9&amp;lt;/figure&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;

.container {
  width: 210px;
  height: 140px;
  position: relative;
  perspective: 1000px;
}

#carousel {
  width: 100%;
  height: 100%;
  position: absolute;
  transform-style: preserve-3d;
}

#carousel figure {
  display: block;
  position: absolute;
  width: 186px;
  height: 116px;
  left: 10px;
  top: 10px;
  border: 2px solid black;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面这一段，没什么特别要说明的，基本的结构样式，以及之前重点说明过的&lt;code&gt;perspective&lt;/code&gt;和&lt;code&gt;preserve-3d&lt;/code&gt;。现在有9个卡片，要环绕成一圈，那么每个的角度就是&lt;code&gt;40deg&lt;/code&gt; （360/90）。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#carousel figure:nth-child(1) { transform: rotateY(   0deg ); }
#carousel figure:nth-child(2) { transform: rotateY(  40deg ); }
#carousel figure:nth-child(3) { transform: rotateY(  80deg ); }
#carousel figure:nth-child(4) { transform: rotateY( 120deg ); }
#carousel figure:nth-child(5) { transform: rotateY( 160deg ); }
#carousel figure:nth-child(6) { transform: rotateY( 200deg ); }
#carousel figure:nth-child(7) { transform: rotateY( 240deg ); }
#carousel figure:nth-child(8) { transform: rotateY( 280deg ); }
#carousel figure:nth-child(9) { transform: rotateY( 320deg ); }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;好了，和立方体的例子到同样的步骤了，现在所有的卡片做了Y轴旋转，但因为观察的视角点没有变，所以看起来还是平面，如下这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/large/8b8af2c8jw1e84s4cel0uj208e05raa0.jpg&quot; alt=&quot;caro&quot; /&gt;&lt;/p&gt;

&lt;p&gt;立方体的位移很好计算，只要是宽度、高度、或者深度的一半就可以了，这个旋转的跑马灯应该怎么计算呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/8b8af2c8jw1e84s5efyxej20f00b6aaj.jpg&quot; alt=&quot;caro-cmpu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从旋转跑马灯的上方观察，每个卡片的宽度是&lt;code&gt;210px&lt;/code&gt;，角度是&lt;code&gt;40deg&lt;/code&gt;，要计算到中心点的距离，根据旁边的三角形可得：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;r = 105 / tan(20deg) = 288px
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#carousel figure:nth-child(1) {transform:rotateY(  0deg) translateZ(288px);}
#carousel figure:nth-child(2) {transform:rotateY( 40deg) translateZ(288px);}
#carousel figure:nth-child(3) {transform:rotateY( 80deg) translateZ(288px);}
#carousel figure:nth-child(4) {transform:rotateY(120deg) translateZ(288px);}
#carousel figure:nth-child(5) {transform:rotateY(160deg) translateZ(288px);}
#carousel figure:nth-child(6) {transform:rotateY(200deg) translateZ(288px);}
#carousel figure:nth-child(7) {transform:rotateY(240deg) translateZ(288px);}
#carousel figure:nth-child(8) {transform:rotateY(280deg) translateZ(288px);}
#carousel figure:nth-child(9) {transform:rotateY(320deg) translateZ(288px);}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;知道了计算方法，如果要改变卡片的个数，或者宽度，只要按照那个公式再计算就好：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var tz = Math.round( ( panelSize / 2 ) / 
  Math.tan( ( ( Math.PI * 2 ) / numberOfPanels ) / 2 ) );
// or simplified to
var tz = Math.round( ( panelSize / 2 ) / 
  Math.tan( Math.PI / numberOfPanels ) );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;计算好卡片的位置之后，然后旋转&lt;code&gt;#carousel&lt;/code&gt;就可以了，当然这个要用js来控制了：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#carousel{
    transform: translateZ( -288px ) rotateY( -160deg );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;js代码如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$(function(){
    $(&#39;#car-pre&#39;).click(function(){
        var deg = $(&#39;#carousel&#39;).attr(&#39;data-deg&#39;) || 0;
        deg = parseInt(deg)+40;

        var value = &#39;translateZ(-288px) rotateY(&#39;+deg+&#39;deg)&#39;;

        $(&#39;#carousel&#39;)
            .attr(&#39;data-deg&#39;,deg)
            .css({
                &#39;-webkit-transform&#39;:value
                ,&#39;-moz-transform&#39;:value
                ,&#39;-o-transform&#39;:value
                ,&#39;transform&#39;:value
            });
    });
    $(&#39;#car-next&#39;).click(function(){
        var deg = $(&#39;#carousel&#39;).attr(&#39;data-deg&#39;) || 0;
        deg = parseInt(deg)-40;

        var value = &#39;translateZ(-288px) rotateY(&#39;+deg+&#39;deg)&#39;;

        $(&#39;#carousel&#39;)
            .attr(&#39;data-deg&#39;,deg)
            .css({
                &#39;-webkit-transform&#39;:value
                ,&#39;-moz-transform&#39;:value
                ,&#39;-o-transform&#39;:value
                ,&#39;transform&#39;:value
            });
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;section id=&quot;caro-con&quot;&gt;&lt;div id=&quot;carousel&quot;&gt;&lt;figure&gt;1&lt;/figure&gt;&lt;figure&gt;2&lt;/figure&gt;&lt;figure&gt;3&lt;/figure&gt;&lt;figure&gt;4&lt;/figure&gt;&lt;figure&gt;5&lt;/figure&gt;&lt;figure&gt;6&lt;/figure&gt;&lt;figure&gt;7&lt;/figure&gt;&lt;figure&gt;8&lt;/figure&gt;&lt;figure&gt;9&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/p&gt;

&lt;div id=&quot;car-btn&quot;&gt;
&lt;button id=&quot;car-pre&quot;&gt;&amp;lt; Prev&lt;/button&gt;
&lt;button id=&quot;car-next&quot;&gt;Next &amp;gt;&lt;/button&gt;
&lt;/div&gt;


&lt;h2&gt;结语&lt;/h2&gt;

&lt;p&gt;终于完成了这篇，梳理的过程对我自己很有提高，希望对你也能有些帮助，有兴趣可以关注我，期待下以后的博客~&lt;/p&gt;
</content>
   </entry>
   
   <entry>
     <title>Grunt.js 在前端项目中的实战</title>
     <link href="http://beiyuu.com/grunt-in-action"/>
     <updated>2013-07-17T00:00:00-07:00</updated>
     <id>http://beiyuu.com/grunt-in-action</id>
     <content type="html">&lt;h2&gt;Grunt是什么？&lt;/h2&gt;

&lt;p&gt;Grunt已经out了，请用&lt;a href=&quot;http://gulpjs.com/&quot;&gt;Gulp&lt;/a&gt;，参考这篇：&lt;a href=&quot;http://www.smashingmagazine.com/2014/06/11/building-with-gulp/&quot;&gt;Building with Gulp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;del&gt;&lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt;是一个基于JavaScript的任务运行工具。&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;del&gt;为什么要使用Grunt，简而言之是为了“自动”，用工具自动完成压缩、编译、单元测试、拼写检查等重复性工作。&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Grunt的社区壮大非常快，现在支持的模块有：&lt;a href=&quot;http://coffeescript.org/&quot;&gt;CoffeeScript&lt;/a&gt;、 &lt;a href=&quot;http://handlebarsjs.com/&quot;&gt;handlerbars&lt;/a&gt;、 &lt;a href=&quot;http://jade-lang.com/&quot;&gt;jade&lt;/a&gt;、 &lt;a href=&quot;http://www.jshint.com/&quot;&gt;JSHint&lt;/a&gt;、 &lt;a href=&quot;http://lesscss.org/&quot;&gt;Less&lt;/a&gt;、 &lt;a href=&quot;http://requirejs.org/&quot;&gt;RequireJS&lt;/a&gt;、 &lt;a href=&quot;http://sass-lang.com/&quot;&gt;Sass&lt;/a&gt;、 &lt;a href=&quot;http://learnboost.github.io/stylus/&quot;&gt;stylus&lt;/a&gt;等。&lt;/del&gt;&lt;/p&gt;

&lt;h2&gt;Grunt基本配置&lt;/h2&gt;

&lt;p&gt;Grunt及其插件都是用&lt;a href=&quot;https://npmjs.org/&quot;&gt;npm&lt;/a&gt;管理的，npm是&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;的包管理程序，所以在使用Grunt之前，你需要先安装NodeJS。&lt;/p&gt;

&lt;h3&gt;安装CLI&lt;/h3&gt;

&lt;p&gt;首先需要在全局环境中安装Grunt command line interface (CLI)，在Mac等系统中需要sudo来执行下面的命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install -g grunt-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这会将&lt;code&gt;grunt&lt;/code&gt;命令安装在系统path中，这样就可以从任何目录执行了。需要注意的是，安装了&lt;code&gt;grunt-cli&lt;/code&gt;并没有安装任务管理工具。&lt;code&gt;CLI&lt;/code&gt;的职责很简单，就是运行&lt;code&gt;Gruntfile&lt;/code&gt;中定义的&lt;code&gt;Grunt&lt;/code&gt;版本，这样你就可以在一台机器运行多个版本的Grunt。&lt;/p&gt;

&lt;p&gt;如果从0.3版本升级，需要先卸载旧版：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm uninstall -g grunt
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;已存在Grunt的项目&lt;/h2&gt;

&lt;p&gt;对于已经使用了Grunt的项目，搭建本地环境是非常方便的，只需要切换到该项目目录，然后执行：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再使用&lt;code&gt;grunt&lt;/code&gt;命令运行Grunt即可。&lt;/p&gt;

&lt;h2&gt;新建Grunt项目&lt;/h2&gt;

&lt;p&gt;最基本的步骤就是给项目添加两个文件&lt;code&gt;package.json&lt;/code&gt;和&lt;code&gt;Gruntfile&lt;/code&gt;。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;：在这个文件里你可以列出项目所需的Grunt插件，npm会自动下载。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Grunfile&lt;/code&gt;：这个文件命名为&lt;code&gt;Gruntfile.js&lt;/code&gt;或者&lt;code&gt;Gruntfile.coffee&lt;/code&gt;，用来描述你所需要的grunt任务。&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;package.json&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;文件需要放置在项目的根目录，和代码一起提交。运行&lt;code&gt;npm install&lt;/code&gt;命令，会安装&lt;code&gt;package.json&lt;/code&gt;中列出的依赖插件的正确版本。&lt;/p&gt;

&lt;p&gt;创建&lt;code&gt;package.json&lt;/code&gt;有以下几种办法：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;大部分&lt;code&gt;grunt-init&lt;/code&gt;模板，会创建项目相关的&lt;code&gt;package.json&lt;/code&gt;文件&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;npm init&lt;/code&gt;命令会创建基本的&lt;code&gt;package.json&lt;/code&gt;文件&lt;/li&gt;
    &lt;li&gt;也可以下面这个范本创建，更多用法可以看&lt;a href=&quot;https://npmjs.org/doc/json.html&quot; target=&quot;_blank&quot; class=&quot;external&quot;&gt;specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;pre&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;my-project-name&quot;,
    &quot;version&quot;: &quot;0.1.0&quot;,
    &quot;devDependencies&quot;: {
        &quot;grunt&quot;: &quot;~0.4.1&quot;,
        &quot;grunt-contrib-jshint&quot;: &quot;~0.6.0&quot;,
        &quot;grunt-contrib-nodeunit&quot;: &quot;~0.2.0&quot;,
        &quot;grunt-contrib-uglify&quot;: &quot;~0.2.2&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;安装Grunt和插件&lt;/h3&gt;

&lt;p&gt;对于已存在&lt;code&gt;package.json&lt;/code&gt;文件的项目，最简单的安装方法就是&lt;code&gt;npm install &amp;lt;module&amp;gt; --save-dev&lt;/code&gt;，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install grunt --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个命令会安装最新版的&lt;code&gt;grunt&lt;/code&gt;，并且把对这个插件的依赖写入&lt;code&gt;package.json&lt;/code&gt;。很方便。&lt;/p&gt;

&lt;h2&gt;Gruntfile&lt;/h2&gt;

&lt;p&gt;和&lt;code&gt;package.json&lt;/code&gt;文件一样，&lt;code&gt;Gruntfile.js&lt;/code&gt;或者&lt;code&gt;Gruntfile.coffee&lt;/code&gt;需要放在根目录下和源码一起提交。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Gruntfile&lt;/code&gt;由以下几个部分组成：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;wrapper&lt;/code&gt;函数&lt;/li&gt;
    &lt;li&gt;项目和任务配置&lt;/li&gt;
    &lt;li&gt;加载Grunt插件和任务&lt;/li&gt;
    &lt;li&gt;自定义任务&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;示例Gruntfile&lt;/h3&gt;

&lt;p&gt;在下面这个例子中，项目信息引自&lt;code&gt;package.json&lt;/code&gt;，grunt-contrib-uglify插件的&lt;code&gt;uglify&lt;/code&gt;任务用来压缩js文件，并且根据项目的metadata生成一条注释。当grunt运行时，uglify任务会默认执行。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON(&#39;package.json&#39;),
    uglify: {
      options: {
        banner: &#39;/*! &amp;lt;%= pkg.name %&amp;gt; &amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */\n&#39;
      },
      build: {
        src: &#39;src/&amp;lt;%= pkg.name %&amp;gt;.js&#39;,
        dest: &#39;build/&amp;lt;%= pkg.name %&amp;gt;.min.js&#39;
      }
    }
  });

  // Load the plugin that provides the &quot;uglify&quot; task.
  grunt.loadNpmTasks(&#39;grunt-contrib-uglify&#39;);

  // Default task(s).
  grunt.registerTask(&#39;default&#39;, [&#39;uglify&#39;]);

};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这就是一个完整的&lt;code&gt;Gruntfile&lt;/code&gt;，我们仔细研究下。&lt;/p&gt;

&lt;h3&gt;wrapper函数&lt;/h3&gt;

&lt;p&gt;每个&lt;code&gt;Gruntfile&lt;/code&gt;（包括插件）使用这个默认的格式，你的所有的Grunt代码也必须写在这个函数中：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;module.export = function(grunt){
    //Do grunt-related things in here
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;项目和任务配置&lt;/h3&gt;

&lt;p&gt;大多Grunt的任务依赖于&lt;a href=&quot;http://gruntjs.com/grunt#grunt.initconfig&quot;&gt;grunt.initConfig&lt;/a&gt;方法中定义的配置。&lt;/p&gt;

&lt;p&gt;在这个例子中，Grunt通过&lt;code&gt;grunt.file.readJSON(&#39;package.json&#39;)&lt;/code&gt;引入了&lt;code&gt;package.json&lt;/code&gt;中定义的Grunt配置。因为&lt;code&gt;&amp;lt;% %&amp;gt;&lt;/code&gt;模板变量可以引用任何配置，所以像文件路径、文件列表这些内容应该存储在变量中，以减少重复。&lt;/p&gt;

&lt;p&gt;和其他任务一样，任务的配置需要和任务名字一样的变量，具体的参数可以查询各任务的文档。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON(&#39;package.json&#39;),
  uglify: {
    options: {
      banner: &#39;/*! &amp;lt;%= pkg.name %&amp;gt; &amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */\n&#39;
    },
    build: {
      src: &#39;src/&amp;lt;%= pkg.name %&amp;gt;.js&#39;,
      dest: &#39;build/&amp;lt;%= pkg.name %&amp;gt;.min.js&#39;
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;加载Grunt的插件和任务&lt;/h3&gt;

&lt;p&gt;很多常用的任务比如&lt;a href=&quot;https://github.com/gruntjs/grunt-contrib-concat&quot;&gt;concatenation&lt;/a&gt;、 &lt;a href=&quot;http://github.com/gruntjs/grunt-contrib-uglify&quot;&gt;minification&lt;/a&gt;、 &lt;a href=&quot;https://github.com/gruntjs/grunt-contrib-jshint&quot;&gt;linting&lt;/a&gt;都有&lt;a href=&quot;https://github.com/gruntjs&quot;&gt;Grung插件&lt;/a&gt;。只要在&lt;code&gt;package.json&lt;/code&gt;中声明，然后通过&lt;code&gt;npm install&lt;/code&gt;安装，就可以在&lt;code&gt;Gruntfile&lt;/code&gt;中配置使用了。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Load the plugin that provides the &quot;uglify&quot; task.
grunt.loadNpmTasks(&#39;grunt-contrib-uglify&#39;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;grunt --help&lt;/code&gt;可以查看所有可用的任务。&lt;/p&gt;

&lt;h3&gt;自定义任务&lt;/h3&gt;

&lt;p&gt;你可以配置让Grunt运行一个或多个默认任务。在例子中，运行&lt;code&gt;grunt&lt;/code&gt;不带任何参数就会执行&lt;code&gt;uglify&lt;/code&gt;任务。这和&lt;code&gt;grunt uglify&lt;/code&gt;或者&lt;code&gt;grunt default&lt;/code&gt;是一样的效果。数组的长度任意。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Default task(s).
grunt.registerTask(&#39;default&#39;, [&#39;uglify&#39;]);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果你需要的任务并没有插件提供，那么也可以自定义，自定义的任务可以不写任务配置&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;module.exports = function(grunt) {

  // A very basic default task.
  grunt.registerTask(&#39;default&#39;, &#39;Log some stuff.&#39;, function() {
    grunt.log.write(&#39;Logging some stuff...&#39;).ok();
  });

};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;自定义的任务也不必一定写在&lt;code&gt;Gruntfile&lt;/code&gt;中，也可以定义在外部的&lt;code&gt;.js&lt;/code&gt;文件中，然后通过&lt;a href=&quot;http://gruntjs.com/grunt#grunt.loadtasks&quot;&gt;grunt.loadTasks&lt;/a&gt;来加载。&lt;/p&gt;

&lt;h2&gt;实战&lt;/h2&gt;

&lt;p&gt;grunt插件中有contrib前缀的是Grunt团队自行开发的插件，也是推荐使用的，下面挑选几个在前端项目中必用的插件，在实际例子中介绍一下使用方法。&lt;/p&gt;

&lt;h3&gt;grunt-contrib-compass&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://compass-style.org/&quot;&gt;Compass&lt;/a&gt;是&lt;a href=&quot;http://sass-lang.com/&quot;&gt;SASS&lt;/a&gt;的一个框架，就像jQuery之于Javascript、Rails之于Ruby。具体的用法可以参考阮一峰的这两篇Blog:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;http://www.ruanyifeng.com/blog/2012/06/sass.html&quot;&gt;SASS用法指南&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://www.ruanyifeng.com/blog/2012/11/compass.html&quot;&gt;Compass用法指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;首先，安装&lt;a href=&quot;https://npmjs.org/package/grunt-contrib-compass&quot;&gt;grunt-contrib-compass&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install grunt-contrib-compass --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如前所述，&lt;code&gt;--save-dev&lt;/code&gt;可以在安装插件的过程中，将对这个插件的依赖自动写入&lt;code&gt;package.json&lt;/code&gt;文件中，方便。&lt;/p&gt;

&lt;p&gt;Compass并没有暴露所有的设置给Grunt，如果有别的需要，可以在config里面指定&lt;code&gt;config.rb&lt;/code&gt;给Compass编译使用。看一个配置的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;module.exports = function(grunt){
    grunt.initConfig({
      compass: {                  // compass任务
        dist: {                   // 一个子任务
          options: {              // 任务的设置
            sassDir: &#39;sass&#39;,
            cssDir: &#39;css&#39;,
            environment: &#39;production&#39;
          }
        },
        dev: {                    // 另一个子任务
          options: {
            sassDir: &#39;sass&#39;,
            cssDir: &#39;style&#39;
          }
        }
      }
    });

    grunt.loadNpmTasks(&#39;grunt-contrib-compass&#39;);

    grunt.registerTask(&#39;default&#39;, [&#39;compass&#39;]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果要使用外部文件的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;grunt.initConfig({
  compass: {
    dist: {
      options: {
        config: &#39;config/config.rb&#39;
      }
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;grunt-contrib-concat&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://npmjs.org/package/grunt-contrib-concat&quot;&gt;grunt-contrib-concat&lt;/a&gt;是一个合并文件的插件，可以将多个css或js文件合并为一个，以节省链接数。同样的，安装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install grunt-contrib-concat --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个插件有一下几个常用配置：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;code&gt;seperator&lt;/code&gt;：被合并的文件会用这个参数来join，例如你在合并压缩后的js文件时，可以加个&lt;code&gt;;&lt;/code&gt;防止出错&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;banner&lt;/code&gt;：在合并后的文件头部加一些额外信息&lt;/li&gt;
    &lt;li&gt;&lt;code&gt;footer&lt;/code&gt;：在合并后的文件尾部加一些额外信息&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;再看一下用法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;grunt.initConfig({
  pkg: grunt.file.readJSON(&#39;package.json&#39;),
  concat: {
    options: {                                                      //配置
      stripBanners: true,
      banner: &#39;/*! &amp;lt;%= pkg.name %&amp;gt; - v&amp;lt;%= pkg.version %&amp;gt; - &#39; +      //添加自定义的banner
        &#39;&amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */&#39;
    },
    dist: {                                                         //任务
        src: [&#39;src/intro.js&#39;, &#39;src/project.js&#39;, &#39;src/outro.js&#39;],    //源目录文件
        dest: &#39;dist/built.js&#39;                                       //合并后的文件
    },
    basic_and_extras: {                                             //另一个任务
        files: {                                                    //另一种更简便的写法
            &#39;dist/basic.js&#39;: [&#39;src/main.js&#39;],
            &#39;dist/with_extras.js&#39;: [&#39;src/main.js&#39;, &#39;src/extras.js&#39;]
        }
    }
  }
});

grunt.loadNpmTasks(&#39;grunt-contrib-concat&#39;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后在&lt;code&gt;default&lt;/code&gt;事件中添加&lt;code&gt;concat&lt;/code&gt;就会默认执行了。&lt;/p&gt;

&lt;h3&gt;grunt-contrib-uglify&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://npmjs.org/package/grunt-contrib-uglify&quot;&gt;grunt-contrib-uglify&lt;/a&gt;用来压缩js文件，用法与&lt;code&gt;concat&lt;/code&gt;类似，先安装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install grunt-contrib-uglify --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后写入相应的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;grunt.initConfig({
  uglify: {
    options: {
      banner: &#39;/*! This is uglify test - &#39; +
        &#39;&amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */&#39;
    },
    app_task: {
      files: {
        &#39;dist/app.min.js&#39;: [&#39;js/app.js&#39;, &#39;js/render.js&#39;]
      }
    }
  }
});

grunt.loadNpmTasks(&#39;grunt-contrib-uglify&#39;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;恩，经过如此处理，你的js代码已经丑陋到无法直视了。&lt;/p&gt;

&lt;h3&gt;grunt-contrib-watch&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://npmjs.org/package/grunt-contrib-watch&quot;&gt;grunt-contrib-watch&lt;/a&gt;是开发必备插件，用来监控文件的修改，然后自动运行grunt任务，省去一遍遍手动执行Grunt命令，安装照旧：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm install grunt-contrib-watch --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用watch插件时，需要注意一点，被watch的文件，可以分开写，这样可以提高watch的性能，不用每次把没修改的文件也执行一遍任务，看看例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;grunt.initConfig({
  watch: {
    css: {
      files: [&#39;public/scss/*.scss&#39;],
      tasks: [&#39;compass&#39;],
      options: {
        // Start a live reload server on the default port 35729
        livereload: true,
      },
    },
    another: {
      files: [&#39;lib/*.js&#39;],
      tasks: [&#39;anothertask&#39;],
      options: {
        // Start another live reload server on port 1337
        livereload: 1337,
      },
    }
  }
});

grunt.loadNpmTasks(&#39;grunt-contrib-watch&#39;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后运行&lt;code&gt;grunt watch&lt;/code&gt;命令，修改文件，就会看到设定的任务执行了。&lt;/p&gt;

&lt;h2&gt;源码&lt;/h2&gt;

&lt;p&gt;Grunt的基本使用就是这些了，当然还有一些&lt;a href=&quot;http://gruntjs.com/project-scaffolding&quot;&gt;搭建脚手架&lt;/a&gt;等等的功能，等待你自己去学习使用吧，更多的&lt;a href=&quot;http://gruntjs.com/plugins/&quot;&gt;Grunt 插件&lt;/a&gt;也等待你去发现。&lt;/p&gt;

&lt;p&gt;贴出来源码，整体看一下：&lt;/p&gt;

&lt;h3&gt;package.json&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Grunt-in-action&quot;,
  &quot;devDependencies&quot;: {
    &quot;grunt&quot;: &quot;~0.4.1&quot;,
    &quot;grunt-contrib-compass&quot;: &quot;~0.3.0&quot;,
    &quot;grunt-contrib-watch&quot;: &quot;~0.4.4&quot;,
    &quot;grunt-contrib-concat&quot;: &quot;~0.3.0&quot;,
    &quot;grunt-contrib-uglify&quot;: &quot;~0.2.2&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Gruntfile.js&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;module.exports = function(grunt){
    grunt.initConfig({
        compass: {                  // Task
            dist: {                   // Target
                options: {              // Target options
                    sassDir: &#39;sass&#39;,
                    cssDir: &#39;css&#39;,
                    environment: &#39;production&#39;
                }
            },
            dev: {                    // Another target
                options: {
                    sassDir: &#39;sass&#39;,
                    cssDir: &#39;style&#39;
                }
            }
        },

        concat: {
            options: {                                       //配置
                stripBanners:true,
                banner: &#39;/*! This is the grunt test &#39; +      //添加自定义的banner
                &#39;&amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */&#39;
            },
            basic: {                                         //另一个任务
                files: {                                     //另一种更简便的写法
                    &#39;dist/style.css&#39;: [&#39;style/screen.css&#39;,&#39;style/ie.css&#39;,&#39;style/print.css&#39;]
                }
            }
        },

        uglify: {
            options: {
                banner: &#39;/*! This is uglify test - &#39; +
                &#39;&amp;lt;%= grunt.template.today(&quot;yyyy-mm-dd&quot;) %&amp;gt; */&#39;
            },
            app_task: {
                files: {
                    &#39;dist/app.min.js&#39;: [&#39;js/app.js&#39;, &#39;js/render.js&#39;]
                }
            }
        },

        watch: {
            css: {
                files: [&#39;sass/*.scss&#39;],
                tasks: [&#39;compass&#39;, &#39;concat&#39;]
            },
            another: {
                files: [&#39;js/*.js&#39;],
                tasks: [&#39;uglify&#39;]
            }
        }
    });

    grunt.loadNpmTasks(&#39;grunt-contrib-compass&#39;);
    grunt.loadNpmTasks(&#39;grunt-contrib-concat&#39;);
    grunt.loadNpmTasks(&#39;grunt-contrib-uglify&#39;);
    grunt.loadNpmTasks(&#39;grunt-contrib-watch&#39;);

    grunt.registerTask(&#39;default&#39;, [&#39;compass&#39;,&#39;concat&#39;, &#39;uglify&#39;]);
}
&lt;/code&gt;&lt;/pre&gt;
</content>
   </entry>
   
   <entry>
     <title>豆瓣FM(iOS 3.0)使用体会</title>
     <link href="http://beiyuu.com/douban-fm"/>
     <updated>2013-06-06T00:00:00-07:00</updated>
     <id>http://beiyuu.com/douban-fm</id>
     <content type="html">&lt;p&gt;豆瓣FM发布了全新设计的3.0版本，很多Feature不错：&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;全新设计，清新、大气&lt;/li&gt;
    &lt;li&gt;操作流畅爽滑，运行交互效率很高&lt;/li&gt;
    &lt;li&gt;终于有歌词啦&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;使用中总结了一些体会，非常感性、毫无逻辑可言，看看就好：&lt;/p&gt;

&lt;h2&gt;产品定位&lt;/h2&gt;

&lt;p&gt;豆瓣FM的产品定位，摘抄如下：&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;它简单、易用，熟悉每个用户的脾气秉性，又能够聚合集体的智慧。无论是在家里、工作场所、咖啡厅还是交通工具上，无论是在休息、等候、娱乐还是工作时，它都能够提供完美、一致的体验。&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;大家对推荐都满意，算法就不说了。简单易用来讲，国内的音乐APP中(虾米、QQ、百度等)，只有豆瓣是打开就播放，确实简单易用。&lt;/p&gt;

&lt;p&gt;这样的打开播放的产品定位下，使用场景就有些变化了。&lt;/p&gt;

&lt;h2&gt;交互框架&lt;/h2&gt;

&lt;h3&gt;引导页&lt;/h3&gt;

&lt;p&gt;从App引导开始，新版FM就想让用户熟悉上下拉动的操作。&lt;/p&gt;

&lt;p&gt;豆瓣这次竟然使用了被吐槽无数次的App引导，还蛮让人意外的。是对交互设计不自信吗？观察周围使用的人以及我自己，基本上没人会去看引导页的内容，况且下拉的操作违反直觉，更容易招惹反感，播放页面也被蒙层，我直观感受是恐惧。&lt;/p&gt;

&lt;p&gt;登录和进入按钮被放置在引导页下方，并且位置尴尬，大约是为了让用户感受到下面还有内容，平衡了讨厌引导的用户需求(估计设计师自己也讨厌引导吧)，却牺牲了美感。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/mw1024/8b8af2c8jw1e5eqcvmp9gj20oq0lp408.jpg&quot; alt=&quot;splash&quot; /&gt;&lt;/p&gt;

&lt;h3&gt;上下拉&lt;/h3&gt;

&lt;p&gt;自从抽屉式导航普及开来之后，横滑展开设置成为一种默认操作。&lt;/p&gt;

&lt;p&gt;上下拉在虾米音乐中也有被应用，但和豆瓣的不同在于：虾米入口界面不是播放页，而是搜索、我的虾米、本地音乐这样的交互界面，此时播放页从下拉上来，更像是另一个维度的事情，并且主界面的交互有很多，播放页到另一个维度是很好理解的事情。硬套生活中所讲的横向、纵向的概念也完全讲的通。&lt;/p&gt;

&lt;p&gt;豆瓣FM的逻辑不完美在于，他的主页是播放页，选择兆赫更像是个设置，硬说他是另一个维度也没错，但是这两个的关系太平等（都只有一个），上下拉就显得违反直觉了。&lt;/p&gt;

&lt;p&gt;而且没人看引导，反直觉的上下拉动的交互，让很多人不知所措，顶部也没有标记表明那是可以拉动的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww4.sinaimg.cn/mw1024/8b8af2c8jw1e5eqd2xcj9j20oq0lpjv1.jpg&quot; alt=&quot;slide&quot; /&gt;&lt;/p&gt;

&lt;h2&gt;设计细节&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;封面不清晰&lt;/li&gt;
&lt;li&gt;对话框关闭按钮在左边，为什么呢？&lt;/li&gt;
&lt;li&gt;兆赫不能搜索，连换一批也不能？更新说明说动态切换，怎么切换？&lt;/li&gt;
&lt;li&gt;暂停、播放按钮太小（上面显示广告也罢了，下面那么大的空间，完全可以利用起来）&lt;/li&gt;
&lt;li&gt;只支持上下拉的手势，却不支持左右后退的手势？（更新说明里我瞟见了手势两个字）&lt;/li&gt;
&lt;li&gt;顶部“正在同步”等状态栏，完全遮挡操作按钮，为什么不加后退手势这种标配呢？&lt;/li&gt;
&lt;li&gt;在上下拉的交互框架中，歌曲状态栏细节做的很到位（比如当上下拉到歌曲名称位置的时候，状态栏才会fix住），但是上拉拖动的时候，状态栏此时是拖动的把手，却自己不动弹，逻辑又奇怪了不是？&lt;/li&gt;
&lt;li&gt;其他的程序BUG，比如红心数目不对等等。&lt;/li&gt;
&lt;li&gt;如何收藏兆赫，研究许久才知道是长按。&lt;/li&gt;
&lt;li&gt;也是琢磨了半天才知道，点头像是进入设置。&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src=&quot;http://ww3.sinaimg.cn/mw1024/8b8af2c8jw1e5eqd8tkg5j20oq0lpjti.jpg&quot; alt=&quot;slide&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再重复一下，APP引导太可怕了！ 我说的全是废话，因为这个版本发布后，看起来好评如潮，哎。&lt;/p&gt;
</content>
   </entry>
   
   <entry>
     <title>史蒂夫·乔布斯：遗失的访谈</title>
     <link href="http://beiyuu.com/jobs-interview"/>
     <updated>2013-05-14T00:00:00-07:00</updated>
     <id>http://beiyuu.com/jobs-interview</id>
     <content type="html">&lt;h2&gt;1、做公司就是做产品&lt;/h2&gt;

&lt;p&gt;乔布斯从12岁开始，就捣鼓着好玩有用的产品，在惠普兼职的那些日子，也让他体会到了科技的魅力，原来凭一个人的力量也可以有如此之大的影响力，也是在惠普他认识了沃兹尼亚克。&lt;/p&gt;

&lt;p&gt;Apple II发布时，乔布斯年仅21岁，从此之后，他的财富不断增加，23岁一百万，24岁一千万，25岁一亿美元。但是钱并不是乔布斯认为最重要的东西，重要的是人才、产品，是产品带给用户的价值。&lt;/p&gt;

&lt;p&gt;做产品要深入的每一个细节，要去控制成本，就必须知道每一个步骤的精确成本，他也建议所有人都去学习一下编程，学习这种思考问题的方式。&lt;/p&gt;

&lt;p&gt;乔布斯举了百事可乐的例子，来说明大公司为什么会失败，因为一旦公司做大，形成垄断市场之后，产品数十年也不会发生多大的变化，而且这些垄断的市场也很难再提高业绩，要提高业绩还得靠营销部门，最终营销人员挤走了产品人员，而营销人员并不懂产品，这情形在百事可乐这样的公司没问题，但在科技公司就会造成悲剧。&lt;/p&gt;

&lt;p&gt;光靠流程和制度，是不能让公司持续成功的，必须全心全意放在产品上。&lt;/p&gt;

&lt;h2&gt;2、如何使公司良好运转&lt;/h2&gt;

&lt;p&gt;对于传统行业来说，最好和普通之间，差距可能就是百分之几十的样子，但是对科技行业来说，最好的与普通的人才，能力的差距可能达百倍，所以一流的公司需要一流的人才，一流的人才也愿意与一流的人才共事。&lt;/p&gt;

&lt;p&gt;团队的协作过程，就像磨石机的工作过程一样，会有摩擦会有障碍，这是必然的，以乔布斯的理解来看，真正优秀的人，只想成功，不在乎是非，用事实说话，其他的都不重要，他认为优秀的人也不太需要你太在乎他们的自尊。&lt;/p&gt;

&lt;p&gt;大约这就是他被谣传暴戾的原因吧，从访谈中可以看的出，他确实不太在乎事实之外的东西，他自己也会犯错，别人也可以说服他，只要基于事实就好。&lt;/p&gt;

&lt;h2&gt;3、品位问题&lt;/h2&gt;

&lt;p&gt;毫无疑问，乔布斯的品位很好，他认为和在大学时旁听排版课程的关系密不可分，的确，内容永远都是最重要的，并且，字体设计那些细致入微的细节，也对他有很深的影响。&lt;/p&gt;

&lt;p&gt;微软是一个成功的公司，善于抓住机遇，但是他们的产品毫无品位，是三流的，或许没有苹果，微软的产品会更糟糕，没有灵魂，非常平庸，但是更令人可悲的是用户并不以为然。&lt;/p&gt;

&lt;p&gt;人类应该追求极致，并分享给同类。&lt;/p&gt;

&lt;p&gt;乔布斯也从来不觉得借鉴别人的创意是可耻的，笨拙的工匠只会抄，灵巧的工匠会借鉴的恰到好处，苹果的产品非常确切的说明了这个事实。&lt;/p&gt;

&lt;p&gt;6、70年代的嬉皮士运动给乔布斯留下了深刻印象，他认为嬉皮士是去努力寻找生活的真相的一群人，他们觉得生活不应该是父母过的那样。嬉皮士的出发点是可贵的。也正是因为这种精神，有人宁愿当诗人也不愿做银行家。&lt;/p&gt;

&lt;p&gt;乔布斯很欣赏这种精神，他也想把这种精神溶入到产品中。只要用户使用产品，就能感受到这种精神，如果与Macintosh的用户交谈，会发现他们喜爱这个产品，在此之前，你很少听人说真心喜欢某个商业产品——是的，但你可以从Macintosh感受到某种奇妙的东西。&lt;/p&gt;

&lt;!--&lt;iframe height=&quot;498&quot; width=&quot;510&quot; src=&quot;http://player.youku.com/embed/XNTUxNDY1NDY4&quot; frameborder=&quot;0&quot; style=&quot;margin:0 auto&quot;&gt;&lt;/iframe&gt;--&gt;




&lt;div style=&quot;text-align:center&quot;&gt;
&lt;embed src=&quot;http://player.youku.com/player.php/sid/XNTUxNDY1NDY4/v.swf&quot; allowFullScreen=&quot;true&quot; quality=&quot;high&quot; width=&quot;480&quot; height=&quot;400&quot; align=&quot;middle&quot; allowScriptAccess=&quot;always&quot; type=&quot;application/x-shockwave-flash&quot;&gt;&lt;/embed&gt;
&lt;/div&gt;

</content>
   </entry>
   
   <entry>
     <title>Tabby</title>
     <link href="http://beiyuu.com/tabby"/>
     <updated>2013-04-12T00:00:00-07:00</updated>
     <id>http://beiyuu.com/tabby</id>
     <content type="html">&lt;p&gt;用&lt;a href=&quot;https://chrome.google.com/webstore/detail/tabby/pifnjkdglcfkfpdjdolfacpfdlgpdkhp&quot; title=&quot;Tabby&quot;&gt;Tabby&lt;/a&gt;有一阵子了，最近把细节完善，发布在了Chrome Store。&lt;/p&gt;

&lt;p&gt;他的功能是，当你打开几十上百个Chrome标签，很占资源，不想加入书签，也不想存入Instapaper等Read It Later的东西，只看一次，但就是不想现在看，这时候只要在页面点击右键，选择“暂存到Tabby”即可。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/tabby/pifnjkdglcfkfpdjdolfacpfdlgpdkhp&quot; title=&quot;Tabby&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/other/Tabby-pro1.png&quot; alt=&quot;Tabby&quot;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;你可以设置使用快捷键（默认关闭），要打开暂存的标签，只需点击Tabby的图标。因为使用了Chrome的书签，即使你卸载插件，还没来得及看的也不会丢失，也会默认同步到别的设备。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/tabby/pifnjkdglcfkfpdjdolfacpfdlgpdkhp&quot; title=&quot;Tabby&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/other/Tabby-pro2.png&quot; alt=&quot;Tabby&quot; &gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;推荐一本书&lt;a href=&quot;http://read.douban.com/ebook/709141/?referral_code=e6acsgl3&quot; title=&quot;我是个年轻人，我心情不太好&quot;&gt;我是个年轻人，我心情不太好&lt;/a&gt;，当时是他的简介让我动心了一下：&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;我25岁，读过一个硕士，但是退学了。我被时间、空间、意义这些事情烦扰着。我知道的东西多得难以置信。我知道名字，年份。数以百计。我知道谁第一个上的珠穆朗玛峰。我知道谁导演了那些美国最蹩脚的肥皂剧。我知道亚里士多德讲的话是啥意思。我知道时间在太阳上会走得慢一点……&lt;/p&gt;

&lt;p&gt;但是，我知道这些有什么意义呢？&lt;/p&gt;

&lt;p&gt;我的生活仍旧是一团糟。&lt;/p&gt;

&lt;p&gt;不，连一团糟都不是，它是一片空白。&lt;/p&gt;

&lt;p&gt;我没有女朋友，甚至没有很好很好的朋友。我有一个哥哥，还有一个比我小很多、还在上幼儿园的“伙伴”。可他们也不算是朋友。&lt;/p&gt;

&lt;p&gt;要是我能有一种非常靠谱的感觉，觉得一切都会好起来，那该多好。&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;不指望这本书能喂你多大一碗鸡汤，就是让你知道这个年纪的共同态，而且或许有解决办法。&lt;/p&gt;

&lt;p&gt;好吧，去下载插件吧：
&lt;a href=&quot;https://chrome.google.com/webstore/detail/tabby/pifnjkdglcfkfpdjdolfacpfdlgpdkhp&quot; title=&quot;Tabby&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;/images/other/Tabby-440.png&quot; alt=&quot;Tabby&quot; width=&quot;300&quot;&gt;&lt;/a&gt;&lt;/p&gt;
</content>
   </entry>
   
   <entry>
     <title>编写可读代码的艺术</title>
     <link href="http://beiyuu.com/readable-code"/>
     <updated>2013-03-21T00:00:00-07:00</updated>
     <id>http://beiyuu.com/readable-code</id>
     <content type="html">&lt;p&gt;这是《The Art of Readable Code》的读书笔记，再加一点自己的认识。强烈推荐此书：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;英文版：&lt;a href=&quot;http://book.douban.com/subject/5442971/&quot; title=&quot;The Art Of Readable Code&quot;&gt;《The Art of Readable Code》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;中文版：&lt;a href=&quot;http://book.douban.com/subject/10797189/&quot; title=&quot;编写可读代码的艺术&quot;&gt;编写可读代码的艺术&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;代码为什么要易于理解&lt;/h2&gt;

&lt;blockquote&gt;&lt;p&gt;&quot;Code should be written to minimize the time it would take for someone else to understand it.&quot;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;日常工作的事实是：&lt;/p&gt;

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


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

&lt;h2&gt;如何命名&lt;/h2&gt;

&lt;h3&gt;变量名中应包含更多信息&lt;/h3&gt;

&lt;h4&gt;使用含义明确的词，比如用&lt;code&gt;download&lt;/code&gt;而不是&lt;code&gt;get&lt;/code&gt;，参考以下替换方案：&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt; send -&amp;gt; deliver, dispatch, announce, distribute, route
 find -&amp;gt; search, extract, locate, recover
start -&amp;gt; lanuch, create, begin, open
 make -&amp;gt; create,set up, build, generate, compose, add, new
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;避免通用的词&lt;/h4&gt;

&lt;p&gt;像&lt;code&gt;tmp&lt;/code&gt;和&lt;code&gt;retval&lt;/code&gt;这样词，除了说明是临时变量和返回值之外，没有任何意义。但是给他加一些有意义的词，就会很明确：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tmp_file = tempfile.NamedTemporaryFile() 
...
SaveData(tmp_file, ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不使用retval而使用变量真正代表的意义：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sum_squares += v[i]; // Where&#39;s the &quot;square&quot; that we&#39;re summing? Bug!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;嵌套的for循环中,&lt;code&gt;i&lt;/code&gt;、&lt;code&gt;j&lt;/code&gt;也有同样让人困惑的时候：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; clubs.size(); i++)
    for (int j = 0; j &amp;lt; clubs[i].members.size(); j++)
        for (int k = 0; k &amp;lt; users.size(); k++) if (clubs[i].members[k] == users[j])
            cout &amp;lt;&amp;lt; &quot;user[&quot; &amp;lt;&amp;lt; j &amp;lt;&amp;lt; &quot;] is in club[&quot; &amp;lt;&amp;lt; i &amp;lt;&amp;lt; &quot;]&quot; &amp;lt;&amp;lt; endl;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;换一种写法就会清晰很多：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; if (clubs[ci].members[mi] == users[ui])  # OK. First letters match.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以，当使用一些通用的词，要有充分的理由才可以。&lt;/p&gt;

&lt;h4&gt;使用具体的名字&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;CanListenOnPort&lt;/code&gt;就比&lt;code&gt;ServerCanStart&lt;/code&gt;好，can start比较含糊，而listen on port确切的说明了这个方法将要做什么。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--run_locally&lt;/code&gt;就不如&lt;code&gt;--extra_logging&lt;/code&gt;来的明确。&lt;/p&gt;

&lt;h4&gt;增加重要的细节，比如变量的单位&lt;code&gt;_ms&lt;/code&gt;，对原始字符串加&lt;code&gt;_raw&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;如果一个变量很重要，那么在名字上多加一些额外的字就会更加易读，比如将&lt;code&gt;string id; // Example: &quot;af84ef845cd8&quot;&lt;/code&gt;换成&lt;code&gt;string hex_id;&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;             Start(int delay)  --&amp;gt;  delay → delay_secs
        CreateCache(int size)  --&amp;gt;  size → size_mb
ThrottleDownload(float limit)  --&amp;gt;  limit → max_kbps
          Rotate(float angle)  --&amp;gt;  angle → degrees_cw
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;更多例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;password  -&amp;gt;  plaintext_password
 comment  -&amp;gt;  unescaped_comment
    html  -&amp;gt;  html_utf8
    data  -&amp;gt;  data_urlenc
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;对于作用域大的变量使用较长的名字&lt;/h4&gt;

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

&lt;h4&gt;合理使用&lt;code&gt;_&lt;/code&gt;、&lt;code&gt;-&lt;/code&gt;等符号，比如对私有变量加&lt;code&gt;_&lt;/code&gt;前缀。&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;var x = new DatePicker(); // DatePicker() 是类的&quot;构造&quot;函数，大写开始
var y = pageHeight(); // pageHeight() 是一个普通函数

var $all_images = $(&quot;img&quot;); // $all_images 是jQuery对象
var height = 250; // height不是

//id和class的写法分开
&amp;lt;div id=&quot;middle_column&quot; class=&quot;main-content&quot;&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;命名不能有歧义&lt;/h3&gt;

&lt;p&gt;命名的时候可以先想一下，我要用的这个词是否有别的含义。举个例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;results = Database.all_objects.filter(&quot;year &amp;lt;= 2011&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在的结果到底是包含2011年之前的呢还是不包含呢？&lt;/p&gt;

&lt;h4&gt;使用&lt;code&gt;min&lt;/code&gt;、&lt;code&gt;max&lt;/code&gt;代替&lt;code&gt;limit&lt;/code&gt;&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;CART_TOO_BIG_LIMIT = 10
    if shopping_cart.num_items() &amp;gt;= CART_TOO_BIG_LIMIT:
        Error(&quot;Too many items in cart.&quot;)

MAX_ITEMS_IN_CART = 10
    if shopping_cart.num_items() &amp;gt; MAX_ITEMS_IN_CART:
     Error(&quot;Too many items in cart.&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对比上例中&lt;code&gt;CART_TOO_BIG_LIMIT&lt;/code&gt;和&lt;code&gt;MAX_ITEMS_IN_CART&lt;/code&gt;，想想哪个更好呢？&lt;/p&gt;

&lt;h4&gt;使用&lt;code&gt;first&lt;/code&gt;和&lt;code&gt;last&lt;/code&gt;来表示闭区间&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;print integer_range(start=2, stop=4)
# Does this print [2,3] or [2,3,4] (or something else)?

set.PrintKeys(first=&quot;Bart&quot;, last=&quot;Maggie&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;first&lt;/code&gt;和&lt;code&gt;last&lt;/code&gt;含义明确，适宜表示闭区间。&lt;/p&gt;

&lt;h4&gt;使用&lt;code&gt;beigin&lt;/code&gt;和&lt;code&gt;end&lt;/code&gt;表示前闭后开([2,9))区间&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;PrintEventsInRange(&quot;OCT 16 12:00am&quot;, &quot;OCT 17 12:00am&quot;)

PrintEventsInRange(&quot;OCT 16 12:00am&quot;, &quot;OCT 16 11:59:59.9999pm&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面一种写法就比下面的舒服多了。&lt;/p&gt;

&lt;h4&gt;Boolean型变量命名&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;bool read_password = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是一个很危险的命名，到底是需要读取密码呢，还是密码已经被读取呢，不知道，所以这个变量可以使用&lt;code&gt;user_is_authenticated&lt;/code&gt;代替。通常，给Boolean型变量添加&lt;code&gt;is&lt;/code&gt;、&lt;code&gt;has&lt;/code&gt;、&lt;code&gt;can&lt;/code&gt;、&lt;code&gt;should&lt;/code&gt;可以让含义更清晰，比如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;             SpaceLeft()  --&amp;gt;  hasSpaceLeft()
bool disable_ssl = false  --&amp;gt;  bool use_ssl = true
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;符合预期&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;public class StatisticsCollector {
    public void addSample(double x) { ... }
    public double getMean() {
        // Iterate through all samples and return total / num_samples
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h2&gt;漂亮的格式&lt;/h2&gt;

&lt;p&gt;写出来漂亮的格式，充满美感，读起来自然也会舒服很多，对比下面两个例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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&amp;lt;double&amp;gt;
     past_items
         ;double maximum;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;什么是充满美感的呢：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// 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&amp;lt;double&amp;gt; past_items;
    int count;  // how many so far
    double minimum;
    double maximum;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;考虑断行的连续性和简洁&lt;/h3&gt;

&lt;p&gt;这段代码需要断行，来满足不超过一行80个字符的要求，参数也需要注释说明：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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 % */);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;考虑到代码的连贯性，先优化成这样：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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 % */);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;连贯性好一点，但还是太罗嗦，额外占用很多空间：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;用函数封装&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;// Turn a partial_name like &quot;Doug Adams&quot; into &quot;Mr. Douglas Adams&quot;.
// If not possible, &#39;error&#39; is filled with an explanation.
string ExpandFullName(DatabaseConnection dc, string partial_name, string* error);

DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection, &quot;Doug Adams&quot;, &amp;amp;error)
        == &quot;Mr. Douglas Adams&quot;);
assert(error == &quot;&quot;);
assert(ExpandFullName(database_connection, &quot; Jake Brown &quot;, &amp;amp;error)
        == &quot;Mr. Jacob Brown III&quot;);
assert(error == &quot;&quot;);
assert(ExpandFullName(database_connection, &quot;No Such Guy&quot;, &amp;amp;error) == &quot;&quot;);
assert(error == &quot;no match found&quot;);
assert(ExpandFullName(database_connection, &quot;John&quot;, &amp;amp;error) == &quot;&quot;);
assert(error == &quot;more than one result&quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面这段代码看起来很脏乱，很多重复性的东西，可以用函数封装：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CheckFullName(&quot;Doug Adams&quot;, &quot;Mr. Douglas Adams&quot;, &quot;&quot;);
CheckFullName(&quot; Jake Brown &quot;, &quot;Mr. Jake Brown III&quot;, &quot;&quot;);
CheckFullName(&quot;No Such Guy&quot;, &quot;&quot;, &quot;no match found&quot;);
CheckFullName(&quot;John&quot;, &quot;&quot;, &quot;more than one result&quot;);

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, &amp;amp;error);
    assert(error == expected_error);
    assert(full_name == expected_full_name);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;列对齐&lt;/h3&gt;

&lt;p&gt;列对齐可以让代码段看起来更舒适：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CheckFullName(&quot;Doug Adams&quot;   , &quot;Mr. Douglas Adams&quot; , &quot;&quot;);
CheckFullName(&quot; Jake  Brown &quot;, &quot;Mr. Jake Brown III&quot;, &quot;&quot;);
CheckFullName(&quot;No Such Guy&quot;  , &quot;&quot;                  , &quot;no match found&quot;);
CheckFullName(&quot;John&quot;         , &quot;&quot;                  , &quot;more than one result&quot;);

commands[] = {
    ...
    { &quot;timeout&quot;      , NULL              , cmd_spec_timeout},
    { &quot;timestamping&quot; , &amp;amp;opt.timestamping , cmd_boolean},
    { &quot;tries&quot;        , &amp;amp;opt.ntry         , cmd_number_inf},
    { &quot;useproxy&quot;     , &amp;amp;opt.use_proxy    , cmd_boolean},
    { &quot;useragent&quot;    , NULL              , cmd_spec_useragent},
    ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;代码用块区分&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;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();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面这一段虽然能看，不过还有优化空间：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再来看一段代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Import the user&#39;s email contacts, and match them to users in our system.
# Then display a list of those users that he/she isn&#39;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[&#39;user&#39;] = user
    display[&#39;friends&#39;] = friends
    display[&#39;suggested_friends&#39;] = suggested_friends
    return render(&quot;suggested_friends.html&quot;, display)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;全都混在一起，视觉压力相当大，按功能化块：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def suggest_new_friends(user, email_password):
    # Get the user&#39;s friends&#39; email addresses.
    friends = user.friends()
    friend_emails = set(f.email for f in friends)

    # Import all email addresses from this user&#39;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&#39;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[&#39;user&#39;] = user
    display[&#39;friends&#39;] = friends
    display[&#39;suggested_friends&#39;] = suggested_friends

    return render(&quot;suggested_friends.html&quot;, display)
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h2&gt;如何写注释&lt;/h2&gt;

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

&lt;h3&gt;应该注释什么&lt;/h3&gt;

&lt;h4&gt;不应该注释什么&lt;/h4&gt;

&lt;p&gt;这样的注释毫无价值：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// 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();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;不要像下面这样为了注释而注释：&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// Find a Node with the given &#39;name&#39; or return NULL.
// If depth &amp;lt;= 0, only &#39;subtree&#39; is inspected.
// If depth == N, only &#39;subtree&#39; and N levels below are inspected.
Node* FindNodeInSubtree(Node* subtree, string name, int depth);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;不要给烂取名注释&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// 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);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注释的大部分都在解释clean是什么意思，那不如换个正确的名字：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Make sure &#39;reply&#39; meets the count/byte/etc. limits from the &#39;request&#39; 
void EnforceLimitsFromRequest(Request request, Reply reply);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;记录你的想法&lt;/h4&gt;

&lt;p&gt;我们讨论了不该注释什么，那么应该注释什么呢？注释应该记录你思考代码怎么写的结果，比如像下面这些：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// 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&#39;s OK; solving this 100% is hard.

// This class is getting messy. Maybe we should create a &#39;ResourceNode&#39; subclass to
// help organize things.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也可以用来记录流程和常量：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// TODO: use a faster algorithm
// TODO(dustin): handle other image formats besides JPEG

NUM_THREADS = 8 # as long as it&#39;s &amp;gt;= 2 * num_processors, that&#39;s good enough.

// Impose a reasonable limit - no human can read that much anyway.
const int MAX_RSS_SUBSCRIPTIONS = 1000;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可用的词有：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;TODO  : Stuff I haven&#39;t gotten around to yet
FIXME : Known-broken code here
HACK  : Adimittedly inelegant solution to a problem
XXX   : Danger! Major problem here
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;站在读者的角度去思考&lt;/h4&gt;

&lt;p&gt;当别人读你的代码时，让他们产生疑问的部分，就是你应该注释的地方。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;struct Recorder {
    vector&amp;lt;float&amp;gt; data;
    ...
    void Clear() {
        vector&amp;lt;float&amp;gt;().swap(data); // Huh? Why not just data.clear()? 
    }
};
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code&gt;// Force vector to relinquish its memory (look up &quot;STL swap trick&quot;)
vector&amp;lt;float&amp;gt;().swap(data);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;说明可能陷阱&lt;/h4&gt;

&lt;p&gt;你在写代码的过程中，可能用到一些hack，或者有其他需要读代码的人知道的陷阱，这时候就应该注释：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;void SendEmail(string to, string subject, string body);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而实际上这个发送邮件的函数是调用别的服务，有超时设置，所以需要注释：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Calls an external service to deliver email.  (Times out after 1 minute.)
void SendEmail(string to, string subject, string body);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;全景的注释&lt;/h4&gt;

&lt;p&gt;有时候为了更清楚说明，需要给整个文件加注释，让读者有个总体的概念：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// This file contains helper functions that provide a more convenient interface to our
// file system. It handles file permissions and other nitty-gritty details.
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;总结性的注释&lt;/h4&gt;

&lt;p&gt;即使是在函数内部，也可以有类似文件注释那样的说明注释：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# 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:
            ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者按照函数的步进，写一些注释：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def GenerateUserReport():
    # Acquire a lock for this user
    ...
    # Read user&#39;s info from the database
    ...
    # Write info to a file
    ...
    # Release the lock for this user
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h3&gt;注释应简明准确&lt;/h3&gt;

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

&lt;h4&gt;精简注释&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// The int is the CategoryType.
// The first float in the inner pair is the &#39;score&#39;,
// the second is the &#39;weight&#39;.
typedef hash_map&amp;lt;int, pair&amp;lt;float, float&amp;gt; &amp;gt; ScoreMap;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样写太罗嗦了，尽量精简压缩成这样：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// CategoryType -&amp;gt; (score, weight)
typedef hash_map&amp;lt;int, pair&amp;lt;float, float&amp;gt; &amp;gt; ScoreMap;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;避免有歧义的代词&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// Insert the data into the cache, but check if it&#39;s too big first.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的&lt;code&gt;it&#39;s&lt;/code&gt;有歧义，不知道所指的是&lt;code&gt;data&lt;/code&gt;还是&lt;code&gt;cache&lt;/code&gt;，改成如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Insert the data into the cache, but check if the data is too big first.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还有更好的解决办法，这里的&lt;code&gt;it&lt;/code&gt;就有明确所指：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// If the data is small enough, insert it into the cache.
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;语句要精简准确&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Depending on whether we&#39;ve already crawled this URL before, give it a different priority.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这句话理解起来太费劲，改成如下就好理解很多：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Give higher priority to URLs we&#39;ve never crawled before.
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;精确描述函数的目的&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// Return the number of lines in this file.
int CountLines(string filename) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样的一个函数，用起来可能会一头雾水，因为他可以有很多歧义：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&quot;&quot; 一个空文件，是0行还是1行？&lt;/li&gt;
&lt;li&gt;&quot;hello&quot; 只有一行，那么返回值是0还是1？&lt;/li&gt;
&lt;li&gt;&quot;hello\n&quot; 这种情况返回1还是2？&lt;/li&gt;
&lt;li&gt;&quot;hello\n world&quot; 返回1还是2？&lt;/li&gt;
&lt;li&gt;&quot;hello\n\r cruel\n world\r&quot; 返回2、3、4哪一个呢？&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;所以注释应该这样写：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Count how many newline bytes (&#39;\n&#39;) are in the file.
int CountLines(string filename) { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;用实例说明边界情况&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// Rearrange &#39;v&#39; so that elements &amp;lt; pivot come before those &amp;gt;= pivot;
// Then return the largest &#39;i&#39; for which v[i] &amp;lt; pivot (or -1 if none are &amp;lt; pivot)
int Partition(vector&amp;lt;int&amp;gt;* v, int pivot);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个描述很精确，但是如果再加入一个例子，就更好了：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// ...
// Example: Partition([8 5 9 8 2], 8) might result in [5 2 | 8 9 8] and return 1
int Partition(vector&amp;lt;int&amp;gt;* v, int pivot);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;说明你的代码的真正目的&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;void DisplayProducts(list&amp;lt;Product&amp;gt; products) {
    products.sort(CompareProductByPrice);
    // Iterate through the list in reverse order
    for (list&amp;lt;Product&amp;gt;::reverse_iterator it = products.rbegin(); it != products.rend();
            ++it)
        DisplayPrice(it-&amp;gt;price);
    ... 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的注释说明了倒序排列，单还不够准确，应该改成这样：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Display each price, from highest to lowest
for (list&amp;lt;Product&amp;gt;::reverse_iterator it = products.rbegin(); ... )
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;函数调用时的注释&lt;/h4&gt;

&lt;p&gt;看见这样的一个函数调用，肯定会一头雾水：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Connect(10, false);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果加上这样的注释，读起来就清楚多了：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def Connect(timeout, use_encryption):  ...

# Call the function using named parameters
Connect(timeout = 10, use_encryption = False)
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;使用信息含量丰富的词&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;// 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.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面这一大段注释，解释的很清楚，如果换一个词来代替，也不会有什么疑惑：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// This class acts as a caching layer to the database.
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;简化循环和逻辑&lt;/h2&gt;

&lt;h3&gt;流程控制要简单&lt;/h3&gt;

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

&lt;h4&gt;条件语句中参数的位置&lt;/h4&gt;

&lt;p&gt;对比下面两种条件的写法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (length &amp;gt;= 10)
while (bytes_received &amp;lt; bytes_expected)

if (10 &amp;lt;= length)
while (bytes_expected &amp;gt; bytes_received)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;到底是应该按照大于小于的顺序来呢，还是有其他的准则？是的，应该按照参数的意义来&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;运算符左边：通常是需要被检查的变量，也就是会经常变化的&lt;/li&gt;
&lt;li&gt;运算符右边：通常是被比对的样本，一定程度上的常量&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;这就解释了为什么&lt;code&gt;bytes_received &amp;lt; bytes_expected&lt;/code&gt;比反过来更好理解。&lt;/p&gt;

&lt;h4&gt;if/else的顺序&lt;/h4&gt;

&lt;p&gt;通常，&lt;code&gt;if/else&lt;/code&gt;的顺序你可以自由选择，下面这两种都可以：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (a == b) {
    // Case One ...
} else {
    // Case Two ...
}

if (a != b) {
    // Case Two ...
} else {
    // Case One ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或许对此你也没有仔细斟酌过，但在有些时候，一种顺序确实好过另一种：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;正向的逻辑在前，比如&lt;code&gt;if(debug)&lt;/code&gt;就比&lt;code&gt;if(!debug)&lt;/code&gt;好&lt;/li&gt;
&lt;li&gt;简单逻辑的在前，这样&lt;code&gt;if&lt;/code&gt;和&lt;code&gt;else&lt;/code&gt;就可以在一个屏幕显示&lt;/li&gt;
&lt;li&gt;有趣、清晰的逻辑在前&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;举个例子来看：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (!url.HasQueryParameter(&quot;expand_all&quot;)) {
    response.Render(items);
    ...
} else {
    for (int i = 0; i &amp;lt; items.size(); i++) {
        items[i].Expand();
    }
    ... 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到&lt;code&gt;if&lt;/code&gt;你首先想到的是&lt;code&gt;expand_all&lt;/code&gt;，就好像告诉你“不要想大象”，你会忍不住去想它，所以产生了一点点迷惑，最好写成：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (url.HasQueryParameter(&quot;expand_all&quot;)) {
    for (int i = 0; i &amp;lt; items.size(); i++) {
        items[i].Expand();
    }
    ... 
} else {
    response.Render(items);
    ... 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;三目运算符(?:)&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;time_str += (hour &amp;gt;= 12) ? &quot;pm&quot; : &quot;am&quot;;

Avoiding the ternary operator, you might write:
    if (hour &amp;gt;= 12) {
        time_str += &quot;pm&quot;;
    } else {
        time_str += &quot;am&quot;;
}
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code&gt;return exponent &amp;gt;= 0 ? mantissa * (1 &amp;lt;&amp;lt; exponent) : mantissa / (1 &amp;lt;&amp;lt; -exponent);

if (exponent &amp;gt;= 0) {
    return mantissa * (1 &amp;lt;&amp;lt; exponent);
} else {
    return mantissa / (1 &amp;lt;&amp;lt; -exponent);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以只在简单表达式的地方用。&lt;/p&gt;

&lt;h4&gt;避免使用do/while表达式&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;do {
    continue;
} while (false);
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h4&gt;尽早return&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;public boolean Contains(String str, String substr) {
    if (str == null || substr == null) return false;
    if (substr.equals(&quot;&quot;)) return true;
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;函数里面尽早的return，可以让逻辑更加清晰。&lt;/p&gt;

&lt;h4&gt;减少嵌套&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;if (user_result == SUCCESS) {
    if (permission_result != SUCCESS) {
        reply.WriteErrors(&quot;error reading permissions&quot;);
        reply.Done();
        return;
    }
    reply.WriteErrors(&quot;&quot;);
} else {
    reply.WriteErrors(user_result);
}
reply.Done();
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code&gt;if (user_result != SUCCESS) {
    reply.WriteErrors(user_result);
    reply.Done();
    return;
}
if (permission_result != SUCCESS) {
    reply.WriteErrors(permission_result);
    reply.Done();
    return;
}
reply.WriteErrors(&quot;&quot;);
reply.Done();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;同样的，对于有嵌套的循环，可以采用同样的办法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; results.size(); i++) {
    if (results[i] != NULL) {
        non_null_count++;
        if (results[i]-&amp;gt;name != &quot;&quot;) {
            cout &amp;lt;&amp;lt; &quot;Considering candidate...&quot; &amp;lt;&amp;lt; endl;
            ...
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;换一种写法，尽早return，在循环中就用continue：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; results.size(); i++) {
    if (results[i] == NULL) continue;
    non_null_count++;

    if (results[i]-&amp;gt;name == &quot;&quot;) continue;
    cout &amp;lt;&amp;lt; &quot;Considering candidate...&quot; &amp;lt;&amp;lt; endl;
    ... 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;拆分复杂表达式&lt;/h3&gt;

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

&lt;h4&gt;用变量&lt;/h4&gt;

&lt;p&gt;将复杂表达式拆分最简单的办法，就是增加一个变量：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if line.split(&#39;:&#39;)[0].strip() == &quot;root&quot;:

//用变量替换
username = line.split(&#39;:&#39;)[0].strip() 
if username == &quot;root&quot;:
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者这个例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;逻辑替换&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;1) not (a or b or c)   &lt;--&gt; (not a) and (not b) and (not c)&lt;/li&gt;
&lt;li&gt;2) not (a and b and c) &lt;--&gt; (not a) or (not b) or (not c)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;所以，就可以这样写：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (!(file_exists &amp;amp;&amp;amp; !is_protected)) Error(&quot;Sorry, could not read file.&quot;);

//替换
if (!file_exists || is_protected) Error(&quot;Sorry, could not read file.&quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;不要滥用逻辑表达式&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;assert((!(bucket = FindBucket(key))) || !bucket-&amp;gt;IsOccupied());
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样的代码完全可以用下面这个替换，虽然有两行，但是更易懂：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;bucket = FindBucket(key);
if (bucket != NULL) assert(!bucket-&amp;gt;IsOccupied());
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;像下面这样的表达式，最好也不要写，因为在有些语言中，x会被赋予第一个为&lt;code&gt;true&lt;/code&gt;的变量的值：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;x = a || b || c
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;拆解大表达式&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;var update_highlight = function (message_num) {
    if ($(&quot;#vote_value&quot; + message_num).html() === &quot;Up&quot;) {
        $(&quot;#thumbs_up&quot; + message_num).addClass(&quot;highlighted&quot;);
        $(&quot;#thumbs_down&quot; + message_num).removeClass(&quot;highlighted&quot;);
    } else if ($(&quot;#vote_value&quot; + message_num).html() === &quot;Down&quot;) {
        $(&quot;#thumbs_up&quot; + message_num).removeClass(&quot;highlighted&quot;);
        $(&quot;#thumbs_down&quot; + message_num).addClass(&quot;highlighted&quot;);
    } else {
        $(&quot;#thumbs_up&quot; + message_num).removeClass(&quot;highighted&quot;);
        $(&quot;#thumbs_down&quot; + message_num).removeClass(&quot;highlighted&quot;);
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里面有很多重复的语句，我们可以用变量还替换简化：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var update_highlight = function (message_num) {
    var thumbs_up = $(&quot;#thumbs_up&quot; + message_num);
    var thumbs_down = $(&quot;#thumbs_down&quot; + message_num);
    var vote_value = $(&quot;#vote_value&quot; + message_num).html();
    var hi = &quot;highlighted&quot;;

    if (vote_value === &quot;Up&quot;) {
        thumbs_up.addClass(hi);
        thumbs_down.removeClass(hi);
    } else if (vote_value === &quot;Down&quot;) {
        thumbs_up.removeClass(hi);
        thumbs_down.addClass(hi);
    } else {
        thumbs_up.removeClass(hi);
        thumbs_down.removeClass(hi);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;变量与可读性&lt;/h3&gt;

&lt;h4&gt;消除变量&lt;/h4&gt;

&lt;p&gt;前一节，讲到利用变量来拆解大表达式，这一节来讨论如何消除多余的变量。&lt;/p&gt;

&lt;h4&gt;没用的临时变量&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;now = datetime.datetime.now()
root_message.last_view_time = now
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的&lt;code&gt;now&lt;/code&gt;可以去掉，因为：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;并非用来拆分复杂的表达式&lt;/li&gt;
&lt;li&gt;也没有增加可读性，因为`datetime.datetime.now()`本就清晰&lt;/li&gt;
&lt;li&gt;只用了一次&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;所以完全可以写作：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;root_message.last_view_time = datetime.datetime.now()
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;消除条件控制变量&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;boolean done = false;
while (/* condition */ &amp;amp;&amp;amp; !done) {
    ...
    if (...) {
        done = true;
        continue; 
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的&lt;code&gt;done&lt;/code&gt;可以用别的方式更好的完成：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;while (/* condition */) {
    ...
    if (...) {
        break;
    } 
}
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h4&gt;减少变量的作用域&lt;/h4&gt;

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

&lt;pre&gt;&lt;code&gt;class LargeClass {
    string str_;
    void Method1() {
        str_ = ...;
        Method2();
    }
    void Method2() {
        // Uses str_
    }
    // Lots of other methods that don&#39;t use str_ 
    ... ;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的&lt;code&gt;str_&lt;/code&gt;的作用域有些大，完全可以换一种方式：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class LargeClass {
    void Method1() {
        string str = ...;
        Method2(str); 
    }
    void Method2(string str) {
        // Uses str
    }
    // Now other methods can&#39;t see str.
};
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h4&gt;不要使用嵌套的作用域&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# No use of example_value up to this point.
if request:
    for value in request.values:
    if value &amp;gt; 0:
        example_value = value 
        break

for logger in debug.loggers:
    logger.log(&quot;Example:&quot;, example_value)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个例子在运行时候会报&lt;code&gt;example_value is undefined&lt;/code&gt;的错，修改起来不算难：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;example_value = None
if request:
    for value in request.values:
        if value &amp;gt; 0: example_value = value 
        break

if example_value:
    for logger in debug.loggers:
    logger.log(&quot;Example:&quot;, example_value)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是参考前面的&lt;strong&gt;消除中间变量&lt;/strong&gt;准则，还有更好的办法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def LogExample(value):
    for logger in debug.loggers:
        logger.log(&quot;Example:&quot;, value)

    if request:
        for value in request.values:
            if value &amp;gt; 0:
                LogExample(value)  # deal with &#39;value&#39; immediately
                break
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;用到了再声明&lt;/h4&gt;

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

&lt;pre&gt;&lt;code&gt;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 &amp;lt;= MAX_SPAM_VOTES:
            filtered_replies.append(reply)

    return filtered_replies
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;读者一次处理变量太多，可以暂缓声明：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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 &amp;lt;= MAX_SPAM_VOTES:
            filtered_replies.append(reply)

    return filtered_replies
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;变量最好只写一次&lt;/h4&gt;

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

&lt;h4&gt;一个例子&lt;/h4&gt;

&lt;p&gt;假设有一个页面，如下，需要给第一个空的&lt;code&gt;input&lt;/code&gt;赋值：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;text&quot; id=&quot;input1&quot; value=&quot;Dustin&quot;&amp;gt;
&amp;lt;input type=&quot;text&quot; id=&quot;input2&quot; value=&quot;Trevor&quot;&amp;gt;
&amp;lt;input type=&quot;text&quot; id=&quot;input3&quot; value=&quot;&quot;&amp;gt;
&amp;lt;input type=&quot;text&quot; id=&quot;input4&quot; value=&quot;Melissa&quot;&amp;gt;
...
var setFirstEmptyInput = function (new_value) {
    var found = false;
    var i = 1;
    var elem = document.getElementById(&#39;input&#39; + i);
    while (elem !== null) {
        if (elem.value === &#39;&#39;) {
            found = true;
            break; 
        }
        i++;
        elem = document.getElementById(&#39;input&#39; + i);
    }
    if (found) elem.value = new_value;
    return elem;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码能工作，有三个变量，我们逐一去看如何优化，&lt;code&gt;found&lt;/code&gt;作为中间变量，完全可以消除：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var setFirstEmptyInput = function (new_value) {
    var i = 1;
    var elem = document.getElementById(&#39;input&#39; + i);
    while (elem !== null) {
        if (elem.value === &#39;&#39;) {
            elem.value = new_value;
            return elem;
        }
        i++;
        elem = document.getElementById(&#39;input&#39; + i);
    }
    return null;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再来看&lt;code&gt;elem&lt;/code&gt;变量，只用来做循环，调用了很多次，所以很难跟踪他的值，&lt;code&gt;i&lt;/code&gt;也可以用&lt;code&gt;for&lt;/code&gt;来修改：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var setFirstEmptyInput = function (new_value) {
    for (var i = 1; true; i++) {
        var elem = document.getElementById(&#39;input&#39; + i);
        if (elem === null)
            return null;  // Search Failed. No empty input found.
        if (elem.value === &#39;&#39;) {
            elem.value = new_value;
            return elem;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;重新组织你的代码&lt;/h2&gt;

&lt;h3&gt;分离不相关的子问题&lt;/h3&gt;

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

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


&lt;p&gt;来看一个例子：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ajax_post({
    url: &#39;http://example.com/submit&#39;,
    data: data,
    on_success: function (response_data) {
        var str = &quot;{\n&quot;;
        for (var key in response_data) {
            str += &quot;  &quot; + key + &quot; = &quot; + response_data[key] + &quot;\n&quot;;
        }
        alert(str + &quot;}&quot;);
        // Continue handling &#39;response_data&#39; ...
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码的目标是发送一个&lt;code&gt;ajax&lt;/code&gt;请求，所以其中字符串处理的部分就可以抽离出来：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var format_pretty = function (obj) {
    var str = &quot;{\n&quot;;
    for (var key in obj) {
        str += &quot;  &quot; + key + &quot; = &quot; + obj[key] + &quot;\n&quot;;
    }
    return str + &quot;}&quot;;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;意外收获&lt;/h4&gt;

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

&lt;pre&gt;&lt;code&gt;var format_pretty = function (obj, indent) {
    // Handle null, undefined, strings, and non-objects.
    if (obj === null) return &quot;null&quot;;
    if (obj === undefined) return &quot;undefined&quot;;
    if (typeof obj === &quot;string&quot;) return &#39;&quot;&#39; + obj + &#39;&quot;&#39;;
    if (typeof obj !== &quot;object&quot;) return String(obj);
    if (indent === undefined) indent = &quot;&quot;;

    // Handle (non-null) objects.

    var str = &quot;{\n&quot;;
    for (var key in obj) {
        str += indent + &quot;  &quot; + key + &quot; = &quot;;
        str += format_pretty(obj[key], indent + &quot; &quot;) + &quot;\n&quot;; }
    return str + indent + &quot;}&quot;;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个函数输出：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
    key1 = 1
    key2 = true
    key3 = undefined
    key4 = null
    key5 = {
        key5a = {
            key5a1 = &quot;hello world&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h4&gt;业务相关的函数&lt;/h4&gt;

&lt;p&gt;那些与目标不相关函数，抽离出来可以复用，与业务相关的也可以抽出来，保持代码的易读性，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;business = Business()
business.name = request.POST[&quot;name&quot;]

url_path_name = business.name.lower()
url_path_name = re.sub(r&quot;[&#39;\.]&quot;, &quot;&quot;, url_path_name) 
url_path_name = re.sub(r&quot;[^a-z0-9]+&quot;, &quot;-&quot;, url_path_name) 
url_path_name = url_path_name.strip(&quot;-&quot;)
business.url = &quot;/biz/&quot; + url_path_name

business.date_created = datetime.datetime.utcnow() 
business.save_to_database()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;抽离出来，就好看很多：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CHARS_TO_REMOVE = re.compile(r&quot;[&#39;\.&#39;]+&quot;)
CHARS_TO_DASH = re.compile(r&quot;[^a-z0-9]+&quot;)

def make_url_friendly(text):
    text = text.lower()
    text = CHARS_TO_REMOVE.sub(&#39;&#39;, text) 
    text = CHARS_TO_DASH.sub(&#39;-&#39;, text) 
    return text.strip(&quot;-&quot;)

business = Business()
business.name = request.POST[&quot;name&quot;]
business.url = &quot;/biz/&quot; + make_url_friendly(business.name) 
business.date_created = datetime.datetime.utcnow() 
business.save_to_database()
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;简化现有接口&lt;/h4&gt;

&lt;p&gt;我们来看一个读写cookie的函数：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var max_results;
var cookies = document.cookie.split(&#39;;&#39;);
for (var i = 0; i &amp;lt; cookies.length; i++) {
    var c = cookies[i];
    c = c.replace(/^[ ]+/, &#39;&#39;);  // remove leading spaces
    if (c.indexOf(&quot;max_results=&quot;) === 0)
        max_results = Number(c.substring(12, c.length));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段代码实在太丑了，理想的接口应该是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;set_cookie(name, value, days_to_expire);
delete_cookie(name);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于并不理想的接口，你永远可以用自己的函数做封装，让接口更好用。&lt;/p&gt;

&lt;h4&gt;按自己需要写接口&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;ser_info = { &quot;username&quot;: &quot;...&quot;, &quot;password&quot;: &quot;...&quot; }
user_str = json.dumps(user_info)
cipher = Cipher(&quot;aes_128_cbc&quot;, 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 = &quot;http://example.com/?user_info=&quot; + base64.urlsafe_b64encode(encrypted_bytes)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;虽然终极目的是拼接用户信息的字符，但是代码大部分做的事情是解析python的object，所以：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def url_safe_encrypt(obj):
    obj_str = json.dumps(obj)
    cipher = Cipher(&quot;aes_128_cbc&quot;, 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)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样在其他地方也可以调用：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;user_info = { &quot;username&quot;: &quot;...&quot;, &quot;password&quot;: &quot;...&quot; }
url = &quot;http://example.com/?user_info=&quot; + url_safe_encrypt(user_info)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;分离子函数是好习惯，但是也要适度，过度的分离成多个小函数，也会让查找变得困难。&lt;/p&gt;

&lt;h3&gt;单任务&lt;/h3&gt;

&lt;p&gt;代码应该是一次只完成一个任务&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var place = location_info[&quot;LocalityName&quot;];  // e.g. &quot;Santa Monica&quot;
if (!place) {
    place = location_info[&quot;SubAdministrativeAreaName&quot;];  // e.g. &quot;Los Angeles&quot;
}
if (!place) {
    place = location_info[&quot;AdministrativeAreaName&quot;];  // e.g. &quot;California&quot;
}
if (!place) {
    place = &quot;Middle-of-Nowhere&quot;;
}
if (location_info[&quot;CountryName&quot;]) {
    place += &quot;, &quot; + location_info[&quot;CountryName&quot;];  // e.g. &quot;USA&quot;
} else {
    place += &quot;, Planet Earth&quot;;
}

return place;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是一个用来拼地名的函数，有很多的条件判断，读起来非常吃力，有没有办法拆解任务呢？&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var town    = location_info[&quot;LocalityName&quot;];               // e.g. &quot;Santa Monica&quot;
var city    = location_info[&quot;SubAdministrativeAreaName&quot;];  // e.g. &quot;Los Angeles&quot;
var state   = location_info[&quot;AdministrativeAreaName&quot;];     // e.g. &quot;CA&quot;
var country = location_info[&quot;CountryName&quot;];                // e.g. &quot;USA&quot;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code&gt;// Start with the default, and keep overwriting with the most specific value. var second_half = &quot;Planet Earth&quot;;
if (country) {
    second_half = country; 
}
if (state &amp;amp;&amp;amp; country === &quot;USA&quot;) {
    second_half = state; 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再来解决前半部分：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var first_half = &quot;Middle-of-Nowhere&quot;;
if (state &amp;amp;&amp;amp; country !== &quot;USA&quot;) {
    first_half = state; 
}
if (city) {
    first_half = city;
}
if (town) {
    first_half = town; 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;大功告成：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;return first_half + &quot;, &quot; + second_half;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果注意到有&lt;code&gt;USA&lt;/code&gt;这个变量的判断的话，也可以这样写：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var first_half, second_half;
if (country === &quot;USA&quot;) {
    first_half = town || city || &quot;Middle-of-Nowhere&quot;;
    second_half = state || &quot;USA&quot;;
} else {
    first_half = town || city || state || &quot;Middle-of-Nowhere&quot;;
    second_half = country || &quot;Planet Earth&quot;;
}
return first_half + &quot;, &quot; + second_half;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;把想法转换成代码&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;用最平实的语言描述代码的目的，就像给读者讲述一样&lt;/li&gt;
&lt;li&gt;注意描述中关键的字词&lt;/li&gt;
&lt;li&gt;让你的代码符合你的描述&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;下面这段代码用来校验用户的权限：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$is_admin = is_admin_request();
if ($document) {
    if (!$is_admin &amp;amp;&amp;amp; ($document[&#39;username&#39;] != $_SESSION[&#39;username&#39;])) {
        return not_authorized();
    }
} else {
    if (!$is_admin) {
        return not_authorized();
    } 
}
// continue rendering the page ...
&lt;/code&gt;&lt;/pre&gt;

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

&lt;pre&gt;&lt;code&gt;有两种情况有权限：
1、你是管理员(admin)
2、你拥有这个文档
否则就没有权限
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;根据描述来写代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;if (is_admin_request()) {
    // authorized
} elseif ($document &amp;amp;&amp;amp; ($document[&#39;username&#39;] == $_SESSION[&#39;username&#39;])) {
    // authorized
} else {
    return not_authorized();
}
// continue rendering the page ...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;写更少的代码&lt;/h3&gt;

&lt;p&gt;最易懂的代码就是没有代码！&lt;/p&gt;

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


&lt;h2&gt;最后&lt;/h2&gt;

&lt;p&gt;还有一些与测试相关的章节，留给你自己去研读吧，再次推荐此书：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;英文版：&lt;a href=&quot;http://book.douban.com/subject/5442971/&quot; title=&quot;The Art Of Readable Code&quot;&gt;《The Art of Readable Code》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;中文版：&lt;a href=&quot;http://book.douban.com/subject/10797189/&quot; title=&quot;编写可读代码的艺术&quot;&gt;编写可读代码的艺术&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
   </entry>
   
   <entry>
     <title>40届全美音乐奖和55届格莱美颁奖演出观感</title>
     <link href="http://beiyuu.com/40-ama-55-grammy"/>
     <updated>2013-02-19T00:00:00-08:00</updated>
     <id>http://beiyuu.com/40-ama-55-grammy</id>
     <content type="html">&lt;h2&gt;40届全美音乐奖(Ameican Musican Awards)&lt;/h2&gt;

&lt;p&gt;1、少了英伦小清新的全美音乐奖，总体来说还是俗气了些；&lt;/p&gt;

&lt;p&gt;2、Justin Bieber的gay气太重了，歌我还是喜欢呀，准备练习一首，哈哈；&lt;/p&gt;

&lt;p&gt;3、Pink真是用生命在唱歌，并且是杂技团出身，脑补前年格莱美颁奖；&lt;/p&gt;

&lt;p&gt;4、我依然没有抓住Taylor Swift的点，女艺人里面喜欢的有很多，Katy Perry，Carrie Underwood、Nelly Furtado、Rihanna甚至Kelly Clarkson、Lady Gaga、Adele，但是依然不懂Taylor Swift凭什么呢？&lt;/p&gt;

&lt;p&gt;5、Carrie Underwood这样才是唱乡村的啊，斯威夫特姐姐，也终于想起来吴怡霈为什么看起来亲切眼熟了；&lt;/p&gt;

&lt;p&gt;6、第一次加入了电子乐，恩，电子乐牛逼，史旺达也牛逼；&lt;/p&gt;

&lt;p&gt;7、Nicki Minaj真是太有才太可爱啦；&lt;/p&gt;

&lt;p&gt;8、鸟叔果然是最棒的，Hi爆全场，期待格莱美上的演出；&lt;/p&gt;

&lt;p&gt;9、Galaxy Note II强势插入的广告，脑补两年前格莱美的iPad广告；&lt;/p&gt;

&lt;div style=&quot;text-align:center;margin-bottom:50px;&quot;&gt;
&lt;embed src=&quot;http://player.youku.com/player.php/sid/XNDc3MTQzMjg0/v.swf&quot; quality=&quot;high&quot; width=&quot;480&quot; height=&quot;400&quot; align=&quot;middle&quot; allowScriptAccess=&quot;sameDomain&quot; allowFullscreen=&quot;true&quot; type=&quot;application/x-shockwave-flash&quot;&gt;&lt;/embed&gt;
&lt;/div&gt;


&lt;h2&gt;55届格莱美(Grammy)颁奖&lt;/h2&gt;

&lt;p&gt;1、今年的格莱美，老将大都歇菜了，对新人感情积累不够，没法High起来，平淡的Happy一下吧；&lt;/p&gt;

&lt;p&gt;2、制作、演出水平还是一如既往的高，不用多说；&lt;/p&gt;

&lt;p&gt;3、Taylor Swift第一个出场，突然解了我一个疑惑，原来美国恐怖故事里那个神经质的年轻修女，长的是像她啊！&lt;/p&gt;

&lt;p&gt;4、Fun. 今年收获颇丰，歌很好听，耕耘这么多年，总算出头了，演出中下雨这舞台设计也还蛮意外的；&lt;/p&gt;

&lt;p&gt;5、查了资料原来Justin Timberlake从超级男孩就开始了，贾老板人气如此之高，大约还是演电影的因素，他的歌除了4年前和T.I.的合唱听起来比较动听，今年我是没感受到，胡子倒是刮得真干净真嫩；&lt;/p&gt;

&lt;p&gt;6、像Maroon 5的这些新歌，我觉得自己时常难以跟上时代的步伐，接受这些都是从我们这城乡结合部的发廊、呼啸而过的摩托、人群中刺耳的手机铃声来的，好奇他们如何发现的？&lt;/p&gt;

&lt;p&gt;7、Rihanna今年一甩骚情女的姿态，玩起了动情，stay这首确实挺打动人，一时间想起来很多；&lt;/p&gt;

&lt;p&gt;8、整场演出最High的部分就是Bruno Mars和Sting了，去年听了很多Reggae，看的最棒的现场也是Reggae，有很美好的回忆也烙上了Reggae，这让人感到快乐的音乐，任何时候都无法拒绝，真心感谢Bob Marley，感谢Marley家族带给世界这样美好的东西；&lt;/p&gt;

&lt;p&gt;9、Jack White以前没听过，Rock的范很足，要吐槽的是他手里那把烂Fender，质感比大G差太多，无怪乎演出结束要砸掉；&lt;/p&gt;

&lt;p&gt;10、Carrie Underwood这次就高端大气上档次了，没有Oklahoma的丁点气息了，当然，一如既往的喜欢这个姑娘，比Taylor好太多了；&lt;/p&gt;

&lt;p&gt;11、今年的“群口相声”改成LL Cool J了，没啥想说的；&lt;/p&gt;

&lt;p&gt;12、数数颁奖嘉宾，老爸老妈的Barney很搞；大爆炸的Penny倒是常来，可是姐姐你能不能有点专业精神，一层一层的下巴，减减肥好吗？2B Girls的Max，致辞毫无亮点，透露出胸大无脑的本性；Johnny Depp，嗯，吊；最碉堡还是Prince，看得出欧美音乐圈相当尊重前辈，我还有张他的打口碟，只是当时的水平无力欣赏，那些影响音乐人的音乐大师，总要一定积累去体会；&lt;/p&gt;

&lt;p&gt;13、一直疑惑的一点是，为什么唱歌好的男艺人大多都是小个子，从Bon Jovi到Alex Rose，从Bruno Mars到Fun.，数不胜数，为什么？&lt;/p&gt;

&lt;p&gt;14、每每看到艺人拿奖，他们也没煽什么情，我却总是替他们激动到不行，实在比国内的节目高级多了，哎；&lt;/p&gt;

&lt;p&gt;15、说起来格莱美这两年都比较low，你说没有Coldplay没有Usher没有Katy Perry没有Lady Gaga没有Justin Bieber没有Black Eyed Peas没有Eminem，连鸟叔都没有，你还让我怎么看，期待明年会更好吧！&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
&lt;embed src=&quot;http://player.youku.com/player.php/sid/XNTEzNzI1Njcy/v.swf&quot; quality=&quot;high&quot; width=&quot;480&quot; height=&quot;400&quot; align=&quot;middle&quot; allowScriptAccess=&quot;sameDomain&quot; allowFullscreen=&quot;true&quot; type=&quot;application/x-shockwave-flash&quot;&gt;&lt;/embed&gt;
&lt;/div&gt;



</content>
   </entry>
   
   <entry>
     <title>只有好的棋手才会走运</title>
     <link href="http://beiyuu.com/build-facebook"/>
     <updated>2013-01-21T00:00:00-08:00</updated>
     <id>http://beiyuu.com/build-facebook</id>
     <content type="html">&lt;p&gt;能和Facebook这样的公司一起成长太幸福，对作者真是羡慕嫉妒恨。&lt;/p&gt;

&lt;p&gt;Facebook快速并且顺利的成长，作者初期选择他，悬念并不大，不过在第一章谈到对公司的忠诚度倒是个不错的话题，树倒猢狲散、墙倒众人推，这就是现实，更何况赤裸裸的签订的打工合同，每个人都会做对自己最有利的选择，同样的，公司招聘也不是做慈善，所以忠诚在这里的定义是，在职期间尽心尽力做到自己该做的事情。&lt;/p&gt;

&lt;p&gt;对于初创互联网公司来说，招聘是重中之重，任何苛刻都不为过，臭味相投的一流人才的内部推荐，是作者首推的方法。最早听到Facebook的“Fire Fast”原则有些惊讶，招聘成本如此之高，还有这样严厉的作为，足见对人的重视。&lt;/p&gt;

&lt;p&gt;盛行工程师文化，并且对应聘者非常挑剔的公司，经理这个职位有时候会显得力量薄弱，所以书中讲团队管理章节的部分就比较没有新意，产品流程业界合格的公司大抵都如此罢。值得注意的一点就是Facebook的快，将迭代这个概念完好的贯彻执行，在Web时代，不完美不重要，错一点也不重要，慢了就很要命。当然，这需要高水平的“黑客”。&lt;/p&gt;

&lt;p&gt;其实整本书趋于平淡，尤其身处行业内、并且经常捕风捉影关注此类信息，不过，我不在其中亲身感受，很多观感自然也是架空的，也无法完整体会Facebook的优秀之处。&lt;/p&gt;

&lt;p&gt;最后一章，作者的投资理念很务实很合理，不过，窃以为这在互联网行业应该也是标配吧。&lt;/p&gt;

&lt;p&gt;当然，祝作者报效祖国成功！&lt;/p&gt;

&lt;p&gt;多废话一句：在我看来，豆瓣和Facebook有许多的相似之处，或许这只是我一厢情愿吧，哈哈。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;豆瓣链接：&lt;a href=&quot;http://book.douban.com/subject/20471120/&quot; title=&quot;打造Facebook&quot;&gt;《打造Facebook》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在线阅读：&lt;a href=&quot;http://read.douban.com/ebook/500486/?referral_code=myg7ot50&quot;&gt;《打造Facebook》&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</content>
   </entry>
   
   <entry>
     <title>令人焦虑的列表设计</title>
     <link href="http://beiyuu.com/bad-list-design"/>
     <updated>2013-01-17T00:00:00-08:00</updated>
     <id>http://beiyuu.com/bad-list-design</id>
     <content type="html">&lt;p&gt;用列表展现数据，再平常不过了，想想通讯录、微薄、播放列表、博客目录，都是。&lt;/p&gt;

&lt;p&gt;先来看看全宇宙最奇葩、但是自认为全世界最好用的软件iTunes的播放列表：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww4.sinaimg.cn/large/8b8af2c8jw1e0wkevnm8yj.jpg&quot; alt=&quot;idiot iTunes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;你能看出正在播放的是哪一首吗？我选择从蓝色条的《So What》开始播放，顺序播下去到当前首，鲜亮的蓝色条并没有动，动的是小喇叭，真瞧得起我的眼神！&lt;/p&gt;

&lt;p&gt;好了，一般人无法体会我对iTunes恨。&lt;/p&gt;

&lt;p&gt;虾米的播放列表设计有模仿iTunes的嫌疑，每次打开，我要瞅半天看到底播放到哪首了，今天再看，已经改成人类的逻辑了，虾米好样的！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww4.sinaimg.cn/large/8b8af2c8jw1e0wkn980w6j.jpg&quot; alt=&quot;xiami&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再来看下让我写这篇博客的起因：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/8b8af2c8jw1e0wkr4pg14j.jpg&quot; alt=&quot;jiandan&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这是&lt;a href=&quot;http://jandan.net/2013/01/17/benzs-museum.html&quot;&gt;煎蛋&lt;/a&gt;评论列表，你能一眼看去就明白那个OOXX的标记属于上一条还是下一条吗？真让人捉急。解决办法很简单，就把那些属于评论的Meta信息放在同一个维度就好了。&lt;/p&gt;

&lt;p&gt;看看正面教材&lt;a href=&quot;http://www.google.com/reader/&quot;&gt;Google Reader&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://ww1.sinaimg.cn/large/8b8af2c8jw1e0wkx96e78j.jpg&quot; alt=&quot;Google Reader&quot; /&gt;&lt;/p&gt;

&lt;p&gt;未读、已读一目了然，还感叹过为什么把未读扫过就一点不想再多看了呢，仅仅改变背景色和字体就轻松达到这个目标，这就是好的设计。&lt;/p&gt;

&lt;p&gt;哦，顺道吐槽一下傻逼的所谓欧洲时间格式，你能分清02/10/08和03/09/06里面的年月日吗？&lt;/p&gt;
</content>
   </entry>
   
 
</feed>
