《OOD启思录》:61条面向对象设计的经验原则

摘自《OOD启思录》 Arthur J.Riel【著】; 鲍志云【译】

“你不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看成警铃,若违背了其中的一条,那么警铃就会响起。” —– Arthur J.Riel

(1) 所有数据都应该隐藏在所在的类的内部。

(2) 类的使用者必须依赖类的共有接口,但类不能依赖它的使用者。

(3) 尽量减少类的协议中的消息。

(4) 实现所有类都理解的最基本公有接口[例如,拷贝操作(深拷贝和浅拷贝)、相等性判断、正确输出内容、从ASCII描述解析等等]。

(5) 不要把实现细节(例如放置共用代码的私有函数)放到类的公有接口中。 如果类的两个方法有一段公共代码,那么就可以创建一个防止这些公共代码的私有函数。

(6) 不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。

(7) 类之间应该零耦合,或者只有导出耦合关系。也即,一个类要么同另一个类毫无关系,要么只使用另一个类的公有接口中的操作。

(8) 类应该只表示一个关键抽象。包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包影响,则将对包中的所有类产生影响,而对其他的包不造成任何影响 .

(9) 把相关的数据和行为集中放置。设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。这种类型的行为暗示着这条经验原则被违反了。

(10) 把不相关的信息放在另一个类中(也即:互不沟通的行为)。朝着稳定的方向进行依赖.

(11) 确保你为之建模的抽象概念是类,而不只是对象扮演的角色。

(12) 在水平方向上尽可能统一地分布系统功能,也即:按照设计,顶层类应当统一地共享工作。

(13) 在你的系统中不要创建全能类/对象。对名字包含Driver、Manager、System、Susystem的类要特别多加小心。规划一个接口而不是实现一个接口。

(14) 对公共接口中定义了大量访问方法的类多加小心。大量访问方法意味着相关数据和行为没有集中存放。

(15) 对包含太多互不沟通的行为的类多加小心。这个问题的另一表现是在你的应用程序中的类的公有接口中创建了很多的get和set函数。

(16) 在由同用户界面交互的面向对象模型构成的应用程序中,模型不应该依赖于界面,界面则应当依赖于模型。

(17) 尽可能地按照现实世界建模(我们常常为了遵守系统功能分布原则、避免全能类原则以及集中放置相关数据和行为的原则而违背这条原则) 。

(18) 从你的设计中去除不需要的类。一般来说,我们会把这个类降级成一个属性。

(19) 去除系统外的类。系统外的类的特点是,抽象地看它们只往系统领域发送消息但并不接受系统领域内其他类发出的消息。

(20) 不要把操作变成类。质疑任何名字是动词或者派生自动词的类,特别是只有一个有意义行为的类。考虑一下那个有意义的行为是否应当迁移到已经存在或者尚未发现的某个类中。

(21) 我们在创建应用程序的分析模型时常常引入代理类。在设计阶段,我们常会发现很多代理没有用的,应当去除。

(22) 尽量减少类的协作者的数量。一个类用到的其他类的数目应当尽量少。

(23) 尽量减少类和协作者之间传递的消息的数量。

(24) 尽量减少类和协作者之间的协作量,也即:减少类和协作者之间传递的不同消息的数量。

(25) 尽量减少类的扇出,也即:减少类定义的消息数和发送的消息数的乘积。

(26) 如果类包含另一个类的对象,那么包含类应当给被包含的对象发送消息。也即:包含关系总是意味着使用关系。

(27) 类中定义的大多数方法都应当在大多数时间里使用大多数数据成员。

(28) 类包含的对象数目不应当超过开发者短期记忆的容量。这个数目常常是6。当类包含多于6个数据成员时,可以把逻辑相关的数据成员划分为一组,然后用一个新的包含类去包含这一组成员。

(29) 让系统功能在窄而深的继承体系中垂直分布。

(30) 在实现语义约束时,最好根据类定义来实现。这常常会导致类泛滥成灾,在这种情况下,约束应当在类的行为中实现,通常是在构造函数中实现,但不是必须如此。

(31) 在类的构造函数中实现语义约束时,把约束测试放在构造函数领域所允许的尽量深的包含层次中。

(32) 约束所依赖的语义信息如果经常改变,那么最好放在一个集中式的第3方对象中。

(33) 约束所依赖的语义信息如果很少改变,那么最好分布在约束所涉及的各个类中。

(34) 类必须知道它包含什么,但是不能知道谁包含它。

(35) 共享字面范围(也就是被同一个类所包含)的对象相互之间不应当有使用关系。

(36) 继承只应被用来为特化层次结构建模。

(37) 派生类必须知道基类,基类不应该知道关于它们的派生类的任何信息。

(38) 基类中的所有数据都应当是私有的,不要使用保护数据。类的设计者永远都不应该把类的使用者不需要的东西放在公有接口中。

(39) 在理论上,继承层次体系应当深一点,越深越好。

(40) 在实践中,继承层次体系的深度不应当超出一个普通人的短期记忆能力。一个广为接受的深度值是6。

(41) 所有的抽象类都应当是基类。

