设计师该了解的移动分辨率(2)

接上篇

实际沟通过程中,还是会遇到一些问题。首先是对像素密度(Pixel Density)的理解。

像素密度(例如72ppi)是屏幕的,图片没有这个属性

像素密度讲的是,一个屏幕上,一英寸有多少个像素。图片的长宽,只跟像素有关,即640×1134,是说宽有640px,高哟1134px。加入在普通的桌面屏幕上,像素密度为72ppi,那么图片的原图大小就是640/72个英寸长。如果在retina屏幕上使用,像素密度一般大于300ppi,那么就是640/300个英寸长。同一张图片,在两个屏幕上,显示出来的物理长度是不同的。

Photoshop中,新建图片的分辨率设置为300ppi真的好嘛?

我们看到,图片是没有这个属性的,就算我在PS中新建了300ppi的图,该是630×1134的像素数,还是这么多。因为本来计算机中的图片,就是记录每个像素值的颜色。那变的是什么呢?变的是字体的单位!PS默认字体单位为point,这个是印刷中的单位,表示这个字的物理长度。注意,是物理长度,而不是像素数。point的默认长度是1/72英尺,如果在72ppi的屏幕上,刚好是1px的长度。现在问题来了,如果你把图片的分辨率设置成了300ppi,那么PS就会“聪明”的识别出来,因为在300ppi中的物理长度变长了,因此,字也要相应的变大,同样是12pt的字,在300ppi中的字要使用更多的像素!

是不是被换算绕晕了?最后的结果是,设计师仍然以为pt和px是相同的,直接跟程序员说,这个字11个像素,程序员一写就瞎眼了。因为pt和px只有在72ppi下才相等。设计师改了ppi之后,这两个值就不相等了。

直接使用72ppi,即可!

那么设计师直接使用72ppi会不会有问题呢?没有问题。图片只认像素,屏幕才认ppi。如果设计师一定要使用其他ppi值,请在PS的首选项->标尺与单位中,将字体的单位选成像素pixel,这样告诉程序员那个字有多少个像素,也是可以的。

移动设计师该了解的Android分辨率

安卓手机千千万,屏幕尺寸也千千万。作为移动端的设计师,仅仅用PS或者AI画出视觉稿,明显是不够的,还应该提供不同屏幕的适配方案。

移动端适配和Web页面适配有相似之处,一般来说,纵向由于可以上下scroll,不需要特别严格的控制,但是横向一般都是确定的,对于不同的宽度,设计师心中应该很清楚自己的设计稿应该如何呈现。对于Web页面,为了适应不同宽度的展现效果,主要会使用Responsive Design的设计技巧。Responsive Deisgn的原理是,宽度变化的小时,通过减少空隙或者按照%来计算宽度,宽度变化太大时,就换一种layout。

安卓端的适配也类似,一般来说,都属于宽度变化较小的情况。但是现在大屏手机层出不穷,为了较好的大屏体验,有时可能也需要layout上的变化。

1. 这里就是100px,为什么程序员不能画出一样宽度的?

不同屏幕上,px和px是不一样大的!要说完全一样宽,那必须是100 Inch或者100 mm这样的单位,才可能表示不同屏幕上的完全对等宽度。可惜现实是,机器只认px。更加糟糕的是,安卓程序员只能使用dp单位。

2. 什么是高清屏

高清屏仅仅是分辨率高?1280*720?错。一个像素1px的大小越小屏幕越清晰。你可以理解每个像素点为一个矩形,越小越好。这里就有另外一个参数,就是像素密度,每英寸里有多少个像素,比如魅族MX2的像素密度就是343ppi,就是说每英寸有343个px。显然,像素密度越高,一个像素越小,屏幕越清晰。

3. 安卓程序员控制的dp单位和px如何换算?

其实这个不重要,仅仅是一些数学计算。重要的是,你可以认为每个dp在不同的设备上一样大

4. 不同手机的宽度有多少dp?

公式为,宽的像素数 / ( 像素密度 / 160 )
计算一下我接触到手机的宽度:

魅族MX2: 800 / (343 / 160) = 373.18 dp

小米2S: 720 / ( 342 / 160 ) = 336.84 dp

三星5S: 1080 / ( 432 / 160 ) = 400 dp

可以看到,三星的5S要比小米宽了约20%。如果直接用px像素数计算区别,恐怕程序员永远也实现不了你的设计稿。

5. 高度需要考虑嘛?

如果你的设计需要认真的考虑高度,其实是同理的,按照计算dp的方式,计算出手机的宽和高的dp,然后决定在不同长宽比的情况下,layout如何自动适应。

6. 如何告诉程序员尺寸?

最好是能直接说dp数是多少。

在PS中,设计师一般拖一个1280*720的canvas直接就画了,然后跟程序员说,这个是多少多少px。程序员抓了半天头发,想了半天,要么根据宽度,重新算一遍得到dp,要么就完全凭感觉吧。。

在实践中,一些设计师会以320ppi, 1280*720的屏幕参数作为基准,此时,一个2px = 1dp,这时设备宽度为360dp。这个仅仅是大致的估算,对于小米2s和MX2的适配可以差不多近似。

7. 更好的告诉程序员尺寸的方法

我个人认为,设计师无论画布有多大,在最终导出的标注图时,可以让标注图的px宽度值为一个实际手机的dp宽度值。例如,为小米2S的导出一张宽336px的图,为魅族MX2的导出一张宽为373px的图,看看两张图的效果。

不过,这样,导出的两张图中的细节尺寸不一样大呀?这时候,对于固定px宽度的元素,设计师就要以某个宽度为准,标注以后,另外一张图在实现的时候,细节尺寸也按这个来,中间的空隙可以自由伸缩。

