核心:整洁面向对象分层架构(Clean Object-oriented and Layered Architecture, COLA)
一共有三大部分:技艺部分、思想部分和实践部分。
技艺部分
技艺部分笔记比较简单,因为我的学习重点并不是Java。
第一章:命名
变量名应该是名词,能够准确描述业务,eg. int elapsedTimeInDays
, SECONDS_PER_DAY
。
函数名要具体体现做什么,而不是怎么做,eg. getLatestEmployee()
比popRecord()
更好。
类可分为实体类和辅助类,实体类承载核心业务数据和核心业务逻辑,命名要充分体现业务逻辑并在团队内达成共识,eg.Customer
。辅助类辅佐实体类完成业务逻辑,命名要通过后缀体现功能,eg.CustomerService
,同时尽量不用Helper/Util之类的后缀,因为含义太过笼统,容易破坏SRP(单一职责原则),比如作者建议把CSVHelper.parse(String)
和CSVHelper.create(int [])
拆开成CSVParser.parse(String)
和CSVBuilder.create(int [])
。这点我存疑,感觉这样拆会让文件变得太多了,同一类操作应该可以合并,但有可能目前接触的都是小项目=-=。
包(Package)代表一组有关系的类的集合,起到分类组合和命名空间的作用,包名应该能够反映一组类在更高抽象层次上的联系。
模块(Module)包含了多个包,在Maven中,模块名就是一个坐标:<groupId, artifactId>,保证模块在Maven仓库中的唯一性。但同时其名称也应该反映模块在系统中的职责。
命名时需要保持整个系统中的名称一致性,每个概念只有一个词(如get和find选一个),并使用对仗词(不出现fileOpen和fClose),并将限定词(Total/Sum/Average等)后置,保证命名风格统一。
在交流时统一业务语言(对业务方和团队内、业务-文档-代码之间)都应该使用同一种编程语言。技术语言也应该统一,如DO、DAO、DTO、Service、ServiceImpl、Component和Repository等。
用中间变量可以将隐藏的计算过程以显性化的方式表达出来;在命名上体现设计模式也可以让代码更容易理解。
第二章:规范
代码规范(缩进、水平对齐、注释格式等)应该统一。
空行组合相关的概念或功能。
Java的命名约定:
- 类名大驼峰
- 方法小驼峰
- 常量命名全大写,单词之间下划线连接
- 枚举类以Enum或Type结尾,枚举类成员名称全大写,单词间用下划线连接
- 抽象类用Abstract开头;异常类用Exception结尾;实现类用Impl结尾;测试类以要测试的类名开头以Test结尾
- 包名统一小写,点分隔符之间有且仅有一个自然语义的英文单词,包名统一使用单数形式。通常以org或com开头,加上公司名,再加上组件或者功能模块名,eg.
org.springframework.beans
日志规范:分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或自定义的级别,比较有用的是ERROR、WARN、INFO、DEBUG。
- ERROR. 表示不能自己恢复的错误,需要立即被关注或解决,如数据库操作错误、 I/O错误、未知的系统错误。ERROR不仅要打印线程堆栈,最好打印出一定的上下文以便排查问题,同时要接入监控和报警系统,因为ERROR需要人工介入处理。
- WARN. 可预知的业务问题,如校验参数不通过、没有访问权限等业务异常,短时间内产生过多的WARN日志也需要关注,所有有必要为WARN配置一个适当阈值的报警。
- INFO. 记录系统的基本运行过程和运行状态,协助排查问题。主要包括系统状态变化日志、业务流程的核心处理、关键动作和业务流程的状态变化。
- DEBUG. 调试输出信息,在开发或预发环境下打开方便开发和调试,线上环境则需要关闭,因为日志量非常大。
异常处理这里只提了UnChecked异常(在运行时发生的,用于表示编码错误),设计了BizException(业务异常)和SysException(系统异常),做统一的类似AOP的处理,在应用处理请求的切面上进行异常处理收敛。不能再业务处理内部到处使用try/catch打印错误日志。
错误码应该在系统搭建之初就制定好相应的规范。对于平台、底层信息或软件产品,可以采用编号式的编码规范(风格固定,正式,但需要配合文档才能看懂),eg.IRA-00001~ORA-02149
这种编号式的对不同的错误波段要预留足够的码号;内部系统使用的服务更推荐用显性化的错误码,灵活性更强,比如定义成类型+场景+自定义标识
。
埋点规范,为了后续的数据分析设计,确保被采集上了的数据能够被统计分析(类似协议,我的理解是有点像DNS查找IP的设计),典型例子如阿里巴巴的超级位置模型(Super Position Model, SPM)。标准的SPM编码一共四段,a.b.c.d
:
- 站点类型
- 外站ID
- b站点上的频道ID
- c频道上的页面ID
第三章:函数
函数是最通用的叫法(函数式语言和面向对象语言都行),方法是面向对象语言中对函数的叫法。
将解释条件意图(if和while语句中的布尔逻辑)作为函数抽离出来,通过函数名把判断条件的语义显性表达可以大大提升代码的可读性和可理解性。
总体而言,参数越少越容易理解。如果函数需要三个以上参数,说明有一些参数可以封装成类。
函数代码要求短小,从而易于理解和维护,作者建议Java语言的函数不要超过20行。同时,函数应该遵循单一职责原则(SRP)。
应该精简辅助代码,如判空、打印日志、鉴权、降级和缓存检查等,不让它过多干扰业务代码。
- 优化判空:Java8引入的新特性Optional类,主要解决空指针异常。Optional类是一个包含可选值的包装类,也就是Optional类既可以含有对象,也可以为空,用这个特性可以代替冗长的null检查。
- 优化缓存判断:用注解方式重构代码。(这个不太懂,得需要用的时候查一下)
- 优雅降级:不在业务逻辑里用try/catch做异常情况下的服务降级,用Spring Cloud Hystrix提供的API,用注解的方式定义降级服务。
组合函数模式可以提升代码可读性和可维护性,要求公有函数(入口函数)读起来像一系列执行步骤的概要,而这些步骤的真正实现细节在私有函数里。
抽象层次一致性(Single Level of Abstration Principle, SLAP)是和组合函数密切相关的一个原则。组合函数要求将一个大函数拆成多个子函数的组合,而SLAP要求函数体中的内容必须在同一个抽象层次上,如果高层次抽象和底层细节融合在一起就会现得凌乱而难以理解。满足SLAP实际上构筑了代码结构的金字塔,金字塔是一种自上而下的、每一层属于同一个逻辑范畴和抽象层次的结构,符合人类思维逻辑的表达方式。
函数式编程和面向对象编程没有本质区别。函数式编程中,函数不仅可以调用函数,也可以作为参数被其他函数调用。从这个角度看,对象在作为值被传递时,也是对业务逻辑的封装,只不过它不仅包含函数,还包含属性。(Java8引入了函数式编程)把函数作为值传递给另一个函数的功能重要的主要原因:
- 减少冗余代码,让代码更简洁,可读性更好。
- 函数是“无副作用”的,即没有对共享的可变数据操作,可以利用多核并行处理,而不用担心线程安全问题。
第四章:设计原则
SOLID原则:
- 单一职责原则(SRP):接口职责应该单一,不要承担过多的职责。
- 开闭原则(OCP):软件应该对扩展开放,对修改关闭。当别人要修改软件功能的时候,使得他不能修改我们原有代码,只能新增代码实现软件功能修改的目的。面向对象中常常用继承和多态实现OCP,还有装饰者模式、策略模式、适配器模式、观察者模式等。
- 里氏替换原则(LSP):所有父类能出现的地方,子类就可以出现,并且替换了也不会出现任何错误。如果在程序中出现instance of、强制类型转换或者函数覆盖,就很有可能违背LSP。
- 接口隔离原则(ISP):类间的依赖关系应该建立在最小的接口上。简单地说:接口的内容一定要尽可能地小,能有多小就多小。用ISP可以将外部依赖降到最小,有利于降低模块之间的耦合度。
- 依赖倒置原则(DIP):模块之间交互应该依赖抽象而非实现。高层模块不应该依赖底层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。典型表现是面向接口编程。
除此之外,作者还提到了一些设计原则。
- DRY:Don't Repeat Yourself,在程序中应该避免重复代码。
- YAGNI:You Ain't Need It. 除了核心功能外,其他的功能一概不要提前设计,可以大大加快开发进程。(和DRY冲突,所以有了Rule of Three原则)
- Rule of Three:三次原则,即当某个功能第三次出现时,就有必要进行抽象化了。
- KISS:Keep It Simple and Stupid,设计越简单越好。
- POLA:Principle of Least Astonishment,最小惊奇原则,写代码越简单易懂越好。
第五章:设计模式
模式是理论和实践之间的中介环节,系统的设计模式不是含有设计模式就好,也不是含有越多设计模式越好。
GoF设计模式中收录了23种设计模式。根据模式所完成的工作类型来划分,模式可分为创建型模式、结构型模式和行为型模式。
创建型模式:描述怎样创建对象,主要特点是将对象的创建与使用分离。
- 单例(Singleton)模式:类只能产生一个实例,保证全局使用的是同一对象。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
结构型模式:描述如何将类或对象按某种布局组成更大的结构。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
行为型模式:描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。
- 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
这23种设计模式并不是孤立存在的,很多模式之间存在一定的关联关系。在大型系统开发中常常会同时使用多种设计模式。
拦截器模式是指通过提供一种通用的扩展机制,可以在业务操作前后提供一些切面的操作,这些切面操作通常是和业务无关的,如日志记录、性能统计、安全控制、事务处理、异常处理和编码转换等。拦截器模式和面向切面编程AOP的思想很相似,但比AOP的实现方式添加和删除更灵活;插件模式扩展发生在软件外部,新扩展以一个单独的组件(比如jar包)的方式加入到软件中,软件本身不需要重新编译、打包;而管道模式则体现了一种分治的思想。
第六章:模型
无论使用那种建模工具和表示法,只要有助于对问题域的理解,均可认为是好的模型。根据使用场景的不同,模型大致可以分为物理模型、概念模型、数学模型和思维模型等。
UML分为结构型和行为型建模图形:
- UML结构型图:类图、对象图、组件图、部署图、包图、组合结构图
- UML行为型图:用例图、顺序图、通信图、状态机图、活动图、时间图、交互概念图
类封装了数据和行为,每个类具有一定的职责,需要完成一定的功能、承担一定的义务。
类图用于描述类以及它们的相互关系,类图中的两个基本元素是类,以及类之间的关系。
领域模型是从业务场景出发对软件开发过程进行整合的模型。软件开发的过程本质上就是问题空间到解决方案空间的一个映射转化,就软件系统来说,问题空间就是系统要解决的领域问题。一个领域可以简单理解为一个问题空间,是一个特定单位边界内业务需求的综合。领域模型就是解决方案空间,是针对特定领域里的关键事务及其关系的可视化表现,是为了准确定义需要解决问题而构造的抽象模型,是业务功能场景在软件系统里的映射转化,其目标是为软件系统构建统一的认知。
敏捷建模的重点是用简单的工具创建简单的模型,创建的模型能用来沟通和理解并且适应变化的需求,如果模型没有价值,不能加速软件的交付就不应该创建它们。
广义模型:除了UML以外,还有一些可以实现对复杂问题进行抽象的模型。
C4模型:使用上下文(Context)、容器(Container)、组件(Component)和代码(Code)等一系列分层的图表来描述不同缩放级别的软件架构。
UI流程图:用页面之间的流转描述系统交互流程。
业务模型:用图形化的方式描述业务。
第七章:DDD的精髓
DDD,领域驱动设计。DDD的革命性在于领域驱动设计是面向对象分析的方法论,它可以利用面向对象的特性(封装、多态)有效地化解复杂性。DDD在类里面除了属性之外,还包含了行为和业务逻辑。
DDD中关注的是业务中的领域划分和领域建模,其开发过程不再以数据模型为起点,而是以领域模型为出发点。领域模型对应的是业务实体,在程序中主要体现为类、聚合根和值对象,它更关注业务语义的显性化表达,而不是数据的存储和数据之间的关系。
显然,领域模型和数据模型并不是意义对应的关系,大部分情况都需要做一层映射。为了你不二者之间的差异,有了对象关系映射(ORM)。但现在使用最多的是MyBatis,它很简单,完全不理会复杂的关系和对象之间的复杂关系映射,只做数据库表和DO之间的简单映射。
DDD的好处:
- 统一语言:业务和技术的一种共同语言。
- 面向对象:DDD强调业务抽象和面向对象编程,而不是过程式业务逻辑实现。重点不同,导致编程世界观不同。
- 业务语义显性化:能够让代码尽量体现领域实体和实体之间的关系原貌。
- 分离业务逻辑和技术细节:Entities只需要安心处理业务逻辑。
DDD的核心概念:
- 领域实体:其实就是对应到现实世界中的事物
- 聚合根:更大范围的封装,把一组相同生命周期、在业务上不可分割的实体和值对象放在一起,只有根实体可以对外暴露引用。在数据变化时必须保持一致性规则。
- 领域服务:一些动作并不属于任何对象,但代表领域中的一个重要行为,则将它声明成一个服务(如转账行为)。识别领域服务,主要看它是否满足:① 服务执行的操作代表了一个领域概念,这个概念无法自然地隶属于一个实体或者值对象 ② 被执行的操作涉及领域中的其他对象 ③ 操作是无状态的。
- 领域事件:在一个特定领域由一个用户动作触发的,是发生在过去的行为产生的事件,而这个事件是系统中的其他部分或者关联系统感兴趣的。在分布式环境下尤为重要,消息是分布式系统互通的重要手段。关于领域事件,需要注意事件命名和事件内容。
- 边界上下文:作用是限定模型的应用范围。在同一个上下文中,要保证模型在逻辑上的统一,而不用考虑它是不是适用于边界之外的情况。不同的上下文实体之间交互可以通过上下文映射(防腐层)来实现。
领域建模方法:
- 用例分析法:通过识别名词获取概念类和属性,通过识别动词寻找关联,再对模型进行精化。
- 四色建模法:以满足业务需要为前提,寻找业务关键时刻、角色、人-事-物和描述,从而构建领域模型。
具体DDD的应用在后面第13章。
思想
第八章:抽象
抽象就是简化事物,抓住事物本质的过程,需要对概念或现象的信息进行过滤,移除不相关的信息,只保留与某种最终目的相关的信息。抽象是面向对象的基础。
层次越往上,抽象程度越高,它所包含的东西就越多,其含义越宽泛,忽略的细节也就越多;层次越往下,抽象程度越低,它所包含的东西越少,细节越多。也就是,内涵越小,外延越大;内涵越大,外延越小。不同层次的抽象会有不同的用途。
如何进行抽象:
- 寻找共性:抽象的过程就是合并同类项、归并分类和寻找共性的过程。
- 提升抽象层次:当发现有些东西无法归到一个类别中时,可以通过上升一个抽象层次的方式,让它们在更高的抽象层次上产生逻辑关系。
- 构筑金字塔:要自下而上地思考,总结概括;自上而下地表达,结论先行。
第九章:分治
分治就是在开发工作中灵活使用分层、分步骤、分场景的解决方法。分治的典型应用包含:
分治算法:二分查找、归并排序等。
函数分解:让函数满足职责单一的原则,并且在函数组合时保证抽象层次一致性。
代码优化:写代码时第一次实现功能、理清逻辑,第二次重构优化。
分治模式:管道模式、责任链模式和装饰者模式等都暗含了分治的思想。
分层设计:在架构体系设计中使用分层设计,分离关注,每一层只对上一层负责,每一层的职责相对简单。如ISO七层网络模型、TCP/IP四层模型。
横切和竖切:数据库的水平拆分和垂直拆分。
第十章:技术人的素养
优秀的技术人员具有的特质:
- 不教条:不有样学样,会根据情况灵活变通,比如在不同迭代中使用不同开发模式、使用不同方法控制代码的复杂度等
- 批判性思维:保持思考的自主性和逻辑的严密性,不被动全盘接受,也不刻意带着偏见去驳斥一个观点
- 成长型思维:把每一次失败当成学习的机会
- 结构化思维:逻辑+套路,在分析和表达时使用有条理的语言和合理的结构。特别地,在进行汇报时可以使用“提出问题 → 定义问题 → 分析问题 → 解决问题 → 展望未来”的结构化表达,还有另一种有用的思维框架是“zoom in/zoom out”,也就是说事情时,应该像电影镜头一样,先从远拉近,再由近拉远
- 工具化思维:高效工作,使用工具完成重复性劳动
- 好奇心:持续学习,深入钻研
- 记笔记:知识内化,形成知识体系,方便回顾
- 有目标:先想清楚目标,再去努力实现
- 选择的自由:发挥主观能动性,为自己过去、现在及未来的行为买单
- 平和的心态:“动机至善,了无私心;用无为的心,做有为的事。”
- 精进:每天进步一点点
第十一章:技术Leader的修养
技术Leader和其他类型的Leader有些不同,在团队中作为技术Leader要尤为注意一些方面:
- 技术氛围:技术Leader需要关心技术细节,而不是将绩效完全与业务KPI绑定。
- 代码分享:团队中分享写好代码的心得和经验,分享好/坏味道代码
- 技术分享:通过准备分享和倾听分享学习
- CR周报:将Code Review的结果透明化
- 读书会:持续学习
- 目标管理:花时间和下属一起讨论、制定目标,并在过程中给予帮助和指导,及时对焦纠偏,确保目标达成。
- OKR:目标与关键成果,注重短期利益和长期战略之间的平衡。OKR可以不和绩效挂钩,主要强调沟通和方向。相比KPI多一个层级的概念,O(objective)有一定模糊性,但KR(key results)需要是可量化的,KR为O服务,不能偏离O的方向。
- SMART原则:Specific - Measurable - Attainable - Relevent - Time bound,不管是OKR还是KPI的设定,都需要满足SMART原则。
- OKR设定:O需要有野心,只有高远的目标才能最大程度地激发人的潜能。
- 技术规划:从团队视角看接下来要做的事情,可以从当前问题(痛点、坑点)、技术领域(性能、稳定性、工程效能)、业务领域(业务特定、技术诉求)、团队特色(差异化、竞争力)四个层次分别定义问题并制定策略。
- 推理阶梯:作为管理者不要轻易对员工做推理,要实事求是、尊重事实。不要随意地使用推理阶梯进行自我推理而非沟通的方式来解决问题。
- 何为Leader:Manager是管理事务,是控制和权威;而Leader是领导人心,是引领和激发。Leader要做一些Manager的管理事务,但管理绝不是Leader工作的全部。技术Leader区别与其他Leader之处在于不仅要“以德服人”,还要“以技服人”。要带好一个技术团队,技术Leader要对技术有热情,有一定技术能力,并帮助团队成员提升自我,有所成长。
- 视人为人:和团队建立情感链接和信任关系后才能更好地开展工作。
- 对待上级——有胆量
- 对待平级——有肺腑
- 对待下级——有心肝
实践
第十二章:COLA架构
软件架构可以分为:
- 业务架构:顶层设计,它对业务的定义和划分会影响组织架构和技术架构。
- 应用架构:根据业务场景的需要,设计应用的拓扑结构,制定应用规范、定义接口和数据交互协议等,并尽量将用于的复杂度控制在一个可以接受的水平,在快速支持业务发展的同时确保系统的可用性和可维护性。COLA架构就是一个典型的应用架构,致力于应用复杂度的管理。
- 系统架构:根据业务情况综合考虑系统的非功能性要求,做出技术选型。
- 数据架构:对数据收集、处理过程提供统一的服务和标准。
- 物理架构:规划软件元件是如何放到硬件上的。
- 运维架构:复杂运维系统的规划、选型、部署上线,建立规范化的运维体系。
典型的应用架构有:
- 分层架构
- 命令查询分离架构CQRS
- 六边形架构(端口-适配器架构)
- 洋葱架构
- DDD(实际上是一种开发思想,而不是架构,但是它是很多架构的来源)
- COLA架构
第十三章:工匠平台
工匠平台实际上是阿里内部收集技术人员指标并对其进行评分和统计,客观反映技术人员的技术贡献的平台。技术度量表如下,本书中是作为COLA架构的一个典型应用,具体内容就不在笔记中记录了。
维度 | 度量 | 指标 |
---|---|---|
应用质量 | 应用 | 重复代码数,长方法数,圈复杂度超标数,破坏规范数 |
技术影响力 | ATA文章 | 文章浏览数,点赞数,评论数,收藏数 |
技术影响力 | 分享 | 分享范围,分享次数 |
技术影响力 | 专利 | 作者类型(第一作者或其他),专利数 |
技术影响力 | 论文 | 作者类型(第一作者或其他),论文数 |
技术贡献 | 代码审查(Code Review) | CR评论数 |
技术贡献 | 重构 | 重构范围,数量 |
技术贡献 | 亮点 | 需要人工评定 |
开发质量 | Bug | 千行代码缺陷率,代码提交量 |
开发质量 | 故障 | 线上故障数,故障等级 |
COLA是一个开源的架构,这边也把Github链接放过来了。
alibaba/COLA: 🥤 COLA: Clean Object-oriented & Layered Architecture (github.com)
读后感
起初只是想随便看些书,让自己不要在一天天写基金本子这些抽象文字的同时忘了曾经也是想过搞技术的。最开始的技艺部分之前多多少少都有接触过,只不过再看一遍能更加系统化地梳理一遍相关知识,虽然还是有些细节并没有非常清楚。中间的思想部分确实是受益匪浅,尤其是技术Leader的修养那一章节,回想我在实验室的几次项目经历,愈发感受到一个好的技术Leader对整个项目团队的重要性,也希望我在之后的日子里能够成为我们小团队一个还不错的Leader吧。总之读这本书的收益远远超过我的预期,原本只是打算作为一个看上去比较有意义的消磨时间的事情,希望之后也能坚持下去。