帐前卒专栏

code, software architect, articles and novels.
代码,软件架构,博客和小说

是几月份去参加的来着?我已经忘记了。
我记得GPU就是用来做图形处理的。做着做着就变成并行计算了。虽然单线程的运算能力不及CPU,但是并发能力却胜CPU.

对了这次大会没有看到Intel的人,唉,都没有人来砸场子。

在下面参观了VR技术,现在做的还很有限。必须使用三点定位,也就是应该有有两个定位器在两个角上。然后再用头盔进行三点定位。手柄的感应做的也很好。以及头盔中视频
做的也不错。

以后的发展方向是VR定位技术,VR头盔的太重。未来可能是眼镜而不是头盔,当然也可能是裸眼3D效果。未来手柄意义可能不大,可能就是一个架子用来对应虚拟化场景的

VR这个的发展还是很迅猛,应该是未来发展的方向。不过未来还可以做的更好。因为现在场景基本上都是定点的。如果人移动起来可能仍然有各种问题。因为三点静止定位难度
系数还是比较低的。所以现在大部分游戏场景就是定在一个地点。不会让你到处走动。不过我相信未来应该有大的发展。

超算或者并行计算最主要的问题在于程序能否并行化。以及并行化后能否有效的减少计算时间。这两个问题一个是涉及到基本的程序流程,一个是代价成本问题。一个运行时间不
长,并且使用次数不多的程序,将其修改为并行化的程序的代价可能远远超过了之前运行的总时间。所以事情总是要权衡考虑。

超算对于计算量大的程序并不是万能灵药。需要你的程序适配超算才能真正的发挥效果。我曾见过某些程序运行在普通PC上效率是超算的数倍。因为超算重要的是核多,而不是
主频高。

GPU vs CPU, 超算 vs 普通PC, 这个类比就是外包公司 vs 公司里自己养的开发团队.是否好用,是否成本低。

今天下午被朋友圈刷屏了。大量的月饼。

月饼、月饼、月饼…

这事情是阿里内部的事情。开除谁,留谁都是阿里的决定。

因为那几个开除抢月饼的是因为阿里的价值观。
所以我仔细研究了一下阿里的价值观。发现一个重大问题: ** 根本与公司的价值没有半毛钱的关系 ** 。

也就是说: ** 即使没有给阿里赚到钱,即使给阿里抹黑,即使损害了阿里的口碑和资产,但是符合了那几个核心价值观,仍然被认为是阿里好员工。 **

这套体系真tm有趣。

如果你不信的话,那几个直接导致“月饼门”爆发的人。包括阿里的HR、第一个爆出“月饼门”的人、将截图发给报社记者的人、阿里的公关们,怎么一个都没有被开除呢?

你们知道吗?“月饼门”直接导致互联网程序员的集体吐槽。甚至有人表示要劝阻想去阿里的程序员。这对阿里是多大的损害?截个图:

这里写图片描述

作为管理者,是不是应该反思一下HR和IT民工们这种相互掐架以及其间的政治斗争。如果真要开除的话,要么全开除;要么全部留下;要么只把HR开除了。否则 **
我实在无法想象一家只有HR的IT公司是怎样的公司 ** 。

** 我倒是鼓励HR们去阿里的。技术人员你们还是请三思吧。 **

另外国内外的安全问题非常严峻。阿里这种公司内部系统如此容易就被攻克。我们有什么理由轻易相信这家公司对外开放的产品呢?

另外我们再深入的考虑一下阿里到底出了什么问题。

  1. 月饼为什么会有剩余?是HR没有统计好人数。还是市场部乱采购?还是本身规划好的活动?
  2. 月饼真的是一人一份吗?是否有剩余月饼先被HR、高层管理者、市场采购者先拿走一部分?
  3. 谁负责的月饼秒杀活动?为什么没有考虑到脚本抢购?
  4. 谁开发的月饼秒杀,为什么不和淘宝的秒杀一样?不会自动跳转到付款页面?怎么不复用代码?
  5. 秒杀活动有验证码,为什么使用的是有安全隐患的验证码?
  6. 阿里内部系统没有验证码组件吗 ?
  7. 月饼秒杀活动到底有没有猫腻。强烈要求公开源代码包括但不限于数据库的各种配置。
  8. 强烈要求公开存储在数据库中所有月饼购买者的信息。看是否有人购买多次但是未付款。
  9. 是否有人收购或者倒卖月饼?是否有人要求周围同事帮忙抢购?这些人都不符合阿里的价值观。请查证。

所以阿里的管理者们你们没有任何反思吗?阿里作为一家活了这么多年的公司,秒杀系统应该是常用的、验证码应该是常用的。但是代码却未能复用。这说明了什么 ?

我抱着唯恐天下不乱的心态看这个“月饼门”的问题。写篇blog真是痛快。

我发现了漏洞,我会抢月饼吗?

不会。

因为我不喜欢吃月饼。

不过我可能会把如何使用漏洞写到blog里面,让其他人去抢。