程序员看到这样的标注图,也很轻松,因为直接认为1px=1dp了,不用换算,直接写就行。

ClassLoader内存溢出-实例分析

本文地址:http://nius.me/classloader-me…-leak-in-cases/

续上篇:ClassLoader内存溢出-从tomcat的reload说起

上一篇简要分析了内存溢出的原理,并提供了Mattias大神写的防御插件。Mattias大神还写了一篇非常手把手的教程Classloader leaks I – How to find classloader leaks with Eclipse Memory Analyser (MAT)教大家如何追踪ClassLoader内存溢出。

不过Mattias的插件也并不能适用所有的场合。比如我实验室遗留下来的java web项目(%>_<%)。只能自己上了!

原理回顾

回顾一下ClassLoader内存溢出的原理:

  1. web容器,一般会用WebAppClassLoader来加载其中一个app的所有类。
  2. 但是在启动web容器的时候,很多类是在其他ClassLoader(就是WebAppClassLoader被实例化之前)那加载的,比如jre的类,使用的一些库的类。注意,这里的加载,仅仅是加载类对象,例如加载了AppContext这个类,但是并不代表这个类已经有实例化的对象了。仅仅是一个Class对象存在。
  3. 等到WebAppClassLoader加载完需要的类之后,就开始启动web app,实例化servlet、spring等等这些东西,这时候,如果存在一个由其他ClassLoader加载的类,实例化了某一个对象,这个对象又保持了当前的WebAppClassLoader对象的引用,就造成了溢出。
  4. 因为在gc的时候,因为一些原因,例如单例模式,让其他ClassLoader加载的类的对象没有被回收,于是WebAppClassLoader就不会被回收。
  5. 进一步导致,由WebAppClassLoader加载的类对象无法回收,但是这些类实例化的对象可能会被回收。类对象包括这个类的静态成员,所以这些东西都进入了Perm区。

我们要做的,就是把捣蛋的对象找到,对症下药。

1. Proxool没有正确关闭

Proxool是众多数据库中连接池中口碑最好的一个。虽然很多人认为应该把连接池这种杂事交给web容器。在Spring中配置的Proxool一般如下:

<bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
        <property name="alias" value="proxoolDataSource" />
        <property name="driver" value="${connection.driver_class}" />
        <property name="driverUrl" value="${connection.url}" />
        <property name="user" value="${connection.username}" />
        <property name="password" value="${connection.password}" />
        <property name="houseKeepingSleepTime" value="${connection.housekeepingsleeptime}" />
        <property name="prototypeCount" value="${connection.prototypecount}" />
        <property name="maximumActiveTime" value="${connection.maximumactivetime}" />
        <property name="statistics" value="${proxool.statistics}" />
        <property name="simultaneousBuildThrottle" value="${proxool.simultaneous.build.throttle}" />
        <property name="maximumConnectionCount" value="${proxool.maximum.connection.count}" />
        <property name="minimumConnectionCount" value="${proxool.minimum.connection.count}" />
    </bean>

 

注意!这个bean没有destroy-method!连接池不会自动关闭!对于实验室的demo型项目,其实我也理解为啥毕业的师兄们都不管这些事。。但是,Proxool的DataSource类没有close方法。囧。中文社区有很多奇葩硬是在bean上加上destroy-method="close",这只能让spring报错,还是没有释放连接池。

于是,大杀器:写一个ServletContextListener

    public void contextDestroyed(ServletContextEvent event) {
        ProxoolFacade.shutdown(1000);
    }

 

然后在web.xml中注册这个listener

 <listener>
        <listener-class>me.nius.mlp.ProxoolShutdownListener</listener-class>
    </listener>

 

嗯。再trace一下,果然对WebAppClassLoader的引用少了Proxool,不过还有一大票其他的=。=

2. jdbc drivers没有取消注册

首先,要使用jdbc的驱动都会到java.sql.DriverManager那注册。当程序reload的时候,应该要deregister。当然tomcat会帮你做这件事,如果你自己没做,在reload的时候,会收到一个warn,说上一次没取消注册,为了防止memory leak,帮你deregister一下。

解决方案同上,写一个ServletContextListener,在contextDestroyed方法里做一下就好了。

3. mysql jdbc driver的AbandonedConnectionCleanupThread

解决了上面的问题,发现居然还有跟jdbc有关的引用。这个好像是mysql专有,不知道oracle或者其他数据库的驱动有没有类似问题。根源在于AbandonedConnectionCleanupThread。这个类看起来是用来清除一些没用的连接的,但是好心办了坏事,在reload的时候,这个对象不会被回收,而且保持了对WebAppClassLoader的引用!

同理,写个方法放到ServletContextListener里

 void shutdownCleanupThread(){
        try {
            AbandonedConnectionCleanupThread.shutdown();
        } catch (InterruptedException e) {
            logger.warn("SEVERE problem cleaning up: " + e.getMessage());
            e.printStackTrace();
        }
    }

 

这个似乎是mysql驱动的一个bug:http://bugs.mysql.com/bug.php?id=69526

4. sun.awt.AppContext

这个是最经典的。很多网上的case study都是讲的这个。这个AppContext是个单例模式,而且是jre的库,所以这个类早早就加载了。但是并没有用到。之后,程序中使用到了这个,调用AppContext.getInstance(),此时这个类保存了当前的ClassLoader的引用,就是WebAppClassLoader!