(42) 所有的基类都应当是抽象类。

(43) 把数据、行为和/或接口的共性尽可能地放到继承层次体系的高端。

(44) 如果两个或更多个类共享公共数据(但没有公共行为),那么应当把公共数据放在一个类中,每个共享这个数据的类都包含这个类。

(45) 如果两个或更多个类有共同的数据和行为(就是方法),那么这些类的每一个都应当从一个表示了这些数据和方法的公共基类继承。

(46) 如果两个或更多个类共享公共接口(指的是消息,而不是方法),那么只有他们需要被多态地使用时,他们才应当从一个公共基类继承。

(47) 对对象类型的显示的分情况分析一般是错误的。在大多数这样的情况下,设计者应当使用多态。

(48) 对属性值的显示的分情况分析常常是错误的。类应当解耦合成一个继承层次结构,每个属性值都被变换成一个派生类。

(49) 不要通过继承关系来为类的动态语义建模。试图用静态语义关系来为动态语义建模会导致在运行时切换类型。

(50) 不要把类的对象变成派生类。对任何只有一个实例的派生类都要多加小心。

(51) 如果你觉得需要在运行时刻创建新的类,那么退后一步以认清你要创建的是对象。现在,把这些对象概括成一个类。

(52) 在派生类中用空方法(也就是什么也不做的方法)来覆写基类中的方法应当是非法的。

(53) 不要把可选包含同对继承的需要相混淆。把可选包含建模成继承会带来泛滥成灾的类。

(54) 在创建继承层次时,试着创建可复用的框架,而不是可复用的组件。

(55) 如果你在设计中使用了多重继承,先假设你犯了错误。如果没犯错误,你需要设法证明。

(56) 只要在面向对象设计中用到了继承,问自己两个问题:(1)派生类是否是它继承的那个东西的一个特殊类型?(2)基类是不是派生类的一部分?

(57) 如果你在一个面向对象设计中发现了多重继承关系,确保没有哪个基类实际上是另一个基类的派生类。

(58) 在面向对象设计中如果你需要在包含关系和关联关系间作出选择,请选择包含关系。

(59) 不要把全局数据或全局函数用于类的对象的薄记工作。应当使用类变量或类方法。

(60) 面向对象设计者不应当让物理设计准则来破坏他们的逻辑设计。但是,在对逻辑设计作出决策的过程中我们经常用到物理设计准则。

(61) 不要绕开公共接口去修改对象的状态。

说说职场中的交流和沟通 [转发]

原文链接

关于职场的交流和沟通,记得我之前好像写过一些。来 Coinbase 已经有快两年了,有了不少的成长,尤其是在职场交流上。说起来,每个公司都有核心文化(core values),Coinbase 的四大核心文化之一,便是清楚有效的交流(clear communication)。所以能感觉到,这里的人,尤其是管理者,对交流和沟通的标杆是相当高的。文化和氛围这东西可意会但很难言传,所以我只能尽我最大努力简单说说自己的体会和经验吧。

交流大致分四种,一种是各种书面的文档、email,自己写好或者发出去,别人大部分时候是异步的阅读,然后有些情况会有评论(comment)或者回复;第二种是一对一的对话,这可能是面对面的交谈,也可能是 slack 这样的工具上的对话;第三种是组会,或者是 slack 群里面多人的讨论;最后一种是演讲(presentation),就是你一个人给大家讲。

先说一些适用于所有情况的基本准则。

首先就是从交流对象的角度去考虑话要怎么说。职场的交流沟通和朋友相处间的交流,最大的区别:交流是你工作的一部分。换句话来说,在职场你说话写字不是为了抒发情感,不是为了发泄感情,而是为了办好事情,让对方了解你想让他了解到的问题和情况,或者是你对一个问题的看法或想法。这里面可能和平时说话不大一样的地方,就是首先你要把对方作为中心,而不是你自己。你要说的,不是你想说的,或者你觉得正确的语序和方式;而是怎么说,会让对方会感兴趣,一下子就明白,并且印象深刻。

我遇到过组里的人,跟外组的人解释一个东西,从头到尾一二三,每条都对。但是外组的人听完了,虽然点头,但是其实这三条里,真正需要那个组的人注意或者帮忙的,就完全被弱化了,甚至不知道对方是不是真的会完全领会基于这三点,他该注意什么,该做什么。所以这种情况,后面加一句:这里你们可能尤其需要注意的,是xxx。这样就能极大强化对话的效果。

另外,说话不在多,不在快,而在准确切到重点上。事实上,尤其是口头的交流,你说多了、快了、除非对方已经知道大部分的内容,大部分你说的,都成了无可奈何的耳旁风。先把最重要的点说了,具体那些细节,等着别人来问再给更多,而不是一股脑儿都倒给对方,大部分时候效果会好很多。

如果是回答问题,确定你真的明白了对方想问的是什么再回答。开会的时候遇到一个人问了问题A,结果另一个人回答了问题B。如果你还不好插话,是不是特别着急?