如果不知道“月饼门”是什么。下面是“月饼门”的知乎介绍:
https://www.zhihu.com/question/50600301

首先呢,上台演讲的人均认为容器不是虚拟化,两者不等价。

嗯,是呀。如果等价的话,这应该叫虚拟化技术大会了。

演讲者均认为这两者的区别在于:容器是应用的包裹。虚拟化是操作系统的包裹。

这就是为什么我收快递总是有那么多层包裹。最后里面的玻璃杯还是tm的碎了。原因?原因可能是快递小哥扔了一下。所以需要商家再增加更多的包裹。

容器既然是应用的包裹。那么理所当然想要管理应用的生命周期。想要监控应用的状态。想要更好的管理和调度应用本身。

你们有没有先问问应用想不想被包裹呀…

容器,你可以理解为应用运行的环境。现在这个环境可以非常容易的迁移。这种迁移不局限于其物理位置。应用可以在一个机器中的相同磁盘的不同扇区之间移动,后来可以在不
同磁盘之间移动,再后来可以在同一集群的不同机器之间移动,再后来在不同的机房之间移动。

可移植是软件的一个属性。现在因为容器,这个属性成为普适属性。这当然是件好事。

因为有了容器。假设我们认为容器和之上的应用并没有状态。或者是初始状态即可。那么我们就获取到软件的可扩展性。容器和之上的软件可以不断的复制以适应规模的需求。

这种假设很理想。实际上这并不是软件研发过程中非常重视的事情。非常重视的事情是你开发出来的软件能不能分布式部署。是否可以通过多部署来线性的增强软件急需的能力。

显然容器的开发者们寄希望于应用的开发者都可以胜任这项事情。呃……借用80/20原则的话,就是80%的开发者都不能胜任这件事情。另外20%的开发者不会在8
0%的业务需求中考虑这件事情。

以上的观点可能会被80%的开发者认同或者反对。

软件行业的未来会像汽车制造厂一样。流水线的装配各种零件,然后制造出来一款成熟的软件。

据我所知,任何一款成熟的软件只要通过ctrl+c , ctrl+v 就可以制造出来另一款成熟的软件。呃……现在流行的方法可能是使用git/fork.
我有了车子,就能制造车子。何必要流水线呢?

其实我很想要一款6个轮子的车子。但是万恶的黑心厂商只造4个轮子的。只有军方才有6个轮子的车子用。未来,我的软件中就不能多加一个功能吗?就不能修改一个流程
吗?就不能不用UDP/TCP吗? 更可笑的在于,我的车子就不能随便换个大奔的logo吗?

容器可以让运维更加轻松。效率提高

这我举双手赞同。当然运维需要先熟悉一下各个操作系统的命令,再熟悉一下各个PAAS的组件,以及容器相关的命令。另外如果容器系统/PAAS/OS挂掉了,需要
知道到底哪些组件出了问题,以及如何恢复。

我们从小学到现在一直学习那么多繁杂的知识,不就是为了让自己过的轻松一些吗?咳咳……来,再让我翻一翻小学奥数题虐一虐面试者~

让我们一键式的部署来管理应用吧~

在这之前先多键式把配置搞定。…嗯…编译出了问题…不能翻墙…先搭建一个vpn…gcc版本好像太低了…嗯…再配个内部域名吧…等等…权限还要设置一下…ldap
怎么设来着?…还需要copy一些私钥…嗯…下班了…明天再弄吧…

总结总结:

下面是十万个为什么时间:

为什么开发者总是为难开发者?
为什么开发者总是为难非开发者?
为什么非开发者总是为难开发者?

再问一个简单的问题:
为什么容器技术不能成为自容器?和容器中的应用一样发布、部署?

该问题的简单答案:
太累了

不,我并不是觉得容器技术不好。也不是觉得现在的运维方案完全够用。我是觉得容器技术还应该向我指出的方向发展一下。

想想应该取个好点的标题。

再加一个图片吧。
这里写图片描述

再修改一下标题…z

很多人问我,这个功能需要给用户发送一封注册/激活邮件,这封邮件不要发送到用户的垃圾邮件中。

简单的说:
不能!!
不能!!
不能!!

重要事情说三遍。
复杂的说:

一封邮件在某个时刻发送到某个用户,可能不是垃圾邮件。但绝对不能保证这封邮件在未来的某个时刻发送给另外一个用户不会变成垃圾邮件。

很多人问我,这个功能发送的邮件为什么用户没有看到。

简单的说:

这个功能发送的邮件被用户邮箱屏蔽了。

复杂的说:

  1. 检查一下用户的邮箱是否正确
  1. 邮件中是否有高频垃圾词汇:例如发票,彩票,中奖,邀请
  1. 是否文字太少,图片太多
  1. 是否email中没有实质内容
  1. 是否email中只有超链接地址。
  1. 是否携带了一些可执行程序的附件
  1. 邮件内容是否过大。附件是否过大
  1. 发送方的邮件是否通过认证的邮件服务器发送
  1. 发送方的email是否已经被对方列为垃圾邮件
  1. 发送方的email中的内容是否曾被对方列为垃圾邮件
  1. 发送方的邮件服务器ip是否被加入到对方邮件的黑名单