这个的解决方案跟前面的不同,前面都是关闭的时候把那些对象销毁,这里要在系统加载的时候,提前调用AppContext.getInstance(),而且要用root ClassLoader来干这件事。

     /**
     * Init AppContext using root ClassLoader to prevent memory leak
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        try {
             final ClassLoader active = Thread.currentThread().getContextClassLoader();
             try {
              //Find the root classloader
              ClassLoader root = active;
              while (root.getParent() != null) {
               root = root.getParent();
              }
              //Temporarily make the root class loader the active class loader
              Thread.currentThread().setContextClassLoader(root);
              //Force the AppContext singleton to be created and initialized
              sun.awt.AppContext.getAppContext();
             } finally {
              //restore the class loader
              Thread.currentThread().setContextClassLoader(active);   
             }
            } catch ( Throwable t) {
             //Carry on if we get an error
            }
    }

 

注意,这里并不是实现ServletContextListener的contextDestroyed方法,而是contextInitialized方法。

Maven jetty plugin 9.1.0的bug

解决了上面的问题,发现用jetty跑,还是没法销毁,而保存WebAppClassLoader的对象,正式Maven jetty plugin里面的对象!这时用tomcat跑已经正常了,找不到对其的引用了,虽然还在内存里(因为java 7回收比较懒)。

仔细看了一下,jetty插件注册了一个ApplicationShutdownHook,这玩意是用在jvm退出的时候干一些释放资源的活用的。这个Hook里有一个org.eclipse.jetty.util.thread.ShutdownThread,这玩意保存了所有的WebAppClassLoader的引用!这不是搞笑么!居然被我发现了一个bug!

更新了一下插件版本到9.2.0,唔,fix了,确实是bug。头一次领悟到一个google不到的bug,想想还有点小激动呢。

这样一来,实验室的项目总算服服帖帖的能够正常reload了,再也不用一次次重启tomcat了,内牛满面啊。

Tomcat/Jetty Memory Leak Prevention

ClassLoader内存溢出是一个古老的话题,厂商怎么可能不重视!诺:

tomcat MemoryLeakProtection
Jetty Howto Prevent Memory Leaks

web容器早就备好了各种武器供大家使用啦!实际上大家可能用了都没意识到,因为避免了,所以不会溢出了= =!

不过,还是会出现遇到容器没法解决的情况,这时候,就得自己上手了!就我个人的使用体验来看,MATVisualVM好用,再次推荐Mattias的手把手教程:Classloader leaks I – How to find classloader leaks with Eclipse Memory Analyser (MAT)

ClassLoader内存溢出-从tomcat的reload说起

原文链接:http://nius.me/classloader-memory-leak/

对于j2ee项目,一直使用eclipse的wtp,每次修改代码后,可以自动热部署。简单的项目wtp似乎没什么问题,但一旦项目代码稍微多一点,就很容易出现各种莫名其妙掉挂的现象,不得不整个重启tomcat服务器,这个时候就很痛苦了。

于是,我换用了maven的jetty插件,启动一个轻量级的jetty服务器,这下热部署似乎没那么多问题了!但是jetty似乎在热部署几次之后,也仍然会崩溃!这是什么情况!tomcat和jetty到底发生了什么?jetty的崩溃最常看见的异常就是OutOfMemoryException,Perm区的内存占满了

short version

不要慌!为了节省时间,先上解决方案:classloader-leak-prevention

  1. 在maven中添加如下配置:
<dependency>
  <groupId>se.jiderhamn</groupId>
  <artifactId>classloader-leak-prevention</artifactId>
  <version>1.9.3</version>
</dependency>
  1. 在web.xml__顶部__添加一个listener
<listener>
    <listener-class>
        se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor
    </listener-class>
</listener>
  1. 去西湖玩一天

long version

看看Perm区到底都是些啥

今天实在忍不了了,上Visual VM,监视一下Perm区的情况。

果然,reload几次之后,Perm的使用量蹭蹭的往上涨。看看Perm区都是啥

怎么这么多char[]呀,好像也看不出什么,还是dump一下吧。中间翻阅了一些博客,觉得应该是class loader的问题。oql查询一下。(这里因为是jetty,所以查询了jetty的WebAppClassLoader,如果使用tomcat或者其他容器,应该有对应的Loader,可以通过Saved Queries->PermGen Analysis->ClassLoader Types看一下有哪些ClassLoader)


上图是刚启动的时候,WebAppClassLoader只有一个实例。经过两次reload,嘿,问题来了。如下图:

居然出现了3个WebAppClassLoader实例,前两个Loader都没有销毁!哟呵呵,gc销不掉,一会就进Perm区了,然后多几次reload,Perm区再大也都撑满了。

发生了什么,gc不靠谱?

首先,我们回忆一下gc的运作过程。通过minor gc,清理eden区(eden generation)的没有被引用的对象,活下来的进入suvivor区,接下来的minor gc会让对象在两个suvivor里面倒腾倒腾,挺过几次的进入old区,这里面进行的就是full gc了,耗时长,如果old区还挺了好几次,就会进入Perm区。Perm里面发生的也是full gc.

一个普通对象,只要没有引用了,就一定会在某一次gc被回收。那么ClassLoader进入了Perm,说明在reload的时候,虽然程序取消了对Classloader的直接引用,但是仍然有其他对象间接的引用了ClassLoader。其实如果单纯的仅仅是ClassLoader一个对象,也就罢了,但是ClassLoader并不是一个普通的对象。

任何一个Java类,都是通过某个ClassLoader加载的。通过这个ClassLoader加载的类的实例,会保存这个类对象的引用,也就是MyClass.class这种。而类对象,会保留这个ClassLoader的引用。反过来,在ClassLoader中,也会保持对这个类对象的引用。(注意区分类对象MyClass.class,不是这个类的实例。好吧如果还是混淆了,我也不知道该怎么说清楚了)。关键在于,ClassLoader和类对象之间是双向引用。

双向引用有什么问题嘛?一般情况下没有问题。因为如果ClassLoader的外界引用,和具体类对象的外界引用都消失了,那么这两个对象都不可达了,都会被gc。但是在一些情况下,类对象可能不仅仅被这个类的实例保存,还可能被其他对象保存!如果这个对象是其他OtherClassLoader加载的呢?那意味着,如果这个对象不回收,那么其引用的类对象不会被回收,于是ClassLoader不会被回收,于是,所有ClassLoader加载的类对象都不会被回收!WebAppClassLoader会加载多少个类?如果你恰好使用的是Spring、Hibernate这种大家伙,嚯嚯。如果对此很感兴趣,这里有一篇写的很详细的:Anatomy of a PermGen Memory Leak

怎么解决

你并不知道什么时候会出现某个外部对象会引用到类对象。所以解决问题的思路是,换一个ClassLoader。一开始的解决方案classloader-leak-prevention就是依赖这个思路的。核心代码如下:

      // Switch to system classloader in before we load/call some JRE stuff that will cause 
      // the current classloader to be available for garbage collection
      Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());

      try {
        java.awt.Toolkit.getDefaultToolkit(); // Will start a Thread
      }
      catch (Throwable t) {
        error(t);
        warn("Consider adding -Djava.awt.headless=true to your JVM parameters");
      }

      java.security.Security.getProviders();
      
      java.sql.DriverManager.getDrivers(); // Load initial drivers using system classloader

      javax.imageio.ImageIO.getCacheDirectory(); // Will call sun.awt.AppContext.getAppContext()

让这一段代码运行在servlet初始化之前,在所有的listener之前。

Using git without a center repo

Sometimes we do not want to use a center repo for many reasons. And we still can use git to transfer our codes, with the help of git bundle

To package your local commits, you can use:

git bundle create changes.bundle -1 master

-1 means package the latest 1 commit, you can package latest n commits with this command. And this command is identical:

git bundle create changes.bundle master~1..master

After packaging, you can send the changes.bundle file by any means (say, email) to your partner. Your partner then can pull the commits just like the remote repo:

git pull /path/to/changes.bundle master

It will just behave the same as you pull from a remote git repo.

手工实现Js的Promise API

原文地址:http://nius.me/implementing-js-promise-api/

用node开发一阵子的同学,很容易就陷入了callback hell,一层层的嵌套回调,代码实在没法看。于是很多异步转同步工具库出现了。Javascript的harmony版本(ES 6)提供了异步转同步的标准工具:Promise。相最近仔细研读了html5rocks的这篇文章Javascript Promises。里面有很多Promise的例子。不过行文有点快,得慢慢理解。这里为很多还不是很明白Promise的魔法到底是怎样实现的同学,提供一个一步步设计、实现Promise API的讨论。

普通的回调

异步代码都是通过回调函数来实现的。一段ajax请求的代码可能如下:

var req = new XMLHttpRequest();
req.open(url);
req.onload = function(data){ /* callback */ };