还有的时候对方因为对一些具体细节不熟悉,问的问题甚至可能不是他真正困惑想问的问题。这种情况在上级问一个下级所属事务相关的问题,但是对那部分事务又不那么熟悉的时候尤为经常发生,这时候如果问题听起来似乎太简单或者太奇怪,你就要试着去推测对方是不是应该想了解另一个问题只是问的方式不对。推测完了先确认你推测的对不对:“您的问题答案是xxx,但我想您是不是想了解xxx的意思?” 这样的去确认,如果对方说不是,你那个答案就够了,那就算了。如果说是,这样,你才有机会回答了实际的问题。

演讲还是组会,说话的时候不要只盯着自己的电脑或者大屏幕,看看大家的表情和眼神,看看是不是已经云游方外了,还是有困惑的表情。如果你不能把大家的注意力带到你身上,那你说再多也只是背景噪音。

其次就是养成一个习惯,帮别人划重点。这个在书面交流中尤其重要。什么意思呢?如果是文档,你的 summary 或者 overview 就要确保假如别人没时间看完,他仍然能准确知道整个文档主要的中心思想。如果是 email,我发长的 email 一定都有两个要素:一个就是最前面的 TL;DR (too long, don’t read),一个就是 email 里一定会有 10% 左右的文字是黑体

因为职位需要,现在经常发周报,以及一些给高层或者整个大组的信件。我们都知道,长的信件除非是那种重要到他们不看会后悔的,大部分人可能看个标题很快就标注已读并存档。谁也没有时间和耐心去在你的信件里找有没有什么值得注意的。这个时候,前面的 TL;DR 确保是你用一句话描述了的大家绝对不能错过的内容的概述。如果需要更多讲解,加一句:see details below。这就帮助大家快速决定是不是有必要去读你的全文。

然后 email 主体再学会用黑体点出最重要的内容,这样别人如果只有30秒跳读你的信件,就会读所有的黑体部分。只要你重点没划错,你就帮你自己让别人没有错过你想传达的信息。

以前遇到别人说:我 email 里都写了啊,你自己没注意看。我会觉得,他说的很有道理。但是现在的我就不一定认同。如果你真的能把 email 写到帮别人划重点,并且划对,其实别人漏掉重要信息的可能性会极小。

最后说一说哪些是满分之外的那100分。

如果你刚和另一个人讨论一个问题有了一些方案、思路、想法、尤其是决定时,第一时间尽可能把讨论内容汇总群发 email 给或者 shared slack 给所有可能相关的人。这一点对于经常需要做决定的人尤其重要。否则你可能会遇到后面有另一个不同的决定,或者有些事情和你的决定好像有矛盾,然后你再去说:我和谁谁谁早谈过了,决定要怎么怎么。你没告诉到所有该告诉的人,谁知道你决定了什么啊。

但是要注意的是,群发的目的是让大家得到一致的信息(on the same page), 内容发出去前只要条件允许,请务必先让参与和你讨论的人过目确认,避免不必要的误解。而不是让和你讨论的人从群发消息上意识到你俩的理解有出入,然后炸了。好的处理,你先给参与讨论的人看了,说我这样发出去有问题吗,对方确认没有问题,这件事就很圆满。没有这一步,到时候那人说:我什么时候说xxx了,根本不是那个意思,那你就去吵吧。吵赢吵输,你都丢掉了一部分人的信任。

任何时候不要给你老板一个惊喜。在没有十足把握下,重要的或者敏感的东西发出去之前,先让老板看一下。他忙或者信任你说不用看了,那是另一回事。但是这样问一下,可以避免很多让你后悔的情况。甚至,哪些东西该由你说,哪些东西你就该等着你的老板先去说,尤其不要出格。

有两个以上人问你同一个问题,就不要反复口头回答或者私聊里回答。尤其是回答起来还比较费劲的问题,把答案记录下来放在一个共享的文档或者网页上,作为标准答案,不断去完善这个标准答案。下次再有人问、再有100个人问,给他发个答案链接就行。就不用去抱怨为什么大家总问你这个问题了。

同样,如果有多个人对你的某个设计、说法、方案有误解,就可能不是别人的理解问题,而是确实容易引起误会,这个时候,静下心来去看看怎么可以做到或者写到更好,而不要轻易觉得是自己找不到知己,或者别人智商不够。

先写这么多吧,职场的交流和沟通绝对是门大学问。初级的人能把事情说对明白(但别人能不能懂要看其理解力高低),中级的人能把大部分人都说懂,高级的人是通过交流和沟通把一整个团队、甚至别的团队结结实实拉到一起,达到自己想要的目的,把问题解决。当然,我也还一直在学习,共勉之。

最短路径算法

import java.util.PriorityQueue;

public class Dijkstra {