邮件的投递机制:

应用程序 =>  本地邮件服务器网关  => 目标邮件服务器网关

假如应用程序正常投递了本地邮件服务器。不代表这邮件能从本地邮件服务器发给目标服务器,也不代表本地邮件服务器能让目标服务器接收,更不代表目标服务器能把这封邮件
展现到用户的收件箱中。每每遇到这种链式的请求感觉都是头大。

有机制降低成为垃圾邮件的几率吗?
简单说:

非垃圾内容,不套用同一个模板内容,低频率发送可以降低垃圾邮件的几率。

复杂点说:

1. 要突破反垃圾邮件规则。首先你得是非垃圾邮件。或者是还未曾出现的垃圾邮件形式。所以要创新。要不断突破,所以不要使用同一套模板内容。
2. 将本地邮件服务器的反垃圾邮件规则禁用掉。这样可以保证从你本地邮件服务的发送率是100%。
但是如果你的本地邮件服务器被列入垃圾邮件黑名单。那这个ip发出去大概率会变成垃圾邮件了。
3. 一般来说如果发送email和接收email 在同一个域名下。例如 发送者是 [email protected]  接收者是 [email protected] 那么一般都不会被邮件
服务器直接屏蔽掉。最坏的情况也就是放到垃圾邮件箱中。所以如果可以,尽量和接收者使用同一个邮件服务器。这会带来开发的难度,也无法解决企业邮箱和私人域邮箱的问题

总之,发送邮件的到达率一定不是100%。所以只要能正常收到一封邮件,就算该功能正常。就不要再纠结:为什么有的人没有正常的收到邮件?

最近写了一个简单的定制解析。