//in jQuery
$.get(url, function(data){ /* callback */ });

出现一层层的回调的原因,就在与,在外围的回调函数中,会继续编写异步代码,绑定新的回调函数。例如:

req.onload =       //回调函数绑定
  function(data){      //回调函数的方法头
    var next = parseData(data);   //回调函数同步方法体
  
    anotherReq.open(next);        //回调函数异步方法体
    anotherReq.onload = function(nextData){  
         //... maybe more
    }
}

实现目标:then链

明确一下我们的目的,就不需要编写层层嵌套的回调函数。实现成readFileSync这种的API是不合适的,因为这种实现一定会等待异步代码返回结果,大大降低性能。我们要让代码看起来是同步的,所以,应该是这样的方式:

doSomething()
.then(doAnotherThing)
.then(doThirdThing);

即通过某种Executor,帮助我们实现自动嵌套绑定回调,让代码看起来是先后的,执行起来是异步的。

1. 代码解耦合

从上面我们看出,一段回调代码会包括三个部分

  1. 回调函数绑定
  2. 定义回调函数方法头
  3. 定义回调函数同步方法体
  4. 定义回调函数异步方法体,就是里面嵌套的深层次回调

我们要把函数绑定和函数定义分离解耦,这样才能把嵌套代码拆出来。

//basic version
var callback = function(){}
req.onload = callback;

//more flexible version
var callback = function(){}

var bind = funtion(callback){
  req.onload = callback;
};

bind(callback);

basic version是很常用的分离方法,但是这种方式很难想象出下一步我们该怎么做。变成more flexible version,大家一看就懂了。绑定过程也变成了定义的过程,延迟bind函数的调用,就可以在bind函数上做文章。上Promise!

//ATTETION: Pseudo code
var callback = function(){};

var promise = new Promise(function(callback){  //bind函数体
  req.open(); //async
  req.onload = callback;
})
promise.then(callback);    //调用bind函数

这段代码是不是很神奇了!(虽然还是看不出来怎么实现then.then.then)但是至少我们分离了回调函数绑定和定义,并延迟了绑定的发生。把一切都交给了promise对象,让promise对象来决定中间的过程怎么做!

2. 实现then链

我们要解决的问题是嵌套回调函数。就是说,callback内部也会有回调函数。怎么办?当然是,把内部的回调绑定和回调函数定义也分离!

//ATTETION: Pseudo code
var callback, innerCallback;

var promise = new Promise(function(callback){
  req.open(); //async
  req.onload = callback;
})