	/**
	 * 1.假设带权有向图G的顶点集合为V,起始点为V0。
	 * 2.初始S={V0},T=V-S={其余顶点},其中集合S表示已经计算出最短距离的顶点,T是还没计算的剩余顶点。
	 * 3.初始化V0到其余各点的距离,D(V0,Vi)代表V0到Vi的最短距离,按如下规则。
	 * 3.若V0可以直接到达某个顶点Vi,则将边的权值赋给(V0,Vi),否则D(V0,Vi)=∞。
	 * 5.从T中选取一个与S中顶点有关联(直接相连)且权值最小的顶点W(这里用到了贪心法思想),加入到S中。
	 * 6.以W作为中间点,若V0到T中某个顶点Vi距离变短,则修改D(V0,Vi)的值。 
	 * 7.重复5)和6)直到T为空。
	 * 
	 * @param src
	 * @param dst
	 * @param graph
	 * @param n
	 * @return
	 */
	public static int dijkstra(int src, int dst, int[][] graph, int n) {
		// 排序队列
		PriorityQueue<Node> pNodes = new PriorityQueue<>();
		int[] visitLog = new int[n + 1];
		// 记录访问过的点
		pNodes.add(new Node(src, 0));
		while (!pNodes.isEmpty()) {
			Node tNode = pNodes.poll();
			// 由于已经排序了 直接返回的就是最短路径
			if (tNode.node == dst) {
				return tNode.cost;
			}
			if (visitLog[tNode.node] == 1) {
				continue;
			}
			visitLog[tNode.node] = 1;
			for (int i = 0; i < n; i++) {
				// 可联通且没有遍历过
				if (graph[tNode.node][i] < 1000 && visitLog[i] != 1) {
					pNodes.add(new Node(i, tNode.cost + graph[tNode.node][i]));
				}
			}
		}
		return -1;
	}

	public static void main(String[] args) {
		int graph[][] = { { 0, 2, 3, 6, 1000, 1000 }, { 2, 0, 1000, 1000, 4, 6 }, { 3, 1000, 0, 2, 1000, 1000 },
				{ 6, 1000, 2, 0, 1, 3 }, { 1000, 4, 1000, 1, 0, 1000 }, { 1000, 6, 1000, 3, 1000, 0 } };// 地图

		System.out.println(Dijkstra.dijkstra(0, 3, graph, 6));
		System.out.println(Floyd.floyd(0, 3, graph, 6));
	}
}

/**
 * 
 * @author shichaopeng
 *
 *
 *
 *         邻接矩阵graph储存路径,同时最终状态代表点点的最短路径。如果没有直接相连的两点那么默认为一个很大的值(不要溢出)!而自己的长度为0.
 *         从第1个到第n个点依次加入图中。每个点加入进行试探是否有路径长度被更改。
 *         而上述试探具体方法为遍历图中每一个点(i,j双重循环),判断每一个点对距离是否因为加入的点而发生最小距离变化。如果发生改变,那么两点(i,j)距离就更改。
 *         重复上述直到最后插点试探完成。
 * 
 *         状态转移方程为: dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
 *         其中dp[x][y]的意思可以理解为x到y的最短路径。所以dp[i][k]的意思可以理解为i到k的最短路径dp[k][j]的意思可以理解为k到j的最短路径.
 */
class Floyd {

	/**
	 * 
	 * @param src
	 * @param dst
	 * @param graph
	 * @param n
	 * @return
	 */
	public static int floyd(int src, int dst, int[][] graph, int n) {
		int[][] r = new int[n][n];
		for (int i = 0; i < r.length; i++) {
			for (int j = 0; j < r.length; j++) {
				r[i][j] = graph[i][j];
			}
		}
		for (int k = 0; k < n; k++) { // 任意2点插入点 让后动态规划
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < n; j++) {
					r[i][j] = Math.min(r[i][j], graph[i][k] + graph[k][j]);
				}
			}
		}
		return r[src][dst];
	}

}

class Node implements Comparable<Node> {

	public int node;
	public int cost;

	public Node(int node, int cost) {
		super();
		this.node = node;
		this.cost = cost;
	}

	@Override
	public int compareTo(Node o) {
		return cost - o.cost;
	}

}

纪念刘和珍君

正因为他是和我们一样的普通人,所以他的行为格外英雄。 [李文亮医生 事件]


中华民国十五年三月二十五日,就是国立北京女子师范大学为十八日在段祺瑞执政府前遇害的刘和珍杨德群〔2〕两君开追悼会的那一天,我独在礼堂外徘徊,遇见程君〔3〕,前来问我道,“先生可曾为刘和珍写了一点什么没有?”我说“没有”。她就正告我,“先生还是写一点罢;刘和珍生前就很爱看先生的文章。”
这是我知道的,凡我所编辑的期刊,大概是因为往往有始无终之故罢,销行一向就甚为寥落,然而在这样的生活艰难中,毅然预定了《莽原》〔4〕全年的就有她。我也早觉得有写一点东西的必要了,这虽然于死者毫不相干,但在生者,却大抵只能如此而已。倘使我能够相信真有所谓“在天之灵”,那自然可以得到
更大的安慰,——但是,现在,却只能如此而已。
可是我实在无话可说。我只觉得所住的并非人间。四十多个青年的血,洋溢在我的周围,使我艰于呼吸视听,那里还能有什么言语?长歌当哭,是必须在痛定之后的。而此后几个所谓学者文人的阴险的论调,尤使我觉得悲哀。我已经出离愤怒了。我将深味这非人间的浓黑的悲凉;以我的最大哀痛显示于非人间,使它们快意于我的苦痛,就将这作为后死者的菲薄的祭品,奉献于逝者的灵前。

