一名经理的工作是“要让在一起工作的一群人取得更好的成绩”。
一个伟大的管理者应该不断地问自己,如何通过影响这些杠杆来提高团队的成绩。随着团队的不断扩大,这个经理人本身做具体的工作有多好已经越来越不重要。更重要的是,他对团队的乘数效应有多大。你要做的是改善你团队的初衷、人员和流程,以实现作为一个团体可以实现的最好的表现。
反馈是一个领导者的基本工作,无论在进展顺利时还是出状况时,用善良和诚恳的态度表达想法,而非指责。
如何为团队建立有效的流程?
随着你管理的人越来越多,你将在塑造文化方面发挥更大的作用。
“在脸书,没有任何错误可以怪到别人头上。”
在业务第一的原则下,通过对领域层(这个领域层就是业务领域)建模,使得实现和业务互相 1 比 1 真实体现的开发方式?
尽管他们在技术使用方面也值得商榷,但真正挫败他们的是业务逻辑。
错误地将开发人员的角色独立出来,导致建模与实现脱节,因此设计无法反映不断深化的分析。
很多应用程序最主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务。
领域模型是对知识严格的组织且有选择的抽象,出于某种目的而概括地反映现实。
先来提一个问题,什么是领域模型?
软件的核心是为其用户解决领域相关的问题的能力,其它特性都要服务于这个基本目的。
通过头脑风暴活动创建新的模型或者修改原有的模型对象,并消化理解这些模型对象中的知识。
模型包含各种类型的知识。
不再使用的或不重要的概念被从模型中移除。当一个不需要的概念与一个需要的概念有关联时,则把重要的概念提取到一个新模型中,不重要的概念可以丢弃。
知识消化由开发人员和领域专家组成的团队来共同完成。
高效的领域建模人员是知识的消化者,努力寻找对大量信息有意义的简单视图,只有找到一组适用于所有细节的抽象概念后,才算成功。
领域模型的不断精化迫使开发人员学习重要的业务原理,而不是机械的进行功能开发。
分析员和程序员将自己的知识输入到模型中,模型的组织更严密,抽象更简洁。领域专家也将他们的知识输入到模型中,模型反映了业务的深层次知识,而且真正的业务原则得以抽象。
项目知识零散地分散在很多人的文档中,我们并没意识到不知道的东西究竟有多少。同时,所有项目都会丢失知识。
模型获得的知识远远不只是“发现名词”。业务活动和规则如同所涉及的实体一样,都是领域的核心。
知识消化所产生的模型能够反映出对知识的深层理解。开发人员对模型实现进行重构,以反映出模型的变化,同时,新的知识就被整合到应用程序中。
当我们的建模不再局限于寻找实体和值对象时,我们才能充分吸取知识。
随着对领域和应用程序所需要的理解逐渐加深,一些开始不可能发现的巧妙抽象就会渐渐浮出水面,而它们恰恰切中问题要害。
有了更深刻的认识后,我们对航运业务的认识从“集装箱在各个地点之间的传输”转变为“运输责任在各个实体之间的传递”。
要想创建一种灵活的、蕴含丰富知识的设计,需要一种通用的、共享的团队语言,以及对语言不断的试验。
日常讨论所使用的术语与代码中所使用的术语不一致。导致对领域的深刻表述常常稍纵即逝。同时翻译使得沟通不畅,并削弱了知识消化。项目需要一种公共语言,领域模型可以成为这种公共语言的核心,同时将团队沟通与软件实现紧密联系到一起。
通用语言的词汇包括类和主要操作的名称。
模型可能缺乏开发人员在代码中所创建的更为微妙和灵活的特性,这要么是因为开发人员认为模型不必具备这些特性,要么是因为编码风格是过程式的,只能隐含的表达领域概念。
通用语言是那些以非代码形式呈现的设计的主要载体,这些包括把整个系统组织在一起的大尺度结构、定义了不同系统和模型之间关系的限界上下文,以及在模型和设计中使用的其他模式。
我们需要将领域对象与系统中的其他功能分离,这样就能够避免将领域概念和其他只与软件技术相关的概念搞混了。
分层的价值在于每一层都只代表程序中的某以特定方面。大多数的分层架构使用的都是这四个概念层的变体:用户界面层(表示层)、应用层、领域层(或模型层)、基础设施层。虽然项目间会有差异,但是将领域层分离出来才是实现模型驱动设计的关键。
应用层负责对领域对象的行为进行协调。
负责处理基本业务规则的的是领域层,而不是应用层。
各层之间是松散连接的,层与层的依赖关系只能是单向的。上层可以直接使用或操作下层元素,通过调用下层元素的公共接口,保持对下层元素的引用。
最早将用户界面与应用层和领域层相连的模式是 MVC。
当使用框架时,项目团队应该明确其使用目的:建立一种可以表达领域模型的实现并且用它来解决重要问题。
对象之间的关联使得建模与实现之间的交互更为复杂。
模型中每个可遍历的关联,软件中都要有相同属性的机制(注:即设计要反映模型将关系)。
设计无需如此直接。(注:在这句话之前,作者举了一个具体的实现方式。所以这句话的意思是,在进行设计时,设计好对应模型的方法即可,不必关注具体,能反映模型即可。)
很多对象不是通过他们的属性定义的,而是通过连续性和标识定义的。
有时,这样的对象必须与另一个具有不同属性的对象相匹配,有时一个对象必须与具有相同属性的另一个对象区分开。
主要由标识定义的对象被称为 Entity(注:即有一个在系统中不重复的标识方式,如 id、uid 等属性或由一些属性组合判断的方式,这个对象可能是跨系统的,可能在不同系统中属性是不同的,但由标识逻辑却可判断是同一个对象。
Entity 最基本的职责是确保连续性,保持简练是实现这一责任的关键。不要讲注意力集中在属性或行为上,应该摆脱这些细枝末节。
- 抓住 Entity 对象定义的最基本特征,尤其是那些勇于识别、查找或匹配对象的特征。只添加那些对概念至关重要的行为和这些行为所必需的属性。
- 将行为和属性转移到与核心 Entity 关联的其他对象中。
- Entity 往往通过协调其关联对象的操作来完成自己的职责。
很多对象没有概念上的标识,它们描述了一个事物的某种特征。
用于描述领域的某个方面而本身没有概念标识的对象称为 Value Object(值对象)。被实例化之后用来表示一些设计元素,对于这些设计元素,我们只关心它们是什么,而不关心它们是谁。
Value Objct 可以是其他对象的集合。
当我们只关心一个模型元素的属性时,应把它归类为 Value Object。我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。Value Object 应该是不可变的。不要为它分配任何标识。
编程语言没有直接支持这些概念上的区别并不说明这些区别没有用处。只是说明我们需要更多的约束机制来确保满足一些重要的规则。命名规则、精心准备的文档和大量讨论都可以强化这些需求。
如果一个 Value 的实现是可变的,那么久不能共享它。无论是否共享 Value Object,在可能的情况下都要将它们设计为不可变的。定义 Value Object 并将其指定为不可变的是一条一般规则。
在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上不属于任何对象。与其把它们强制地归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是 Service。
有些操作从本质上讲是一些活动或动作,而不是事物,但由于我们的建模范式是对象,因此要想办法将它们划归到对象这个范畴里。
当我们勉强将一个操作放到不符合对象定义的对象中时,这个对象就会产生概念上的混淆,而且会变得很难理解或重构。复杂的操作很容易把一个简单对象搞乱,使对象的角色变得模糊。由于这些操作常常会牵扯到很多领域对象————需要协调这些对象以便使它们工作,而这会产生对所有这些对象的依赖,将那些本来可以独立理解的概念掺杂在一起。
Service,强调的是与其它对象的关系。Service 也可以有抽象而有意义的定义,也应该有定义的职责,而且这种职责以及履行它的接口也应该作为领域模型的一部分来加以定义。操作名称应该来自于通用语言,如果通用语言中没有这个名称,则应该将其引入到通用语言中。参数和结果应该是领域对象。
当领域中某个重要的过程或转换操作不是 Entity 或 Value Object 的自然职责是时,应该在模型中添加一个作为独立接口的操作,并将其声明为 Service。定义接口时要使用模型语言,并确保操作名称是通用语言的术语。此外,应使 Service 成为无状态的。这种无状态是指任何客户都可以使用某个 Service 的任何实例,而不必关心该实例的历史状态。Service 执行时将使用可全局访问的信息,甚至会改变这些全局信息(也就是说,,它可能具有副作用)。但 Service 不保持影响其自身行为的状态。
Service 并不只在领域层中使用,需要区分属于领域层的 Service 和那些属于其它层的 Service,并划分职责,以便将它们明确的区分开。
例如:如果银行应用程序可以把我们的交易进行转换并导出到一个电子表格文件中,以便进行分析,那么这个导出操作就是应用层 Service。“文件格式”在银行领域中是没有意义的,也不涉及业务规则。
Module 的使用有一些技术上的原因,但主要原因却是“认知超载”,Module 为人们提供了两种观察模型的方式,一是可以在 Module 中查看细节,而不会被整个模型淹没,二是观察 Module 之间的关系,而不考虑其内部细节。
Module 之间应该是低耦合的,而在 Module 的内部则是高内聚的。一个人一次考虑的事情是有限的(因此才要低耦合)。不连贯的思想和“一锅粥”似的思想同样难于理解(因此才要高内聚)。
低耦合高内聚作为通用的设计原则既适用于各种对象,也适用于 Module。
选择能够描述系统的 Module,并使之包含一个内聚的概念集合,这通常会实现 module 之间的低耦合,但如果效果不理想,则应寻找一种更改模型的方式来消除概念之间的耦合,或者找打一个可作为 Module 基础的概念(这个概念先前可能被忽视了),基于这个概念组织的 Module 可以以一种有意义的方法将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分许这些概念。对模型进行精化,直到可以根据高层领域概念对模型进行划分。
Module 的名称应该是通用语言中的术语。Module 及其名称反映出领域的深层知识。
仅仅研究概念关系是不够的,它并不能替代技术措施,这二者是相同问题的不同层次,都是必须要完成的。当必须做出一个折中选择时,务必保证概念清晰,即使这意味着 Module 之间会产生更多的引用,或者改变 Module 偶尔会缠身“涟漪效应”,开发人员只要理解了模型所描述的内容,就可以应付这些问题。
领域模型中的每个概念都应该在实现元素中反映出来。实现中的对象、指针和检索机制必须直接、清楚地映射到模型元素,如果没有做到这一点就要重写代码,或者回头修改模型,或者同时修改代码和模型。不要在领域对象中添加任何与领域对象所表示的概念没有紧密关系的元素。领域对象的职责是表示模型。
以上 4 中模式为对象模型提供了构造块,但模型驱动设计并不是说必须将每个模型都建模为对象。一些工具还支持其他的模型范式,如规则引擎。这些其它工具和技术是模型驱动设计的补充,而不是要取而代之。目前主流的范式是对象设计,大多数人都比较容易理解面向对象设计的基本知识。
虽然模型驱动设计不一定是面向对象的,但它确实需要一种富有表达力的模型结构实现,无论是对象、规则还是工作流,都是如此。如果可用工具无法提高表达力,就要重新考虑选择工具。缺乏表达力的实现将削弱各种范式的优势。
将非对象元素混合到以面向对象为主的系统中时,要把通用语言作为依靠的基础。即使同居之间没有严格联系,语言使用上的高度一致性也能防止各个设计部分分裂。坚持在多个环境中使用一致的名称,坚持使用通用语言讨论这些名称,将有助于消除两种环境之间的鸿沟。
主要的挑战有以下两类:在整个生命周期中维护完整性;防止模型陷入管理生命周期复杂性造成的困境中。
采用三种模式解决这些问题:聚合(Aggregate),通过定义清晰的所属关系和边界,并避免混乱、错综复杂的对象关系网来实现模型的内聚;使用工程(Factory)来创建和重建复杂对象和聚合,从而封装它们的内部结构;在生命周期的中间和末尾使用存储库(Repository)来提供查找和检索持久化对象并封装庞大基础设施的手段。
具有复杂关联的模型中,要想保证对象更改的一致性是很困难的,不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用。
每个 Aggregate 都有一个根和一个边界。边界定义了 Aggregate 的内部都有什么,根则是 Aggregate 所包含的一个特定 Entity。对 Aggregate 而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。
为了实现概念上的 Aggregate 需要对所有事务应用一组规则:
- 根 Entity 具有全局标识,最终负责检查固定规则;
- 根 Entiry 具有全局标识,边界内的 Entity 具有本地标识,这些标识只在 Aggregate 内才是唯一的;
- Aggregate 外部的对象不能引用除根 Entity 之外的任何内部对象;
- 根 Entity 可以把对内部的引用传递给他们(副本、临时引用,非共享的方式);
- 只有根才能直接通过数据库查询。其它所有对象必须通过遍历关系来发现;
- Aggregate 内部的对象可以保持对其它 Aggregate 根的引用;
- 删除操作必须一次删除 Aggregate 边界之内的所有对象(所以要依赖上面的设计原则,外部对象只对其根 Entity 有引用);
- 当提交对 Aggregate 边界内部的任何对象的修改时,整个 Aggregate 的所有固定规则都必须被满足。
- 由于根控制访问,因此只能通过根来修改内部对象。
当创建一个对象或创建整个 Agggregate 时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用 Factory 进行封装。
应该将创建复杂对象的实例和 Aggregate 的职责转移给单独的对象,这个对象本身可能没有承担领域模型中的职责,但它仍是领域设计的一部分。
开发人员可能使用查询(注:查询某些属性)从数据库中提取他们所需的数据,或是直接提取具体的对象,而不是通过 Aggregate 的根来得到这些对象。这样就导致领域逻辑进入查询和客户代码中,而 Entity 和 Value Object 则变成单纯的数据容器。这将导致开发人员简化领域层,最终使模型变得无关紧要。
在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。随意的数据库查询会破坏领域对象的封装和 Aggregate,妨碍模型驱动的设计。
为美中需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作,将实际的存储和查询技术封装起来。将所有对象的存储和访问操作交给 Repository 来操作。
Repository 和 Factory 不是冲突的,Repository 内会需要借助 Factory 重建一个已有对象。
后续是一些具体的实践方式,总之还是遵循业务第一、核心业务第一、开发资源向核心业务实现倾斜(而非技术难度),分辨好知识边界的情况下,通过一系列手段实现柔性架构,保证实现的健康成长(真实反馈业务、易维护、易扩展)。
以比特币白皮书来说,区块链的本质是一个 P2P(点对点)的分布式账本,节点发出交易时需要向周围节点广播,全节点(还有轻节点等,轻节点一般只保存和自己有关的数据)将收到的交易信息写入区块体中,在共识机制(PoW 即挖矿,还有其他如 PoS、DPoS 机制)作用下获取到记账的权限(即认证某些交易数据,向区块链中写入区块的权限),将新区块广播至周围节点,其它节点校验数据有效性和记账权限后根据区块数据同步本地状态,从而达成整个区块网络的状态一致,同时由于新区块头中有前一区块的关联信息,从而使区块之间达成连接,形成区块链。
简单来说,就是网络中所有结点做完全相同的操作在本地维护一个状态一致性的分布式系统。
比特币的节点在本地维护了一个账本叫做 UTXO(Unspent Transaction Output)的状态,即未使用的交易输出,可以简单的理解为某笔未使用的转账输入,用于进行后续转账的转出使用。
比特币区块的区块体中以哈希树的结构存储了新发生的每笔交易。
单笔交易的一种协议信息的示例如下:
In:
// 表示这次交易使用的资金的来源
Previous tx: f5d8...430901c91
// 资金来源所在交易的哈希
Index: 0
// 资金来源交易哈希中的哪个转出
scriptSig: 3045...798a4 618c...41501
// 对声明的资金来源的解锁脚本,含有签名和公钥
// 可以简单理解为银行卡号和密码
Out: // 表明这次交易的输出
Value: 5000000000
// 金额
scriptPubKey: OP_DUP OP_HASH160 4043...549d OP_EQUALVERIFY OP_CHECKSIG
// 锁定脚本,含有对公钥的哈希和一些运算符,使用这笔转出的交易需要在它的 scriptSig 提供可以通过脚本的信息才能解锁
// 可以理解为银行卡号的哈希和一些操作指令
[私钥签名, 公钥](解锁脚本提供)[OP_DUP, OP_HASH160, 公钥哈希, OP_EQUALVERIFY, OP_CHECKSIG](锁定脚本提供)
OP_CHECKSIG
的过程
以太坊黄皮书
https://ethereum.github.io/yellowpaper/paper.pdf
以太坊同样是一个交易驱动的状态机,它支持智能合约,是可编程的区块链,一种去中心化应用的平台。
我们看到比特币有一定的脚本执行能力,但是其脚本比较简单,只支持几种固定的交易协议和脚本命令。以太坊则不同,其是图灵完备的,区块链开发者可在支持范围内自由编程。
由于以太坊的出块速度较快,它出现临时分叉的可能性也就越高,ommersHash 正是出于抑制分叉的目的设计的,思路如下,被生效区块写入 ommersHash (最多写入两个)的废块也可以获得出块奖励,第一级是 7/8,第二级是 6/8,生效区块写入一个废块也会有 1/32 的奖励。
布隆过滤器是一种比较高效但是不太准的查找算法,大致机制如下:
如:
可以看到布隆过滤器对存在的判断是会误报的,但是不存在的一定不会误报,所以适合大数据量的过滤。
与比特币不同的是,以太坊的区块体中有三棵树:状态树、交易树、收据树,树的数据会被全节点维护在本地数据库中。
目前以太坊也是使用 PoW (工作量证明)即挖矿的方式确定记账权。
由一个 seed 生成一个小数据集(MB 级别),再由小的数据集生成一个大的数据集(GB 级别大小),在使用区块头和一个 nonce 值求取哈希,映射至大数据集,读取大数据集中目标位置和相邻元素,再哈希,循环 64 次,算出一个哈希和挖矿目标值对比。失败则更换区块头中 nonce 重新计算。
同时以太坊也有像 PoS 机制转换的计划,在以太坊的难度调节机制中存在一个难度因子,此难度因子每 10万个区块会翻一倍,是一个指数级的因子,所以又称为难度炸弹,由于 PoS 机制的上线不顺利,而难度炸弹导致出块速度提升过快,所以以太坊修改了其代码,使得难度因子计算时减去了 300 万个区块,也是由于代码升级导致了以太坊网络的一次硬分叉,还好这次分叉网络节点都进行了代码升级。
前面提到,以太坊网络中存在合约账户,账户内存有一段代码,所以智能合约就是按照既定逻辑执行的代码。
外部账户调用合约账户,外部账户发起的合约账户调用也可以调用另一个合约账户。
外部账户调用时,将目标函数和参数写在 data 域中,向合约账户发起交易。
合约调用合约的一种方式:
contract A {
event LogCallFoo (string str);
function foo (string str) returns (uint) {
emit LogCallFoo(str);
return 123;
}
}
contract B {
uint ua; // callAFooDirectly 的执行返回值
function callAFooDirectly (address addr) public {
A a = A(addr);
ua = a.foo("call foo directly); // 调用 a 地址的 foo 方法
}
}
这是一个拍卖出价的智能合约:
contract SimpleAuctionV1 {
address public beneficiary; // 受益人
uint public auctionEnd; // 结束时间
address public highestBidder; // 当前最高出价地址
mapping(address => unit) bids; // 所有出价
address[] bidders; // 所有出价地址
bool ended; // 是否结束
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, unit amount);
constructor(uint _biddingTime, address _beneficiary) public {
beneficiary = _beneficiary;
auctionEnd = now + _biddingTime
}
}
// 参与拍卖的地址向此合约地址发起交易并支付货币
function bid() public payable {
require(now <= auctionEnd);
require(bids[msg.sender]+msg.value > bids[highestBidder]);
// 没出过价则把出价人存起来
if (!(bids[msg.sender] == unit(0))) {
bidders.push(msg.sender);
}
// 如果出价最高则修改当前最高出价人
highestBidder = msg.sender;
bids[msg.sender] += msg.value;
emit HighestBidIncreased(msg.sender, bins[msg.sender]);
}
// 拍卖结束
function auctionEnd()public {
require(now > auctionEnd);
require(!ended);
// 把最高出价转给受益人
beneficiary.transfer(bids[highestBidder]);
// 给没竞拍成功的人退钱
for (uint i = 0; i< bidders.length; i++) {
address bidder = bidders[i];
if (bidder == highestBidder) continue;
bidder.transfer(bids[bidder]);
}
ended = true;
emit AuctionEnded(highestBidder, bids[highestBidder]);
}
beneficiary.transfer
,如果 beneficiary 是一个无法接受支付的合约地址(未声明 payable 关键词),那么会导致 auctionEnd
执行失败,导致所有出价锁在智能合约地址中,永远无法取出Code Is Law。
智能合约的逻辑由代码决定,已发布的合约代码无法修改,如果发布新的代码则会生成另一个合约账户地址,所以就算有 Bug 也无法修改。
The DAO(Decentralized Autonomous Organization):利用了重入攻击,黑客在自己的合约账户得收款函数中写了向 The DAO 发起调用的代码,使用循环调用的方式,转走了 5000万/1.5亿美元的以太币,约 10% 的以太坊系统总量的以太币,为了弥补对以太坊稳定性的重大影响,经过软分叉修复失败,以太币质押投票,以太坊选择了硬分叉的方式,将 The DAO 中的以太币强行转出,造成了社区分裂,造成以太坊硬分叉为 ETH、ETC。
从智能合约的例子中也可以看到,智能合约的开发者可以在合约账户的内部维护一个状态树。
其中的一种应用方式就是基于智能合约在区块链的网络内发行 Token,将所有持有人的状态维护在合约账户中。
NFT 正是这样的一种应用,全称为非同质化代币。NFT 的一大特征是,基于区块链的特性公开且防篡改的确定了某地址对某个数字作品的所有权。
一个 ERC721 标准(一种以太坊上发行 NFT 的标准)的 NFT 在区块链的数据示例:
由于 https 的内容会被修改,tokenURI 一般会用 IPFS 代替(分布式文件存储系统)。这个示例表明某地址拥有编号为 6 的某个物品,其内容为 tokenURI 的内容。
大多数 NFT 的实现上看,在区块链上只是记录了一个作品拥有者的地址、作品的编号、以及这个作品的链接,而作品本身在链下。所以也出现过现实中艺术家的作品被他人拿走铸造成 NFT 出售的情况,所以 NFT 不能解决线下的问题。从理论上讲,NFT 会提供数字资产的流通性,但是不能给物品本身赋予价值。
通过调用智能合约的函数,可以进行 NFT 的转移、交易。所以可以知道,NFT 不单指某一个代币,而是一种代币类型,你也可以通过创建自己的智能合约,发行自己的 NFT 或 NFT 平台。
区块链应该会是元宇宙实现的一个技术,作为可任意复制的数字世界的数字资产进行确权的基础,如现有的 Decentraland 的虚拟土地、创造品的售卖。
同时,对于在数字世界中诞生的虚拟物品,由于不需要解决线下问题,NFT 的应用性应该会更高一些。
本书的一大重点是给你的 “我做不到” 给出了强有力的理论支撑!
人的本能潜意识是趋易避难,甚至在这个过程中,本能脑还会对趋易避难的行为给出合理或妥协的解释,使得理智接受。
焦虑也源自求而不得,或是目标设置太高,或是急于求成。
要面对现实,接受这个啥都做不成的自己,因为那是你的本能,然后保持耐心,保持积累和思考,延迟满足。
复利曲线、平台期等都能说明耐心的必然性和必要性。运用理智,将痛苦的进步过程变为本能潜意识的享乐,别把目标设置的太高,不断的突破舒适区边缘的成就感就能带来这种享乐的舒适。
本能潜意识的想法不能作为依据,但可以作为引子。
正确的方法需要:
如何突破舒适区边缘(能力边界)呢?深度学习和主动反思。
深度学习:
主动反思:“吾日三省吾身”(可以结合潜意思作答,对反思的问题快速作答,再逐步挖掘。见《零秒思考》)。
算法:一组操作的方式
研究抽象数据类型的一个重要类型是控制数据结构的复杂度。
部分排序实现见 demo
KMP