callback = function(){

   var innerPromise = new Promise(function(innerCallback){
     innerReq.open();  //async
     innerReq.onload = innerCallback;
   })
   innerPromise.then(innerCallback);
}

promise.then(callback);

是不是有点像了!我们只要把innerCallback传给内部的innerPromise的then函数就行了!而innerCallback完全可以在其他地方定义!

我们的目标是then链,但是现在,有两个Promise对象,第二个promise在回调函数执行的时候才被定义,和外面的promise不在一个次元啊。也就是说,要实现then链的话,回调函数们都被传给最外围的promise,而内层的innerPromise的then函数都没办法触发。

那就让他们到一个次元去!用callback的返回值把innerPromise传回去,别忘了,callback可是被传给了promise对象呀,所以promise完全可以拿到callback的返回值呀!这样,外围promise就有了内部promise的引用,就可以把第二个、第三个then函数接到的参数,依次告诉内层的promise!

//ATTETION: Pseudo code
var callback, innerCallback;

var promise = new Promise(function(callback){
  req.open(); //async
  req.onload = callback;
})

callback = function(){

   var innerPromise = new Promise(function(innerCallback){
     innerReq.open();  //async
     innerReq.onload = innerCallback;
   })
   return innerPromise;  //告诉外部的promise
}

promise.then(callback).then(innerCallback);

3. 魔法所在:then方法

是不是还是有点绕?这里magic的地方在于,promise对传入的callback会进行改造,再传给bind函数,为了更明确一点,我替换一下变量名:

//ATTETION: Pseudo code
var callback, innerCallback;

var promise = new Promise(function(resolve){  //注意,这里的resolve已经不是callback了!
  req.open(); //async
  req.onload = resolve;
})

callback = function(){

   var innerPromise = new Promise(function(innerResolve){ //这里也不是innerCallback了!
     innerReq.open();  //async
     innerReq.onload = innerResolve;
   })
   return innerPromise;  //告诉外部的promise
}

promise.then(callback).then(innerCallback);

是不是有点明白了?then方法对callback进行改造,例如,把callback外包一个resolve函数。来,自己动手实现一个naive版的Promise!

//ATTETION: Pseudo code, but you can run it.
Promise = function(bind){
  this.level = 0;  //
  this.current = 0;
  this.callbacks = [];
  
  this.bind = bind; //保存传入的bind函数
};

Promise.prototype.then = function(callback){

   var self = this;
   
   this.callbacks[this.current++] = callback;
   
   if(!self.isSolved){
     //从callbacks队列的头部取下第一个resolve,进行绑定
     var res = self.getResolve(this.nextCallback());
     self.bind(res);
     self.isSolved = true;
   }

   return self;   //返回自己,形成then链
};

/**
 * 将callback转换成resolve
 * 
 * @param  {Function} callback 
 * @return {Function} resolve           
 */
Promise.prototype.getResolve = function(callback){
    if(!callback) return;
    var self = this;

    return function(){
      console.log('resolve');

      var nextPromise = callback();   //获得返回的promise,注意callback调用没有参数
      if(nextPromise instanceof Promise){
      
        nextPromise.inherit(self);   //传递当前状态给nextPromise
        nextPromise.execute();   //执行下一个promise
      }
   };
};

Promise.prototype.nextCallback = function(){
   return this.callbacks[this.level++];
};

Promise.prototype.inherit = function(promise){
   this.level = promise.level;
   this.current = promise.current;
   this.callbacks = promise.callbacks;
   this.isSolved = promise.isSolved;
};

/**
 * 执行一个promise,从callbacks中获得一个callback,进行绑定
 */
Promise.prototype.execute = function(){
    var res = this.getResolve(this.nextCallback());
    var nextPromise = this.bind(res);
    if(!nextPromise) return;
    nextPromise.inherit(this);
    nextPromise.execute();
};

这里通过一个数组来记录所有通过链式调用传入的callback方法,然后通过inherit方法,把所有状态完完全全的交给nextPromise,从而让nextPromise继续绑定剩余的callback方法。

这个假想实现有一处硬伤,就是仅仅能让异步代码依次运行,但是异步代码之间没有通信!就是innerPromise得不到外围promise执行结束后的数据呀!es6的解决方案是,如果callback方法返回的不是一个promise,而是一个值,那么这个值将作为下一个callback的首个参数(唯一参数)。

真实的实现还要复杂很多,参考polyfill,这是一个能用现有js实现的Promise库。这个库要做到异步函数立即执行。上面的假实现仅仅做到在调用then时执行bind,实际上bind希望马上执行,毕竟有异步代码在里面,修改上面的代码,这个也是可以实现的。

总结

是不是已经想明白啦?Promise实际上充当了一个执行者的角色,动态的执行每一层次的异步函数的绑定,从而让代码可以看上去,一步步来,then and then and then…真是人类智慧的结晶啊~

数据库事务

学数据库的时候就会经常提到事务这个词,事务的ACID,事务的rollback,为什么需要事务。但以前写的项目中,很少重视这个问题(好像因为我很少写后台的缘故)。最近写高并发的东西需要事务体现的特别明显。 最简单的例子:

public void updateName(Long id, String name){
    User user = userDao.findById(id);
    user.setName(name);
    userDao.save(user);
}

public void updateGender(Long id, boolean male){
    User user = userDao.findById(id);
    user.setGender(male);
    userDao.save(user);
}

如果直接在方法中这么写,会出现并发问题。由于多个线程最终还是串行的(CPU是串行的),所以,这段代码的执行顺序可能如下:

thread1: User user = userDao.findById(id);  //   name: "Alis", gender: false
thread2: User user = userDao.findById(id);  //   name: "Alis", gender: false