真的猛士,敢于直面惨淡的人生,敢于正视淋漓的鲜血。这是怎样的哀痛者和幸福者?然而造化又常常为庸人设计,以时间的流驶,来洗涤旧迹,仅使留下淡红的血色和微漠的悲哀。在这淡红的血色和微漠的悲哀中,又给人暂得偷生,维持着这似人非人的世界。我不知道这样的世界何时是一个尽头!
我们还在这样的世上活着;我也早觉得有写一点东西的必要了。离三月十八日也已有两星期,忘却的救主快要降临了罢,我正有写一点东西的必要了。

在四十余被害的青年之中,刘和珍君是我的学生。学生云者,我向来这样想,这样说,现在却觉得有些踌躇了,我应该对她奉献我的悲哀与尊敬。她不是“苟活到现在的我”的学生,是为了中国而死的中国的青年。
她的姓名第一次为我所见,是在去年夏初杨荫榆女士做女子师范大学校长,开除校中六个学生自治会职员的时候。〔5〕其中的一个就是她;但是我不认识。直到后来,也许已经是刘百昭率领男女武将,强拖出校之后了,才有人指着一个学生告诉我,说:这就是刘和珍。其时我才能将姓名和实体联合起来,心中却暗自诧异。我平素想,能够不为势利所屈,反抗一广有羽翼的校长的学生,无论如何,总该是有些桀骜锋利的,但她却常常微笑着,态度很温和。待到偏安于宗帽胡同〔6〕,赁屋授课之后,她才始来听我的讲义,于是见面的回数就较多了,也还是始终微笑着,态度很温和。待到学校恢复旧观〔7〕,往日的教职员以为责任已尽,准备陆续引退的时候,我才见她虑及母校前途,黯然至于泣下。此后似乎就不相见。总之,在我的记忆上,那一次就是永别了。

我在十八日早晨,才知道上午有群众向执政府请愿的事;下午便得到噩耗,说卫队居然开枪,死伤至数百人,而刘和珍君即在遇害者之列。但我对于这些传说,竟至于颇为怀疑。我向来是不惮以最坏的恶意,来推测中国人的,然而我还不料,也不信竟会下有残到这地步。况且始终微笑着的和蔼的刘和珍君,更何至于无端在府门前喋血呢?
然而即日证明是事实了,作证的便是她自己的尸骸。还有一具,是杨德群君的。而且又证明着这不但是杀害,简直是虐杀,因为身体上还有棍棒的伤痕。
但段政府就有令,说她们是“暴徒”!
但接着就有流言,说她们是受人利用的。
惨象,已使我目不忍视了;流言,尤使我耳不忍闻。我还有什么话可说呢?我懂得衰亡民族之所以默无声息的缘由了。沉默呵,沉默呵!不在沉默中爆发,就在沉默中灭亡。

但是,我还有要说的话。
我没有亲见;听说,她,刘和珍君,那时是欣然前往的。自然,请愿而已,稍有人心者,谁也不会料到有这样的罗网。但竟在执政府前中弹了,从背部入,斜穿心肺,已是致命的创伤,只是没有便死。同去的张静淑〔8〕君想扶起她,中了四弹,其一是手枪,立仆;同去的杨德群君又想去扶起她,也被击,弹从左肩入,穿胸偏右出,也立仆。但她还能坐起来,一个兵在她头部及胸部猛击两棍,于是死掉了。
始终微笑的和蔼的刘和珍君确是死掉了,这是真的,有她自己的尸骸为证;沉勇而友爱的杨德群君也死掉了,有她自己的尸骸为证;只有一样沉勇而友爱的张静淑君还在医院里呻吟。当三个女子从容地转辗于文明人所发明的枪弹的攒射中的时候,这是怎样的一个惊心动魄的伟大呵!中国军人的屠戮妇婴的伟绩,八国联军的惩创学生的武功,不幸全被这几缕血痕抹杀了。
但是中外的杀人者却居然昂起头来,不知道个个脸上有着血污……。

时间永是流驶,街市依旧太平,有限的几个生命,在中国是不算什么的,至多,不过供无恶意的闲人以饭后的谈资,或者给有恶意的闲人作“流言”的种子。至于此外的深的意义,我总觉得很寥寥,因为这实在不过是徒手的请愿。人类的血战前行的历史,正如煤的形成,当时用大量的木材,结果却只是一小块,但请愿是不在其中的,更何况是徒手。
然而既然有了血痕了,当然不觉要扩大。至少,也当浸渍了亲族;师友,爱人的心,纵使时光流驶,洗成绯红,也会在微漠的悲哀中永存微笑的和蔼的旧影。陶潜〔9〕说过,“亲戚或余悲,他人亦已歌,死去何所道,托体同山阿。”倘能如此,这也就够了。