fastjson 解析 Map key value时有bug.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public static class KVPair<K,V> {
private K key;
private V value;
public KVPair(){}
public KVPair(K key, V value){
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {

return value;
}
public void setValue(V value) {
this.value = value;
}

}

这是一个简单的 KeyValue 类。这个类被fastjson解析出来的结果是:

1
2
3
4
5

public static void main(String[] args) {
KVPair<String, String> kv = new KVPair<String, String>("aaa", "bbb");
System.out.println(JSON.toJSONString(kv));
}
1
2
3

输出结果为:
{"key":"aaa","value":"bbb"}

在fastjson 1.1.23 版本时, 只要改动一下 setValue() 这个方法,就可以得到这样的解析结果:

1
2
3
4
5
6
7
8
9
10
11

改动后的 setValue(V value) 方法为:

public V setValue(V value) {
this.value = value;
return this.value;
}

输出结果为:
{"aaa":"bbb"}

这明显是 fastjson的bug。所以在1.1.46版本,已经不存在这个问题了。不管如何变化 setValue()函数,最后的输出结果仍然为:

1
2
3

输出结果:
{"key":"aaa","value":"bbb"}

现在问题来了。我更希望解析为下面的这种。 ```

{"aaa":"bbb"}
1
2
3
4

这样占用的资源更少,并且也很清楚的表达了意思。这怎么写?fastjson提供的specialConfig 来提供特殊的序列化配置。
首先我们先对这个class创建一个特殊的序列化方法:

public static final SerializeConfig JSON_WRITE_CONFIG = new SerializeConfig();
    static {
        JSON_WRITE_CONFIG.put(KVPair.class, new KVPairSerailzer());
    }
1
2
3

然后我们来构造这个特殊的序列化类:

public class KVPairSerailzer implements ObjectSerializer{

    @SuppressWarnings("rawtypes")
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType) throws IOException {
        
        SerializeWriter out = serializer.getWriter();
        if (object ==null) {
            return ;
        }
        KVPair pair = (KVPair)object; 
        out.write("{");
        write(out,pair.getKey());
        out.write(":");
        write(out,pair.getValue());
        out.write("}");
    }
    
    public void write(SerializeWriter out, Object value) {
        if (value == null) {
            return;
        } else if (value instanceof Integer) {
            out.writeInt((Integer)value);
        } else if (value instanceof Long) {
            out.writeLong((Long)value);
        } else if (value instanceof String) {
            out.write("\"");
            out.write((String)value);
            out.write("\"");
        } else if (value instanceof Double) {
            out.write(Double.toString((Double)value));
        } else if (value instanceof Float) {
            out.write(Float.toString((Float)value));
        } else {
            throw new RuntimeException("not support value:"+value);
        }
    }

}
1
2
3
4
5

fastjson的逻辑是: 开始要传入一个序列化配置。这个序列化配置中有针对各个类如何进行序列化的子类(也就是映射关系)。如果我们将JSON_WRITE_C
ONFIG传入到序列化过程中,那么针对 KVPair.class这个类,就可以使用KVPairSerialzer()进行序列化。KVPairSerialze
r在这个类需要继承ObjectSerializer, 重要的是重构这个方法:

public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType)
1
2
3
4
5
6
7

这里只要获取 serializer .getWriter(),然后将序列化后的字符串写入到writer中就结束了。SerializerWriter
其实就是一个可变长char数组,细节没有什么好讲的。

另外 1.1.46 之前的版本如果不希望将某些field不放在序列化后的json中,需要加入一个
实现了PropertyPreFilter的类才行。例如下面这个类:

public class FieldJsonIgnoreFilter implements PropertyPreFilter  {
    private Set<String> excludes;

    @Override
    public boolean apply(JSONSerializer serializer, Object object, String name) {
        if(object instanceof ComputerCluster) {
            return !excludes.contains(name);
        }
        return true;
    }
    public FieldJsonIgnoreFilter() {
        this.excludes = Sets.newHashSet();
        
    }
    public void add(String excludeFieldName) {
        excludes.add(excludeFieldName);
    }
    
    public static final FieldJsonIgnoreFilter FIELD_JSON_IGNORE_FILTER = new FieldJsonIgnoreFilter();
    static {
        FIELD_JSON_IGNORE_FILTER.add("aaa");  // 如果fieldName 为 aaa 则不进行序列化。
        FIELD_JSON_IGNORE_FILTER.add("bbb");
        FIELD_JSON_IGNORE_FILTER.add("ccc");
        
    }
}
1
2
3

1.1.46版本之后只需要在在field上面加入Annotation.就可以实现上面的效果。

@JSONField(serialize=false)
private String aaa;

发现内存泄露除了仔细看代码的确没有太好的方法。首先看gc log, 确定是内存泄露,而不是内存不够。内存泄露的特点就是以每次Full
GC后使用的最低内存为起点,拟合一条线。如果这条线是随时间递增的一条曲线,那么很大程度上代表着内存泄露。

然后使用 jmap -histo [pid] 来查看你的所有对象所占内存的比例。你可能很不幸的发现[B 这个byte数组对象占用了绝大多数。这的确没有更好的
方法了。只能一点点的看代码。检查一下有没有写成循环的地方。检查一下有没有申请的内存没有释放。检查一下全局变量或者单例中的map啥的。最后,你大概只能以怀疑一
切的态度检查所有的代码。

ok. 下面以八卦的方式讲讲我这次遇到的内存泄露(memory leak).

这几天一直在写一个入库组件。这个组件的目的就是解析传输过来的数据,并写入到数据库中。嗯。听起来很简单。但是解析格式比较复杂,而且还要使用某种特定的计算公式进
行去重处理。另外解析的数据需要以上千条记录的形式输入到数据库中。嗯,为什么会这样设计呢?因为历史遗留问题。。。嗯历史遗留问题这几个字非常管用。不管放在什么语
境中,反正觉得困难就可以说历史遗留问题。其实是为了小步快跑,慢慢申请时间。上头大概不会有人希望你这模块做大半年还没有做来。希望你这么做的,大概都是你的死敌。

好吧上面就历史背景。在这个背景下。我这边入库组件,写了一个分布式的,多线程解析,多线程批量入库的代码。本来觉得这种设计挺好。但是因为gap lock,
多线程和分布式的问题,批量写总是在不知不觉中造成死锁。为了代码的精简,为了不引入更多的问题,索性将批量写改为了单条写。

好了,这下memory leak问题来了。难道是因为之前都死锁了,所以memory leak 就没有暴露出来? 有可能哦~

先看一下gc log。为啥是memory leak。看一下下图。其中蓝线的是内存使用情况。蓝线的最低点是每次Full GC
带来的内存大量释放。GC的最低点你可以看到是不断增加的。所以很大的可能是内存泄露。

![](http://img.blog.csdn.net/20160126214648953?watermark/2/text/aHR0cDovL2Jsb2
cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravit
y/Center)
为什么说很大可能是内存泄露呢?不同的程序有不同的内存使用模式。比如说我的程序中有一个大map.
这个map会不断的填充数据。但是数据集是有限的。但是这个map在最终填充完毕前,内存的使用量会不断的增长。如果内存不够了,仍然会有outofmemory 错
误,并且那个gc的图和我给的gc图会很像。这不代表这内存泄露,这只能说内存不够用。但是这需要先证明数据集是有限的,并且系统空闲内存可以完全放入这个数据集。否
则你只能采取其他方法来防范内存不够用的情况。

好吧。我看到这个图。感觉是内存泄露。为啥?因为我的数据集有限,并且粗略算了一下也不大。即使有缓存计算的策略存在,这些空间仍然不会造成outofMemory的
现象。那到底是什么原因?先使用一下jmap -histo [pid]. 看了一下比例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

num #instances #bytes class name
----------------------------------------------
1: 21571308 1163654064 [B
2: 1770275 125384008 [I
3: 1715985 120562976 [[B
4: 1715382 120535928 [Ljava.io.InputStream;
5: 3430930 109789592 [Z
6: 1715198 68607920 com.mysql.jdbc.PreparedStatement$BatchParams
7: 621372 44778960 [C
8: 59015 11608344 [Ljava.lang.Object;
9: 469551 11269224 java.lang.String
10: 335730 8057520 org.dom4j.tree.DefaultAttribute
11: 76733 2455456 org.dom4j.tree.DefaultElement
12: 49621 2376880 [Ljava.lang.String;
13: 47685 1525920 java.util.HashMap$Node
14: 41482 1327424 com.paratera.importdata.CacheKeys
15: 46753 1122072 java.lang.StringBuilder
16: 44715 1073160 java.util.ArrayList
17: 32577 1042464 java.util.concurrent.ConcurrentHashMap$Node
18: 40990 983760 java.lang.Long
19: 13911 667728 java.nio.HeapCharBuffer
20: 13832 663936 java.nio.HeapByteBuffer
21: 24729 593496 java.lang.StringBuffer
22: 13476 539040 [Ljava.util.Formatter$Flags;

哦哦哦~排名第一的是byte[] , 我X. 其实根本查不出来是什么原因。除非你的代码里,很多 new byte[]. 否则使用byte[]
基本是你的调用的各种组件里的byte[]. 好吧。现在的怀疑的对象扩展到了自己的全部组件。。

前五名都看不出任何问题。直到第六名。

![](http://img.blog.csdn.net/20160127111800214?watermark/2/text/aHR0cDovL2Jsb2
cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravit
y/Center)
就是这货。这货的变量里 byte[] byte[][] , int[] , InputStream[]
排名前几的都有!!!不用说了。一定是这货。这货就是mysql jdbc执行 addBatch() 放入的。然后我细细看了一下代码。发现
executeBatch() 调用才会清理了 addBatch()中的 BatchParam,
如果只是调用了execute()方法,则写入数据库时不会清理BatchParam.
所以还是之前将批量入库转换为单条数据入库导致的。只将executeBatch()改为了execute(),
虽然功能上立马变成了单条数据入库。但实际上却直接引入了内存泄露问题。

当然因为代码封装/函数化的问题, addBatch() 和 executeBatch()
被放入到了不同的函数中。。。。所以再次自己挖坑自己埋。自己掉坑里,一定是自己之前坑挖的不对。最后的解决方法也很简单。将addBatch()去掉就立马好了。

好了。。。。写的多了。。。天晚了。。。洗洗睡去了。。。

** 哦对了。打条公司信息,有想加入【并行科技】java团队的发简历到[email protected](请注明来源,方便过hr关).  公司只做高大上的超算/高性能计算的 toB  toG 业务,不做 toC业务。待遇不是问题。问题是你到底值不值你要的待遇。。。 **

下面是火星救援的影评:

这是一部在火星的衣食住行手册。旨在讲述各种设备如何的不靠谱以及如何使用人类智慧驾驭这些不靠谱的设备。

这个影片和各种好莱坞大片一样。故事的叙述方式也一模一样。

图片太耗资源了。。省略了。

故事是这样叙述的:

1. 我们制造一个问题(来点暴风/吹飞一个人)

2. 解决这个问题(全队撤离)

3. 解决这个问题产生一个结果(这个被吹飞的人没死,还健康的活在火星上)

4. 这个结果又产生了问题(这个人怎么活在火星上,怎么营救)

5. 怎样解决这个问题(水,氧气,食物,温度,电力,通信,交通)

6. 问题都解决了。来让我们制造新的问题。(新的远航火箭报废,食物循环报废)

7. 解决新的问题。(中国太阳神,弹弓效应缩短时间,启用已经在火星上的战神4火箭)

8. 推向高潮,如何推?快速制造问题,快速解决问题。(火箭超重=>敞蓬车,距离太远=> 空气喷射)

9. 再加点猛料(上天后陷入昏迷;航天器爆破;破手套空气喷射手忙脚乱;最后营救时,不管速度多慢死活不能一次抓住)

10. 结局

故事这种叙述方式就是

问题=> 解决问题=> 后果(坏) 则继续解决问题;后果(好)则制造点新的问题。一步步推进,直到结局。不过高潮时一定要快速的制造和解决问题。

下面到了影片的吐槽时间:

你们知道吗?使用超级计算机不用一直连着网线,作业提交后就不用再管,直到算出结果后看两眼。而且像他这种权限的根本不能进入到超级计算机的机房中。对了,公司最近做
了一款应用ParaAlarm,可以在手机上跟踪提交到超算系统中的作业状态。下载二维码在这里:

![](http://img.blog.csdn.net/20151222103511331?watermark/2/text/aHR0cDovL2Jsb2
cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravit
y/Center)
你们知道吗?这种发射火箭的大项目都不用测试。这就是找死。做软件的都知道。

你们知道吗?全队成员都飞回去了,怎么还留给你几百吨的氢气呢?

你们知道吗?如果气压都能把气仓炸飞,那怎么可能用塑料布就可以密封了?早知道这样,都用塑料带布搭建基地就可以了呀。

你们知道吗?中国的火箭根本不会叫太阳神号。因为太阳神是西方崇拜的神。中国从来不崇拜太阳。有的就只有金乌这种鸟,并且还要让后羿射下来几个。所以火箭绝对不会叫太
阳神,那一定会被射下来的。而且我觉得可能是在哪个国家上映就在哪个国家播使用那个国家的火箭。比如在印度播出的话,那些片段就会变成印度的太阳神火箭。

不过那个宇航员不管是胖变瘦还是瘦变胖,还是很令人佩服的(LP说使用的是替身…恕我眼瞎)。在开始没有多久,土豆发芽的一幕还是挺不错。最后营救时两个宇航员死
活抓不住还是很令人揪心的。

另外电影要下架了。最后再贴张海报吧。

![](http://img.blog.csdn.net/20151222103548743?watermark/2/text/aHR0cDovL2Jsb2
cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravit
y/Center)

从网易有道离职一段时间了。LP和tt都说最好不要写前公司的总结,写好写坏总有人来找茬。但是我想这毕竟是个人的观点和看法,代表着自己这些年的观点。自己应该总结
一下,与他人无关。可能再过多少年回顾一下,也会觉得荒谬无比。

网易有道之前是一家不错的公司。但是近几年不知道是产品不给力还是管理层的问题。整个公司的状态越来越不好。员工很难撑过3年。这里并不是黑网易有道。网易有道里的人
都很不错。只不过很多人从网易有道出来后,才有了更大的成就。嗯…这是为什么呢?

之前一直在有道云笔记组做server端码农。做了许多没人用的功能,然后做的心累了,然后就离开了。可能笔记的老大最近也做累了,也离开了。大概大家都累了吧,笔记
团队的核心成员换了一茬又一茬。之前cloudstorage的初创成员一个都不在了。细细想想可能是产品一直无法盈利。做一款有用但是无法盈利的产品对公司长远发展
可能有好处。但是对员工的近期发展实在没有啥优势。因为人员不能扩张,老员工难以升职加薪。不过后来也和盈利团队的成员聊了聊。结果发现盈利团队的成员也各有各的不满
。嗯…这到底是为什么呢?

嗯。以上答案只有还在网易有道的管理层自己摸索了。

有道云笔记一直在做竞争对手evernote的功能。一直在按照符合国情的方式抄袭,并不考虑颠覆的事情。只有后来做协同办公才算和evernote走上了不同的路。
虽然原型还是slack,tower, QQ等,但在这些原型上已有些突破。可是不知道为什么后来没有坚持此策略,又回到了笔记上来,又准备加一些华而不实的功能。我
自己认为做软件需要将一款软件细致打磨,做到极致,或者颠覆创新开创新纪元。但是不停的堆功能是怎么回事?

说到堆功能,在这里再黑黑PM。还是少招吧。堆功能大概是PM的本职工作。在我的圈子里从没有PM每天想着优化功能,减少功能。一个PM想需求的速度肯定是一个程序员
编写代码的数倍。要么降低创造需求的速度,要么扩招程序员。管理者需要自己权衡。PM的需求来源,我知道的有几个:

  • 用户评价反馈(一般以负面反馈为准)一般可以推动程序优化

  • 用户调研

  • 业界行业标杆(evernote )

  • 用户数据统计分析 (特征分析)+PM主观臆想

  • boss的直接拍板

  • 对外资源置换 (合作方需求)

  • 推广活动
    所以说需求来源真是太多了。PM需要有整个需求进程的把控吗?哦…圈内的PM经常连功能什么时候上线的都不知道。认真的PM真是圈内稀缺,虎头蛇尾的PM多见。
    吐槽完毕。下面是知识点:

  • 在网易有道认识了许多人,交到许多朋友。膜拜了许多精英。知识面,视野比入职时开阔许多。

  • 有时间多多搜索,少请教。没时间多多请教,少搜索。

  • 公司组件的存活周期可能比开源项目还要短。公司一定要有组件维护组才行。

  • 一定要code review, 一定要有良好的编码风格。一定要保持代码的整洁。

  • 一定要print和用户相关的log。特别是做互联网应用的

  • 一定要尽早测试,测试的testcase的范围是上次上线的功能和这次上线的功能。

  • 一定要开发白天可以上线的server程序。发布尽量工作时间做。

  • 服务代码要精简,需要大量共用组件,每个功能的开发最好不要牵扯到其他项目的开发。保持船小好调头。

  • 有条件做压测,没有条件就线上压测吧。

  • 要有人对需求约束负责。很多约束条件在开发初期就要定下来。千万不要说可以放无限多的内容,无字数限制,无大小限制,可以承载无限大的压力这样的话。

  • 公司如果在走下坡路,走法有断崖式和缓坡式。各有利弊请自主选择。但不要每次开会只说好事不说坏事。

  • 贵司每半年的考核非常烂,我非常抵制。考核应尽量简单。另外不管怎么考核,这些考核永远是主观的。升职加薪本来就是看个人能力,而这事主管心里是清楚的。

  • 不要简单堆砌功能。功能在精不在多。请回顾一下庞大的功能,其中80%的功能都应该被砍掉。

  • 应从公司的技术层面进行战略部署,而不是每个项目和产品各自为战。

  • 公司在开发者机器上的配置上不要抠门,机器速度/网速是保持开发者良好心态的关键。

  • 公司需要尽量配置可以翻墙的网络。

  • 公司对研发成员不要实行打卡制。

  • 公司对员工应不时发些福利。不患寡而患不均。
    看了一下访问量。。。发现大家的确喜欢看八卦~~ 看客们有感想写在下面的评论里吗?

上一篇大概说了一下JS是怎么画UML 图形的。
这一篇简单介绍一下这个JS 解析 UML语法是怎么实现的。
https://github.com/bramp/js-sequence-diagrams/blob/master/src/grammar.ebnf

这个是Markdown UML语言的语法结构。摘抄了部分:
这一部分是将词和词组成语句。而语句和语句就组成篇章。

document ::= statement*
statement ::=
( 'title' ':' message
| 'participant' actor
| 'note' ('left of' | 'right of' | 'over') actor ':' message
| actor ( '-' | '--' ) ( '>' | '>>' )? actor ':' message
)

这里的意思是 UML是由 statement(语句)构成的。
statement有下面几种表示方法:
1. ‘title’ ‘:’ message 这里带上”的都是token,在编译原理中,token标识不能再被解释的词。
2. ‘participant’ actor
3. ‘note’ (‘left of’ | ‘right of’ | ‘over’) actor ‘:’ message
表示note与Actor之间的关系
4. actor ( ‘-’ | ‘–’ ) ( ‘>’ | ‘>>’ )? actor ‘:’ message 标识actor之间的关系。

下面的词法分析会将字符转换为UML中有含义的字。类似字符组成词。用正则式将字母变成有含义的词。

/** js sequence diagrams
 *  http://bramp.github.io/js-sequence-diagrams/
 *  (c) 2012-2013 Andrew Brampton (bramp.net)
 *  Simplified BSD license.
 */
%lex

%options case-insensitive

%{
    // Pre-lexer code can go here
%}

%%

[\n]+             return 'NL';
\s+               /* skip whitespace */
\#[^\n]*          /* skip comments */
"participant"     return 'participant';
"left of"         return 'left_of';
"right of"        return 'right_of';
"over"            return 'over';
"note"            return 'note';
"title"           return 'title';
","               return ',';
[^\->:\n,]+       return 'ACTOR';
"--"              return 'DOTLINE';
"-"               return 'LINE';
">>"              return 'OPENARROW';
">"               return 'ARROW';
:[^#\n]+          return 'MESSAGE';
<<EOF>>           return 'EOF';
.                 return 'INVALID';

/lex

%start start

%% /* language grammar */

start
    : document 'EOF' { return yy; }
    ;

document
    : /* empty */
    | document line
    ;

line
    : statement { }
    | 'NL'
    ;

statement
    : 'participant' actor  { $2; }
    | signal               { yy.addSignal($1); }
    | note_statement       { yy.addSignal($1); }
    | 'title' message      { yy.setTitle($2);  }
    ;

note_statement
    : 'note' placement actor message   { $$ = new Diagram.Note($3, $2, $4); }
	| 'note' 'over' actor_pair message { $$ = new Diagram.Note($3, Diagram.PLACEMENT.OVER, $4); }
    ;

actor_pair
    : actor             { $$ = $1; }
	| actor ',' actor   { $$ = [$1, $3]; }
    ;

placement
    : 'left_of'   { $$ = Diagram.PLACEMENT.LEFTOF; }
	| 'right_of'  { $$ = Diagram.PLACEMENT.RIGHTOF; }
    ;

signal
    : actor signaltype actor message
    { $$ = new Diagram.Signal($1, $2, $3, $4); }
    ;

actor
    : ACTOR { $$ = yy.getActor($1); }
    ;

signaltype
    : linetype arrowtype  { $$ = $1 | ($2 << 2); }
	| linetype            { $$ = $1; }
    ;

linetype
    : LINE      { $$ = Diagram.LINETYPE.SOLID; }
	| DOTLINE   { $$ = Diagram.LINETYPE.DOTTED; }
    ;

arrowtype
    : ARROW     { $$ = Diagram.ARROWTYPE.FILLED; }
	| OPENARROW { $$ = Diagram.ARROWTYPE.OPEN; }
    ;

message
    : MESSAGE { $$ = $1.substring(1).trim().replace(/\\n/gm, "\n"); }
    ;
%%

例如下面这一句:

[^\->:\n,]+       return 'ACTOR';

就是将不是“->:\n,” 这些字符的多个字组成词,这个词叫做ACTOR.

一般来说终结符也就是类似’note’ 这样的字符需要先被定义。否则 ‘note’这样的字符集合就变成了markdown UML的ACTOR.

而下面的是将ACTOR词转成对象actor。

actor
    : ACTOR { $$ = yy.getActor($1); }
    ;
    
1
2
3
4
5
A->B: 一句话证明你很寂寞。
Note left of A: thinking
B->B: counting
B>>A: 这句话有一共六十九笔
Note right of X: SB

Created with Raphaël 2.1.2 寂寞是什么 A A C C S S B B 一句话证明你很寂寞。
thinking counting 这句话有一共六十九笔 SB

好吧上面就是UML的grammar. 其实用什么语言都可以写出来。不局限于JS. 具体grammar这块使用的是jison和ebnf.
有闲空的人可以看一下JS调用jison来做js parser以及EBNF表达式。

这一篇开始讨论代码层面的问题。主要是markdown的功能的设计实现。

这一篇先从markdown外围的代码开始讲起:
UML序列图是从 https://github.com/bramp/js-sequence-diagrams
copy得到的。

当如这个UML代码还用到了两个JS,功能库和画图.

<script src="underscore-min.js"></script>
<script src="raphael-min.js"></script>

首先讲讲这个JS最核心的类:
[ https://github.com/bramp/js-sequence-diagrams/blob/master/src/sequence-
diagram.js ](https://github.com/bramp/js-sequence-diagrams/blob/master/src
/sequence-diagram.js)

首先这个类分为其实使用两种风格的。一种就是现在CSDN采用的风格,叫做simple. 另外一种是手绘风格,叫做hand。定义如下:

var themes = {
simple : RaphaelTheme,
hand : HandRaphaelTheme
};
Diagram.prototype.drawSVG = function (container, options) {
var default_options = {
theme: 'hand'
};
options = _.defaults(options || {}, default_options);
if (!(options.theme in themes))
throw new Error("Unsupported theme: " + options.theme);
var drawing = new themes[options.theme](this);
drawing.draw(container);
}; // end of drawSVG

drawSVG是最外层JS要画图调用的方法。例如:

<div id="diagram">Diagram will be placed here</div>
<script src="sequence-diagram-min.js"></script>
<script> 
  var diagram = Diagram.parse("A->B: Does something");
  diagram.drawSVG('diagram');
</script>

下面就可以画出图形。所以drawSVG是核心入口。

drawSVG的实现过程中,最重要的一句:

drawing.draw(container);

这一句调用的draw function定义如下:

    draw : function(container) {
        var diagram = this.diagram;
        this.init_paper(container);
        this.init_font();
        this.layout();
        var title_height = this._title ? this._title.height : 0;
        this._paper.setStart();
        this._paper.setSize(diagram.width, diagram.height);
        var y = DIAGRAM_MARGIN + title_height;
        this.draw_title();
        this.draw_actors(y);
        this.draw_signals(y + this._actors_height);
        this._paper.setFinish();
},

这几句看起来平淡无奇,就是初始化背景,初始化字体,初始化各种,然后画标题,画角色(也就是序列图中的实体),画调用箭头。

这里面看似简单,但是最为繁琐的就是:

this.layout();

布局:要遍历所有的要画到画布上的东西,要提前预知,各个东西的长宽,然后根据长宽,各个东西相距的距离,算出画布的尺寸。这段JS并没有做到固定长宽后,各个东西等
比缩放。

这个做完之后,开始画角色(也就是实体)

Created with Raphaël 2.1.2 A A 这就是角色A 包括上下两个方块和中间的竖线

再然后,两个角色之间是有间距的。例如:

Created with Raphaël 2.1.2 距离是什么? A A B B 我们两个角色之间的间距 包括上下两个方块和中间的竖线

间距的计算并不是很容易,首先确定每个角色的基本的高和宽,然后根据将信号signal计算在内(signal 包括两种,一种是 箭头关系,另外一种是 注释框)所
以大家可以看到注释框和箭头也是有从上到下的顺序的。signal的宽度和字的个数以及宽度均有关系(这里计算较为复杂,此处略去太多字…有兴趣的看源码)。

然后顺序遍历,依次确定A,B,C…各个角色所在的绝对坐标。

每一个signal都是有高度的,将所有的signal 高度加和,就得到了下端角色方框的坐标。然后连线就大功告成。

看源码发现了许多没有的功能…

例如 UML中增加title
以及UML中注释框除了Right, Left, 还有Over, 对于Over还可以置于多个角色之上。

title 和 Over

1
2
3
4
5
title: 距离是什么?

A->B: 我们两个角色之间的间距
Note Over A,B: 包括上下两个方块和中间的竖线
Note Over A: I'm A

表现:

Created with Raphaël 2.1.2 距离是什么? A A B B 我们两个角色之间的间距 包括上下两个方块和中间的竖线
I’m A

[ https://github.com/bramp/js-sequence-diagrams/blob/master/src/jquery-
plugin.js ](https://github.com/bramp/js-sequence-diagrams/blob/master/src
/jquery-plugin.js)

这里写图片描述

这个类中,如果没有定义option属性,那么就只能用simple的形式了。感觉CSDN的dev好像没有定义这个option. 我这边也无能为力。

[ https://github.com/bramp/js-sequence-diagrams/blob/master/src/diagram.js]

这个类没有什么好讲的,就是通过parser构造出来的实体类。

剩下的两个gammar JS. 这种词法解析的放到后面一篇再说. 网络实在不好,啥都timeout, 另外也该睡觉了。

0%