thread1: user.setName("Bob"); // name: "Bob", gender: false
thread2: user.setGender(true); // name: "Alis", gender: true

thread1: userDao.save(user);   // name: "Bob", gender: false
thread2: userDao.save(user);   // name: "Alis", gender: true

//expected result in database: name: "Bob", gender: true;
//final result in database: name: "Alis", gender: true

可以看出,我们实际上想最终得到一个男Bob,结果得到了一个女Alis。当然,由于并发执行,并不能保证执行顺序,也有可能是一个女Bob,男Bob,完全凭运气。因为两个线程不曾互相通气,说哪个属性已经被改变了。

这个场景似乎在Web上很不容易想象出来,但仔细一想又会存在。例如,小明正在修改自己的个人信息,同时,由于连续使用3天,系统给小明的信息中更新了一下积分,如果积分和个人信息都放在profile表里,就很有可能发生并发,导致数据覆盖。虽然可能性很小,但一旦发生,结果往往是不可挽回的。

解决的办法很简单,事务。

JDBC就提供了事务

conn = DriverManager.getConnection();
conn.setAutoCommit(false);

//do queries...

conn.commit();

使用Hibernate,也提供了相应的接口

Transaction tx = session.beginTransaction();

// do queries...

tx.commit()

使用Spring的话,也提供了@Transactional注解,在需要事务的方法上加上注解就好了

@Transactional
public void updateName(){ 
     //...
}

想来Spring还着实做了不少简化开发的工作啊。

状态模式、面向对象与函数式

最近好友墙裂向我推荐Clojure,就仔细看了一下这门Lisp方言。《黑客与画家》中经常提到的一个概念就是Lisp等语言的比其他的编程语言在表达能力上更加强大。我对Clojure仅仅看了两天,不过Javascript倒是用了两年了,Js的设计中借鉴了很多函数式语言的特性,又同时借鉴了一下Java的语法,Java->Javascript->Lisp倒是形成了一个不错的学习曲线。

设计模式是OO编程中必不可少的一部分,但同时,很多人又批评这完全是由于OO语言自身的缺陷导致的。最著名的莫过于Peter Norvig在1996年的一份讲稿: Design Patterns in Dynamic Programming。这里指出23种模式中,有16种模式在Lisp是语言本来就有的特性或者实现起来更加简单的(16 of the 23 patterns in Design Patterns were “invisible or simpler” in Lisp)。先来看一个简单的模式,状态模式。

状态模式

我相信使用Java或者C++的童鞋对状态模式一定不陌生,简单易懂,经常用到。

状态模式

如上图。Context类有多个状态,有一个currentState属性,当调用Context.handle时,委托给currentState.handle()。通过实现State接口,可以定义多种状态,通过切换具体的object,可以实现状态切换。

是否需要这么多State类

状态模式的精髓在于,通过不同的State类来代表不同的状态。等等,真的是类嘛?实际上是State各个实现类实例化之后的对象。什么是一个对象?对象的概念在很多语言中都有,无论在哪,都可以看成是一个数据集合,同时具有多种方法,即对象是有行为的属性集合。在状态模式中,我们实际使用中,仅仅使用了一个object,并没有多次使用这个类(作为object的模板),即没有出现也不需要出现具有不同属性的object。那为什么不直接定义object呢?至少第一眼看上去,可以节省额外的Class开销。

这里我们回顾一下,面向对象编程三大特性:

  1. 封装
  2. 继承
  3. 多态

如果我们直接定义一个个object(或者function),放弃强类型,这三个特性会怎么样?

封装

这个不会有太大的改变,因为不同的object/function仅仅暴露方法就行了,甚至自己就是那个方法,内部运作仍然可以封装。

继承

如果使用object,将不需要一个State接口。接口的意义一方面是多态,即让currentState引用可以指向多个实现类的对象,但对于弱类型语言,这个问题不存在;另一方面是,在编码(其实是编译)阶段防止程序员犯错,State实现类必须实现handle接口,但如果直接定义的是handle function,这个问题也无关紧要。特别是,如果没有完善的测试用例,程序员仍然会犯错,有了完善的测试用例,这点防范措施实在没啥意义。

多态

仍然是多态的,通过切换object/function。

JavaScript实现

大概实现一下。

var context = {
        handle:null,
        init: function(){
            handle = initStateHandle;
        },
        switchState: function(){
            handle = otherStateHandle;
        }
    };

    var initStateHandle = function(){
        //do init state stuff
    }
    var otherStateHandle = function(){
        //do other stuff
    }
    
    //init
    context.init();
    context.handle();
    
    //change state
    context.switchState();
    context.handle();

很久之前我也实现过一个State Pattern in JavaScript,实现思路是类似的。这段代码也可以用Lisp实现,由于刚接触Lisp,就不献丑了。

可以看出,用Js实现的代码短,直接而自然,一看就明白,不需要很多类文件,也不需要费尽心思用继承、委托等等OO技术,仅仅关注好业务逻辑就可以了。

依赖注入

在状态模式中,我们的质疑点是,State的实现类的定义仅仅为了一个对象。Java确实有这么一个框架,帮你实例化一次某个类,然后在全局,任意的注入这个对象,就是Spring。如果把Class定义看做对象定义(仅仅多写几个单词),确实可以应对这个质疑点。但是看看额外的开销吧,要启动一个完整的Spring框架。

当然,依赖注入是一个十分先进的思想,即当我想要任何对象的时候,不需要自己去找,像神一样,说:“要有光”,就把光的对象给你了。十分火的前端框架Angularjs就使用了依赖注入的思想,在函数的参数列表里声明好需要的对象,框架就给你注入了,你完全不用管这个对象是哪来的。