我已经说过:我向来是不惮以最坏的恶意来推测中国人的。但这回却很有几点出于我的意外。一是当局者竟会这样地凶残,一是流言家竟至如此之下劣,一是中国的女性临难竟能如是之从容。
我目睹中国女子的办事,是始于去年的,虽然是少数,但看那干练坚决,百折不回的气概,曾经屡次为之感叹。至于这一回在弹雨中互相救助,虽殒身不恤的事实,则更足为中国女子的勇毅,虽遭阴谋秘计,压抑至数千年,而终于没有消亡的明证了。倘要寻求这一次死伤者对于将来的意义,意义就在此罢。
苟活者在淡红的血色中,会依稀看见微茫的希望;真的猛士,将更奋然而前行。
呜呼,我说不出话,但以此记念刘和珍君!

如何成事?-读冯唐的《成事》

1、有一个好的身体,身体是革命的本钱,事还没干,人就躺下了,怎么都成功不了,所以说我们要有好的身体,要把锻炼身体作为一种习惯,不知道该干什么的时候,看书和锻炼身体是最好的两个选择,当然冯唐也在书中写了应该如何锻炼身体,这个我们可以对照着练习,其实也很简单,每周集中锻炼三次,每次超过一个小时,要出汗,让后不断地减持下去就好。

2、要专一,冯唐写过《不二》这本书,其实不二这两个字说的就是专一这个道理,专一其实有很多种解释,其实就是一件事一件事的去做,专心致志的做事,不要吃纸碗里的看着锅里的,一次只做一件事情,完成了再去干第二件事情,养成习惯,离成事就近了一步。

3、第三点我觉得应该是埋头干事,只要不死就往死里干,事情做的多了,身体也好了,精神也好了,外面的世界关你屁事,关我屁事,好好干活就行,把手头的活干好了,该休息的时候放肆的休息。冯唐也说,其实现在很多的社会人,其实就是想的太多做的太少,嫌这嫌那,找各种借口,其实就是想偷懒,思想懈怠,要相成事就要埋头苦干。

4、大处着眼、小处着手。这八个字是书中提到最多的八个字,也是曾国藩成事的至理名言,通俗的讲,就是有目标有规划,要通盘考虑,完了以后就是埋头苦干,就是小处着手,从一件一件小事做起,一个一个的做,不能眼高手低。其实曾国藩能够打赢太平天国也是用的这个道理,曾国藩的湘军一开始打仗并不厉害,但是曾国藩就用笨方法,结硬寨打呆仗,就是跟你耗着,慢慢的跟你打,最终取得胜利。

5、要勤快。“百种弊病,皆从懒生”这也是曾国藩的原话,所有的事情都是懒惹的祸。任何时候不要懒,如果从小事开始变的勤快呢?先从早起开始,冯唐在书中说到早起不下十次之多,太阳都能早起,我们为什么不能早起,如果连早起的习惯都不能养成,连早起的毅力都没有,那么这个人肯定不能成事。所以一定要记住“百种弊病,皆从懒生”,勤快从早起开始。

6、另外还要养成一些好的习惯,比如读书、锻炼、定目标、反省等等,多去山里,多去图书馆、多去健身房,吃喝玩乐也要有,但一定要适度,人生短短几十年,如果真想成事,做出一番成就,要养成这些好的习惯,如果天天消耗、夜夜笙歌,一定不是一个能够成事的人。

洗牌算法

-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

java 实现

import java.util.Arrays;
import java.util.Random;

public class Shuffle {

