@spiritnotes
2016-03-22T11:16:33.000000Z
字数 13347
阅读 2098
Scala
增加新的类型
增加新的控制结构:例子基于Actor的并发编程
面向对象与函数式编程的组合
val
var
自动类型推断
递归函数中,返回类型必须被明确说明
方法只有一个参数,调用的时候可以省略点和括号
Scala没有操作符重载,因为其没有传统意义上的操作符,其很多字符都可以作为方法名
函数式风格帮助写出更易读懂,同样也更不容易犯错的代码
scala.io.Source
val v:Iterator[String] = Source.fromFile(path).getLines // .toList
类定义中,可以放置字段和方法,统称为成员。字段,不管是val还是var,都是指定对象的变量。方法,包含了可执行的代码。字段保留对象的状态或数据,而方法使用这些数据执行对象的运算工作。类被实例化时,运行环境会预留一些内存来保留对象的状态映像-即变量的内容。字段的另一种说法是实例变量。
单行分号是可省略的
跨越多行时,Scala多数情况能在正确的位置分割语句,可以加括号或者将中缀操作符放置末尾
除了以object外,与类定义差不多。
单例对象与某类共享同一个名称时,就称为该类的伴生对象。其必须定义在同一个文件里。可以相互访问私有成员。
new只能用在伴生类上用以创建对象。单例对象不带参数,因为其不是通过new创建的。每个单例对象实现为虚构类(synthetic class,名字是伴生类+$)的实例,并指向静态的变量,与Java的静态类有着相同的初始化语义。单例对象在第一次访问时才被初始化。
创建含main函数的独立对象。
如果不是脚本,推荐按照类名命名文件名,易于寻找。脚本以结果表达式结束。
直接extends Application输入代码执行
Application声明了合适签名的main方法
花括号之间的代码被收集进了单例对象的主构造器,并在类被初始化时执行
重载:参数个数或类型不一样的多个方法
任何方法嗾使操作符
后缀操作符是不用点或括号调用的不带任何参数的方法,一般是有副作用加上括号
< ...
&& || !
& | ^ ~ << >> >>>
== !=
检查左边是否为null,不是则调用其equals方法
Java里的引用的==是比较引用相等性
根据操作符的第一个字符判断优先级
类没有主体,不需要指定空的花括号
this
this
定义私有gcd方法以及val g=gcd值用以分子分母化简
def +(that:R):R =
def *(that:R):R =
字符数字标识符:字母数字, $保留给编译器使用
尽量少使用,保持与Java一致且_有很多非标识符用法
习惯上常量也是驼峰写法
操作符标识符:由一个或多个操作符字符组成
混合标识符:由字母数组组成,后面跟一个下划线和一个操作符标识符 unary_-, myvar_=
字面量标识符:使用``包含的任意字符串
implicit def intToRational(x:Int) = new...
使用val v = if ... else ... 方式赋值
某个对象的成员
把函数定义在其他函数里面。
函数值是对象
可以将方法或嵌入函数转变为对象
定义
封闭项
不带自由变量的函数字面量
开放项
带自由变量的函数字面量
依照函数字面量在运行时创建的函数值(对象)就称为闭包,名字源于捕获自由变量的绑定从而对函数字面量执行的“关闭”行动
得到的函数值中将包含指向捕获的自由变量的索引
闭包捕获了变量本身,而不是变量指向的值
变量改变,闭包也改变
闭包也可以改变外部变量的值
闭包如由函数创建,其绑定对象为函数调用当次的本地变量
(args:Int*)
可以指定最后一个参数是重复的,变长的
函数内部,重复参数的类型是声明参数类型的数组 Array[T]
使用时需要产生参数序列 sum(1 to 5 :_*)
tail recursive
递归调用是函数体执行的最后一件事
编译器自动优化,无需任何开销
调试
关闭尾递归 -g:notailcalls
局限
仅仅优化直接递归调用返回同一函数
所有间接都不支持
一个读取文件列表并过滤出文件的例子
使用高阶函数
非函数式方法中,通用为函数,非通过也就是参数部分为数据
而使用高阶函数作为参数,可进一步提高抽象
使用高阶函数如exists简化代码
将原来接受多个参数的函数变成接受一个参数的函数的过程
新函数返回一个以原来第二个参数作为参数的函数
def A(x:Int)(y:Int) = x*y
可以理解为 def a(x:Int) = (y:Int)=> x+y,但两者不同
可以通过占位符获得参考 val z=a(1)_
twice方法
withclose方法 --》借贷模式
原始
def f(g:()=>Unit) = {...}
f{ ()=> printle(....) }
参数的类型不要使用 g:()=>, 使用 g:=>
优化
def f(g:=>Unit) = {....}
f { println(...) }
实例
def until(condition:=>Boolean)(g:=>Uint) {.....}
until (x == 10) { x -= 1 ....}
如果要在带名函数中使用return,必须定义其返回类型
完成一个练习项目
Any
AnyVal
基本数据类型
Boolean
Unit
AnyRef
所有类型的子类
Null
实例 null
Nothing
标明不正常的终止
如异常返回
弥补多重继承导致的问题
对基类共同成员的初始化
虚拟基类
封装方法和字段的定义,混入到类中重用
特质不能有任何“类”参数,即主构造器不能有参数
spuer调用是动态绑定,而类是静态绑定的
把廋接口变成胖接口
class X extends Ordered[X]{
def compare(that: X) = this.v - that.v
}
自动帮助实现各种不等式操作,equals仍然需要自己实现
通过super调用来堆叠改变
可以为类或对象添加多个相互调用的特质,从最后一个开始
使用super,子类会调用父类的同名函数
从右到左
当需要调用super而实际super为抽象特质时,则将实现作为抽象
abstract override def log(...) {}
多继承不能线性化
如果行为不会被重用,则具体类
如果要在多个不相关的类中重用,则特质
希望从Java中继承它,使用抽象类
计划以编译后方式发布它,则外部组织能够写一些继承自它的类,则抽象类
效率重要,使用类,Java中类虚方法调用快于接口方法调用
采用Java平台完整的包机制
几种方式
C#方式: package x{ calss x ... package y { ... }}
package x.y;package z 位于文件顶端,不带方括号
包可以嵌套
内部作用域里面的包可以隐藏外部作用域里面的同名包
可以访问上层作用域中的名称
需要访问非直接作用域中的名称,使用绝对包名 _root_.
包声明链 x.y.z 不会使中间包 x 和 x.y 变成可见
目的:使用更短的名称
可以引入包、类以及对象
可以在def函数引入具体类实例 def ..(f:F){import f._}
_ 作为通配符使用
可以出现在任何位置,将其放在需要的地方,避免名字冲突
可以重命名和隐藏特定成员
选择器 .{A,B}
重命名 .{A=>B, C}
隐藏 .{A=>_, _}
java.lang / scala / Predef 总是被引入
私有成员
与Java相同
该成员只能在包含了成员定义的类或对象内部可见
内部类也是一样,私有成员外部类不可访问
保护成员
只在定义了成员的类的子类中可以被访问
Java中同一个包中其他类也可以访问,Scala更严格
公开成员
任何地方可以访问
访问作用域
可以通过使用限定词强调 private[x]
private 限定词还可以指向所属类或对象
private[this] 仅能在包含了定义的同一个对象中访问,对象私有
protected[X] 允许子类以及修饰符所属的包、类或对象X访问带有此标记的定义
assert(condition)
assert(condition, explanation)
ensuring判断
对结果进行判断 位于结果之后 = {} ensuring( ... _ ...)
JVM -ea -da 进行控制
scalatest
Suite
FunSuite
以函数值的方法定义测试,重载了execute
===
expect() { ... }
intercept(calssof[异常类])
样本类
case class 修饰
Scala编译器自动为你的类添加一些句法上的便捷设定
与类名一致的工厂方法
类参数列表中的所有参数隐式获得val前缀,当作字段维护
添加toString、hashCode、equals的“自然”实现,做结构化的比较
模式匹配
选择器 match {备选项} 用以替代 switch {选择器} {备选项}
通过以代码编写的先后词项尝试每个模式来完成计算
特点
match为表达式,以值为结果
不会掉到下一个case中
没有模式匹配,MatchError会被抛出
通配模式
case _ => ... 匹配任何对象
case BinOp(_, _, _) 忽略对象中不关心的部分
常量模式
匹配自身,任何字面量
“true”、5
val或单例对象(如Nil)
变量模式
类似通配符,可匹配任意对象
把变量绑定在匹配的对象上
case x => use x
以小写字母开始的简单名被当作是模式变量
对于使用val定义的小写字母变量,可使用`name`,将其变为常量
构造器模式
假如其名字指向样本类,则首先检查该对象是该名称的样本类的成员,然后检查对象的构造器参数是符合额外提供的模式的
支持深度匹配
序列模式
固定长度
List(_, _, _)
任意长度
List(0, _*)
注意类型,使用List只能匹配List及其子类,可使用Array等
元组模式
case (a,b,c) =>
类型模式
case s:String =>
case m: Map[_,_] =>
类型擦除,运行期时其泛型已经不可见
Array除外,特殊处理
变量绑定
变量名@模式,匹配成功后,将变量设置为匹配的对象
模式守卫接在模式之后,开始于if
可以是任意引用模式中变量的布尔表达式,返回true才匹配成功
case n:Int if 0 < n =>
全匹配的样本要跟在更具体的简化方法之后
封闭类除了类定义所在的文件之外不能再添加任何新的子类
使用继承自封闭类的样本类做匹配,编译器将通过警告信息标志出缺失的模式组合
如果匹配未用全部case class,可以为匹配表达式添加 @unchecked 注解进行警告抑制
(e: @unchecked) match {...}
两种形式
Some(x)
None
集合类的 get 方法就是返回 Option 对象
分离可选值
使用模式匹配
x match {case Some(s)=>s; case None=> ...}
拆分元组
val (x,y) = tuple
解构样本类的对象
val Bin(op, left, right) = binOject
用作偏函数的样本序列
样本序列可以用作能够出现函数字面值的任何地方
样本序列可以多个入口点,每个都有自己的参数列表
val withDefault: Option[Int] => Int = {case Some(x): x; case None => 0}
withDefault(Some(10))
withDefault(None)
两者都自动转为了Option类
Actor中使用广泛
偏函数
val second: PartialFunction[List[Int], Int] = {case x::y::_ => y}
相当于定义一个偏函数类型,会有apply和isdefinedAt两个函数
def apply() = xs match {case x::y::_ => y}
def isDefinedAt(xs:List[Int]) = xs match {case x::y::_ => true; case _ => false}
for
映射的键值对
for (Some(x) <- listofOptions) // None的值不会被匹配
List(1,2,3) ....
同质的
列表类型是协变的
空列表 List()/Nil 类型为 List[Nothing],是所有List类型的子类
List(2,3):::List(3,4)
cons: 2::LIst(3,4)
drop(n) / dropRight(n) 去掉左右n个元素
count / filter / exists / foreach / forall / isEmpty / length / map / mkString / reverse / sort
head / tail / init 前n-1个元素
拆分
var List(a,b,c) = list
var a::b::rest = list
层级结构
Iterable
Seq
List
头部添加删除快
不可变
数组Array
随机访问快速
列表缓冲 ListBuffer
+= 元素添加
+: 前缀添加
toList 到 List
数组缓存
队列 Queue
enqueue
item 添加元素
items 添加多个元素
dequeue
返回对偶
+= / ++=
栈Stack
push
top
pop
RichString
Seq[Char]
Set
集合
含有可变与不可变多种
创建
val v = Set(a,b,c)
访问
v += d
可变加入自身
不可变产生一个新的set
+ / - / ++ / -- / ** 交集 /size / contains / Set.empty[String] / += / -= / ++= / --= /clear
Map
映射
创建
Map('c'->2 ... )
Map(('c',2)....)
mutable.Map.empty[String, Int]
有可变与不可变
scala.collection.mutable.Map
scala.collection.mutable.HashMap
scala.collection.immutable.SortedMap
scala.collection.mutable.LinkedHashMap
默认获得HashMap
访问
m('a')
if (m.contains('a')) ...
m.getOrElse('a', 0)
m.get('a') 返回一个Option对象
修改
m += ('4'->2, ...)
m -= 'a'
m = m1 + ('a'->4, ....)
++ / -- / size / contains / isempty / ++= / --=
遍历
for ((k,v) <- m) ...
for (k <- m.keySet)
for (v <- m.values)
for ((k,v) <- m) yield (v,k)
特点
不可变集合和映射返回的类型和其初始化个数有关
Iterable的elements返回迭代器
hasNext
next
有序集和映射
只有可变版本
由红黑树实现, TreeSet、TreeMap
SortedMap、SortedSet
同步的集和映射
val map:Map[Int,Int] = new HashMap[Int,Int] with SynchronizedMap[Int,Int]
说明
可以在实际中实验使用可变与不可变
初始化集合时候可以赋值新的类型
List[Any](42)
toList / toArray 转变成为List和数组、
元组
用来聚集值,可为异构多类型
创建
v = (1,"abs, 4.0)
val t = new Tuple3[Int, Int, String]
v = Tupel2(1,2)
访问
访问使用 t._1, t._2, t._3
因为可能类型不一样,因此不能使用apply方式访问
val (first, second, _) = t
必须有括号,否则为多个元素赋同样的值
zip可将Array合并成元组的Array
keys.zip(values).toMap
var s 隐含公开的getter和setter
private[this] var h
def hour:Int = h
def hour_=(v:Int) {h = v}
赋值 = _ 表示使用该类型的默认零值
具有以下三种操作方式的数据结构
haed
tail
append
是不可变的
主构造器可以为private,这样只能类以及伴生对象可以访问,可以通过辅助构造器来对外
class x(xxx) private{ ... }
协变
是指子类型化的类型有类型参数具有同样的超类子类关系
泛型的参数类型被当做方法参数的类型,包含它的类或特质就可能不能与这个类型参数一起协变
如Queue[Int],其某方法append是针对Int的,将其赋值为Queue[Any]后调用该方法还是用的针对Int的
如何检查
将类或者特质结构体的所有位置分为正、负、中立
位置是指类/特质的结构体内可能用到类型参数的地方
+的类型参数可以放在正的位置,其他类似
逆变
子类型化的类型与类型参数协变关系相反
例子:输出通道 输出Output[Any]是OutPut[Int]的子类
Function1特质
Function1 [-S, +T]{ def apply(x:S):T }
参数是逆变的,返回值是协变的
+-被称为变化型注解
Java数组是支持协变的,其运行时保存了数组的元素类型
下界
U >: T定义T为U的下界,U必须是T的超类
class Queue[+T] ... def append[U >: T] = new Queue[U] ...
种类
类型成员
可以不定义具体的类型
type T
定义为子类的抽象类型
为类型定义短小的,具有说明先的别名
抽象类型
函数的参数不支持协变, x(d:F)与x(d:C)不是重写
Animal eat Food
Cow eat Grass
animal = Cow, animal eat Fish
Animal {type SuiteAbleFood <: Food ; def eat(food:SuiteableFood)}
Cow extends Animal {type SuiteableForrd = Grass; override def eat(food: Grass)}
路径依赖类型
类型可以作为对象的成员
可以理解为type语句是可执行的
类中的内部类与具体实例相关,不同实例是不同类
new o1.Inner
枚举 scala.Enumeration即是使用了内部类
抽象val
val init:String
定义类型不赋值
同def init:String,但限制了不可改变
抽象var
var hour:Int
实际上隐式声明了其getter和setter方法
初始化问题
由于特质无构造器参数,如果特质的构造器需要使用到抽象val,而其在子类中定义
fields预初始化字段
调用超类之前初始化子类的字段
new {val ...} with RationalTrait
object t extends {val ...} with Ratianal
其this指向的是包含正在构造的类或对象的对象,不是被构造对象本身
懒加载val
初始化延迟到第一次使用的时候
由于是按需初始化,文本顺序不用考虑
优点
比扩展方法更灵活、简洁
支持目标类型的转换
即是说可以自动对参数进行转换
不显式书写利于代码的进化
规则
标记规则:只有标记为implicit的定义才是可用的,可以标记变量、函数、对象定义
作用域规则:插入的隐式转换必须以单一标识符的形式处于作用域中,或与转换的源或目标类型关联在一起
源类型和目标类型的伴生对象中
无歧义规则:隐式转换唯有不存在其他可插入转换的前提下才能插入
c1(x)+y /// c2(x)+y 有歧义
可去掉一个
明确写出想要的转换
单一调用原则:只会尝试一个隐式操作
不会出现c1(c2(x)) + y
显式操作先行规则:如编写的代码类型检查无误,则不会尝试任何隐式操作
何处尝试
转换为期望类型
如 val c:Int = 3.5
指定(方法)调用者的转换
用途
接受者转换使得新的类可以更为平滑地集成到现有类层级中
整数 + 有理数
支持在语言中编写领域特定语言 DSL
-> 是类ArrowAssoc的方法,定义了从Any到ArrowAssoc的隐式转换
如 obj.doIt,而obj无doIt方法,则其会转换
隐式参数
被提供的是完整的最后一节柯里化参数
implicit关键字是用于全体参数列表,而不是单独的参数
def x()(implicit x:X, y:Y)
implicit val x:X = new ...
implicit val y:Y = new ....
方式是匹配参数类型与作用域内的隐式值类型
常用来提供转换信息
def x[T](...)(implicit order:T=>Ordered[T]):T=...
省略到代码里面对order的调用,则代码中完全不见order
implicit在参数上,说明该函数值是隐式可访问的单个标识符
视界
def x[T <% Ordered[T]](...) = ...
任意T都好,只要能将T当做Ordered[T]即可
如何调试
明确写到代码中测试
scalac -Xprint:typer
原理
List是抽象类
类型参数是协变的
所有列表操作都可以通过 isEmpty/ head/ tail三种基本方法定义
Nil 为 case object, :: 为final case
::利于模式匹配
:: ::: 绑定在右操作数上
ListBuffer
对 += 以及 toList 都只要常量时间即可完成
所有能产生值的for表达式都被转换成map、flatmap、filter的组合调用
不要yield的被转为fikter和foreach
具体
带一个生成器的for
for (x<-e)yield y
e.map(x=>y)
带if
for (x<-e if y) yield z
e.filter(y).map(z)
两个生成器开头的
for (x<-xs;y<-ys;seq) yield z
xs.flatMap(ys.map(seq z))
包含模式
直接转为模式
map、flatMap、filter也可以使用for实现
for (x<- xs) yield f(x)
for (x <- xs; y <- f(x)) yield y
for (x <- xs if p(x)) yield x
泛化For
列表、数组以及很多类中都支持map、flatMap等
Range/Iterator/Stream/Set
类型
定义了map
支持单一生成器组成的for表达式
定义flatmap和map
支持若干个生成器组成的for
定义foreach
允许for循环,单个或多个生成器
定义filter
允许if开头的过滤器表达式
for转译发生在类型检查之前
方法签名
class C[A]
def map[B](f:A=>B):C[B] = {}
def flatMap[B](f:A=>C[B]):C[B]
def filter(p:A=>Boolean):C[A]
def foreach(b:A=>Unit):Unit
目的:为已经存在的类型定义新模式,而这种模式不需要遵守类型的内部表达式
email
object Email extends (String,String)=>String {...}
注入方法,生成指定子集的元素
def apply(user:S,domain:S) = user +"@"+domain
抽取方法,传入相同子集源并抽取部分
def unapply(str:S):Option[(S,S)]={...Some(x,y) else None}
应该满足 X.unapply(X.apply(x,y)) == Some(x,y)
case Email(Twice(x @ UpperCae()), domain) =>
0个或1个变量的模式
1个返回 Option[X]
0个返回Boolean,为true或false
变参抽取器
def apply(parts:X*):X ...
def unapplySeq(x:X):Option[Seq[Y]] ....
同样可以返回类型为 Option[(String, Seq[String])]
Seq的匹配采用X(a, b, _*)
List、Array的匹配器是其对象中的unapply定义的
抽取器可以让模式与数据表达无关,表征独立
样本类易于建立、定义,代码更少
样本类用于匹配更为高效
如果样本类继承自sealed基类,则编译器会检测出
正则表达式
位于 scala.util.matching.Regex, Regex(string)
原始字符串可以避免多次输入反斜杠
string.r 直接生成正则表达式
val Decimal = """(-1)?(\d+)(\.\d*)?""".r
方法
.findFirstIn str
.findAllIn str
regex findPrefixOf str
对每个正则表达式都定义了抽取器
val Decimal(sign, int, dec) = "-1.23"
可以用于for语句和模式匹配中
why
元编程工具
使用scaladoc产生文档
漂亮地打印出符合你偏爱风格的代码
代码的常见错误加成
实验性类型检查
把程序当做输入程序
注解通过让程序员将需要传递给工具的指令标示在它们的源代码中以提供对这些工具的支持
例如
文档中标记为废除
文件检查器关闭
副作用检测器可以得到指示,验证指定的方法是否具有副作用
语法
用于任何的声明或定义上
@deprecated def 。。。
用于表达式上
(e:@unchecked) match ...
一般形式
@annot(exp1....){val name1=const1 ....}
无参数可以省略()
@xx 与 @xx() 是一样的
标准注解
废弃
@deorecated
易变
@volatile
二进制序列化
@serializable
@SerialVersionUID(1234)
@transient
get/set
@scala.reflect.BeanProperty
不检查
@unchecked
特点
嵌套的标签系统
开始标签可以有属性
XML字面量
Scala支持表达式为单个XML,不需要加引号
类
Node类,单个元素的NodeSeq
Text类,只包含文本的节点
NodeSeq类,节点序列
xml.NodeSeq.Empty
转义符{}
可以在XML中使用转移符,且可以嵌套
{} 中可以生成NodeSeq节点或Scala结果,其被转为字符串插入
以打印方式输出,{}中的xml关键字符会被转义
要显式 { } 则必须重复2次
可以为类增加 toXML 方法
拆解
抽取文本 .text
抽取子元素
\ "b" 抽取子标签为 "b" 的元素
\\ "b" 在所有Node中深度搜索
抽取属性 \ "@name"
其他
toString
scala.xml.XML.saveFull
scala.xml.XML.loadFile
模式匹配
与XML字面量相似,差别在于插入转义括号 {}
case <a>{c}</a>
case <a>{c@_*}</a> 对子元素序列进行匹配
for (therm @ <cc>{_*}</cc> <- therms)
原理
Java中 == 对于引用表示 对象一致性
Scala中 eq 表示引用一致性, == 等于 equals
陷阱
以错误的签名定义 equals 方法
重载是按照参数的静态类型而不是运行期类型来解析
equals 函数参数类型应该定义为 Any
有可能错误地以 == (X) 重载了Any中的final ==(Any)
修改equals但没有同时修改 hashcode
hashCode与equals总应该一同定义
hashCode只能依赖equals方法依赖的字段
用可变字段定义equals
放入集合中,再改变该值,则该对象无法查找到,而elements中含有
集合中的contains是针对每个元素与新元素进行equals比较
未能按等同关系定义 equals 方法
要求对等关系
自反射性 a==a
对称性
传递性
一致性
对于非空x, x.equals(null) = false
父类和子类
新增元素后,不满足对称性
解决
宽松
子类的equals中分别针对子类、父类进行模式匹配判断
现在不可传递了 childa = father = childb
更明确
this.x == that.x && this.getClass == that.getClass
对 new P(1,1){override val y = 2} 太过严格
canEquals
通过定义canEqual函数来识别
重定义equals时同时重定义canEqual
def canEqual(other:Any) = other.isInstanceOf[Point]
def equals() ... case that:Point => (that canEqual this) && ...
违反里氏替代原则,set contains cp // set contains p
actor
actor是一个类似线程的实体,有一个用来接受消息的邮箱
创建
继承scala.actors.Actor并完成act方法
scala.actor.Actor对象中的actor方法创建actor
val x = actor { ... code ...}
直接运行,不需start方法
启动 .start()
消息
发送: x ! "hello"
接受 : receive { case ... }
发送消息不会被阻塞,接受消息不会被打断
消息处理
actor只会处理传给receive方法的偏函数中的某个样本相匹配的消息
对于每个消息,调用传入的偏函数的 isDefinedAt 判断是否与某个样本匹配,然后处理该消息
receive 方法将选定邮箱中第一个让 isDefinedAt 返回true的消息
偏函数的apply方法将应用到该消息上
无匹配消息,则actor阻塞,直到收到匹配的消息
原生线程当做actor
scala.actors.Actor.self
self ! 5
self.receive{ case x=> x }
.receiveWithin(misecends ) { ... }
重用线程
react函数
react找到并处理消息后并不返回
返回类型是Nothing
在后台,react完成时抛出一个异常
不需返回,因此不需要保留当前线程的调用栈
react 函数每个case里面再调用 act()
loop { react { ...... }}
良好风格
Actor不应阻塞
处理消息时不应该阻塞
应该允许某种表示动作可以执行的消息发给它
只通过消息与actor通信
优先选择不可变消息
让消息自包含
消息中包含冗余消息,记录上下文便于消息回来后的处理