回到Java

喷Java的程序员多了去了,写Ruby、Python、Lisp的程序员,鄙视Java由来已久。但即使这样,为什么Java还这么有市场呢?所有人都会反感Java的Hello world,因为Java一定需要定义一个类,产生了很多额外的代码。纯面向对象的特性,最直接的就是,理解所有的代码的思路是一样的,所有人写出来的代码,也都是一样的。所有的类,都定义在某个package中,有自己的namespace,要解决特定的问题,嗯,一定是找某一个类,并通过某种方式实例化对象。某种意义上,这反而让思维简单了,只需要通过一种单一的方式来理解代码。剩下的,就是用这种单一的方式,该如何处理各种复杂的问题,就出现了设计模式。

所以,Java程序员可以很容易的培训出来,一切都是严谨的,规范的,不可突破的,换句话说,没什么黑客精神的。黑客强调的个性、简单、自由,在这里都没有,带来的好处是,生产力的大大提高,Java程序员遍地都是,从手工艺者,变成了工人。

参考:

  1. Design Patterns in Dynamic Programming
  2. Head First Design Patterns

中华琳娜

《爱情买卖》是一首很接地气的歌,被称为“神曲”。大约是觉得太烂俗了,所以一般听惯了流行歌的人都比较不屑这首歌。这种音乐种类间的互相不买账也一直存在,学院派根本就觉得流行是个渣渣,高端得来美声、民歌。唱摇滚的说我就是要颠覆你们,一切现存的都是颠覆对象。

把学院派的民歌歌手龚琳娜放到一群唱通俗流行歌的选手中间PK,按照传统逻辑,嗯。

龚琳娜唱歌的思路很明显,就是用音乐表现一切可以表现的东西。《金箍棒》很多人说欣赏不了,其实这就是《金箍棒》表达的内容。孙悟空是个什么角色?大闹天宫,我行我素,本来就不是“正道”,你说用什么唱法去表现孙悟空?首先中国传统元素少不了,听起来得是个中国歌。孙悟空武艺高强,不把各路神仙放在眼里,所以歌曲一开始的“俺老孙”,唱的那叫一个飞扬跋扈,得意洋洋~这不是孙悟空是谁?后面的“呔哩个呔,呔哩个呔”,这不是孙猴子蹦来跳去,挥着金箍棒打来打去,大闹天空嘛!闹完了,孙悟空自封“齐天大圣”,气势磅礴,又有点孙悟空的嬉皮笑脸。

《金箍棒》一共就那么几个词,但一个捣蛋的孙悟空的形象已经完全在人脑子里形成了,完全不需要大段的歌词,听着听着,大伙就乐了。

相反,流行歌是严重依赖歌词的。你可以说流行音乐的旋律好听,但是不听歌词,你能知道流行歌在唱什么嘛?大部分流行歌做不到。

回到《爱情买卖》。这周全能星战,龚琳娜用来PK孙楠的歌曲,让所有人目瞪口呆。同一段旋律(主题),用民谣、流行节奏、爵士、吆喝、说唱、美声、花腔、戏曲、音乐剧、乡村摇滚,各种不同的唱法来表现,每种一小段。让人应接不暇。听完先是对龚琳娜佩服至极,学院派就是学院派,什么唱法都来一个,同时又感觉每种都没听爽,怎么一下就过去了呢。老锣解释,这是要表现现代社会,变化太快,就是要表现人们接受不过来的那种感觉。

你看,老锣编曲想表现的就是接受不了,很显然,他的目的达到了!但你让我说夸这首歌好听,这确实不如流行歌好听。

初中音乐老师在讲经典和流行的时候,说了一句话:“流行音乐就是要好听的。”这句话我当时十分不理解,这不是废话嘛?现在就觉得很好理解,流行乐除了好听,就没了。这种好听是用那些有限的几个大众能理解的和弦构成的好听,所谓“大众能够欣赏的音乐”,当然就是简单,熟悉,好听。所以流行乐要想创新是十分难的,因为创作的空间有限,你把歌剧里面大起大落的旋律放流行歌里,就没法传唱了,大部分人唱不了呀,那还怎么流行!?所以流行歌的创新确实是非常难的,发展也是很缓慢,大家都限制在“条条框框”里面,畏首畏尾,又不能烂俗,又不能难唱。

龚琳娜的歌,首首都难唱。只能听,不能唱。谁唱谁知道。全能星战里面翻唱了几首流行歌,也绝对把非常有特色、突破性的龚琳娜元素加入到里面了。第一首《明月几时有》,看把评委们吓的。网上评论非常好,为什么?龚琳娜一张嘴,苏轼的形象马上浮现出来,这才是音乐的表现力!龚琳娜一张嘴开始“啊”,很多人都听哭了。《小河淌水》也是,没有歌词的地方,反倒显得干净,表现力极强,这才是艺术,这才是音乐。

孙楠在这场比赛里说,艺术是有标准的。流行歌如果按照她的方式发展下去,就没法发展了。我觉得孙楠这两句说的都很对,但口气不对。首先艺术确实有标准,从艺术的标准看,龚琳娜的歌首首都是艺术。艺术本来就是要表现生活的,龚的《爱情买卖》,就是要表达这世界变化再快,商业化再发达,爱情还是不能买卖。这就是艺术。其次,流行歌确实不能按照龚琳娜的方式来发展。龚琳娜的歌大家都唱不了,怎么传唱、怎么流行,KTV怎么生存?这些话,其实在我看来恰恰是对龚琳娜极高的赞赏。