	public static void shuffle(int[] arr) {
		int i = arr.length;
		Random random = new Random();
		while (i > 1) {
			i = i - 1;
			int j = random.nextInt(i);
			int temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
		System.out.println(Arrays.toString(arr));
	}

	public static void main(String[] args) {
		int[] arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
		for (int i = 0; i < 50; i++)
			shuffle(arr);
	}
}

运行50次结果

[7, 3, 0, 2, 9, 1, 8, 4, 6, 5]
[3, 2, 5, 9, 1, 6, 7, 0, 4, 8]
[7, 8, 6, 1, 4, 2, 0, 9, 5, 3]
[9, 3, 1, 7, 5, 6, 4, 8, 2, 0]
[5, 9, 0, 2, 1, 4, 7, 6, 3, 8]
[4, 8, 3, 9, 6, 1, 5, 0, 2, 7]
[0, 6, 9, 1, 3, 5, 4, 2, 7, 8]
[6, 3, 8, 4, 5, 2, 0, 9, 1, 7]
[8, 9, 3, 1, 4, 7, 6, 2, 0, 5]
[1, 8, 4, 7, 2, 3, 0, 5, 9, 6]
[9, 1, 3, 5, 0, 8, 6, 4, 2, 7]
[1, 7, 9, 4, 6, 3, 8, 0, 5, 2]
[0, 8, 4, 7, 3, 2, 6, 5, 9, 1]
[1, 7, 8, 9, 6, 5, 0, 4, 3, 2]
[5, 4, 1, 8, 0, 7, 3, 2, 9, 6]
[8, 6, 2, 0, 1, 5, 9, 3, 4, 7]
[5, 7, 4, 9, 0, 3, 6, 2, 1, 8]
[8, 9, 5, 4, 7, 0, 1, 3, 2, 6]
[2, 5, 6, 7, 3, 1, 4, 9, 0, 8]
[0, 7, 8, 2, 6, 5, 9, 1, 3, 4]
[4, 3, 6, 0, 5, 1, 2, 9, 8, 7]
[6, 1, 5, 8, 9, 4, 7, 0, 2, 3]
[9, 4, 0, 2, 5, 6, 3, 8, 7, 1]
[5, 2, 7, 0, 4, 3, 9, 6, 1, 8]
[4, 3, 1, 7, 9, 0, 6, 2, 8, 5]
[5, 8, 6, 1, 4, 9, 2, 0, 7, 3]
[3, 0, 2, 6, 1, 5, 8, 7, 9, 4]
[1, 6, 0, 4, 7, 3, 5, 9, 2, 8]
[5, 8, 6, 0, 2, 4, 7, 3, 9, 1]
[8, 7, 3, 6, 4, 1, 0, 9, 2, 5]
[9, 0, 7, 1, 5, 8, 4, 3, 6, 2]
[0, 4, 9, 7, 8, 1, 6, 5, 2, 3]
[5, 0, 4, 2, 1, 7, 9, 8, 3, 6]
[7, 2, 8, 3, 5, 4, 1, 0, 6, 9]
[2, 6, 4, 8, 0, 5, 9, 7, 1, 3]
[0, 2, 9, 7, 8, 6, 1, 4, 3, 5]
[6, 4, 1, 2, 9, 7, 5, 3, 8, 0]
[3, 2, 4, 0, 5, 9, 8, 1, 6, 7]
[9, 0, 8, 7, 6, 2, 3, 4, 1, 5]
[4, 1, 5, 3, 8, 9, 0, 6, 2, 7]
[8, 5, 6, 0, 2, 4, 7, 3, 1, 9]
[1, 4, 9, 6, 3, 2, 8, 7, 0, 5]
[9, 5, 6, 2, 0, 7, 1, 3, 4, 8]
[1, 8, 3, 6, 4, 5, 7, 0, 9, 2]
[8, 3, 7, 5, 1, 9, 0, 2, 4, 6]
[2, 0, 6, 4, 5, 1, 9, 7, 8, 3]
[9, 6, 1, 2, 4, 8, 3, 5, 7, 0]
[6, 3, 4, 0, 2, 1, 8, 9, 5, 7]
[2, 9, 1, 3, 5, 6, 4, 8, 7, 0]
[3, 5, 7, 1, 6, 0, 8, 9, 4, 2]

BackOff重试算法

可以使用斐波那契数列计算重试时间,也可以使用指数补偿计算重试时间。

定义一个接口

@FunctionalInterface
public interface ExponentialBackOffFunction <T> {
	T execute();
}

定义重试工具类

import static java.util.Arrays.asList;

import java.net.SocketTimeoutException;
import java.util.List;

import javax.net.ssl.SSLHandshakeException;

import lombok.extern.log4j.Log4j;

@Log4j
public final class ExponentialBackOff {
	
	private static final int[] FIBONACCI = new int[] { 1, 1, 2, 3, 5, 8, 13 };
	private static final List<Class<? extends Exception>> EXPECTED_COMMUNICATION_ERRORS = asList(
			SSLHandshakeException.class, SocketTimeoutException.class);

	private ExponentialBackOff() {
	}

	// E(c) = Math.pow(2, c) - 1
	// 考虑到补偿时间的均匀分布,补偿时间的数学期望是所有可能性的平均值。
	// 也就是说,在c次冲突之后,补偿时隙数量在 [0,1,...,N] 中,其中 [公式] E(c) = Math.pow(2, c) - 1
	// 则补偿时间的数学期望 E(c) = (Math.pow(2, c) - 1)/2
	public static long getWaitTimeExponential(int attempt) {
		double waitTime = (Math.pow(2, attempt) - 1) / 2;
		return Math.round(waitTime * 100);
	}
	
	// 这个地方使用斐波那契数去增加重试时间
	public static long getWaitTimeDefault(int attempt) {
		return FIBONACCI[attempt] * 100;
	}

	public static <T> T execute(ExponentialBackOffFunction<T> fn) {
		for (int attempt = 0; attempt < FIBONACCI.length; attempt++) {
			try {
				return fn.execute();
			} catch (Exception e) {
				handleFailure(attempt, e);
			}
		}
		throw new RuntimeException("Failed to communicate.");
	}

	private static void handleFailure(int attempt, Exception e) {
		if (e.getCause() != null && !EXPECTED_COMMUNICATION_ERRORS.contains(e.getCause().getClass()))
			throw new RuntimeException(e);
		doWait(attempt);
	}

	private static void doWait(int attempt) {
		try {
			long sleepTime = getWaitTimeExponential(attempt);
			System.out.println("attempt " + attempt + " sleep " + sleepTime);
			Thread.sleep(sleepTime);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}
}

测试案例

public class Test {
	public static void main(String[] args) {
		ExponentialBackOff.execute(new Work());
	}
}

class Work implements ExponentialBackOffFunction<String> {