全能星战,本来就没有说是“流行歌”的舞台。龚琳娜也没有说要引领“流行歌”的发展。流行歌手听到龚琳娜,马上吓坏了,这样的歌以后变成流行歌,那我们还怎么混?当然变不成流行歌,龚琳娜和老锣本来的目标就是发扬中国传统音乐,至于音乐的具体表现形式,到现在为止好像还真没考虑过流行。这点孙大哥大可放心,真流行不起来,人家追求的艺术跟你的艺术就不是一种东西。

龚琳娜的“怪”,恰恰在于,龚琳娜唱的音乐,就从来不受拘束。根本就不是流行,不是一切所有已知的风格、形式、体裁。所以,大家只好称为“神曲”。因为没有办法形容。

依我看,不如龚琳娜的这种风格,就叫中华琳娜。洒脱不羁,自由浪漫,探索音乐中的无限可能。

龚琳娜 《爱情买卖》 http://www.bilibili.tv/video/av872088/
(B站上黄色的字幕把龚琳娜的音乐剧的故事性讲的很明白,欢迎欣赏)

苍南支教——音乐课

这次去支教的四个人当中,就我会识谱,会弹琴的一个没有,所以音乐课就全部交给我了,于是我除了4年级都教了一遍。小学的音乐说简单也简单,说难也难。说简单是因为让小朋友们听唱学起来很快,说难是要让他们安静的听你讲讲五线谱是怎么回事,简直太难了,根本听不懂,也不想听。课本上确实是用心选过的歌曲,中外的歌、曲、戏、剧都有,希望让小朋友们对音乐有一个完整的认识,免得以后只能欣赏流行歌。我小学的时候大概是没有意识到这一点,所以说给小朋友们,他们也不会明白。

课本里一般一节课会包含一些欣赏的曲目,一些是让小朋友们学唱的歌。欣赏的一般比学唱的复杂一些,但课本选的范唱也会对原曲进行节选、简化。三年级节选了圣桑《动物狂欢节》里面的《公鸡和母鸡》,确实很好听,我让小朋友们去听乐曲中的鸡叫声,咯咯哒!咯咯哒!有几个同学听出来了,但还是不买乐曲的帐,为什么不唱出来呢?我也回答不了,一句要学会欣赏啊!就糊弄过去了。6年级课本上有《图兰朵》,配合茉莉花教的。于是我就把图兰朵猜谜的故事跟他们讲了一遍,他们对猜谜特别感兴趣,就是猜完了,就把图兰朵忘了。当我说讲完国内各个版本的茉莉花,跟他们说,《图兰朵》可以算是外国版本的茉莉花时,小伙伴们都惊呆了。合着我前面铺垫了半天的图兰朵公主的故事都白说了。不说了,直接上张艺谋版本的《图兰朵》,大家听出这一段的庄严大气的茉莉花的旋律没有?小伙伴们说,舞台上的人好像僵尸啊,好恐怖啊。

低年级教的基本是儿歌,例如《小红帽》,二年级小朋友们都特别喜欢这首歌,我也很喜欢,多好听呀。高年级会有《茉莉花》这种绕来绕去的歌。孩子们的听谱能力还是可以的,先上一遍网上下好的范唱,然后我再一句句的示范唱一遍,他们跟唱一遍,基本就记住了。

中国人的乐感真是没的说。但是乐感好,就会让小朋友们自己改谱子啊囧。教5年级《雏鹰之歌》,中间一段几个音都是do,大概是do do do do 的样子,楞是被唱成了do do re do,我纠正了好几遍。孩子们才不管谱子呢,虽然他们都很想唱对,但估计对他们来说那里感觉就不是do啊,于是嘴里出来的还是不对,于是我就放弃了。嗯,不要在意细节。孩子们对节奏的把握也很好。虽然我在说了半天谱子上的浮点是怎么回事,他们还是一脸不明觉厉的看着我。但是我范唱一遍之后,大家就都按照正确的节奏来唱了,甚至大家一起唱的时候,我都忘了浮点,孩子们唱的还都是对的。孩子们的音域也挺不错,毕竟是童声,《茉莉花》最后一句饶来绕去的高音,6年级的小朋友完全hold住啊,虽然大家唱的乱七八糟的,但确实每个音都唱出来了。

找到课本上的伴奏、范唱是一件很不容易的事。理论上应该教师用书会自带光盘,但我其实上课都是提前去教室里随手拿一本学生用书。所以,得去网上下。好容易找到一个有对应歌曲的网站,还收费,幸好不贵,十块钱基本随便下了。另外,课本确实想与时俱进,于是弄了一些电视剧、电影的主题曲进去,结构都是孩子妈妈们、奶奶们小时候看的电影了,跟看着《爸爸去哪儿》长大的孩子们完全脱节啊。孩子们根本没看过这些电影,所以也没法与剧情、歌曲产生共鸣。不过要做到课本的与时俱进,确实也是一件难事。一年级时听一首什么歌来着,我一搜是一部5几年还是6几年的电影,歌是好听,可孩子们问,这是什么电影呀?我只好说,是一部老片,看起来不错。

说到与时俱进,我们一开始以为小朋友们都喜欢听流行歌,不喜欢课本上的歌。一教才发现,他们哪懂什么教流行歌曲呀,还是喜欢课本上的歌。我也喜欢,课本上的歌其实都挺好听的,而且适合小孩唱。估计只有初中二年级才会对课本上的歌嗤之以鼻,被流行歌蒙住双眼,当年我就是这样的,当时问来教务处玩的四年级小朋友,你们喜欢听什么歌呀?《外婆的澎湖湾》!于是我就惊呆了!这不是他们爸妈小时候的歌么?后来才发现是课本上教过的。你看,好听的歌不管到什么时候,都好听。