	@Override
	public String execute() {
		int a = 5 / 0;
		return a + "";
	}
}

运行结果

使用指数补偿:
attempt 0 sleep 0
attempt 1 sleep 50
attempt 2 sleep 150
attempt 3 sleep 350
attempt 4 sleep 750
attempt 5 sleep 1550
attempt 6 sleep 3150
使用斐波那契数列
attempt 0 sleep 100
attempt 1 sleep 100
attempt 2 sleep 200
attempt 3 sleep 300
attempt 4 sleep 500
attempt 5 sleep 800
attempt 6 sleep 1300

微服务编排框架

发现一个优秀的微服务编排框架:zeebe

微服务核心研究之–编排

当一个系统采用了微服务架构后,会拆分成很多新的微服务,但原有的业务可能还是没有变化,如何在微服务架构下实现原有的业务?相对于传统架构,微服务架构下更需要通过各微服务之间的协作来实现一个完整的业务流程,可以说服务编排是微服务架构下的必备技能。但是,编排涉及到RPC、分布式事务等等,编排的质量不能仅仅取决于老师傅的手艺,需要有完善的编排框架来支撑。

关于微服务的组合(协调):
编制(Orchestration)—— 面向可执行的流程:通过一个可执行的流程来协同内部及外部的服务交互。通过中心流程来控制总体的目标,涉及的操作,服务调用顺序。
编排(Choreography)—— 面向合作:通过消息的交互序列来控制各个部分资源的交互。参与交互的资源都是对等的,没有集中的控制。

哲学家就餐问题 [go/java 实现练习并发编程]

java

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

class Philosopher implements Runnable{
	private final String name;
	private final ReentrantLock left;
	private final ReentrantLock right;
	private final AtomicInteger hunger = new AtomicInteger(3);
	public Philosopher(String name, ReentrantLock left, ReentrantLock right) {
		this.name = name;
		this.left = left;
		this.right = right;
	}
	@Override
	public void run() {
		while (this.hunger.get() > 0) {
			System.out.println(name + " Hungry");
			this.left.lock();
			this.right.lock();
			System.out.println(name + " Eating");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.right.unlock();
			this.left.unlock();
			System.out.println(name + " Thinking");
			this.hunger.decrementAndGet();
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}	
}

public class DiningPhilosopher {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		String[] philosophers = new String[] {"Mark", "Russell", "Rocky", "Haris", "Root"};
		ReentrantLock fork0 = new ReentrantLock();
		ReentrantLock forkLeftLock = fork0;
		List<Philosopher> philosopherList = new ArrayList<Philosopher>();
		for(int i=1;i<philosophers.length;i++) {
			String name = philosophers[i];
			ReentrantLock forkRightLock = new ReentrantLock();
			philosopherList.add(new Philosopher(name, forkLeftLock, forkRightLock));
			forkLeftLock = forkRightLock;
		}
		philosopherList.add(new Philosopher(philosophers[0], fork0, forkLeftLock));
		for (Philosopher philosopher : philosopherList) {
			executor.submit(philosopher);
		}
		executor.shutdown();
	}
}

go

package main

import (
	"hash/fnv"
	"log"
	"math/rand"
	"os"
	"sync"
	"time"
)

// 初始化5个人
var ph = []string{"Mark", "Russell", "Rocky", "Haris", "Root"}

const hunger = 3                // 每个哲学家吃几次饭
const think = time.Second / 100 // 思考时间
const eat = time.Second / 100   // 吃饭时间

var fmt = log.New(os.Stdout, "", 0)

var dining sync.WaitGroup

func diningProblem(phName string, dominantHand, otherHand *sync.Mutex) {
	fmt.Println(phName, "Seated")
	h := fnv.New64a()
	h.Write([]byte(phName))
	rg := rand.New(rand.NewSource(int64(h.Sum64())))
	rSleep := func(t time.Duration) {
		time.Sleep(t/2 + time.Duration(rg.Int63n(int64(t))))
	}
	for h := hunger; h > 0; h-- {
		fmt.Println(phName, "Hungry")
		dominantHand.Lock() // 获取资源
		otherHand.Lock()
		fmt.Println(phName, "Eating")
		rSleep(eat)
		dominantHand.Unlock() // 释放资源
		otherHand.Unlock()
		fmt.Println(phName, "Thinking")
		rSleep(think)
	}
	fmt.Println(phName, "Satisfied")
	dining.Done()
	fmt.Println(phName, "Left the table")
}

func main() {
	fmt.Println("Table empty")
	dining.Add(5)
	fork0 := &sync.Mutex{}
	forkLeft := fork0
	for i := 1; i < len(ph); i++ {
		forkRight := &sync.Mutex{}
		go diningProblem(ph[i], forkLeft, forkRight)
		forkLeft = forkRight
	}
	go diningProblem(ph[0], fork0, forkLeft)
	dining.Wait() // 等待结束
	fmt.Println("Table empty")
}